codexmeter 1.0.27 → 1.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmeter",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Local telemetry dashboard for Codex CLI usage",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,6 +3,12 @@ import { fetchPricing } from './pricing-fetch.js';
3
3
  // Pricing map: populated by initPricing() from online lookup with local fallback
4
4
  let PRICING = null;
5
5
 
6
+ const PRICE_CHANGE_DATE = '2026-07-30';
7
+ const PREVIOUS_PRICING = {
8
+ 'gpt-5.6-terra': { input: 2.50, output: 15.00, cached_input: 0.25, cache_write: 3.125 },
9
+ 'gpt-5.6-luna': { input: 1.00, output: 6.00, cached_input: 0.10, cache_write: 1.25 },
10
+ };
11
+
6
12
  /** Initialize pricing from online source; falls back to local catalog on timeout/failure. Call before cost calculations. */
7
13
  export async function initPricing() {
8
14
  PRICING = await fetchPricing();
@@ -14,15 +20,22 @@ function getPricing() {
14
20
  return PRICING;
15
21
  }
16
22
 
23
+ function getPricingEntry(modelName, pricingDate) {
24
+ if (pricingDate && pricingDate < PRICE_CHANGE_DATE && PREVIOUS_PRICING[modelName]) {
25
+ return PREVIOUS_PRICING[modelName];
26
+ }
27
+ return getPricing()[modelName];
28
+ }
29
+
17
30
  // Codex sessions: ~75% input (mostly cached), ~25% output
18
31
  // With ~95% prompt cache hit rate (from Codex Monitor)
19
32
  const INPUT_FRACTION = 0.75;
20
33
  const OUTPUT_FRACTION = 0.25;
21
34
  const CACHE_HIT_RATE = 0.95;
22
35
 
23
- export function getCacheAwareRate(modelName) {
36
+ export function getCacheAwareRate(modelName, pricingDate = null) {
24
37
  if (!modelName) return null;
25
- const entry = getPricing()[modelName];
38
+ const entry = getPricingEntry(modelName, pricingDate);
26
39
  if (!entry) return null;
27
40
 
28
41
  const effectiveInputRate =
@@ -32,9 +45,9 @@ export function getCacheAwareRate(modelName) {
32
45
  return INPUT_FRACTION * effectiveInputRate + OUTPUT_FRACTION * entry.output;
33
46
  }
34
47
 
35
- export function calculateCostFromUsage(modelName, usage) {
48
+ export function calculateCostFromUsage(modelName, usage, pricingDate = null) {
36
49
  if (!modelName || !usage) return null;
37
- const entry = getPricing()[modelName];
50
+ const entry = getPricingEntry(modelName, pricingDate);
38
51
  if (!entry) return null;
39
52
 
40
53
  const inputTokens = usage.input_tokens || 0;
@@ -52,19 +65,35 @@ export function calculateCostFromUsage(modelName, usage) {
52
65
  ) / 1_000_000;
53
66
  }
54
67
 
55
- function estimateCostFromTotalTokens(modelName, tokensUsed) {
56
- const rate = getCacheAwareRate(modelName);
68
+ function estimateCostFromTotalTokens(modelName, tokensUsed, pricingDate) {
69
+ const rate = getCacheAwareRate(modelName, pricingDate);
57
70
  if (rate === null) return null;
58
71
  return (tokensUsed / 1_000_000) * rate;
59
72
  }
60
73
 
61
- export function priceSession(modelName, { totalTokens = 0, usageBuckets = null } = {}) {
62
- const exactCost = calculateCostFromUsage(modelName, usageBuckets);
74
+ export function priceSession(modelName, {
75
+ totalTokens = 0,
76
+ usageBuckets = null,
77
+ usageByDay = null,
78
+ pricingDate = null,
79
+ } = {}) {
80
+ const dailyUsage = Object.entries(usageByDay || {});
81
+ if (dailyUsage.length) {
82
+ let cost = 0;
83
+ for (const [day, usage] of dailyUsage) {
84
+ const dailyCost = calculateCostFromUsage(modelName, usage, day);
85
+ if (dailyCost === null) return { cost: null, source: 'unpriced' };
86
+ cost += dailyCost;
87
+ }
88
+ return { cost, source: 'exact' };
89
+ }
90
+
91
+ const exactCost = calculateCostFromUsage(modelName, usageBuckets, pricingDate);
63
92
  if (exactCost !== null) {
64
93
  return { cost: exactCost, source: 'exact' };
65
94
  }
66
95
 
67
- const heuristicCost = estimateCostFromTotalTokens(modelName, totalTokens);
96
+ const heuristicCost = estimateCostFromTotalTokens(modelName, totalTokens, pricingDate);
68
97
  if (heuristicCost !== null) {
69
98
  return { cost: heuristicCost, source: 'heuristic' };
70
99
  }
@@ -80,7 +109,7 @@ export function getModelPricing(modelName) {
80
109
  return getPricing()[modelName] || null;
81
110
  }
82
111
 
83
- export const CATALOG_VERSION = '2026-07-08';
112
+ export const CATALOG_VERSION = '2026-07-30';
84
113
  export const CACHE_ASSUMPTIONS = {
85
114
  input_fraction: INPUT_FRACTION,
86
115
  output_fraction: OUTPUT_FRACTION,
package/server/ingest.js CHANGED
@@ -104,8 +104,8 @@ export async function runIngest(codexHome, state, opts = {}) {
104
104
  rollout_path: t.rollout_path,
105
105
  source_raw: t.source_raw,
106
106
  cwd_raw: t.cwd_raw,
107
- repo_key: deriveRepoKey(nc),
108
- repo_label: deriveRepoLabel(nc),
107
+ repo_key: deriveRepoKey(nc, t.git_origin_url),
108
+ repo_label: deriveRepoLabel(nc, t.git_origin_url),
109
109
  started_at: t.created_at,
110
110
  ended_at: t.updated_at,
111
111
  elapsed_seconds: null,
@@ -207,10 +207,10 @@ export async function runIngest(codexHome, state, opts = {}) {
207
207
  s.usage_by_day = buildUsageByDayMetrics(s.model_name, s._usage_by_day_raw);
208
208
  s.has_usage_by_day = s.usage_by_day.length > 0;
209
209
  }
210
- delete s._usage_by_day_raw;
211
210
  delete s._first_usage_timestamp_ms;
212
211
  delete s._usage_reset_detected;
213
212
  finalizeSessionMetrics(s, toDayKey);
213
+ delete s._usage_by_day_raw;
214
214
  s.live_sort_day = deriveLiveSortDay(s, toDayKey);
215
215
  s.materialized = true;
216
216
  }
@@ -529,6 +529,8 @@ function finalizeSessionMetrics(session, toDayKey) {
529
529
  const priced = priceSession(session.model_name, {
530
530
  totalTokens: session.tokens_used,
531
531
  usageBuckets: session.usage_total,
532
+ usageByDay: session._usage_by_day_raw,
533
+ pricingDate: session.started_at ? toDayKey(session.started_at * 1000) : null,
532
534
  });
533
535
  session.cost = priced.cost;
534
536
  session.cost_source = priced.source;
@@ -539,7 +541,7 @@ function buildUsageByDayMetrics(modelName, usageByDay) {
539
541
  const entries = [];
540
542
  for (const [dayKey, usage] of Object.entries(usageByDay || {})) {
541
543
  const tokens = (usage?.input_tokens || 0) + (usage?.output_tokens || 0);
542
- const cost = calculateCostFromUsage(modelName, usage);
544
+ const cost = calculateCostFromUsage(modelName, usage, dayKey);
543
545
  entries.push({
544
546
  day: dayKey,
545
547
  tokens,
@@ -25,13 +25,15 @@ export function normalizeCwd(cwd) {
25
25
  return p;
26
26
  }
27
27
 
28
- export function deriveRepoKey(normalizedCwd) {
29
- const label = deriveRepoLabel(normalizedCwd);
28
+ export function deriveRepoKey(normalizedCwd, gitOriginUrl) {
29
+ const label = deriveRepoLabel(normalizedCwd, gitOriginUrl);
30
30
  return label === 'unknown' ? 'unknown' : `repo:${label}`;
31
31
  }
32
32
 
33
- export function deriveRepoLabel(normalizedCwd) {
33
+ export function deriveRepoLabel(normalizedCwd, gitOriginUrl) {
34
34
  if (!normalizedCwd) return 'unknown';
35
+ const originLabel = deriveOriginRepoLabel(gitOriginUrl);
36
+ if (originLabel) return originLabel;
35
37
  const worktreeMatch = normalizedCwd.match(/\.codex\/worktrees\/[^/]+\/([^/]+)/);
36
38
  if (worktreeMatch) return collapseWorktreeLabel(worktreeMatch[1]);
37
39
  if (normalizedCwd.includes('.codex')) return '.codex';
@@ -46,6 +48,15 @@ function collapseWorktreeLabel(label) {
46
48
  .replace(/-worktrees?-[a-z0-9._-]+$/i, '') || 'unknown';
47
49
  }
48
50
 
51
+ function deriveOriginRepoLabel(gitOriginUrl) {
52
+ const clean = String(gitOriginUrl || '')
53
+ .trim()
54
+ .replace(/\\/g, '/')
55
+ .replace(/[?#].*$/, '')
56
+ .replace(/\/+$/, '');
57
+ return clean.split('/').pop()?.replace(/\.git$/i, '').toLowerCase() || null;
58
+ }
59
+
49
60
  export function classifyAgentFamily(agentRole) {
50
61
  if (!agentRole) return 'generic';
51
62
  if (AGENT_FAMILY_MAP[agentRole]) return AGENT_FAMILY_MAP[agentRole];
@@ -1,8 +1,8 @@
1
1
  // Local pricing catalog for models used in codex-cli/codex app lifetime.
2
2
  const FALLBACK = {
3
3
  'gpt-5.6-sol': { input: 5.00, output: 30.00, cached_input: 0.50, cache_write: 6.25 },
4
- 'gpt-5.6-terra': { input: 2.50, output: 15.00, cached_input: 0.25, cache_write: 3.125 },
5
- 'gpt-5.6-luna': { input: 1.00, output: 6.00, cached_input: 0.10, cache_write: 1.25 },
4
+ 'gpt-5.6-terra': { input: 2.00, output: 12.00, cached_input: 0.20, cache_write: 2.50 },
5
+ 'gpt-5.6-luna': { input: 0.20, output: 1.20, cached_input: 0.02, cache_write: 0.25 },
6
6
  'gpt-5.5': { input: 5.00, output: 30.00, cached_input: 0.50 },
7
7
  'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
8
8
  'gpt-5-mini': { input: 0.75, output: 4.50, cached_input: 0.075},
@@ -54,6 +54,7 @@ export function readThreads(codexHome, onProgress) {
54
54
  parent_thread_id: row.parent_thread_id || null,
55
55
  cli_version: row.cli_version || '',
56
56
  git_branch: row.git_branch || null,
57
+ git_origin_url: row.git_origin_url || null,
57
58
  });
58
59
  read++;
59
60
  if (onProgress && read % 200 === 0) {