@nyte-ai/host 0.0.18 → 0.0.19

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 (102) hide show
  1. package/THIRD-PARTY-NOTICES.md +66 -0
  2. package/dist/chunks/{canvas-preview-5hxat7hh.js → canvas-preview-11nadadc.js} +8 -6
  3. package/dist/chunks/{canvas-preview-rp6fz0w5.js → canvas-preview-2jwbe14k.js} +5 -5
  4. package/dist/chunks/{canvas-preview-m02v4yy6.js → canvas-preview-55s0y68g.js} +3 -2
  5. package/dist/chunks/{canvas-preview-xn10d4f8.js → canvas-preview-5ekjx6sw.js} +9 -4
  6. package/dist/chunks/{canvas-preview-75db60xy.js → canvas-preview-8xc0vn7v.js} +5 -4
  7. package/dist/chunks/{canvas-preview-zn2d0k9d.js → canvas-preview-af86jz8t.js} +14 -6
  8. package/dist/chunks/{canvas-preview-d9wwhbtz.js → canvas-preview-aq5j4p25.js} +1 -1
  9. package/dist/chunks/{canvas-preview-j1tnf51w.js → canvas-preview-b2v8wfnz.js} +1 -1
  10. package/dist/chunks/canvas-preview-befkjea2.js +60 -0
  11. package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
  12. package/dist/chunks/{canvas-preview-vax9ne4e.js → canvas-preview-cawy6vbz.js} +22 -11
  13. package/dist/chunks/{canvas-preview-senpjwep.js → canvas-preview-crmzxxt8.js} +3 -3
  14. package/dist/chunks/{canvas-preview-ymgz0937.js → canvas-preview-drctegz4.js} +8 -4
  15. package/dist/chunks/{canvas-preview-yrfz2ak6.js → canvas-preview-e3vnkkc5.js} +6 -6
  16. package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
  17. package/dist/chunks/{canvas-preview-1ta3avah.js → canvas-preview-fxa0rfb0.js} +10 -5
  18. package/dist/chunks/{canvas-preview-8xgt67ka.js → canvas-preview-g5fkjs34.js} +1 -1
  19. package/dist/chunks/{canvas-preview-g3qj3y3k.js → canvas-preview-jeddjex8.js} +13 -3
  20. package/dist/chunks/{canvas-preview-0nrnbt76.js → canvas-preview-k1yg4fgp.js} +199 -125
  21. package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
  22. package/dist/chunks/{canvas-preview-ewgspmsd.js → canvas-preview-n2a35zv5.js} +1 -1
  23. package/dist/chunks/{canvas-preview-h8hshnff.js → canvas-preview-pkzqg96v.js} +1 -1
  24. package/dist/chunks/{canvas-preview-evh92ywd.js → canvas-preview-tnqe7k0b.js} +15 -10
  25. package/dist/chunks/{canvas-preview-ehf3vp31.js → canvas-preview-vm9zdp2t.js} +45 -58
  26. package/dist/chunks/{canvas-preview-76fwvd6e.js → canvas-preview-w54ymtb3.js} +1 -1
  27. package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
  28. package/dist/chunks/{canvas-preview-98aj5p34.js → canvas-preview-ycepb05s.js} +1 -1
  29. package/dist/src/canvas-preview.js +7 -3
  30. package/dist/src/deploy/cloudflare/worker.js +1 -1
  31. package/dist/src/deploy/vercel/server/routes/[...].js +2 -2
  32. package/dist/src/deploy/vercel/server/utils/host.js +1 -1
  33. package/dist/src/deploy/vercel/server/workflows/drive.js +3 -3
  34. package/dist/src/images/exif.d.ts +1 -0
  35. package/dist/src/images/exif.js +6 -0
  36. package/dist/src/images/node.d.ts +37 -0
  37. package/dist/src/images/node.js +12 -0
  38. package/dist/src/images/photon.d.ts +3 -0
  39. package/dist/src/images/photon.js +7 -0
  40. package/dist/src/images/worker.d.ts +1 -0
  41. package/dist/src/images/worker.js +23 -0
  42. package/dist/src/index.d.ts +0 -1
  43. package/dist/src/index.js +23 -22
  44. package/dist/src/listen-fetch.js +3 -3
  45. package/dist/src/listen.js +4 -4
  46. package/dist/src/local/command.d.ts +5 -0
  47. package/dist/src/local/command.js +6 -2
  48. package/dist/src/local/files.js +2 -2
  49. package/dist/src/local/git-watch.d.ts +1 -1
  50. package/dist/src/local/git-watch.js +1 -1
  51. package/dist/src/local/git.js +3 -3
  52. package/dist/src/local/github.js +2 -2
  53. package/dist/src/local/jobs.d.ts +1 -1
  54. package/dist/src/local/jobs.js +2 -1
  55. package/dist/src/local/provider.js +9 -9
  56. package/dist/src/local/snapshots.js +2 -2
  57. package/dist/src/local/worktrees.js +2 -2
  58. package/dist/src/mcp/connect.d.ts +0 -13
  59. package/dist/src/mcp/connect.js +1 -1
  60. package/dist/src/mcp/hub.js +2 -2
  61. package/dist/src/open.d.ts +2 -30
  62. package/dist/src/open.js +20 -18
  63. package/dist/src/serve/binding.js +1 -1
  64. package/dist/src/serve/handler.js +2 -2
  65. package/dist/src/sqlite.d.ts +2 -1
  66. package/dist/src/sqlite.js +4 -3
  67. package/dist/src/store-usage.js +4 -1
  68. package/dist/src/usage-history.d.ts +54 -0
  69. package/dist/src/usage-history.js +14 -0
  70. package/dist/src/usage-service.d.ts +2 -1
  71. package/dist/src/usage-service.js +7 -3
  72. package/dist/src/usage-worker.js +8 -4
  73. package/dist/src/usage.d.ts +150 -27
  74. package/dist/src/usage.js +12 -2
  75. package/package.json +16 -8
  76. package/src/canvas-preview.ts +4 -3
  77. package/src/images/exif.ts +69 -0
  78. package/src/images/node.ts +163 -0
  79. package/src/images/photon.ts +141 -0
  80. package/src/images/worker.ts +29 -0
  81. package/src/index.ts +19 -1
  82. package/src/local/command.ts +64 -12
  83. package/src/local/files.ts +8 -3
  84. package/src/local/git-watch.ts +16 -6
  85. package/src/local/git.ts +7 -2
  86. package/src/local/github.ts +3 -4
  87. package/src/local/jobs.ts +55 -69
  88. package/src/local/snapshots.ts +4 -2
  89. package/src/local/worktrees.ts +4 -4
  90. package/src/mcp/connect.ts +1 -14
  91. package/src/open.ts +16 -36
  92. package/src/serve/binding.ts +4 -3
  93. package/src/sqlite.ts +33 -8
  94. package/src/store-usage.ts +5 -2
  95. package/src/usage-history.ts +219 -0
  96. package/src/usage-service.ts +12 -3
  97. package/src/usage.ts +251 -176
  98. package/dist/chunks/canvas-preview-a3yw0j70.js +0 -72
  99. package/dist/chunks/canvas-preview-azg56359.js +0 -68
  100. package/dist/src/store-owner.d.ts +0 -15
  101. package/dist/src/store-owner.js +0 -9
  102. package/src/store-owner.ts +0 -98
