nomnomtokens 0.1.0 → 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.
- package/README.md +10 -9
- package/dist/index.js +283 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/web/nitro.json +1 -1
- package/web/public/_nuxt/{CC85Q8DD.js → 075nz0Lp.js} +1 -1
- package/web/public/_nuxt/{CoCv6Hmx.js → BGnU88BC.js} +1 -1
- package/web/public/_nuxt/{DvKiMgoh.js → BnfUOzcc.js} +1 -1
- package/web/public/_nuxt/{Dw4si2jw.js → CiWIPYVi.js} +1 -1
- package/web/public/_nuxt/{Cay8LZ9u.js → CqSNCP5f.js} +1 -1
- package/web/public/_nuxt/{CNp1wm92.js → Cu3rIkF2.js} +1 -1
- package/web/public/_nuxt/Cutk9Ewh.js +1 -0
- package/web/public/_nuxt/DCVp7cZB.js +96 -0
- package/web/public/_nuxt/builds/latest.json +1 -1
- package/web/public/_nuxt/builds/meta/63ad5f37-5b40-456d-8f53-69ff57b4903c.json +1 -0
- package/web/public/_nuxt/entry.DTNjKx6y.css +2 -0
- package/web/public/_nuxt/{C-xeWIVA.js → fjfS4ymU.js} +1 -1
- package/web/public/_nuxt/limits.DDC9tNkw.css +1 -0
- package/web/public/_nuxt/{Cy-tzONg.js → moFy8lON.js} +2 -2
- package/web/server/chunks/_/filters.mjs +1 -1
- package/web/server/chunks/build/{Sparkline-DDDyGBE4.mjs → Sparkline-Cxifh2uK.mjs} +2 -2
- package/web/server/chunks/build/{Sparkline-DDDyGBE4.mjs.map → Sparkline-Cxifh2uK.mjs.map} +1 -1
- package/web/server/chunks/build/{_id_-HbVNBXK4.mjs → _id_-BKmH0aHH.mjs} +3 -3
- package/web/server/chunks/build/{_id_-HbVNBXK4.mjs.map → _id_-BKmH0aHH.mjs.map} +1 -1
- package/web/server/chunks/build/{limits-B44okAo1.mjs → limits-CnRLojCD.mjs} +25 -20
- package/web/server/chunks/build/limits-CnRLojCD.mjs.map +1 -0
- package/web/server/chunks/build/limits-styles.CKdzkgZ8.mjs +8 -0
- package/web/server/chunks/build/limits-styles.CKdzkgZ8.mjs.map +1 -0
- package/web/server/chunks/build/nuxt-link-mYiLFsJs.mjs +1 -1
- package/web/server/chunks/build/{pages-CffxtNMi.mjs → pages-DlIWqsLr.mjs} +3 -3
- package/web/server/chunks/build/{pages-CffxtNMi.mjs.map → pages-DlIWqsLr.mjs.map} +1 -1
- package/web/server/chunks/build/{providers-CUjXlgDS.mjs → providers-DOMeWSDZ.mjs} +2 -2
- package/web/server/chunks/build/{providers-CUjXlgDS.mjs.map → providers-DOMeWSDZ.mjs.map} +1 -1
- package/web/server/chunks/build/{scopes-CQokoqM9.mjs → scopes-Bagepvnf.mjs} +15 -9
- package/web/server/chunks/build/scopes-Bagepvnf.mjs.map +1 -0
- package/web/server/chunks/build/{sessions-BvRUAKPl.mjs → sessions-peyhKv2_.mjs} +2 -2
- package/web/server/chunks/build/{sessions-BvRUAKPl.mjs.map → sessions-peyhKv2_.mjs.map} +1 -1
- package/web/server/chunks/build/{timeline-BbSoi8xO.mjs → timeline-P2RrZyBs.mjs} +2 -2
- package/web/server/chunks/build/{timeline-BbSoi8xO.mjs.map → timeline-P2RrZyBs.mjs.map} +1 -1
- package/web/server/chunks/build/{useFormat-D8in50HT.mjs → useFormat-BfI0x0lv.mjs} +6 -3
- package/web/server/chunks/build/{useFormat-D8in50HT.mjs.map → useFormat-BfI0x0lv.mjs.map} +1 -1
- package/web/server/chunks/nitro/nitro.mjs +430 -152
- package/web/server/chunks/nitro/nitro.mjs.map +1 -1
- package/web/server/chunks/routes/api/stats/limits.get.mjs +4 -3
- package/web/server/chunks/routes/api/stats/limits.get.mjs.map +1 -1
- package/web/server/chunks/routes/api/stats/providers.get.mjs +1 -1
- package/web/server/chunks/routes/api/stats/scopes.get.mjs +1 -1
- package/web/server/chunks/routes/api/stats/series.get.mjs +1 -1
- package/web/server/chunks/routes/api/stats/session/_id_.get.mjs +1 -1
- package/web/server/chunks/routes/api/stats/sessions.get.mjs +1 -1
- package/web/server/chunks/routes/api/stats/summary.get.mjs +1 -1
- package/web/server/chunks/routes/renderer.mjs +2 -2
- package/web/server/chunks/virtual/entry.mjs +8 -8
- package/web/server/chunks/virtual/precomputed.mjs +1 -1
- package/web/server/chunks/virtual/precomputed.mjs.map +1 -1
- package/web/server/chunks/virtual/styles.mjs +3 -1
- package/web/server/chunks/virtual/styles.mjs.map +1 -1
- package/web/server/index.mjs +1 -1
- package/web/public/_nuxt/CYcD169V.js +0 -1
- package/web/public/_nuxt/TnCE9lfg.js +0 -86
- package/web/public/_nuxt/builds/meta/57c20bc9-f17f-4623-94bd-ea8746650367.json +0 -1
- package/web/public/_nuxt/entry.CwLI8Fh7.css +0 -2
- package/web/server/chunks/build/limits-B44okAo1.mjs.map +0 -1
- package/web/server/chunks/build/scopes-CQokoqM9.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -12,9 +12,10 @@ npx nomnomtokens
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
Claude Code
|
|
16
|
-
event contract is deliberately not AI-shaped, so
|
|
17
|
-
CI minutes and cloud bills — plug in as adapters
|
|
15
|
+
Claude Code, Cursor (when its local IDE database has token fields), and Codex
|
|
16
|
+
CLI/IDE session rollouts. The event contract is deliberately not AI-shaped, so
|
|
17
|
+
other agents — and eventually CI minutes and cloud bills — plug in as adapters
|
|
18
|
+
without touching the core.
|
|
18
19
|
|
|
19
20
|
## What you get
|
|
20
21
|
|
|
@@ -134,9 +135,10 @@ the core, the contract is wrong and that's a bug worth reporting.
|
|
|
134
135
|
|
|
135
136
|
## Roadmap
|
|
136
137
|
|
|
137
|
-
Shipped: Claude Code adapter, Cursor adapter (local `state.vscdb`),
|
|
138
|
-
|
|
139
|
-
`packages/adapters` but are not
|
|
138
|
+
Shipped: Claude Code adapter, Cursor adapter (local `state.vscdb`), Codex
|
|
139
|
+
adapter (`~/.codex/sessions/**/rollout-*.jsonl`), all six screens, limits with
|
|
140
|
+
forecasting, live updates. CSV helpers exist in `packages/adapters` but are not
|
|
141
|
+
wired into `scan` / the UI yet.
|
|
140
142
|
|
|
141
143
|
Cursor caveat: the IDE often stores zero token counts in local bubbles. We only
|
|
142
144
|
emit events when numbers are present (exact `tokenCount`, or
|
|
@@ -144,9 +146,8 @@ emit events when numbers are present (exact `tokenCount`, or
|
|
|
144
146
|
local counts will not appear until Cursor writes them — or until a future
|
|
145
147
|
optional CSV enrich from the Cursor dashboard.
|
|
146
148
|
|
|
147
|
-
Next: wire up CSV import, OTLP receiver,
|
|
148
|
-
|
|
149
|
-
the critical path.
|
|
149
|
+
Next: wire up CSV import, OTLP receiver, export, alerts. Then cloud mode — a
|
|
150
|
+
second sink for the same events, never a rewrite, and never in the critical path.
|
|
150
151
|
|
|
151
152
|
## License
|
|
152
153
|
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,18 @@ var PRICES = {
|
|
|
50
50
|
"claude-haiku-4-5": { input: 1, output: 5 },
|
|
51
51
|
"claude-3-5-haiku": { input: 0.8, output: 4 },
|
|
52
52
|
"claude-3-haiku": { input: 0.25, output: 1.25 },
|
|
53
|
-
// Cursor-routed
|
|
53
|
+
// Cursor / Codex-routed OpenAI ids (public list prices where known; Composer is unpriced).
|
|
54
|
+
// Dotted and dashed forms: resolveModelKey normalises `.` → `-`.
|
|
55
|
+
"gpt-5.6": { input: 1.75, output: 14 },
|
|
56
|
+
"gpt-5-6": { input: 1.75, output: 14 },
|
|
57
|
+
"gpt-5.6-terra": { input: 1.75, output: 14 },
|
|
58
|
+
"gpt-5-6-terra": { input: 1.75, output: 14 },
|
|
59
|
+
"gpt-5.6-luna": { input: 1.75, output: 14 },
|
|
60
|
+
"gpt-5-6-luna": { input: 1.75, output: 14 },
|
|
61
|
+
"gpt-5.5": { input: 1.25, output: 10 },
|
|
62
|
+
"gpt-5-5": { input: 1.25, output: 10 },
|
|
63
|
+
"gpt-5.4-mini": { input: 0.25, output: 2 },
|
|
64
|
+
"gpt-5-4-mini": { input: 0.25, output: 2 },
|
|
54
65
|
"gpt-5.2": { input: 1.75, output: 14 },
|
|
55
66
|
"gpt-5-2": { input: 1.75, output: 14 },
|
|
56
67
|
"gpt-5.2-codex": { input: 1.75, output: 14 },
|
|
@@ -261,7 +272,7 @@ function parseLimits(payload, now = Date.now()) {
|
|
|
261
272
|
window,
|
|
262
273
|
usedPct: raw.used_percentage,
|
|
263
274
|
// the payload uses unix seconds; everything downstream is unix ms
|
|
264
|
-
resetsAt: typeof raw.resets_at === "number" ? raw.resets_at * 1e3 : null
|
|
275
|
+
resetsAt: typeof raw.resets_at === "number" && Number.isFinite(raw.resets_at) ? raw.resets_at * 1e3 : null
|
|
265
276
|
});
|
|
266
277
|
}
|
|
267
278
|
return out;
|
|
@@ -440,6 +451,263 @@ var ClaudeCodeAdapter = class {
|
|
|
440
451
|
};
|
|
441
452
|
}
|
|
442
453
|
};
|
|
454
|
+
|
|
455
|
+
// packages/adapters/src/codex/parse.ts
|
|
456
|
+
function num2(v) {
|
|
457
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
458
|
+
}
|
|
459
|
+
function windowLabel(minutes) {
|
|
460
|
+
if (typeof minutes !== "number" || !Number.isFinite(minutes) || minutes <= 0) return null;
|
|
461
|
+
if (minutes === 300) return "5h";
|
|
462
|
+
if (minutes === 10080) return "7d";
|
|
463
|
+
if (minutes === 43200) return "30d";
|
|
464
|
+
if (minutes % (24 * 60) === 0) return `${minutes / (24 * 60)}d`;
|
|
465
|
+
if (minutes % 60 === 0) return `${minutes / 60}h`;
|
|
466
|
+
return `${minutes}m`;
|
|
467
|
+
}
|
|
468
|
+
function labelFromCwd(cwd) {
|
|
469
|
+
if (!cwd) return "unknown";
|
|
470
|
+
const parts = cwd.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
471
|
+
return parts[parts.length - 1] ?? "unknown";
|
|
472
|
+
}
|
|
473
|
+
function qtyFromUsage(usage) {
|
|
474
|
+
const input = num2(usage.input_tokens);
|
|
475
|
+
const cached = num2(usage.cached_input_tokens);
|
|
476
|
+
return {
|
|
477
|
+
in: Math.max(0, input - cached),
|
|
478
|
+
out: num2(usage.output_tokens),
|
|
479
|
+
cacheCreate: num2(usage.cache_write_input_tokens),
|
|
480
|
+
cacheCreate1h: 0,
|
|
481
|
+
cacheRead: cached
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function parseLimits2(rate, provider, ts) {
|
|
485
|
+
if (!rate) return [];
|
|
486
|
+
const out = [];
|
|
487
|
+
for (const key of ["primary", "secondary"]) {
|
|
488
|
+
const win = rate[key];
|
|
489
|
+
if (!win || typeof win.used_percent !== "number") continue;
|
|
490
|
+
const window = windowLabel(win.window_minutes) ?? key;
|
|
491
|
+
out.push({
|
|
492
|
+
ts,
|
|
493
|
+
provider,
|
|
494
|
+
window,
|
|
495
|
+
usedPct: win.used_percent,
|
|
496
|
+
resetsAt: typeof win.resets_at === "number" && Number.isFinite(win.resets_at) ? win.resets_at * 1e3 : null
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
var CodexRolloutParser = class {
|
|
502
|
+
prices;
|
|
503
|
+
provider;
|
|
504
|
+
sessionId = null;
|
|
505
|
+
cwd = null;
|
|
506
|
+
model = null;
|
|
507
|
+
/** Last emitted limit per window — Codex repeats the same % on every token_count. */
|
|
508
|
+
lastLimits = /* @__PURE__ */ new Map();
|
|
509
|
+
constructor(opts = {}) {
|
|
510
|
+
this.prices = opts.prices ?? defaultPrices;
|
|
511
|
+
this.provider = opts.provider ?? "codex";
|
|
512
|
+
}
|
|
513
|
+
reset() {
|
|
514
|
+
this.sessionId = null;
|
|
515
|
+
this.cwd = null;
|
|
516
|
+
this.model = null;
|
|
517
|
+
this.lastLimits.clear();
|
|
518
|
+
}
|
|
519
|
+
/** Last path segment of the session cwd — safe to show as a project label. */
|
|
520
|
+
scopeLabel() {
|
|
521
|
+
return labelFromCwd(this.cwd);
|
|
522
|
+
}
|
|
523
|
+
/** @returns zero or more ingest records for this line. */
|
|
524
|
+
async parseLine(line) {
|
|
525
|
+
const trimmed = line.trim();
|
|
526
|
+
if (!trimmed) return [];
|
|
527
|
+
let raw;
|
|
528
|
+
try {
|
|
529
|
+
raw = JSON.parse(trimmed);
|
|
530
|
+
} catch {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
const payload = raw.payload;
|
|
534
|
+
if (!payload) return [];
|
|
535
|
+
if (raw.type === "session_meta") {
|
|
536
|
+
this.sessionId = payload.session_id ?? payload.id ?? this.sessionId;
|
|
537
|
+
if (typeof payload.cwd === "string" && payload.cwd) this.cwd = payload.cwd;
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
if (raw.type === "turn_context") {
|
|
541
|
+
if (typeof payload.model === "string" && payload.model) this.model = payload.model;
|
|
542
|
+
if (typeof payload.cwd === "string" && payload.cwd) this.cwd = payload.cwd;
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
if (raw.type !== "event_msg" || payload.type !== "token_count") return [];
|
|
546
|
+
const ts = raw.timestamp ? Date.parse(raw.timestamp) : Number.NaN;
|
|
547
|
+
if (!Number.isFinite(ts)) return [];
|
|
548
|
+
const records = [];
|
|
549
|
+
for (const limit of parseLimits2(payload.rate_limits, this.provider, ts)) {
|
|
550
|
+
const prev = this.lastLimits.get(limit.window);
|
|
551
|
+
if (prev && prev.usedPct === limit.usedPct && prev.resetsAt === limit.resetsAt) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
this.lastLimits.set(limit.window, { usedPct: limit.usedPct, resetsAt: limit.resetsAt });
|
|
555
|
+
records.push({ type: "limit", limit });
|
|
556
|
+
}
|
|
557
|
+
const last = payload.info?.last_token_usage;
|
|
558
|
+
const total = payload.info?.total_token_usage;
|
|
559
|
+
if (!last) return records;
|
|
560
|
+
const qty = qtyFromUsage(last);
|
|
561
|
+
if (qty.in + qty.out + qty.cacheCreate + qty.cacheRead === 0) return records;
|
|
562
|
+
const sessionId = this.sessionId;
|
|
563
|
+
const totalIn = num2(total?.input_tokens);
|
|
564
|
+
const totalOut = num2(total?.output_tokens);
|
|
565
|
+
const id = `${this.provider}:${sessionId ?? "nosession"}:${totalIn}:${totalOut}`;
|
|
566
|
+
const hash = this.cwd ? await scopeHash(this.cwd) : "unknown";
|
|
567
|
+
const event = {
|
|
568
|
+
id,
|
|
569
|
+
ts,
|
|
570
|
+
provider: this.provider,
|
|
571
|
+
kind: "tokens",
|
|
572
|
+
sessionId,
|
|
573
|
+
scopeHash: hash,
|
|
574
|
+
unitLabel: this.model,
|
|
575
|
+
qty,
|
|
576
|
+
costUsd: costOf(this.model, qty, this.prices),
|
|
577
|
+
meta: {
|
|
578
|
+
reasoningOutputTokens: num2(last.reasoning_output_tokens)
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
records.push({ type: "event", event });
|
|
582
|
+
return records;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
function codexHome() {
|
|
586
|
+
return process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
587
|
+
}
|
|
588
|
+
function codexSessionsDir(home = codexHome()) {
|
|
589
|
+
return join(home, "sessions");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// packages/adapters/src/codex/index.ts
|
|
593
|
+
async function* readLinesFrom2(path, start) {
|
|
594
|
+
const stream = createReadStream(path, { start });
|
|
595
|
+
const decoder = new StringDecoder("utf8");
|
|
596
|
+
let buffer = "";
|
|
597
|
+
let consumed = start;
|
|
598
|
+
for await (const chunk of stream) {
|
|
599
|
+
buffer += decoder.write(chunk);
|
|
600
|
+
let nl = buffer.indexOf("\n");
|
|
601
|
+
while (nl !== -1) {
|
|
602
|
+
const line = buffer.slice(0, nl);
|
|
603
|
+
buffer = buffer.slice(nl + 1);
|
|
604
|
+
consumed += Buffer.byteLength(line, "utf8") + 1;
|
|
605
|
+
yield { line, offset: consumed };
|
|
606
|
+
nl = buffer.indexOf("\n");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function* walkRollouts(root) {
|
|
611
|
+
let entries;
|
|
612
|
+
try {
|
|
613
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
614
|
+
} catch {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
for (const entry of entries) {
|
|
618
|
+
const path = join(root, entry.name);
|
|
619
|
+
if (entry.isDirectory()) {
|
|
620
|
+
yield* walkRollouts(path);
|
|
621
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.startsWith("rollout-")) {
|
|
622
|
+
yield path;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
var CodexAdapter = class {
|
|
627
|
+
name = "codex";
|
|
628
|
+
home;
|
|
629
|
+
sessions;
|
|
630
|
+
prices;
|
|
631
|
+
constructor(opts = {}) {
|
|
632
|
+
this.home = opts.home ?? codexHome();
|
|
633
|
+
this.sessions = codexSessionsDir(this.home);
|
|
634
|
+
this.prices = opts.prices;
|
|
635
|
+
}
|
|
636
|
+
async detect() {
|
|
637
|
+
return existsSync(this.sessions) || existsSync(this.home);
|
|
638
|
+
}
|
|
639
|
+
async *scan(state) {
|
|
640
|
+
const seenScopes = /* @__PURE__ */ new Set();
|
|
641
|
+
for await (const path of walkRollouts(this.sessions)) {
|
|
642
|
+
const info = await stat(path).catch(() => null);
|
|
643
|
+
if (!info) continue;
|
|
644
|
+
const cursor = state.get(path);
|
|
645
|
+
if (cursor && cursor.size === info.size && cursor.mtime === info.mtimeMs) continue;
|
|
646
|
+
const start = cursor && info.size >= cursor.size ? cursor.offset : 0;
|
|
647
|
+
const parser = new CodexRolloutParser(this.prices ? { prices: this.prices } : {});
|
|
648
|
+
let offset = start;
|
|
649
|
+
for await (const { line, offset: next } of readLinesFrom2(path, start)) {
|
|
650
|
+
offset = next;
|
|
651
|
+
for (const record of await parser.parseLine(line)) {
|
|
652
|
+
if (record.type === "event" && !seenScopes.has(record.event.scopeHash)) {
|
|
653
|
+
seenScopes.add(record.event.scopeHash);
|
|
654
|
+
yield {
|
|
655
|
+
type: "scope",
|
|
656
|
+
scopeHash: record.event.scopeHash,
|
|
657
|
+
label: parser.scopeLabel(),
|
|
658
|
+
provider: this.name
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
yield record;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
state.set({ sourcePath: path, mtime: info.mtimeMs, size: info.size, offset });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
watch(emit) {
|
|
668
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
669
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
670
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
671
|
+
let closed = false;
|
|
672
|
+
const pump = async (path) => {
|
|
673
|
+
if (closed || inFlight.has(path) || !path.endsWith(".jsonl")) return;
|
|
674
|
+
inFlight.add(path);
|
|
675
|
+
try {
|
|
676
|
+
const info = await stat(path).catch(() => null);
|
|
677
|
+
if (!info) return;
|
|
678
|
+
const known = offsets.get(path);
|
|
679
|
+
let offset = known === void 0 ? info.size : info.size < known ? 0 : known;
|
|
680
|
+
let parser = parsers.get(path);
|
|
681
|
+
if (!parser || offset === 0) {
|
|
682
|
+
parser = new CodexRolloutParser(this.prices ? { prices: this.prices } : {});
|
|
683
|
+
parsers.set(path, parser);
|
|
684
|
+
}
|
|
685
|
+
for await (const { line, offset: next } of readLinesFrom2(path, offset)) {
|
|
686
|
+
offset = next;
|
|
687
|
+
for (const record of await parser.parseLine(line)) emit(record);
|
|
688
|
+
}
|
|
689
|
+
offsets.set(path, offset);
|
|
690
|
+
} finally {
|
|
691
|
+
inFlight.delete(path);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const watcherPromise = import('chokidar').then(({ watch }) => {
|
|
695
|
+
const w = watch(this.sessions, {
|
|
696
|
+
// sessions / YYYY / MM / DD / rollout-*.jsonl
|
|
697
|
+
depth: 4,
|
|
698
|
+
ignoreInitial: true,
|
|
699
|
+
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
700
|
+
});
|
|
701
|
+
w.on("add", (p) => void pump(p));
|
|
702
|
+
w.on("change", (p) => void pump(p));
|
|
703
|
+
return w;
|
|
704
|
+
});
|
|
705
|
+
return () => {
|
|
706
|
+
closed = true;
|
|
707
|
+
void watcherPromise.then((w) => w.close());
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
};
|
|
443
711
|
function cursorUserDataDir() {
|
|
444
712
|
if (process.env.CURSOR_USER_DATA_DIR) return process.env.CURSOR_USER_DATA_DIR;
|
|
445
713
|
const home = homedir();
|
|
@@ -640,7 +908,7 @@ function readCursorSnapshot(stateDbPath, workspaces) {
|
|
|
640
908
|
}
|
|
641
909
|
|
|
642
910
|
// packages/adapters/src/cursor/parse.ts
|
|
643
|
-
function
|
|
911
|
+
function num3(v) {
|
|
644
912
|
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
645
913
|
}
|
|
646
914
|
function parseTs(raw) {
|
|
@@ -655,10 +923,10 @@ function parseTs(raw) {
|
|
|
655
923
|
}
|
|
656
924
|
function hasExactTokens(b) {
|
|
657
925
|
const tc = b.tokenCount;
|
|
658
|
-
return
|
|
926
|
+
return num3(tc?.inputTokens) > 0 || num3(tc?.outputTokens) > 0;
|
|
659
927
|
}
|
|
660
928
|
function tokensUsedOf(b) {
|
|
661
|
-
return
|
|
929
|
+
return num3(b.contextWindowStatusAtCreation?.tokensUsed);
|
|
662
930
|
}
|
|
663
931
|
function eventsFromComposer(composer, bubbles, scopeHash2, opts = {}) {
|
|
664
932
|
const prices = opts.prices ?? defaultPrices;
|
|
@@ -687,8 +955,8 @@ function eventsFromComposer(composer, bubbles, scopeHash2, opts = {}) {
|
|
|
687
955
|
if (ts === null) continue;
|
|
688
956
|
const qty = {
|
|
689
957
|
...EMPTY_TOKEN_QTY,
|
|
690
|
-
in:
|
|
691
|
-
out:
|
|
958
|
+
in: num3(b.tokenCount?.inputTokens),
|
|
959
|
+
out: num3(b.tokenCount?.outputTokens)
|
|
692
960
|
};
|
|
693
961
|
events2.push({
|
|
694
962
|
id: `${provider}:${b.composerId}:${b.bubbleId}`,
|
|
@@ -837,7 +1105,7 @@ var CursorAdapter = class {
|
|
|
837
1105
|
|
|
838
1106
|
// packages/adapters/src/index.ts
|
|
839
1107
|
function allAdapters() {
|
|
840
|
-
return [new ClaudeCodeAdapter(), new CursorAdapter()];
|
|
1108
|
+
return [new ClaudeCodeAdapter(), new CursorAdapter(), new CodexAdapter()];
|
|
841
1109
|
}
|
|
842
1110
|
async function detectAdapters() {
|
|
843
1111
|
const found = [];
|
|
@@ -1446,7 +1714,7 @@ function sql(strings, ...params) {
|
|
|
1446
1714
|
return new SQL([new StringChunk(str)]);
|
|
1447
1715
|
}
|
|
1448
1716
|
sql2.raw = raw;
|
|
1449
|
-
function
|
|
1717
|
+
function join11(chunks, separator) {
|
|
1450
1718
|
const result = [];
|
|
1451
1719
|
for (const [i, chunk] of chunks.entries()) {
|
|
1452
1720
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1456,7 +1724,7 @@ function sql(strings, ...params) {
|
|
|
1456
1724
|
}
|
|
1457
1725
|
return new SQL(result);
|
|
1458
1726
|
}
|
|
1459
|
-
sql2.join =
|
|
1727
|
+
sql2.join = join11;
|
|
1460
1728
|
function identifier(value) {
|
|
1461
1729
|
return new Name(value);
|
|
1462
1730
|
}
|
|
@@ -3915,7 +4183,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
3915
4183
|
const baseTableName = this.tableName;
|
|
3916
4184
|
const tableName = getTableLikeName(table);
|
|
3917
4185
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
3918
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
4186
|
+
if (typeof tableName === "string" && this.config.joins?.some((join11) => join11.alias === tableName)) {
|
|
3919
4187
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
3920
4188
|
}
|
|
3921
4189
|
if (!this.isPartialSelect) {
|
|
@@ -4774,7 +5042,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
|
|
|
4774
5042
|
createJoin(joinType) {
|
|
4775
5043
|
return (table, on) => {
|
|
4776
5044
|
const tableName = getTableLikeName(table);
|
|
4777
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
5045
|
+
if (typeof tableName === "string" && this.config.joins.some((join11) => join11.alias === tableName)) {
|
|
4778
5046
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
4779
5047
|
}
|
|
4780
5048
|
if (typeof on === "function") {
|
|
@@ -6325,6 +6593,8 @@ async function doctor(opts = {}) {
|
|
|
6325
6593
|
check(existsSync(projects), "Claude Code transcripts", projects);
|
|
6326
6594
|
const cursorDb = cursorStateDbPath();
|
|
6327
6595
|
check(existsSync(cursorDb), "Cursor state database", cursorDb);
|
|
6596
|
+
const codexSessions = codexSessionsDir();
|
|
6597
|
+
check(existsSync(codexSessions), "Codex session rollouts", codexSessions);
|
|
6328
6598
|
const adapters = await detectAdapters();
|
|
6329
6599
|
check(adapters.length > 0, `${adapters.length} adapter(s) detected`, adapters.map((a) => a.name).join(", ") || "none");
|
|
6330
6600
|
console.log();
|
|
@@ -6433,7 +6703,7 @@ async function scan(opts = {}) {
|
|
|
6433
6703
|
const adapters = await detectAdapters();
|
|
6434
6704
|
if (adapters.length === 0) {
|
|
6435
6705
|
console.error(c.yellow("No sources detected on this machine."));
|
|
6436
|
-
console.error(c.dim("Looks for ~/.claude/projects
|
|
6706
|
+
console.error(c.dim("Looks for ~/.claude/projects, Cursor state.vscdb, and ~/.codex/sessions. Run `nnt doctor`."));
|
|
6437
6707
|
process.exitCode = 1;
|
|
6438
6708
|
return;
|
|
6439
6709
|
}
|