@token-dashboard/codex-usage-uploader 0.1.6 → 0.1.7

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/src/collector.js DELETED
@@ -1,676 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { createInterface } from 'node:readline';
5
- import { loadCodexAuthIdentity, identityIsBound, promptValue } from './auth.js';
6
- import { RolloutParser } from './parser.js';
7
- import { StateDb } from './state-db.js';
8
- import { nowTs, sleep, stableStringify, readPersistentCollectorId, writePersistentCollectorId } from './utils.js';
9
- import {
10
- ARCHIVED_SESSIONS_SOURCE_ROOT,
11
- MAX_BATCH_BYTES,
12
- MAX_EVENTS_PER_BATCH,
13
- PERSISTENT_COLLECTOR_ID_PATH,
14
- REGISTER_REFRESH_SECONDS,
15
- SCAN_CHUNK_MAX_BYTES,
16
- SCAN_CHUNK_MAX_EVENTS,
17
- SESSIONS_SOURCE_ROOT,
18
- } from './constants.js';
19
-
20
- export class CodexUsageUploader {
21
- constructor({
22
- sessionsDir,
23
- stateDbPath,
24
- backendUrl,
25
- intervalSeconds,
26
- codexAuthPath,
27
- persistentCollectorIdPath = PERSISTENT_COLLECTOR_ID_PATH,
28
- scanChunkMaxEvents = SCAN_CHUNK_MAX_EVENTS,
29
- scanChunkMaxBytes = SCAN_CHUNK_MAX_BYTES,
30
- }) {
31
- this.sessionsDir = sessionsDir;
32
- this.stateDb = new StateDb(stateDbPath);
33
- this.backendUrl = backendUrl?.replace(/\/+$/, '') || null;
34
- this.intervalSeconds = intervalSeconds;
35
- this.codexAuthPath = codexAuthPath;
36
- this.persistentCollectorIdPath = persistentCollectorIdPath;
37
- this.scanChunkMaxEvents = scanChunkMaxEvents;
38
- this.scanChunkMaxBytes = scanChunkMaxBytes;
39
- this.identity = this.ensureIdentity();
40
- }
41
-
42
- close() {
43
- this.stateDb.close();
44
- }
45
-
46
- ensureIdentity() {
47
- const identity = this.stateDb.getIdentity();
48
- if (!identity.collectorId) {
49
- const persisted = readPersistentCollectorId(this.persistentCollectorIdPath);
50
- identity.collectorId = persisted ?? `${Date.now().toString(16)}${Math.random()
51
- .toString(16)
52
- .slice(2, 14)}`;
53
- }
54
- writePersistentCollectorId(this.persistentCollectorIdPath, identity.collectorId);
55
- if (!identity.deviceId) {
56
- identity.deviceId = `${os.hostname()}-${Math.random()
57
- .toString(16)
58
- .slice(2, 14)}`;
59
- }
60
- if (!identity.hostname) {
61
- identity.hostname = os.hostname();
62
- }
63
- const detected = loadCodexAuthIdentity(this.codexAuthPath);
64
- for (const key of ['employeeEmail', 'employeeName']) {
65
- if (!identity[key] && detected[key]) {
66
- identity[key] = detected[key];
67
- }
68
- }
69
- this.stateDb.setIdentity(identity);
70
- return this.stateDb.getIdentity();
71
- }
72
-
73
- resolveIdentityValues({
74
- employeeId = undefined,
75
- employeeEmail = undefined,
76
- employeeName = undefined,
77
- deviceId = undefined,
78
- hostname = undefined,
79
- } = {}) {
80
- const current = this.stateDb.getIdentity();
81
- const detected = loadCodexAuthIdentity(this.codexAuthPath);
82
- return {
83
- employeeId:
84
- employeeId !== undefined ? employeeId : current.employeeId ?? null,
85
- employeeEmail:
86
- employeeEmail !== undefined
87
- ? employeeEmail
88
- : current.employeeEmail ?? detected.employeeEmail ?? null,
89
- employeeName:
90
- employeeName !== undefined
91
- ? employeeName
92
- : current.employeeName ?? detected.employeeName ?? null,
93
- deviceId: deviceId !== undefined ? deviceId : current.deviceId ?? null,
94
- hostname: hostname !== undefined ? hostname : current.hostname ?? null,
95
- };
96
- }
97
-
98
- configureIdentity({
99
- employeeId = null,
100
- employeeEmail = null,
101
- employeeName = null,
102
- deviceId = undefined,
103
- hostname = undefined,
104
- } = {}) {
105
- const updates = { employeeId, employeeEmail, employeeName };
106
- if (!identityIsBound(updates)) {
107
- throw new Error(
108
- 'At least one of employeeId, employeeEmail, or employeeName must be provided.',
109
- );
110
- }
111
- if (deviceId !== undefined) updates.deviceId = deviceId;
112
- if (hostname !== undefined) updates.hostname = hostname;
113
- this.stateDb.setIdentity(updates);
114
- this.identity = this.ensureIdentity();
115
- return this.identity;
116
- }
117
-
118
- configureIdentityWithDefaults(overrides = {}) {
119
- const resolved = this.resolveIdentityValues(overrides);
120
- return this.configureIdentity(resolved);
121
- }
122
-
123
- async configureIdentityInteractive(overrides = {}) {
124
- const resolved = this.resolveIdentityValues(overrides);
125
- const detected = loadCodexAuthIdentity(this.codexAuthPath);
126
- if (detected.employeeEmail || detected.employeeName) {
127
- console.log(
128
- 'Detected identity defaults from ~/.codex/auth.json. Press Enter to accept.',
129
- );
130
- }
131
- if (overrides.employeeEmail === undefined) {
132
- resolved.employeeEmail = await promptValue(
133
- 'Employee email',
134
- resolved.employeeEmail,
135
- );
136
- }
137
- if (identityIsBound(resolved)) {
138
- return this.configureIdentity(resolved);
139
- }
140
- if (overrides.employeeName === undefined) {
141
- resolved.employeeName = await promptValue(
142
- 'Employee name',
143
- resolved.employeeName,
144
- );
145
- }
146
- if (identityIsBound(resolved)) {
147
- return this.configureIdentity(resolved);
148
- }
149
- if (overrides.employeeId === undefined) {
150
- resolved.employeeId = await promptValue(
151
- 'Employee ID',
152
- resolved.employeeId,
153
- );
154
- }
155
- return this.configureIdentity(resolved);
156
- }
157
-
158
- resetBackfillState() {
159
- this.stateDb.resetBackfillState();
160
- }
161
-
162
- getQueueStats() {
163
- return this.stateDb.getQueueStats();
164
- }
165
-
166
- getArchivedSessionsDir() {
167
- return path.join(path.dirname(this.sessionsDir), ARCHIVED_SESSIONS_SOURCE_ROOT);
168
- }
169
-
170
- getScanRoots({ includeArchived = false } = {}) {
171
- const roots = [
172
- {
173
- sourceRoot: SESSIONS_SOURCE_ROOT,
174
- dir: this.sessionsDir,
175
- },
176
- ];
177
- if (includeArchived) {
178
- roots.push({
179
- sourceRoot: ARCHIVED_SESSIONS_SOURCE_ROOT,
180
- dir: this.getArchivedSessionsDir(),
181
- });
182
- }
183
- return roots;
184
- }
185
-
186
- iterRolloutFiles(rootDir) {
187
- if (!fs.existsSync(rootDir)) return [];
188
- const files = [];
189
- const walk = (dir) => {
190
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
191
- const fullPath = path.join(dir, entry.name);
192
- if (entry.isDirectory()) {
193
- walk(fullPath);
194
- } else if (entry.isFile() && /^rollout-.*\.jsonl$/.test(entry.name)) {
195
- files.push(fullPath);
196
- }
197
- }
198
- };
199
- walk(rootDir);
200
- files.sort();
201
- return files;
202
- }
203
-
204
- buildSnapshot({ includeArchived = false } = {}) {
205
- const entries = [];
206
- for (const root of this.getScanRoots({ includeArchived })) {
207
- for (const filePath of this.iterRolloutFiles(root.dir)) {
208
- const relpath = path.relative(root.dir, filePath).split(path.sep).join('/');
209
- const stat = fs.statSync(filePath, { bigint: true });
210
- const existing = this.stateDb.getFileState(root.sourceRoot, relpath);
211
- const fallbackState =
212
- !existing && root.sourceRoot === ARCHIVED_SESSIONS_SOURCE_ROOT
213
- ? this.stateDb.getFileState(SESSIONS_SOURCE_ROOT, relpath)
214
- : null;
215
- if (
216
- existing &&
217
- existing.file_size === stat.size &&
218
- existing.file_mtime_ns === stat.mtimeNs
219
- ) {
220
- continue;
221
- }
222
- entries.push({
223
- filePath,
224
- relpath,
225
- sourceRoot: root.sourceRoot,
226
- progressPath: `${root.sourceRoot}/${relpath}`,
227
- snapshotSize: stat.size,
228
- snapshotMtimeNs: stat.mtimeNs,
229
- fallbackState,
230
- });
231
- }
232
- }
233
- return entries;
234
- }
235
-
236
- buildBackfillSnapshot() {
237
- return this.buildSnapshot({ includeArchived: true });
238
- }
239
-
240
- emptyPayload() {
241
- return { sessions: [], turns: [], events: [] };
242
- }
243
-
244
- payloadHasData(payload) {
245
- return Boolean(
246
- payload.sessions.length || payload.turns.length || payload.events.length,
247
- );
248
- }
249
-
250
- payloadBytes(payload) {
251
- return Buffer.byteLength(stableStringify(payload), 'utf8');
252
- }
253
-
254
- mergePayload(basePayload, incoming) {
255
- const sessions = new Map(
256
- (basePayload.sessions ?? []).map((item) => [item.sessionId, item]),
257
- );
258
- const turns = new Map(
259
- (basePayload.turns ?? []).map((item) => [item.turnId, item]),
260
- );
261
- const events = new Map(
262
- (basePayload.events ?? []).map((item) => [item.eventUid, item]),
263
- );
264
- for (const item of incoming.sessions ?? []) sessions.set(item.sessionId, item);
265
- for (const item of incoming.turns ?? []) turns.set(item.turnId, item);
266
- for (const item of incoming.events ?? []) events.set(item.eventUid, item);
267
- return {
268
- sessions: [...sessions.values()],
269
- turns: [...turns.values()],
270
- events: [...events.values()],
271
- };
272
- }
273
-
274
- shouldFlushChunk(payload) {
275
- if (!this.payloadHasData(payload)) return false;
276
- return (
277
- payload.events.length >= this.scanChunkMaxEvents ||
278
- this.payloadBytes(payload) >= this.scanChunkMaxBytes
279
- );
280
- }
281
-
282
- wouldExceedUploadThreshold(payload) {
283
- if (!this.payloadHasData(payload)) return false;
284
- return (
285
- payload.events.length > MAX_EVENTS_PER_BATCH ||
286
- this.payloadBytes(payload) > MAX_BATCH_BYTES
287
- );
288
- }
289
-
290
- appendPayloadToBuffer(incoming) {
291
- if (!this.payloadHasData(incoming)) {
292
- return 0;
293
- }
294
- const existing = this.stateDb.getBufferingBatch();
295
- let queuedBatches = 0;
296
- let payloadToSave = incoming;
297
-
298
- if (existing) {
299
- const basePayload = JSON.parse(existing.payload_json);
300
- const merged = this.mergePayload(basePayload, incoming);
301
- if (this.wouldExceedUploadThreshold(merged)) {
302
- queuedBatches += this.stateDb.sealStaleBatches(true);
303
- } else {
304
- payloadToSave = merged;
305
- }
306
- }
307
-
308
- this.stateDb.saveBufferingPayload(payloadToSave);
309
- return queuedBatches + (this.stateDb.sealBufferIfThresholdHit() ? 1 : 0);
310
- }
311
-
312
- async scanSnapshotEntries(entries, { onFileProcessed } = {}) {
313
- const result = {
314
- filesScanned: 0,
315
- sessions: 0,
316
- turns: 0,
317
- events: 0,
318
- batchesQueued: 0,
319
- };
320
-
321
- for (const entry of entries) {
322
- const {
323
- filePath,
324
- relpath,
325
- sourceRoot,
326
- snapshotSize,
327
- snapshotMtimeNs,
328
- fallbackState,
329
- } = entry;
330
- const existing = this.stateDb.getFileState(sourceRoot, relpath);
331
- const baselineState = existing ?? fallbackState ?? null;
332
- result.filesScanned += 1;
333
-
334
- if (
335
- !existing &&
336
- fallbackState &&
337
- fallbackState.file_size === snapshotSize &&
338
- fallbackState.file_mtime_ns === snapshotMtimeNs
339
- ) {
340
- this.stateDb.upsertFileState(
341
- sourceRoot,
342
- relpath,
343
- snapshotSize,
344
- snapshotMtimeNs,
345
- Number(fallbackState.last_line_no),
346
- JSON.parse(fallbackState.state_json || '{}'),
347
- );
348
- onFileProcessed?.({
349
- entry,
350
- filesProcessed: result.filesScanned,
351
- totals: {
352
- filesScanned: result.filesScanned,
353
- sessions: result.sessions,
354
- turns: result.turns,
355
- events: result.events,
356
- batchesQueued: result.batchesQueued,
357
- },
358
- });
359
- continue;
360
- }
361
-
362
- let parserState = {};
363
- let startLineNo = 0;
364
- if (baselineState && snapshotSize > baselineState.file_size) {
365
- startLineNo = Number(baselineState.last_line_no);
366
- parserState = JSON.parse(baselineState.state_json || '{}');
367
- }
368
-
369
- const parser = new RolloutParser(this.identity, relpath, parserState);
370
- let fileChunk = this.emptyPayload();
371
- let lastLineNo = 0;
372
- let lineNo = 0;
373
-
374
- if (Number(snapshotSize) > 0) {
375
- const input = fs.createReadStream(filePath, {
376
- encoding: 'utf8',
377
- start: 0,
378
- end: Number(snapshotSize) - 1,
379
- });
380
- const rl = createInterface({ input, crlfDelay: Infinity });
381
-
382
- try {
383
- for await (const line of rl) {
384
- lineNo += 1;
385
- if (!line) continue;
386
- lastLineNo = lineNo;
387
- if (lineNo <= startLineNo) continue;
388
- const parsed = parser.processLine(lineNo, line);
389
- result.sessions += parsed.sessions.length;
390
- result.turns += parsed.turns.length;
391
- result.events += parsed.events.length;
392
- fileChunk = this.mergePayload(fileChunk, parsed);
393
- if (this.shouldFlushChunk(fileChunk)) {
394
- result.batchesQueued += this.appendPayloadToBuffer(fileChunk);
395
- fileChunk = this.emptyPayload();
396
- }
397
- }
398
- } finally {
399
- rl.close();
400
- }
401
- }
402
-
403
- if (this.payloadHasData(fileChunk)) {
404
- result.batchesQueued += this.appendPayloadToBuffer(fileChunk);
405
- }
406
-
407
- this.stateDb.upsertFileState(
408
- sourceRoot,
409
- relpath,
410
- snapshotSize,
411
- snapshotMtimeNs,
412
- lastLineNo,
413
- parser.exportState(),
414
- );
415
-
416
- onFileProcessed?.({
417
- entry,
418
- filesProcessed: result.filesScanned,
419
- totals: {
420
- filesScanned: result.filesScanned,
421
- sessions: result.sessions,
422
- turns: result.turns,
423
- events: result.events,
424
- batchesQueued: result.batchesQueued,
425
- },
426
- });
427
- }
428
-
429
- return result;
430
- }
431
-
432
- async scanRollouts() {
433
- return this.scanSnapshotEntries(this.buildSnapshot());
434
- }
435
-
436
- collectorRequestBody() {
437
- return {
438
- collectorId: this.identity.collectorId,
439
- deviceId: this.identity.deviceId,
440
- hostname: this.identity.hostname ?? null,
441
- employeeId: this.identity.employeeId ?? null,
442
- employeeEmail: this.identity.employeeEmail ?? null,
443
- employeeName: this.identity.employeeName ?? null,
444
- };
445
- }
446
-
447
- sanitizeUploadPayload(payload) {
448
- return {
449
- sessions: Array.isArray(payload.sessions) ? payload.sessions : [],
450
- turns: Array.isArray(payload.turns) ? payload.turns : [],
451
- events: Array.isArray(payload.events)
452
- ? payload.events.map((event) => ({
453
- ...event,
454
- credits:
455
- typeof event?.credits === 'number' && Number.isFinite(event.credits)
456
- ? event.credits
457
- : event?.credits &&
458
- typeof event.credits === 'object' &&
459
- typeof event.credits.balance === 'number' &&
460
- Number.isFinite(event.credits.balance)
461
- ? event.credits.balance
462
- : null,
463
- }))
464
- : [],
465
- };
466
- }
467
-
468
- async postJson(apiPath, payload) {
469
- if (!this.backendUrl) throw new Error('backendUrl is not configured');
470
- const response = await fetch(`${this.backendUrl}${apiPath}`, {
471
- method: 'POST',
472
- headers: { 'Content-Type': 'application/json' },
473
- body: JSON.stringify(payload),
474
- });
475
- if (!response.ok) {
476
- const text = await response.text();
477
- const detail = text ? `: ${text.slice(0, 500)}` : '';
478
- throw new Error(`HTTP ${response.status} ${response.statusText}${detail}`);
479
- }
480
- const text = await response.text();
481
- return text ? JSON.parse(text) : {};
482
- }
483
-
484
- async ensureRemoteRegistration() {
485
- if (!this.backendUrl) return;
486
- const checkpoint = this.stateDb.getCheckpoint('last_register_at');
487
- if (checkpoint && nowTs() - Number(checkpoint) < REGISTER_REFRESH_SECONDS) {
488
- return;
489
- }
490
- await this.postJson(
491
- '/codex-usage/collectors/register',
492
- this.collectorRequestBody(),
493
- );
494
- this.stateDb.setCheckpoint('last_register_at', String(nowTs()));
495
- }
496
-
497
- async flushPendingBatches({ failFast = false, concurrency = 5, onBatchUploaded } = {}) {
498
- if (!this.backendUrl) {
499
- return { uploadedBatches: 0, failedBatches: 0, lastError: null };
500
- }
501
- try {
502
- await this.ensureRemoteRegistration();
503
- } catch (error) {
504
- if (failFast) {
505
- throw new Error(
506
- `Collector registration failed: ${
507
- error instanceof Error ? error.message : String(error)
508
- }`,
509
- );
510
- }
511
- return { uploadedBatches: 0, failedBatches: 1, lastError: error };
512
- }
513
-
514
- let uploadedBatches = 0;
515
- let failedBatches = 0;
516
- let lastError = null;
517
- const collectorBody = this.collectorRequestBody();
518
-
519
- const rows = this.stateDb.iterPendingBatches();
520
-
521
- const uploadOne = async (row) => {
522
- const payload = this.sanitizeUploadPayload(JSON.parse(row.payload_json));
523
- const requestBody = {
524
- idempotencyKey: row.batch_key,
525
- collector: collectorBody,
526
- payloadSizeBytes: Number(row.payload_bytes),
527
- sessions: payload.sessions ?? [],
528
- turns: payload.turns ?? [],
529
- events: payload.events ?? [],
530
- };
531
- await this.postJson('/codex-usage/upload', requestBody);
532
- };
533
-
534
- const pending = new Set();
535
- let rowIndex = 0;
536
-
537
- const enqueue = () => {
538
- while (pending.size < concurrency && rowIndex < rows.length) {
539
- const row = rows[rowIndex++];
540
- const task = uploadOne(row)
541
- .then(() => {
542
- this.stateDb.markBatchUploaded(row.id);
543
- uploadedBatches += 1;
544
- onBatchUploaded?.({ row, uploadedBatches });
545
- })
546
- .catch((error) => {
547
- this.stateDb.markBatchFailed(
548
- row.id,
549
- Number(row.attempt_count) + 1,
550
- error instanceof Error ? error.message : String(error),
551
- );
552
- failedBatches += 1;
553
- lastError = error;
554
- if (failFast) {
555
- throw error;
556
- }
557
- })
558
- .finally(() => {
559
- pending.delete(task);
560
- });
561
- pending.add(task);
562
- }
563
- };
564
-
565
- enqueue();
566
- while (pending.size > 0) {
567
- try {
568
- await Promise.race(pending);
569
- } catch {
570
- if (failFast) break;
571
- }
572
- enqueue();
573
- }
574
-
575
- if (failFast && lastError) {
576
- throw new Error(
577
- `Upload batch failed: ${
578
- lastError instanceof Error ? lastError.message : String(lastError)
579
- }`,
580
- );
581
- }
582
-
583
- return { uploadedBatches, failedBatches, lastError };
584
- }
585
-
586
- async runCycle({ forceSeal = false } = {}) {
587
- const result = await this.scanRollouts();
588
- result.batchesQueued += this.stateDb.sealStaleBatches(forceSeal);
589
- const flushResult = await this.flushPendingBatches();
590
- result.uploadedBatches = flushResult.uploadedBatches;
591
- result.failedBatches = flushResult.failedBatches;
592
- const queueStats = this.getQueueStats();
593
- return {
594
- ...result,
595
- ...queueStats,
596
- remainingQueuedBatches:
597
- queueStats.bufferingBatchCount +
598
- queueStats.pendingBatchCount +
599
- queueStats.retryingBatchCount,
600
- remainingQueuedEvents: queueStats.queuedEvents,
601
- };
602
- }
603
-
604
- async runForegroundCatchUp({ snapshot, onProgress } = {}) {
605
- const startedAt = Date.now();
606
- const backfillSnapshot = snapshot ?? this.buildBackfillSnapshot();
607
- const totalFiles = backfillSnapshot.length;
608
- onProgress?.({
609
- phase: 'start',
610
- totalFiles,
611
- filesProcessed: 0,
612
- eventsParsed: 0,
613
- batchesQueued: 0,
614
- });
615
-
616
- let scanResult;
617
- try {
618
- scanResult = await this.scanSnapshotEntries(backfillSnapshot, {
619
- onFileProcessed: ({ entry, filesProcessed, totals }) => {
620
- onProgress?.({
621
- phase: 'file',
622
- file: entry.progressPath ?? entry.relpath,
623
- totalFiles,
624
- filesProcessed,
625
- eventsParsed: totals.events,
626
- batchesQueued: totals.batchesQueued,
627
- });
628
- },
629
- });
630
- } catch (error) {
631
- onProgress?.({
632
- phase: 'error',
633
- stage: 'scan',
634
- message: error instanceof Error ? error.message : String(error),
635
- });
636
- throw error;
637
- }
638
-
639
- scanResult.batchesQueued += this.stateDb.sealStaleBatches(true);
640
-
641
- await this.ensureRemoteRegistration();
642
-
643
- const queueStats = this.getQueueStats();
644
- const result = {
645
- totalFiles,
646
- filesProcessed: totalFiles,
647
- eventsParsed: scanResult.events,
648
- sessionsParsed: scanResult.sessions,
649
- turnsParsed: scanResult.turns,
650
- batchesQueued: scanResult.batchesQueued,
651
- pendingBatches:
652
- queueStats.pendingBatchCount + queueStats.retryingBatchCount,
653
- pendingEvents: queueStats.queuedEvents,
654
- durationMs: Date.now() - startedAt,
655
- };
656
-
657
- onProgress?.({
658
- phase: 'done',
659
- ...result,
660
- });
661
-
662
- return result;
663
- }
664
-
665
- async watch() {
666
- let firstCycle = true;
667
- while (true) {
668
- const result = await this.runCycle({ forceSeal: firstCycle });
669
- console.log(
670
- `[uploader] scanned_files=${result.filesScanned} sessions=${result.sessions} turns=${result.turns} events=${result.events} queued_batches=${result.batchesQueued} uploaded_batches=${result.uploadedBatches} remaining_batches=${result.remainingQueuedBatches}`,
671
- );
672
- firstCycle = false;
673
- await sleep(this.intervalSeconds * 1000);
674
- }
675
- }
676
- }
package/src/constants.js DELETED
@@ -1,44 +0,0 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
-
4
- export const PRODUCT_NAME = 'Codex 用量上报';
5
- export const CLI_NAME = 'codex-usage-uploader';
6
- export const SESSIONS_SOURCE_ROOT = 'sessions';
7
- export const ARCHIVED_SESSIONS_SOURCE_ROOT = 'archived_sessions';
8
- export const DEFAULT_CODEX_AUTH_PATH = path.join(os.homedir(), '.codex', 'auth.json');
9
- export const DEFAULT_SESSIONS_DIR = path.join(os.homedir(), '.codex', 'sessions');
10
- export const DEFAULT_INSTALL_ROOT = path.join(os.homedir(), '.codex-usage-uploader');
11
- export const DEFAULT_CONFIG_FILE = path.join(DEFAULT_INSTALL_ROOT, 'config.json');
12
- export const DEFAULT_STATE_DB = path.join(DEFAULT_INSTALL_ROOT, 'state.sqlite');
13
- export const DEFAULT_LOG_DIR = path.join(DEFAULT_INSTALL_ROOT, 'logs');
14
- export const DEFAULT_STDOUT_LOG_PATH = path.join(DEFAULT_LOG_DIR, 'stdout.log');
15
- export const DEFAULT_STDERR_LOG_PATH = path.join(DEFAULT_LOG_DIR, 'stderr.log');
16
- export const DEFAULT_APP_ROOT = path.join(DEFAULT_INSTALL_ROOT, 'app');
17
- export const DEFAULT_CURRENT_APP_DIR = path.join(DEFAULT_APP_ROOT, 'current');
18
- export const DEFAULT_LOCAL_BIN_DIR = path.join(DEFAULT_INSTALL_ROOT, 'bin');
19
- export const DEFAULT_LOCAL_BIN_PATH = path.join(DEFAULT_LOCAL_BIN_DIR, CLI_NAME);
20
- export const DEFAULT_HOME_BIN_LINK = path.join(os.homedir(), 'bin', CLI_NAME);
21
- export const DEFAULT_LAUNCHD_LABEL = 'com.token-dashboard.codex-usage-uploader';
22
- export const DEFAULT_PLIST_PATH = path.join(
23
- DEFAULT_INSTALL_ROOT,
24
- 'launchd',
25
- `${DEFAULT_LAUNCHD_LABEL}.plist`,
26
- );
27
- export const DEFAULT_LAUNCH_AGENT_PATH = path.join(
28
- os.homedir(),
29
- 'Library',
30
- 'LaunchAgents',
31
- `${DEFAULT_LAUNCHD_LABEL}.plist`,
32
- );
33
- export const PERSISTENT_COLLECTOR_ID_PATH = path.join(
34
- os.homedir(),
35
- '.codex-usage-uploader-collector-id',
36
- );
37
- export const DEFAULT_BACKEND_URL = 'http://101.126.66.51:8086';
38
- export const SCAN_CHUNK_MAX_EVENTS = 50;
39
- export const SCAN_CHUNK_MAX_BYTES = 256 * 1024;
40
- export const MAX_EVENTS_PER_BATCH = 200;
41
- export const MAX_BATCH_BYTES = 1_000_000;
42
- export const MAX_BUFFER_AGE_SECONDS = 60;
43
- export const REGISTER_REFRESH_SECONDS = 600;
44
- export const STATUS_ONLINE_THRESHOLD_SECONDS = 600;