@@ -3,22 +3,61 @@ import {
3
3
  recordedUsage2,
4
4
  summarizeModels2
5
5
  } from "./canvas-preview-srgfpdmf.js";
6
+ import {
7
+ localDay2
8
+ } from "./canvas-preview-11nadadc.js";
9
+ import {
10
+ fileScanSchema2,
11
+ UsageScanCache2,
12
+ readHistory2,
13
+ readCacheDocument2,
14
+ writeCacheDocument2
15
+ } from "./canvas-preview-kp75gb63.js";
6
16
 
7
17
  // src/usage.ts
8
- import { Buffer } from "node:buffer";
9
- import { constants, existsSync } from "node:fs";
10
- import { lstat, open, opendir, stat } from "node:fs/promises";
18
+ import { existsSync } from "node:fs";
19
+ import { lstat, opendir, stat } from "node:fs/promises";
11
20
  import { homedir } from "node:os";
12
21
  import { join, resolve } from "node:path";
13
22
  import process from "node:process";
14
- import { StringDecoder } from "node:string_decoder";
15
23
  import { Type } from "typebox";
24
+ import { Compile } from "typebox/compile";
16
25
  import { Value } from "typebox/value";
17
26
  import { calculateCost } from "@nyte-ai/ai";
18
27
  var tokens = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
28
+ var count = Type.Number({ minimum: 0 });
29
+ var modelUsage = Type.Object({
30
+ input: count,
31
+ output: count,
32
+ cacheRead: count,
33
+ cacheWrite: count,
34
+ cacheWrite1h: Type.Optional(count),
35
+ reasoning: Type.Optional(count),
36
+ totalTokens: count,
37
+ cost: Type.Object({
38
+ input: Type.Number(),
39
+ output: Type.Number(),
40
+ cacheRead: Type.Number(),
41
+ cacheWrite: Type.Number(),
42
+ total: Type.Number()
43
+ })
44
+ });
45
+ var recordedAt = Type.Union([Type.Number(), Type.Null()]);
46
+ var snapshot = Type.Object({
47
+ model: Type.String(),
48
+ usage: modelUsage,
49
+ costUSD: Type.Optional(Type.Number()),
50
+ at: recordedAt
51
+ });
52
+ var claudeCodeFileScan = Type.Object({
53
+ keyed: Type.Array(Type.Tuple([Type.String(), snapshot])),
54
+ anonymous: Type.Array(snapshot),
55
+ malformedRecords: Type.Integer({ minimum: 0 })
56
+ });
19
57
  var envelope = Type.Object({ type: Type.String() });
20
58
  var assistantRecord = Type.Object({
21
59
  type: Type.Literal("assistant"),
60
+ timestamp: Type.Optional(Type.String()),
22
61
  requestId: Type.Optional(Type.String()),
23
62
  costUSD: Type.Optional(Type.Unknown()),
24
63
  message: Type.Object({
@@ -43,38 +82,49 @@ function betterSnapshot(previous, next) {
43
82
  const usage = next.usage;
44
83
  return usage.totalTokens > previous.usage.totalTokens || usage.totalTokens === previous.usage.totalTokens && (usage.output > previous.usage.output || usage.output === previous.usage.output && previous.costUSD === undefined && next.costUSD !== undefined);
45
84
  }
46
- async function scanClaudeCodeFile(path, signal) {
47
- const keyed = new Map;
48
- const anonymous = [];
49
- let malformedRecords = 0;
50
- for await (const line of historyLines(path, signal)) {
51
- signal?.throwIfAborted();
52
- if (!line.text.trim())
53
- continue;
85
+ function parseRecordedAt(value) {
86
+ if (value === undefined)
87
+ return null;
88
+ const parsed = Date.parse(value);
89
+ return Number.isNaN(parsed) ? null : parsed;
90
+ }
91
+ function recordedIn(window, at) {
92
+ if (at === null)
93
+ return window.sinceDay === null;
94
+ const day = localDay2(at);
95
+ return day <= window.untilDay && (window.sinceDay === null || day >= window.sinceDay);
96
+ }
97
+ var claudeCodeParser = (from) => {
98
+ const keyed = new Map(from?.keyed);
99
+ const anonymous = [...from?.anonymous ?? []];
100
+ let malformedRecords = from?.malformedRecords ?? 0;
101
+ const line = ({ text, terminated }) => {
102
+ if (!text.trim())
103
+ return;
54
104
  let value;
55
105
  try {
56
- value = JSON.parse(line.text);
106
+ value = JSON.parse(text);
57
107
  } catch {
58
- if (line.terminated)
108
+ if (terminated)
59
109
  malformedRecords++;
60
- continue;
110
+ return;
61
111
  }
62
112
  if (!Value.Check(envelope, value)) {
63
113
  malformedRecords++;
64
- continue;
114
+ return;
65
115
  }
66
116
  if (value.type !== "assistant")
67
- continue;
117
+ return;
68
118
  if (!Value.Check(assistantRecord, value)) {
69
119
  malformedRecords++;
70
- continue;
120
+ return;
71
121
  }
72
122
  const raw = value.message.usage;
73
123
  const cacheWrite = raw.cache_creation_input_tokens ?? 0;
74
124
  const cacheWrite1h = raw.cache_creation?.ephemeral_1h_input_tokens;
75
125
  if ((cacheWrite1h ?? 0) + (raw.cache_creation?.ephemeral_5m_input_tokens ?? 0) > cacheWrite) {
76
126
  malformedRecords++;
77
- continue;
127
+ return;
78
128
  }
79
129
  const usage = {
80
130
  ...emptyModelUsage2(),
@@ -89,40 +139,31 @@ async function scanClaudeCodeFile(path, signal) {
89
139
  const snapshot = {
90
140
  model: value.message.model,
91
141
  usage,
92
- costUSD: Value.Check(reportedCost, value.costUSD) ? value.costUSD : undefined
142
+ at: parseRecordedAt(value.timestamp)
93
143
  };
144
+ if (Value.Check(reportedCost, value.costUSD))
145
+ snapshot.costUSD = value.costUSD;
94
146
  const messageId = value.message.id?.trim() || null;
95
147
  const requestId = value.requestId?.trim() || null;
96
148
  if (messageId === null && requestId === null) {
97
149
  anonymous.push(snapshot);
98
- continue;
150
+ return;
99
151
  }
100
152
  const identity = JSON.stringify([messageId, requestId]);
101
153
  if (betterSnapshot(keyed.get(identity), snapshot))
102
154
  keyed.set(identity, snapshot);
103
- }
104
- return { keyed: [...keyed], anonymous, malformedRecords };
105
- }
106
- async function scanCached(path, cache, seen, parse) {
107
- seen.add(path);
108
- if (cache === undefined)
109
- return parse();
155
+ };
156
+ return { line, scan: () => ({ keyed: [...keyed], anonymous: [...anonymous], malformedRecords }) };
157
+ };
158
+ async function readHistoryInWindow(path, window, read) {
159
+ read.seen.add(path);
110
160
  const info = await lstat(path);
111
161
  if (!info.isFile())
112
162
  throw new Error("History is not a regular file.");
113
- const hit = cache.get(path);
114
- if (hit !== undefined && hit.size === info.size && hit.mtimeMs === info.mtimeMs)
115
- return hit.scan;
116
- const scan = await parse();
117
- cache.set(path, { size: info.size, mtimeMs: info.mtimeMs, scan });
118
- return scan;
119
- }
120
- function pruneCache(cache, seen) {
121
- if (cache === undefined)
163
+ const opens = window.sinceDay === null ? 0 : new Date(`${window.sinceDay}T00:00:00`).getTime();
164
+ if (info.mtimeMs < opens)
122
165
  return;
123
- for (const path of cache.keys())
124
- if (!seen.has(path))
125
- cache.delete(path);
166
+ return readHistory2(path, info, read.parser, read.cache, read.signal);
126
167
  }
127
168
  async function readClaudeCodeUsage2(options) {
128
169
  options.signal?.throwIfAborted();
@@ -161,7 +202,14 @@ async function readClaudeCodeUsage2(options) {
161
202
  if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
162
203
  continue;
163
204
  try {
164
- const scan = await scanCached(path, options.cache, seen, () => scanClaudeCodeFile(path, options.signal));
205
+ const scan = await readHistoryInWindow(path, options.window, {
206
+ parser: claudeCodeParser,
207
+ cache: options.cache,
208
+ seen,
209
+ signal: options.signal
210
+ });
211
+ if (scan === undefined)
212
+ continue;
165
213
  malformedRecords += scan.malformedRecords;
166
214
  anonymous.push(...scan.anonymous);
167
215
  for (const [identity, snapshot] of scan.keyed) {
@@ -181,12 +229,14 @@ async function readClaudeCodeUsage2(options) {
181
229
  }
182
230
  }
183
231
  options.signal?.throwIfAborted();
184
- pruneCache(options.cache, seen);
232
+ options.cache?.keepOnly(seen);
185
233
  const catalog = options.models.getModels("anthropic").filter((model) => model.provider === "anthropic");
186
234
  const rows = [];
187
235
  let unpricedRecords = 0;
188
236
  for (const snapshot of [...keyed.values(), ...anonymous]) {
189
237
  options.signal?.throwIfAborted();
238
+ if (!recordedIn(options.window, snapshot.at))
239
+ continue;
190
240
  const usage = { ...snapshot.usage, cost: { ...snapshot.usage.cost } };
191
241
  if (snapshot.costUSD !== undefined) {
192
242
  usage.cost.total = snapshot.costUSD;
@@ -218,6 +268,19 @@ async function readClaudeCodeUsage2(options) {
218
268
  return { kind: "failed", message: "Could not read Claude Code local history." };
219
269
  }
220
270
  }
271
+ var codexRow = Type.Object({ model: Type.String(), usage: modelUsage, at: recordedAt });
272
+ var codexCursor = Type.Object({
273
+ model: Type.String(),
274
+ sawMeta: Type.Boolean(),
275
+ signature: Type.Optional(Type.String()),
276
+ forkAnchorMs: Type.Optional(Type.Number())
277
+ });
278
+ var codexFileScan = Type.Object({
279
+ modern: Type.Array(Type.Tuple([Type.String(), codexRow])),
280
+ legacy: Type.Array(codexRow),
281
+ malformedRecords: Type.Integer({ minimum: 0 }),
282
+ cursor: codexCursor
283
+ });
221
284
  var codexTokens = Type.Object({
222
285
  input_tokens: Type.Optional(tokens),
223
286
  cached_input_tokens: Type.Optional(tokens),
@@ -227,6 +290,7 @@ var codexTokens = Type.Object({
227
290
  });
228
291
  var usageRecord = Type.Object({
229
292
  type: Type.Literal("token_usage_record"),
293
+ timestamp: Type.Optional(Type.String()),
230
294
  payload: Type.Object({
231
295
  response_id: Type.Optional(Type.String()),
232
296
  usage: codexTokens
@@ -269,12 +333,6 @@ function codexUsage(raw) {
269
333
  };
270
334
  }
271
335
  var FORK_COPY_MAX_GAP_MS = 1000;
272
- function parseMs(value) {
273
- if (value === undefined)
274
- return;
275
- const parsed = Date.parse(value);
276
- return Number.isNaN(parsed) ? undefined : parsed;
277
- }
278
336
  async function readCodexUsage2(options) {
279
337
  options.signal?.throwIfAborted();
280
338
  const home = resolve(options.homeDir ?? (process.env.CODEX_HOME?.trim() || join(homedir(), ".codex")));
@@ -302,7 +360,14 @@ async function readCodexUsage2(options) {
302
360
  for (const root of present) {
303
361
  for await (const path of jsonlFiles(root, options.signal)) {
304
362
  try {
305
- const scan = await scanCached(path, options.cache, seen, () => scanCodexRollout(path, options.signal));
363
+ const scan = await readHistoryInWindow(path, options.window, {
364
+ parser: codexParser,
365
+ cache: options.cache,
366
+ seen,
367
+ signal: options.signal
368
+ });
369
+ if (scan === undefined)
370
+ continue;
306
371
  malformedRecords += scan.malformedRecords;
307
372
  if (scan.modern.length > 0) {
308
373
  for (const [id, row] of scan.modern)
@@ -318,12 +383,14 @@ async function readCodexUsage2(options) {
318
383
  }
319
384
  }
320
385
  options.signal?.throwIfAborted();
321
- pruneCache(options.cache, seen);
386
+ options.cache?.keepOnly(seen);
322
387
  const catalog = options.models.getModels("openai-codex").filter((model) => model.provider === "openai-codex");
323
388
  const rows = [];
324
389
  let unpricedRecords = 0;
325
390
  for (const row of [...responses.values(), ...legacy]) {
326
391
  options.signal?.throwIfAborted();
392
+ if (!recordedIn(options.window, row.at))
393
+ continue;
327
394
  const usage = { ...row.usage, cost: { ...row.usage.cost } };
328
395
  const matches = catalog.filter((model) => model.id === row.model);
329
396
  const model = matches.length === 1 ? matches[0] : undefined;
@@ -350,76 +417,81 @@ async function readCodexUsage2(options) {
350
417
  return { kind: "failed", message: "Could not read Codex local history." };
351
418
  }
352
419
  }
353
- async function scanCodexRollout(path, signal) {
354
- const modern = new Map;
355
- const legacy = [];
356
- let malformedRecords = 0;
357
- let model = "";
358
- let sawMeta = false;
359
- let signature;
360
- let forkAnchorMs;
361
- for await (const line of historyLines(path, signal)) {
362
- signal?.throwIfAborted();
363
- if (!line.text.trim())
364
- continue;
420
+ var codexParser = (from) => {
421
+ const modern = new Map(from?.modern);
422
+ const legacy = [...from?.legacy ?? []];
423
+ let malformedRecords = from?.malformedRecords ?? 0;
424
+ const cursor = { ...from?.cursor ?? { model: "", sawMeta: false } };
425
+ const line = ({ text, terminated }) => {
426
+ if (!text.trim())
427
+ return;
365
428
  let value;
366
429
  try {
367
- value = JSON.parse(line.text);
430
+ value = JSON.parse(text);
368
431
  } catch {
369
- if (line.terminated)
432
+ if (terminated)
370
433
  malformedRecords++;
371
- continue;
434
+ return;
372
435
  }
373
436
  if (!Value.Check(envelope, value)) {
374
437
  malformedRecords++;
375
- continue;
438
+ return;
376
439
  }
377
440
  if (Value.Check(turnContext, value)) {
378
441
  if (value.payload.model !== undefined)
379
- model = value.payload.model;
380
- continue;
442
+ cursor.model = value.payload.model;
443
+ return;
381
444
  }
382
445
  if (Value.Check(sessionMeta, value)) {
383
- if (sawMeta)
384
- continue;
385
- sawMeta = true;
446
+ if (cursor.sawMeta)
447
+ return;
448
+ cursor.sawMeta = true;
386
449
  const forked = value.payload.forked_from_id !== undefined || value.payload.parent_thread_id !== undefined;
387
- if (forked)
388
- forkAnchorMs = parseMs(value.timestamp);
389
- continue;
450
+ const anchor = parseRecordedAt(value.timestamp);
451
+ if (forked && anchor !== null)
452
+ cursor.forkAnchorMs = anchor;
453
+ return;
390
454
  }
391
455
  if (Value.Check(usageRecord, value)) {
392
456
  const id = value.payload.response_id;
393
- if (id === undefined || model === "")
394
- continue;
457
+ if (id === undefined || cursor.model === "")
458
+ return;
395
459
  const usage = codexUsage(value.payload.usage);
396
460
  if (usage.totalTokens > 0 && !modern.has(id))
397
- modern.set(id, { model, usage });
398
- continue;
461
+ modern.set(id, { model: cursor.model, usage, at: parseRecordedAt(value.timestamp) });
462
+ return;
399
463
  }
400
464
  if (!Value.Check(tokenCount, value))
401
- continue;
465
+ return;
402
466
  const last = value.payload.info.last_token_usage;
403
- if (last === undefined || model === "")
404
- continue;
467
+ if (last === undefined || cursor.model === "")
468
+ return;
405
469
  const next = JSON.stringify(last);
406
- if (next === signature)
407
- continue;
408
- signature = next;
409
- if (forkAnchorMs !== undefined) {
410
- const at = parseMs(value.timestamp);
411
- if (at !== undefined && at - forkAnchorMs < FORK_COPY_MAX_GAP_MS) {
412
- forkAnchorMs = at;
413
- continue;
470
+ if (next === cursor.signature)
471
+ return;
472
+ cursor.signature = next;
473
+ const at = parseRecordedAt(value.timestamp);
474
+ if (cursor.forkAnchorMs !== undefined) {
475
+ if (at !== null && at - cursor.forkAnchorMs < FORK_COPY_MAX_GAP_MS) {
476
+ cursor.forkAnchorMs = at;
477
+ return;
414
478
  }
415
- forkAnchorMs = undefined;
479
+ delete cursor.forkAnchorMs;
416
480
  }
417
481
  const usage = codexUsage(last);
418
482
  if (usage.totalTokens > 0)
419
- legacy.push({ model, usage });
420
- }
421
- return { modern: [...modern], legacy, malformedRecords };
422
- }
483
+ legacy.push({ model: cursor.model, usage, at });
484
+ };
485
+ return {
486
+ line,
487
+ scan: () => ({
488
+ modern: [...modern],
489
+ legacy: [...legacy],
490
+ malformedRecords,
491
+ cursor: { ...cursor }
492
+ })
493
+ };
494
+ };
423
495
  async function* jsonlFiles(root, signal) {
424
496
  const directories = [root];
425
497
  for (const directory of directories) {
@@ -441,37 +513,37 @@ async function* jsonlFiles(root, signal) {
441
513
  }
442
514
  }
443
515
  }
444
- async function* historyLines(path, signal) {
445
- const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
446
- try {
447
- const stat = await file.stat();
448
- if (!stat.isFile())
449
- throw new Error("History is not a regular file.");
450
- const buffer = Buffer.alloc(64 * 1024);
451
- const decoder = new StringDecoder("utf8");
452
- let pending = "";
453
- let position = 0;
454
- while (position < stat.size) {
455
- signal?.throwIfAborted();
456
- const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, stat.size - position), position);
457
- if (bytesRead === 0)
458
- break;
459
- position += bytesRead;
460
- const lines = `${pending}${decoder.write(buffer.subarray(0, bytesRead))}`.split(`
461
- `);
462
- pending = lines.pop() ?? "";
463
- for (const text of lines)
464
- yield { text, terminated: true };
465
- }
466
- pending += decoder.end();
467
- if (pending)
468
- yield { text: pending, terminated: false };
469
- } finally {
470
- await file.close();
471
- }
472
- }
473
516
  function createUsageScanCaches2() {
474
- return { claudeCode: new Map, codex: new Map };
517
+ return { claudeCode: new UsageScanCache2, codex: new UsageScanCache2 };
518
+ }
519
+ var USAGE_SCAN_CACHE_VERSION = 1;
520
+ var usageScanCacheDocument = Type.Object({
521
+ version: Type.Literal(USAGE_SCAN_CACHE_VERSION),
522
+ claudeCode: Type.Array(Type.Tuple([Type.String(), fileScanSchema2(claudeCodeFileScan)])),
523
+ codex: Type.Array(Type.Tuple([Type.String(), fileScanSchema2(codexFileScan)]))
524
+ });
525
+ var usageScanCacheValidator = Compile(usageScanCacheDocument);
526
+ async function loadUsageScanCaches2(path) {
527
+ const document = await readCacheDocument2(path, usageScanCacheValidator);
528
+ if (document === undefined)
529
+ return createUsageScanCaches2();
530
+ return {
531
+ claudeCode: new UsageScanCache2(document.claudeCode),
532
+ codex: new UsageScanCache2(document.codex)
533
+ };
534
+ }
535
+ async function saveUsageScanCaches2(path, caches) {
536
+ if (!caches.claudeCode.changed && !caches.codex.changed)
537
+ return;
538
+ const saved = await writeCacheDocument2(path, {
539
+ version: USAGE_SCAN_CACHE_VERSION,
540
+ claudeCode: [...caches.claudeCode.entries()],
541
+ codex: [...caches.codex.entries()]
542
+ });
543
+ if (!saved)
544
+ return;
545
+ caches.claudeCode.markSaved();
546
+ caches.codex.markSaved();
475
547
  }
476
548
  async function readLocalUsage2(options) {
477
549
  const failed = (message) => {
@@ -481,11 +553,13 @@ async function readLocalUsage2(options) {
481
553
  const [claudeCode, codex] = await Promise.all([
482
554
  readClaudeCodeUsage2({
483
555
  models: options.models,
556
+ window: options.window,
484
557
  signal: options.signal,
485
558
  cache: options.caches?.claudeCode
486
559
  }).catch(() => failed("Could not read Claude Code history.")),
487
560
  readCodexUsage2({
488
561
  models: options.models,
562
+ window: options.window,
489
563
  signal: options.signal,
490
564
  cache: options.caches?.codex
491
565
  }).catch(() => failed("Could not read Codex history."))
@@ -494,4 +568,4 @@ async function readLocalUsage2(options) {
494
568
  return { claudeCode, codex };
495
569
  }
496
570
 
497
- export { readClaudeCodeUsage2, readCodexUsage2, createUsageScanCaches2, readLocalUsage2 };
571
+ export { readClaudeCodeUsage2, readCodexUsage2, createUsageScanCaches2, loadUsageScanCaches2, saveUsageScanCaches2, readLocalUsage2 };
@@ -0,0 +1,141 @@
1
+ // src/usage-history.ts
2
+ import { Buffer } from "node:buffer";
3
+ import { randomUUID } from "node:crypto";
4
+ import { constants } from "node:fs";
5
+ import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
6
+ import { dirname } from "node:path";
7
+ import { Type } from "typebox";
8
+ function fileScanSchema2(scan) {
9
+ return Type.Object({
10
+ size: Type.Number({ minimum: 0 }),
11
+ mtimeMs: Type.Number(),
12
+ position: Type.Number({ minimum: 0 }),
13
+ committed: scan
14
+ });
15
+ }
16
+
17
+ class UsageScanCache2 {
18
+ #files;
19
+ #changed = false;
20
+ constructor(files = []) {
21
+ this.#files = new Map(files);
22
+ }
23
+ keys() {
24
+ return this.#files.keys();
25
+ }
26
+ entries() {
27
+ return this.#files.entries();
28
+ }
29
+ get(path) {
30
+ return this.#files.get(path);
31
+ }
32
+ set(path, scan) {
33
+ if (this.#files.get(path) === scan)
34
+ return;
35
+ this.#files.set(path, scan);
36
+ this.#changed = true;
37
+ }
38
+ keepOnly(seen) {
39
+ for (const path of this.#files.keys()) {
40
+ if (seen.has(path))
41
+ continue;
42
+ this.#files.delete(path);
43
+ this.#changed = true;
44
+ }
45
+ }
46
+ get changed() {
47
+ return this.#changed;
48
+ }
49
+ markSaved() {
50
+ this.#changed = false;
51
+ }
52
+ }
53
+ async function readHistory2(path, info, parser, cache, signal) {
54
+ const hit = cache?.get(path);
55
+ const unchanged = hit !== undefined && hit.size === info.size && hit.mtimeMs === info.mtimeMs;
56
+ if (unchanged && hit.position === info.size)
57
+ return hit.committed;
58
+ const from = unchanged || hit !== undefined && info.size > hit.size ? hit : undefined;
59
+ const fold = parser(from?.committed);
60
+ let position = from?.position ?? 0;
61
+ let unterminated;
62
+ for await (const line of historyLines(path, position, signal)) {
63
+ signal?.throwIfAborted();
64
+ if (!line.terminated) {
65
+ unterminated = line;
66
+ continue;
67
+ }
68
+ fold.line(line);
69
+ position = line.end;
70
+ }
71
+ const committed = fold.scan();
72
+ cache?.set(path, unchanged ? hit : { size: info.size, mtimeMs: info.mtimeMs, position, committed });
73
+ if (unterminated === undefined)
74
+ return committed;
75
+ fold.line(unterminated);
76
+ return fold.scan();
77
+ }
78
+ async function* historyLines(path, start, signal) {
79
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
80
+ try {
81
+ const stat = await file.stat();
82
+ if (!stat.isFile())
83
+ throw new Error("History is not a regular file.");
84
+ const buffer = Buffer.alloc(64 * 1024);
85
+ let pending = Buffer.alloc(0);
86
+ let pendingStart = start;
87
+ let position = start;
88
+ while (position < stat.size) {
89
+ signal?.throwIfAborted();
90
+ const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, stat.size - position), position);
91
+ if (bytesRead === 0)
92
+ break;
93
+ position += bytesRead;
94
+ const read = buffer.subarray(0, bytesRead);
95
+ const chunk = pending.length === 0 ? read : Buffer.concat([pending, read]);
96
+ let from = 0;
97
+ for (let newline = chunk.indexOf(10);newline !== -1; newline = chunk.indexOf(10, from)) {
98
+ yield {
99
+ text: chunk.toString("utf8", from, newline),
100
+ terminated: true,
101
+ end: pendingStart + newline + 1
102
+ };
103
+ from = newline + 1;
104
+ }
105
+ pendingStart += from;
106
+ pending = Buffer.from(chunk.subarray(from));
107
+ }
108
+ if (pending.length > 0)
109
+ yield {
110
+ text: pending.toString("utf8"),
111
+ terminated: false,
112
+ end: pendingStart + pending.length
113
+ };
114
+ } finally {
115
+ await file.close();
116
+ }
117
+ }
118
+ async function readCacheDocument2(path, validator) {
119
+ try {
120
+ const value = JSON.parse(await readFile(path, "utf8"));
121
+ return validator.Check(value) ? value : undefined;
122
+ } catch {
123
+ return;
124
+ }
125
+ }
126
+ async function writeCacheDocument2(path, document) {
127
+ const temporary = `${path}.${randomUUID()}.tmp`;
128
+ try {
129
+ await mkdir(dirname(path), { recursive: true });
130
+ await writeFile(temporary, JSON.stringify(document), { mode: 384 });
131
+ await rename(temporary, path);
132
+ return true;
133
+ } catch {
134
+ await rm(temporary, { force: true }).catch(() => {
135
+ return;
136
+ });
137
+ return false;
138
+ }
139
+ }
140
+
141
+ export { fileScanSchema2, UsageScanCache2, readHistory2, readCacheDocument2, writeCacheDocument2 };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./canvas-preview-2zdyh9ny.js";
4
4
  import {
5
5
  package_default
6
- } from "./canvas-preview-g3qj3y3k.js";
6
+ } from "./canvas-preview-jeddjex8.js";
7
7
 
8
8
  // src/deploy/vercel/server/utils/host.ts
9
9
  import process from "node:process";
@@ -54,7 +54,7 @@ function expand(values, server) {
54
54
  value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => {
55
55
  const found = process.env[name];
56
56
  if (found === undefined)
57
- throw new Error(`${server} needs the environment variable ${name}. Set it where Nyte runs, then restart Nyte (for the CLI, \`nyte service stop\`).`);
57
+ throw new Error(`${server} needs the environment variable ${name}. Set it where Nyte runs, then restart Nyte.`);
58
58
  return found;
59
59
  })
60
60
  ]));