cctrackr 0.1.0 → 0.1.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var xo=Object.defineProperty;var _e=(o,e)=>()=>(o&&(e=o(o=0)),e);var ye=(o,e)=>{for(var t in e)xo(o,t,{get:e[t],enumerable:!0})};var Bt={};ye(Bt,{makeEntry:()=>Io});function Io(o={}){return{timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e3,output_tokens:500,cache_creation_input_tokens:0,cache_read_input_tokens:0}},...o}}var St=_e(()=>{"use strict"});var Zt={};ye(Zt,{calculateEntryCost:()=>it,calculateTieredCost:()=>et,fetchPricing:()=>ee,getAllPricing:()=>Ht,getModelPricing:()=>W,getPricingInfo:()=>ht,initPricing:()=>se,resetPricing:()=>je,setPricingData:()=>ft,updatePricing:()=>oe});import{readFileSync as Nt,writeFileSync as Ao,existsSync as Ce,mkdirSync as Po}from"fs";import{join as qt,dirname as Ro}from"path";import{fileURLToPath as zo}from"url";import{homedir as Fo}from"os";function Qt(){try{if(!Ce(jt))return 1/0;let o=JSON.parse(Nt(jt,"utf-8"));return Date.now()-new Date(o.fetched_at).getTime()}catch{return 1/0}}function qo(){try{return Ce(jt)?JSON.parse(Nt(jt,"utf-8")).data:null}catch{return null}}function Se(o){try{Po(Be,{recursive:!0});let e={fetched_at:new Date().toISOString(),data:o};Ao(jt,JSON.stringify(e,null,2),"utf-8")}catch{}}function te(){let o=Ro(zo(import.meta.url)),e=qt(o,"..","..","pricing","models.json");try{return JSON.parse(Nt(e,"utf-8"))}catch{let t=qt(process.cwd(),"pricing","models.json");return JSON.parse(Nt(t,"utf-8"))}}function J(o){try{let e={},t={},r=/claude-(?:opus|sonnet|haiku)-[\d.-]+(?:-\d{8})?/g,c=new Set,s;for(;(s=r.exec(o))!==null;)c.add(s[0]);let n=/<tr[^>]*>[\s\S]*?<\/tr>/gi,a=o.match(n)||[];for(let i of a){let d=i.match(/claude-(?:opus|sonnet|haiku)-[\w.-]+/);if(!d)continue;let m=d[0],l=[],u,f=/\$(\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g;for(;(u=f.exec(i))!==null;)l.push(parseFloat(u[1].replace(/,/g,"")));if(l.length>=2){let y={input_cost_per_million:l[0],output_cost_per_million:l[1],cache_creation_cost_per_million:l[0]*1.25,cache_read_cost_per_million:l[0]*.1,context_window:2e5};l.length>=4&&(y.cache_creation_cost_per_million=l[2],y.cache_read_cost_per_million=l[3]),e[m]=y}}if(Object.keys(e).length===0)return null;for(let i of Object.keys(e)){let d=i.match(/^(claude-(?:opus|sonnet|haiku)-[\d.-]+)-\d{8}$/);d&&(t[d[1]]=i,t[d[1]+"-latest"]=i)}return{version:new Date().toISOString().slice(0,10),models:e,aliases:t}}catch{return null}}async function ee(){try{let o=await fetch(No,{headers:{"User-Agent":"cctrack/0.1.0"},signal:AbortSignal.timeout(1e4)});if(!o.ok)return null;let e=await o.text();return J(e)}catch{return null}}async function oe(){let o=te(),e=await ee();if(!e||Object.keys(e.models).length===0)return process.env.DEBUG&&console.warn("cctrack: pricing fetch returned 0 models, using bundled pricing"),{data:o,newModels:[],source:"bundled (fetch failed)"};let t={version:new Date().toISOString().slice(0,10),models:{...o.models},aliases:{...o.aliases}},r=[];for(let[c,s]of Object.entries(e.models))t.models[c]||r.push(c),t.models[c]=s;for(let[c,s]of Object.entries(e.aliases))t.aliases[c]=s;return Se(t),{data:t,newModels:r,source:"fetched + bundled"}}function ne(){if(at)return at;if(Qt()<Xt){let e=qo();if(e)return at=e,at}return at=te(),at}async function se(){if(Qt()>=Xt){let e=await ee();if(e&&Object.keys(e.models).length>0){let t=te(),r={version:new Date().toISOString().slice(0,10),models:{...t.models,...e.models},aliases:{...t.aliases,...e.aliases}};Se(r),at=r}}}function ft(o){at=o}function je(){at=null}function ht(){let o=ne(),e=Qt(),t;if(e===1/0)t="no cache";else{let c=Math.floor(e/36e5),s=Math.floor(e%(3600*1e3)/(60*1e3));t=`${c}h ${s}m ago`}return{source:e<Xt?"cached (fetched)":"bundled",modelCount:Object.keys(o.models).length,version:o.version,cacheAge:t}}function Ht(){return ne()}function W(o){let e=ne();if(e.models[o])return e.models[o];let t=e.aliases[o];return t&&e.models[t]?e.models[t]:null}function et(o,e,t,r=2e5){if(o<=0)return 0;let c=e/1e6;if(t!==void 0&&o>r){let s=t/1e6;return r*c+(o-r)*s}return o*c}function it(o,e,t,r,c,s){let n=W(o);if(!n)return s!==void 0?{input:0,output:0,cacheWrite:0,cacheRead:0,total:s}:{input:0,output:0,cacheWrite:0,cacheRead:0,total:0};let a=et(e,n.input_cost_per_million,n.input_cost_per_million_above_200k),i=et(t,n.output_cost_per_million,n.output_cost_per_million_above_200k),d=et(r,n.cache_creation_cost_per_million,n.cache_creation_cost_per_million_above_200k),m=et(c,n.cache_read_cost_per_million,n.cache_read_cost_per_million_above_200k);return{input:a,output:i,cacheWrite:d,cacheRead:m,total:a+i+d+m}}var at,Be,jt,Xt,No,_t=_e(()=>{"use strict";at=null,Be=qt(Fo(),".cctrack"),jt=qt(Be,"pricing.json"),Xt=1440*60*1e3,No="https://platform.claude.com/docs/en/about-claude/pricing";if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,c={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,input_cost_per_million_above_200k:6,output_cost_per_million_above_200k:30,cache_creation_cost_per_million_above_200k:7.5,cache_read_cost_per_million_above_200k:.6,context_window:2e5},"claude-opus-4-20250514":{input_cost_per_million:15,output_cost_per_million:75,cache_creation_cost_per_million:18.75,cache_read_cost_per_million:1.5,input_cost_per_million_above_200k:30,output_cost_per_million_above_200k:150,cache_creation_cost_per_million_above_200k:37.5,cache_read_cost_per_million_above_200k:3,context_window:2e5},"claude-haiku-3-5-20241022":{input_cost_per_million:.8,output_cost_per_million:4,cache_creation_cost_per_million:1,cache_read_cost_per_million:.08,context_window:2e5}},aliases:{"claude-sonnet-4-6":"claude-sonnet-4-20250514","claude-opus-4-6":"claude-opus-4-20250514"}};r(()=>{ft(c)}),o("getModelPricing",()=>{e("returns pricing for exact model match",()=>{let s=W("claude-sonnet-4-20250514");t(s).not.toBeNull(),t(s.input_cost_per_million).toBe(3)}),e("resolves alias to pricing",()=>{let s=W("claude-sonnet-4-6");t(s).not.toBeNull(),t(s.input_cost_per_million).toBe(3)}),e("returns null for unknown model (no fuzzy matching)",()=>{let s=W("claude-sonnet");t(s).toBeNull()}),e("returns null for empty string",()=>{t(W("")).toBeNull()}),e("returns null for partial match (no substring matching)",()=>{t(W("sonnet-4-20250514")).toBeNull()})}),o("calculateTieredCost",()=>{e("returns 0 for 0 tokens",()=>{t(et(0,3,6)).toBe(0)}),e("returns 0 for negative tokens",()=>{t(et(-100,3,6)).toBe(0)}),e("calculates base rate for tokens under threshold",()=>{let s=et(1e5,3,6);t(s).toBeCloseTo(.3,6)}),e("calculates base rate at exactly 200k threshold",()=>{let s=et(2e5,3,6);t(s).toBeCloseTo(.6,6)}),e("applies tiered pricing above 200k",()=>{let s=et(200001,3,6),n=2e5*(3/1e6)+1*(6/1e6);t(s).toBeCloseTo(n,10)}),e("calculates large tiered cost correctly",()=>{let s=et(1e6,3,6),n=2e5*(3/1e6)+8e5*(6/1e6);t(s).toBeCloseTo(n,6),t(s).toBeCloseTo(.6+4.8,6)}),e("uses base rate when no tiered price provided",()=>{let s=et(3e5,.8);t(s).toBeCloseTo(3e5*(.8/1e6),6)})}),o("calculateEntryCost",()=>{e("calculates cost for known model",()=>{let s=it("claude-sonnet-4-20250514",1e3,500,200,300);t(s.input).toBeCloseTo(1e3*(3/1e6),10),t(s.output).toBeCloseTo(500*(15/1e6),10),t(s.cacheWrite).toBeCloseTo(200*(3.75/1e6),10),t(s.cacheRead).toBeCloseTo(300*(.3/1e6),10),t(s.total).toBeCloseTo(s.input+s.output+s.cacheWrite+s.cacheRead,10)}),e("falls back to costUSD for unknown model",()=>{let s=it("unknown-model",1e3,500,0,0,.42);t(s.total).toBe(.42),t(s.input).toBe(0)}),e("returns zero cost when no pricing and no embedded cost",()=>{let s=it("unknown-model",1e3,500,0,0);t(s.total).toBe(0)}),e("works with alias model names",()=>{let s=it("claude-opus-4-6",1e3,500,0,0);t(s.input).toBeCloseTo(1e3*(15/1e6),10),t(s.output).toBeCloseTo(500*(75/1e6),10)})}),o("parsePricingHtml",()=>{e("returns null for empty HTML",()=>{t(J("")).toBeNull()}),e("returns null for HTML with no pricing tables",()=>{t(J("<html><body>no data</body></html>")).toBeNull()}),e("extracts pricing from table rows with 2 prices",()=>{let n=J(`
2
+ var xo=Object.defineProperty;var _e=(o,e)=>()=>(o&&(e=o(o=0)),e);var ye=(o,e)=>{for(var t in e)xo(o,t,{get:e[t],enumerable:!0})};var Bt={};ye(Bt,{makeEntry:()=>Io});function Io(o={}){return{timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e3,output_tokens:500,cache_creation_input_tokens:0,cache_read_input_tokens:0}},...o}}var St=_e(()=>{"use strict"});var Ht={};ye(Ht,{calculateEntryCost:()=>it,calculateTieredCost:()=>et,fetchPricing:()=>ee,getAllPricing:()=>qt,getModelPricing:()=>W,getPricingInfo:()=>ht,initPricing:()=>se,resetPricing:()=>je,setPricingData:()=>ft,updatePricing:()=>oe});import{readFileSync as Vt,writeFileSync as Ao,existsSync as Ce,mkdirSync as Po}from"fs";import{join as Nt,dirname as Ro}from"path";import{fileURLToPath as zo}from"url";import{homedir as Fo}from"os";function Qt(){try{if(!Ce(jt))return 1/0;let o=JSON.parse(Vt(jt,"utf-8"));return Date.now()-new Date(o.fetched_at).getTime()}catch{return 1/0}}function qo(){try{return Ce(jt)?JSON.parse(Vt(jt,"utf-8")).data:null}catch{return null}}function Se(o){try{Po(Be,{recursive:!0});let e={fetched_at:new Date().toISOString(),data:o};Ao(jt,JSON.stringify(e,null,2),"utf-8")}catch{}}function te(){let o=Ro(zo(import.meta.url)),e=[Nt(o,"..","..","pricing","models.json"),Nt(o,"..","pricing","models.json")];for(let t of e)try{return JSON.parse(Vt(t,"utf-8"))}catch{}throw new Error("Could not find bundled pricing/models.json")}function J(o){try{let e={},t={},r=/claude-(?:opus|sonnet|haiku)-[\d.-]+(?:-\d{8})?/g,c=new Set,s;for(;(s=r.exec(o))!==null;)c.add(s[0]);let n=/<tr[^>]*>[\s\S]*?<\/tr>/gi,a=o.match(n)||[];for(let i of a){let d=i.match(/claude-(?:opus|sonnet|haiku)-[\w.-]+/);if(!d)continue;let m=d[0],l=[],u,f=/\$(\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g;for(;(u=f.exec(i))!==null;)l.push(parseFloat(u[1].replace(/,/g,"")));if(l.length>=2){let y={input_cost_per_million:l[0],output_cost_per_million:l[1],cache_creation_cost_per_million:l[0]*1.25,cache_read_cost_per_million:l[0]*.1,context_window:2e5};l.length>=4&&(y.cache_creation_cost_per_million=l[2],y.cache_read_cost_per_million=l[3]),e[m]=y}}if(Object.keys(e).length===0)return null;for(let i of Object.keys(e)){let d=i.match(/^(claude-(?:opus|sonnet|haiku)-[\d.-]+)-\d{8}$/);d&&(t[d[1]]=i,t[d[1]+"-latest"]=i)}return{version:new Date().toISOString().slice(0,10),models:e,aliases:t}}catch{return null}}async function ee(){try{let o=await fetch(No,{headers:{"User-Agent":"cctrack/0.1.0"},signal:AbortSignal.timeout(1e4)});if(!o.ok)return null;let e=await o.text();return J(e)}catch{return null}}async function oe(){let o=te(),e=await ee();if(!e||Object.keys(e.models).length===0)return process.env.DEBUG&&console.warn("cctrack: pricing fetch returned 0 models, using bundled pricing"),{data:o,newModels:[],source:"bundled (fetch failed)"};let t={version:new Date().toISOString().slice(0,10),models:{...o.models},aliases:{...o.aliases}},r=[];for(let[c,s]of Object.entries(e.models))t.models[c]||r.push(c),t.models[c]=s;for(let[c,s]of Object.entries(e.aliases))t.aliases[c]=s;return Se(t),{data:t,newModels:r,source:"fetched + bundled"}}function ne(){if(at)return at;if(Qt()<Xt){let e=qo();if(e)return at=e,at}return at=te(),at}async function se(){if(Qt()>=Xt){let e=await ee();if(e&&Object.keys(e.models).length>0){let t=te(),r={version:new Date().toISOString().slice(0,10),models:{...t.models,...e.models},aliases:{...t.aliases,...e.aliases}};Se(r),at=r}}}function ft(o){at=o}function je(){at=null}function ht(){let o=ne(),e=Qt(),t;if(e===1/0)t="no cache";else{let c=Math.floor(e/36e5),s=Math.floor(e%(3600*1e3)/(60*1e3));t=`${c}h ${s}m ago`}return{source:e<Xt?"cached (fetched)":"bundled",modelCount:Object.keys(o.models).length,version:o.version,cacheAge:t}}function qt(){return ne()}function W(o){let e=ne();if(e.models[o])return e.models[o];let t=e.aliases[o];return t&&e.models[t]?e.models[t]:null}function et(o,e,t,r=2e5){if(o<=0)return 0;let c=e/1e6;if(t!==void 0&&o>r){let s=t/1e6;return r*c+(o-r)*s}return o*c}function it(o,e,t,r,c,s){let n=W(o);if(!n)return s!==void 0?{input:0,output:0,cacheWrite:0,cacheRead:0,total:s}:{input:0,output:0,cacheWrite:0,cacheRead:0,total:0};let a=et(e,n.input_cost_per_million,n.input_cost_per_million_above_200k),i=et(t,n.output_cost_per_million,n.output_cost_per_million_above_200k),d=et(r,n.cache_creation_cost_per_million,n.cache_creation_cost_per_million_above_200k),m=et(c,n.cache_read_cost_per_million,n.cache_read_cost_per_million_above_200k);return{input:a,output:i,cacheWrite:d,cacheRead:m,total:a+i+d+m}}var at,Be,jt,Xt,No,_t=_e(()=>{"use strict";at=null,Be=Nt(Fo(),".cctrack"),jt=Nt(Be,"pricing.json"),Xt=1440*60*1e3,No="https://platform.claude.com/docs/en/about-claude/pricing";if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,c={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,input_cost_per_million_above_200k:6,output_cost_per_million_above_200k:30,cache_creation_cost_per_million_above_200k:7.5,cache_read_cost_per_million_above_200k:.6,context_window:2e5},"claude-opus-4-20250514":{input_cost_per_million:15,output_cost_per_million:75,cache_creation_cost_per_million:18.75,cache_read_cost_per_million:1.5,input_cost_per_million_above_200k:30,output_cost_per_million_above_200k:150,cache_creation_cost_per_million_above_200k:37.5,cache_read_cost_per_million_above_200k:3,context_window:2e5},"claude-haiku-3-5-20241022":{input_cost_per_million:.8,output_cost_per_million:4,cache_creation_cost_per_million:1,cache_read_cost_per_million:.08,context_window:2e5}},aliases:{"claude-sonnet-4-6":"claude-sonnet-4-20250514","claude-opus-4-6":"claude-opus-4-20250514"}};r(()=>{ft(c)}),o("getModelPricing",()=>{e("returns pricing for exact model match",()=>{let s=W("claude-sonnet-4-20250514");t(s).not.toBeNull(),t(s.input_cost_per_million).toBe(3)}),e("resolves alias to pricing",()=>{let s=W("claude-sonnet-4-6");t(s).not.toBeNull(),t(s.input_cost_per_million).toBe(3)}),e("returns null for unknown model (no fuzzy matching)",()=>{let s=W("claude-sonnet");t(s).toBeNull()}),e("returns null for empty string",()=>{t(W("")).toBeNull()}),e("returns null for partial match (no substring matching)",()=>{t(W("sonnet-4-20250514")).toBeNull()})}),o("calculateTieredCost",()=>{e("returns 0 for 0 tokens",()=>{t(et(0,3,6)).toBe(0)}),e("returns 0 for negative tokens",()=>{t(et(-100,3,6)).toBe(0)}),e("calculates base rate for tokens under threshold",()=>{let s=et(1e5,3,6);t(s).toBeCloseTo(.3,6)}),e("calculates base rate at exactly 200k threshold",()=>{let s=et(2e5,3,6);t(s).toBeCloseTo(.6,6)}),e("applies tiered pricing above 200k",()=>{let s=et(200001,3,6),n=2e5*(3/1e6)+1*(6/1e6);t(s).toBeCloseTo(n,10)}),e("calculates large tiered cost correctly",()=>{let s=et(1e6,3,6),n=2e5*(3/1e6)+8e5*(6/1e6);t(s).toBeCloseTo(n,6),t(s).toBeCloseTo(.6+4.8,6)}),e("uses base rate when no tiered price provided",()=>{let s=et(3e5,.8);t(s).toBeCloseTo(3e5*(.8/1e6),6)})}),o("calculateEntryCost",()=>{e("calculates cost for known model",()=>{let s=it("claude-sonnet-4-20250514",1e3,500,200,300);t(s.input).toBeCloseTo(1e3*(3/1e6),10),t(s.output).toBeCloseTo(500*(15/1e6),10),t(s.cacheWrite).toBeCloseTo(200*(3.75/1e6),10),t(s.cacheRead).toBeCloseTo(300*(.3/1e6),10),t(s.total).toBeCloseTo(s.input+s.output+s.cacheWrite+s.cacheRead,10)}),e("falls back to costUSD for unknown model",()=>{let s=it("unknown-model",1e3,500,0,0,.42);t(s.total).toBe(.42),t(s.input).toBe(0)}),e("returns zero cost when no pricing and no embedded cost",()=>{let s=it("unknown-model",1e3,500,0,0);t(s.total).toBe(0)}),e("works with alias model names",()=>{let s=it("claude-opus-4-6",1e3,500,0,0);t(s.input).toBeCloseTo(1e3*(15/1e6),10),t(s.output).toBeCloseTo(500*(75/1e6),10)})}),o("parsePricingHtml",()=>{e("returns null for empty HTML",()=>{t(J("")).toBeNull()}),e("returns null for HTML with no pricing tables",()=>{t(J("<html><body>no data</body></html>")).toBeNull()}),e("extracts pricing from table rows with 2 prices",()=>{let n=J(`
3
3
  <tr><td>claude-sonnet-4-20250514</td><td>$3.00 / MTok</td><td>$15.00 / MTok</td></tr>
4
4
  `);t(n).not.toBeNull(),t(n.models["claude-sonnet-4-20250514"]).toBeDefined(),t(n.models["claude-sonnet-4-20250514"].input_cost_per_million).toBe(3),t(n.models["claude-sonnet-4-20250514"].output_cost_per_million).toBe(15)}),e("extracts pricing with 4 prices (including cache)",()=>{let n=J(`
5
5
  <tr><td>claude-opus-4-20250514</td><td>$15.00</td><td>$75.00</td><td>$18.75</td><td>$1.50</td></tr>
@@ -18,16 +18,16 @@ var xo=Object.defineProperty;var _e=(o,e)=>()=>(o&&(e=o(o=0)),e);var ye=(o,e)=>{
18
18
  `);t(n).toBeNull()}),e("handles invalid JSON-LD gracefully",()=>{let n=J(`
19
19
  <script type="application/ld+json">not json</script>
20
20
  <tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>
21
- `);t(n).not.toBeNull()}),e("sets version to current date",()=>{let n=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>");t(n.version).toMatch(/^\d{4}-\d{2}-\d{2}$/)}),e("defaults context_window to 200000",()=>{let n=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>");t(n.models["claude-sonnet-4-20250514"].context_window).toBe(2e5)}),e("derives cache costs from input when only 2 prices",()=>{let a=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>").models["claude-sonnet-4-20250514"];t(a.cache_creation_cost_per_million).toBeCloseTo(3*1.25,6),t(a.cache_read_cost_per_million).toBeCloseTo(3*.1,6)})}),o("getPricingInfo",()=>{e("returns correct model count",()=>{let s=ht();t(s.modelCount).toBe(3)}),e("returns version",()=>{let s=ht();t(s.version).toBe("test")})}),o("getAllPricing",()=>{e("returns the full pricing data",()=>{let s=Ht();t(s.models).toBeDefined(),t(s.aliases).toBeDefined(),t(Object.keys(s.models)).toHaveLength(3)})}),o("cache functions",()=>{e("resetPricing clears in-memory cache",()=>{ft(c),t(W("claude-sonnet-4-20250514")).not.toBeNull(),je(),ft({version:"empty",models:{},aliases:{}}),t(W("claude-sonnet-4-20250514")).toBeNull(),ft(c)}),e("setPricingData overrides all lookups",()=>{ft({version:"custom",models:{"my-custom-model":{input_cost_per_million:99,output_cost_per_million:199,cache_creation_cost_per_million:10,cache_read_cost_per_million:1,context_window:1e5}},aliases:{"my-alias":"my-custom-model"}}),t(W("my-custom-model")).not.toBeNull(),t(W("my-custom-model").input_cost_per_million).toBe(99),t(W("my-alias").input_cost_per_million).toBe(99),t(W("claude-sonnet-4-20250514")).toBeNull(),ft(c)})}),o("calculateEntryCost edge cases",()=>{e("calculates with all four token types using tiered pricing",()=>{let s=it("claude-opus-4-20250514",3e5,1e5,5e4,1e4);t(s.input).toBeCloseTo(2e5*(15/1e6)+1e5*(30/1e6),6),t(s.output).toBeCloseTo(1e5*(75/1e6),6),t(s.total).toBeGreaterThan(0)}),e("handles zero tokens for all types",()=>{let s=it("claude-sonnet-4-20250514",0,0,0,0);t(s.total).toBe(0),t(s.input).toBe(0),t(s.output).toBe(0)})})}});import{Command as Cn}from"commander";import kt from"chalk";import Pe from"cli-table3";import{readdirSync as ke,statSync as Co,existsSync as be}from"fs";import{join as vt}from"path";import{homedir as Bo}from"os";function I(){let o=[],e=process.env.CLAUDE_CONFIG_DIR;if(e){let c=vt(e,"projects");be(c)&&o.push(c)}let t=Bo(),r=[vt(t,".claude","projects"),vt(t,".config","claude","projects")];for(let c of r)be(c)&&o.push(c);return[...new Set(o)]}var Vt=new Map;function B(o){Vt.clear();let e=[];for(let t of o)try{let r=ke(t,{withFileTypes:!0});for(let c of r){if(!c.isDirectory())continue;let s=jo(c.name),n=vt(t,c.name);ve(n,e,s)}}catch{}return e}function ve(o,e,t){try{let r=ke(o,{withFileTypes:!0});for(let c of r){let s=vt(o,c.name);c.isDirectory()?ve(s,e,t):c.name.endsWith(".jsonl")&&(e.push(s),Vt.set(s,t))}}catch{}}var Pt=new Map;function So(o){if(Pt.has(o))return Pt.get(o);try{let e=Co(o).isDirectory();return Pt.set(o,e),e}catch{return Pt.set(o,!1),!1}}function jo(o){let e=o.replace(/^-/,"").split("-"),t="/",r=0;for(;r<e.length;){let s=!1;for(let n=e.length-r;n>=1;n--){let a=e.slice(r,r+n).join("-"),i=[a,a.replace(/-/g,"_")],d=!1;for(let m of i){let l=vt(t,m);if(So(l)){t=l,r+=n,s=!0,d=!0;break}}if(d)break}if(!s)return e[e.length-1]||o}let c=t.split("/").filter(Boolean);return c[c.length-1]||o}function Rt(o){return Vt.get(o)??"unknown"}function tt(o){return o||"unknown"}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r,afterEach:c}=import.meta.vitest,{mkdirSync:s,writeFileSync:n,rmSync:a}=await import("fs"),{join:i}=await import("path"),{tmpdir:d}=await import("os"),m=i(d(),"cctrack-test-fs");r(()=>{s(m,{recursive:!0})}),c(()=>{a(m,{recursive:!0,force:!0})}),o("findJsonlFiles",()=>{e("finds .jsonl files inside project directories",()=>{let l=i(m,"-Users-me-myproject","session1");s(l,{recursive:!0}),n(i(l,"usage.jsonl"),"{}");let u=B([m]);t(u).toHaveLength(1),t(u[0]).toContain("usage.jsonl")}),e("maps files to project names via getProjectForFile",()=>{let l=i(m,"-xtest-zfake-myproject","sess");s(l,{recursive:!0}),n(i(l,"data.jsonl"),"{}");let u=B([m]);t(u).toHaveLength(1),t(Rt(u[0])).toBe("myproject")}),e("returns empty for empty directory",()=>{t(B([m])).toHaveLength(0)}),e("returns empty for non-existent directory",()=>{t(B([i(m,"nope")])).toHaveLength(0)})}),o("getProjectDirs",()=>{e("includes CLAUDE_CONFIG_DIR when set",()=>{let l=i(m,"custom");s(i(l,"projects"),{recursive:!0});let u=process.env.CLAUDE_CONFIG_DIR;process.env.CLAUDE_CONFIG_DIR=l;try{let f=I();t(f.some(y=>y.includes("custom"))).toBe(!0)}finally{u!==void 0?process.env.CLAUDE_CONFIG_DIR=u:delete process.env.CLAUDE_CONFIG_DIR}}),e("deduplicates paths",()=>{let l=I();t(l.length).toBe(new Set(l).size)})}),o("extractProjectName",()=>{e("returns cwd as-is (already normalized by parser)",()=>{t(tt("tradeforge")).toBe("tradeforge")}),e("returns unknown for empty string",()=>{t(tt("")).toBe("unknown")}),e("returns the normalized project name",()=>{t(tt("cctrack")).toBe("cctrack")})}),o("decodeProjectDir",()=>{e("decodes encoded project directory to last component",()=>{let l=i(m,"-xtest-zfake-tradeforge","sess");s(l,{recursive:!0}),n(i(l,"test.jsonl"),"{}");let u=B([m]);t(Rt(u[0])).toBe("tradeforge")})})}import{createReadStream as To,appendFileSync as Do,mkdirSync as Mo}from"fs";import{createInterface as Eo}from"readline";import{join as xe}from"path";import{homedir as $o}from"os";import{z as A}from"zod/v4";var zt=A.object({timestamp:A.string().datetime(),sessionId:A.string().optional(),version:A.string().optional(),cwd:A.string().optional(),message:A.object({id:A.string().optional(),model:A.string().optional(),usage:A.object({input_tokens:A.number(),output_tokens:A.number(),cache_creation_input_tokens:A.number().optional().default(0),cache_read_input_tokens:A.number().optional().default(0),speed:A.enum(["standard","fast"]).optional()}),content:A.array(A.object({text:A.string().optional()})).optional()}),costUSD:A.number().optional(),requestId:A.string().optional(),isApiErrorMessage:A.boolean().optional()}),we={pro:20,max5:100,max20:200},dt=300*60*1e3,Ft={safe:0,warning:50,critical:80,exceeded:100};async function ut(o){let e=[],t=0,r={apiErrors:0,synthetic:0},c=Rt(o),s=Eo({input:To(o,"utf-8"),crlfDelay:1/0});for await(let n of s){let a=n.trim();if(a)try{let i=JSON.parse(a);if(!i.message?.usage)continue;let d=zt.safeParse(i);if(!d.success){t++;continue}let m=d.data;if(m.isApiErrorMessage===!0){r.apiErrors++;try{let l=m.message.content;if(l?.some(f=>f.text?.includes("rate limit")||f.text?.includes("hit your limit"))){let f=xe($o(),".cctrack");Mo(f,{recursive:!0}),Do(xe(f,"rate-events.jsonl"),JSON.stringify({timestamp:m.timestamp,model:m.message.model,content:l?.map(y=>y.text).join(" ")})+`
21
+ `);t(n).not.toBeNull()}),e("sets version to current date",()=>{let n=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>");t(n.version).toMatch(/^\d{4}-\d{2}-\d{2}$/)}),e("defaults context_window to 200000",()=>{let n=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>");t(n.models["claude-sonnet-4-20250514"].context_window).toBe(2e5)}),e("derives cache costs from input when only 2 prices",()=>{let a=J("<tr><td>claude-sonnet-4-20250514</td><td>$3.00</td><td>$15.00</td></tr>").models["claude-sonnet-4-20250514"];t(a.cache_creation_cost_per_million).toBeCloseTo(3*1.25,6),t(a.cache_read_cost_per_million).toBeCloseTo(3*.1,6)})}),o("getPricingInfo",()=>{e("returns correct model count",()=>{let s=ht();t(s.modelCount).toBe(3)}),e("returns version",()=>{let s=ht();t(s.version).toBe("test")})}),o("getAllPricing",()=>{e("returns the full pricing data",()=>{let s=qt();t(s.models).toBeDefined(),t(s.aliases).toBeDefined(),t(Object.keys(s.models)).toHaveLength(3)})}),o("cache functions",()=>{e("resetPricing clears in-memory cache",()=>{ft(c),t(W("claude-sonnet-4-20250514")).not.toBeNull(),je(),ft({version:"empty",models:{},aliases:{}}),t(W("claude-sonnet-4-20250514")).toBeNull(),ft(c)}),e("setPricingData overrides all lookups",()=>{ft({version:"custom",models:{"my-custom-model":{input_cost_per_million:99,output_cost_per_million:199,cache_creation_cost_per_million:10,cache_read_cost_per_million:1,context_window:1e5}},aliases:{"my-alias":"my-custom-model"}}),t(W("my-custom-model")).not.toBeNull(),t(W("my-custom-model").input_cost_per_million).toBe(99),t(W("my-alias").input_cost_per_million).toBe(99),t(W("claude-sonnet-4-20250514")).toBeNull(),ft(c)})}),o("calculateEntryCost edge cases",()=>{e("calculates with all four token types using tiered pricing",()=>{let s=it("claude-opus-4-20250514",3e5,1e5,5e4,1e4);t(s.input).toBeCloseTo(2e5*(15/1e6)+1e5*(30/1e6),6),t(s.output).toBeCloseTo(1e5*(75/1e6),6),t(s.total).toBeGreaterThan(0)}),e("handles zero tokens for all types",()=>{let s=it("claude-sonnet-4-20250514",0,0,0,0);t(s.total).toBe(0),t(s.input).toBe(0),t(s.output).toBe(0)})})}});import{Command as Cn}from"commander";import kt from"chalk";import Pe from"cli-table3";import{readdirSync as ke,statSync as Co,existsSync as be}from"fs";import{join as vt}from"path";import{homedir as Bo}from"os";function I(){let o=[],e=process.env.CLAUDE_CONFIG_DIR;if(e){let c=vt(e,"projects");be(c)&&o.push(c)}let t=Bo(),r=[vt(t,".claude","projects"),vt(t,".config","claude","projects")];for(let c of r)be(c)&&o.push(c);return[...new Set(o)]}var Kt=new Map;function B(o){Kt.clear();let e=[];for(let t of o)try{let r=ke(t,{withFileTypes:!0});for(let c of r){if(!c.isDirectory())continue;let s=jo(c.name),n=vt(t,c.name);ve(n,e,s)}}catch{}return e}function ve(o,e,t){try{let r=ke(o,{withFileTypes:!0});for(let c of r){let s=vt(o,c.name);c.isDirectory()?ve(s,e,t):c.name.endsWith(".jsonl")&&(e.push(s),Kt.set(s,t))}}catch{}}var Pt=new Map;function So(o){if(Pt.has(o))return Pt.get(o);try{let e=Co(o).isDirectory();return Pt.set(o,e),e}catch{return Pt.set(o,!1),!1}}function jo(o){let e=o.replace(/^-/,"").split("-"),t="/",r=0;for(;r<e.length;){let s=!1;for(let n=e.length-r;n>=1;n--){let a=e.slice(r,r+n).join("-"),i=[a,a.replace(/-/g,"_")],d=!1;for(let m of i){let l=vt(t,m);if(So(l)){t=l,r+=n,s=!0,d=!0;break}}if(d)break}if(!s)return e[e.length-1]||o}let c=t.split("/").filter(Boolean);return c[c.length-1]||o}function Rt(o){return Kt.get(o)??"unknown"}function tt(o){return o||"unknown"}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r,afterEach:c}=import.meta.vitest,{mkdirSync:s,writeFileSync:n,rmSync:a}=await import("fs"),{join:i}=await import("path"),{tmpdir:d}=await import("os"),m=i(d(),"cctrack-test-fs");r(()=>{s(m,{recursive:!0})}),c(()=>{a(m,{recursive:!0,force:!0})}),o("findJsonlFiles",()=>{e("finds .jsonl files inside project directories",()=>{let l=i(m,"-Users-me-myproject","session1");s(l,{recursive:!0}),n(i(l,"usage.jsonl"),"{}");let u=B([m]);t(u).toHaveLength(1),t(u[0]).toContain("usage.jsonl")}),e("maps files to project names via getProjectForFile",()=>{let l=i(m,"-xtest-zfake-myproject","sess");s(l,{recursive:!0}),n(i(l,"data.jsonl"),"{}");let u=B([m]);t(u).toHaveLength(1),t(Rt(u[0])).toBe("myproject")}),e("returns empty for empty directory",()=>{t(B([m])).toHaveLength(0)}),e("returns empty for non-existent directory",()=>{t(B([i(m,"nope")])).toHaveLength(0)})}),o("getProjectDirs",()=>{e("includes CLAUDE_CONFIG_DIR when set",()=>{let l=i(m,"custom");s(i(l,"projects"),{recursive:!0});let u=process.env.CLAUDE_CONFIG_DIR;process.env.CLAUDE_CONFIG_DIR=l;try{let f=I();t(f.some(y=>y.includes("custom"))).toBe(!0)}finally{u!==void 0?process.env.CLAUDE_CONFIG_DIR=u:delete process.env.CLAUDE_CONFIG_DIR}}),e("deduplicates paths",()=>{let l=I();t(l.length).toBe(new Set(l).size)})}),o("extractProjectName",()=>{e("returns cwd as-is (already normalized by parser)",()=>{t(tt("tradeforge")).toBe("tradeforge")}),e("returns unknown for empty string",()=>{t(tt("")).toBe("unknown")}),e("returns the normalized project name",()=>{t(tt("cctrack")).toBe("cctrack")})}),o("decodeProjectDir",()=>{e("decodes encoded project directory to last component",()=>{let l=i(m,"-xtest-zfake-tradeforge","sess");s(l,{recursive:!0}),n(i(l,"test.jsonl"),"{}");let u=B([m]);t(Rt(u[0])).toBe("tradeforge")})})}import{createReadStream as To,appendFileSync as Do,mkdirSync as Mo}from"fs";import{createInterface as Eo}from"readline";import{join as xe}from"path";import{homedir as $o}from"os";import{z as A}from"zod/v4";var zt=A.object({timestamp:A.string().datetime(),sessionId:A.string().optional(),version:A.string().optional(),cwd:A.string().optional(),message:A.object({id:A.string().optional(),model:A.string().optional(),usage:A.object({input_tokens:A.number(),output_tokens:A.number(),cache_creation_input_tokens:A.number().optional().default(0),cache_read_input_tokens:A.number().optional().default(0),speed:A.enum(["standard","fast"]).optional()}),content:A.array(A.object({text:A.string().optional()})).optional()}),costUSD:A.number().optional(),requestId:A.string().optional(),isApiErrorMessage:A.boolean().optional()}),we={pro:20,max5:100,max20:200},dt=300*60*1e3,Ft={safe:0,warning:50,critical:80,exceeded:100};async function ut(o){let e=[],t=0,r={apiErrors:0,synthetic:0},c=Rt(o),s=Eo({input:To(o,"utf-8"),crlfDelay:1/0});for await(let n of s){let a=n.trim();if(a)try{let i=JSON.parse(a);if(!i.message?.usage)continue;let d=zt.safeParse(i);if(!d.success){t++;continue}let m=d.data;if(m.isApiErrorMessage===!0){r.apiErrors++;try{let l=m.message.content;if(l?.some(f=>f.text?.includes("rate limit")||f.text?.includes("hit your limit"))){let f=xe($o(),".cctrack");Mo(f,{recursive:!0}),Do(xe(f,"rate-events.jsonl"),JSON.stringify({timestamp:m.timestamp,model:m.message.model,content:l?.map(y=>y.text).join(" ")})+`
22
22
  `)}}catch{}continue}if(m.message.model==="<synthetic>"){r.synthetic++;continue}c!=="unknown"&&(m.cwd=c),e.push(m)}catch{t++}}return{entries:e,errors:t,skipped:r}}async function j(o){let e={entries:[],errors:0,skipped:{apiErrors:0,synthetic:0}},t=20;for(let r=0;r<o.length;r+=t){let c=o.slice(r,r+t),s=await Promise.all(c.map(ut));for(let n of s){for(let a of n.entries)e.entries.push(a);e.errors+=n.errors,e.skipped.apiErrors+=n.skipped.apiErrors,e.skipped.synthetic+=n.skipped.synthetic}}return e}if(import.meta.vitest){let l=function(g,p){n(m,{recursive:!0});let b=i(m,g);return c(b,p.join(`
23
- `)),b};Oo=l;let{describe:o,it:e,expect:t,afterAll:r}=import.meta.vitest,{writeFileSync:c,unlinkSync:s,mkdirSync:n,rmSync:a}=await import("fs"),{join:i}=await import("path"),{tmpdir:d}=await import("os"),m=i(d(),"cctrack-test-parser");r(()=>{try{a(m,{recursive:!0,force:!0})}catch{}});let u=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{id:"msg_1",model:"claude-sonnet-4-20250514",usage:{input_tokens:100,output_tokens:50}},requestId:"req_1"}),f=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:0,output_tokens:0}},isApiErrorMessage:!0}),y=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{model:"<synthetic>",usage:{input_tokens:50,output_tokens:20}}});o("parseJsonlFile",()=>{e("parses valid entries",async()=>{let g=l("valid.jsonl",[u]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.entries[0].message.usage.input_tokens).toBe(100),t(p.errors).toBe(0),s(g)}),e("filters API error entries",async()=>{let g=l("api-err.jsonl",[u,f]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.skipped.apiErrors).toBe(1),s(g)}),e("filters synthetic model entries",async()=>{let g=l("synthetic.jsonl",[u,y]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.skipped.synthetic).toBe(1),s(g)}),e("counts invalid JSON as errors",async()=>{let g=l("invalid.jsonl",[u,"not json at all",'{"broken']),p=await ut(g);t(p.entries).toHaveLength(1),t(p.errors).toBe(2),s(g)}),e("counts schema validation failures as errors",async()=>{let g=JSON.stringify({timestamp:"not-a-date",message:{usage:{input_tokens:1,output_tokens:1}}}),p=l("bad-schema.jsonl",[u,g]),b=await ut(p);t(b.entries).toHaveLength(1),t(b.errors).toBe(1),s(p)}),e("defaults cache tokens to 0",async()=>{let g=l("no-cache.jsonl",[u]),p=await ut(g);t(p.entries[0].message.usage.cache_creation_input_tokens).toBe(0),t(p.entries[0].message.usage.cache_read_input_tokens).toBe(0),s(g)}),e("handles empty files",async()=>{let g=l("empty.jsonl",[""]),p=await ut(g);t(p.entries).toHaveLength(0),s(g)})}),o("parseAllFiles",()=>{e("combines entries from multiple files",async()=>{let g=l("multi1.jsonl",[u]),p=l("multi2.jsonl",[u]),b=await j([g,p]);t(b.entries).toHaveLength(2),t(b.errors).toBe(0),s(g),s(p)}),e("combines errors and skipped counts",async()=>{let g=l("comb1.jsonl",[u,f]),p=l("comb2.jsonl",[y,"bad json"]),b=await j([g,p]);t(b.entries).toHaveLength(1),t(b.skipped.apiErrors).toBe(1),t(b.skipped.synthetic).toBe(1),t(b.errors).toBe(1),s(g),s(p)}),e("handles empty file list",async()=>{let g=await j([]);t(g.entries).toHaveLength(0),t(g.errors).toBe(0)}),e("handles more than BATCH_SIZE (20) files",async()=>{let g=[];for(let b=0;b<25;b++)g.push(l(`batch-${b}.jsonl`,[u]));let p=await j(g);t(p.entries).toHaveLength(25),t(p.errors).toBe(0);for(let b of g)s(b)})})}var Oo;import{createHash as Lo}from"crypto";function rt(o){return o.requestId?`req:${o.requestId}`:o.message.id?`msg:${o.message.id}`:`hash:${Lo("sha256").update(`${o.timestamp}|${o.message.model}|${o.message.usage.input_tokens}|${o.message.usage.output_tokens}`).digest("hex").slice(0,16)}`}function x(o){let e=new Set,t=[];for(let r of o){let c=rt(r);e.has(c)||(e.add(c),t.push(r))}return t}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest,{makeEntry:r}=await Promise.resolve().then(()=>(St(),Bt));o("createDedupKey",()=>{e("uses requestId when available (highest priority)",()=>{let c=r({requestId:"req_123",message:{...r().message,id:"msg_456"}});t(rt(c)).toBe("req:req_123")}),e("falls back to message.id when no requestId",()=>{let c=r({message:{...r().message,id:"msg_456"}});t(rt(c)).toBe("msg:msg_456")}),e("falls back to hash when no requestId or message.id",()=>{let c=r(),s=rt(c);t(s).toMatch(/^hash:[a-f0-9]{16}$/)}),e("produces same hash for identical entries",()=>{let c=r(),s=r();t(rt(c)).toBe(rt(s))}),e("produces different hash for different token counts",()=>{let c=r(),s=r({message:{...r().message,usage:{...r().message.usage,input_tokens:999}}});t(rt(c)).not.toBe(rt(s))}),e("treats empty-string requestId as falsy (falls through to msg or hash)",()=>{let c=r({requestId:""}),s=rt(c);t(s).not.toBe("req:"),t(s).toMatch(/^(msg:|hash:)/)})}),o("deduplicateEntries",()=>{e("removes duplicates by requestId",()=>{let c=[r({requestId:"r1"}),r({requestId:"r1"}),r({requestId:"r2"})];t(x(c)).toHaveLength(2)}),e("removes duplicates by message.id",()=>{let c=[r({message:{...r().message,id:"m1"}}),r({message:{...r().message,id:"m1"}})];t(x(c)).toHaveLength(1)}),e("removes duplicates by hash fallback",()=>{let c=[r(),r()];t(x(c)).toHaveLength(1)}),e("preserves insertion order",()=>{let c=[r({requestId:"r1"}),r({requestId:"r2"}),r({requestId:"r1"})],s=x(c);t(s[0].requestId).toBe("r1"),t(s[1].requestId).toBe("r2")}),e("handles cross-file dedup (same requestId different entries)",()=>{let c=r({requestId:"r1",cwd:"/project-a"}),s=r({requestId:"r1",cwd:"/project-b"});t(x([c,s])).toHaveLength(1)}),e("returns empty array for empty input",()=>{t(x([])).toHaveLength(0)})})}_t();function $(o,e="calculate"){let t=o.message.usage,r=o.message.model??"unknown",c={input_tokens:t.input_tokens,output_tokens:t.output_tokens,cache_write_tokens:t.cache_creation_input_tokens??0,cache_read_tokens:t.cache_read_input_tokens??0,total_tokens:t.input_tokens+t.output_tokens+(t.cache_creation_input_tokens??0)+(t.cache_read_input_tokens??0)},s=it(r,c.input_tokens,c.output_tokens,c.cache_write_tokens,c.cache_read_tokens,o.costUSD),n={input_cost:s.input,output_cost:s.output,cache_write_cost:s.cacheWrite,cache_read_cost:s.cacheRead,total_cost:s.total},a;return e==="display"?a={input_cost:0,output_cost:0,cache_write_cost:0,cache_read_cost:0,total_cost:o.costUSD??0}:a=n,{tokens:c,cost:a,calculatedCost:n,displayCost:o.costUSD}}function re(){return{input_tokens:0,output_tokens:0,cache_write_tokens:0,cache_read_tokens:0,total_tokens:0}}function ae(){return{input_cost:0,output_cost:0,cache_write_cost:0,cache_read_cost:0,total_cost:0}}function Tt(o,e){return{input_tokens:o.input_tokens+e.input_tokens,output_tokens:o.output_tokens+e.output_tokens,cache_write_tokens:o.cache_write_tokens+e.cache_write_tokens,cache_read_tokens:o.cache_read_tokens+e.cache_read_tokens,total_tokens:o.total_tokens+e.total_tokens}}function Dt(o,e){return{input_cost:o.input_cost+e.input_cost,output_cost:o.output_cost+e.output_cost,cache_write_cost:o.cache_write_cost+e.cache_write_cost,cache_read_cost:o.cache_read_cost+e.cache_read_cost,total_cost:o.total_cost+e.total_cost}}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Zt)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let n=(a={})=>({timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e3,output_tokens:500,cache_creation_input_tokens:200,cache_read_input_tokens:300}},...a});o("processEntry",()=>{e("extracts correct token breakdown",()=>{let a=$(n());t(a.tokens.input_tokens).toBe(1e3),t(a.tokens.output_tokens).toBe(500),t(a.tokens.cache_write_tokens).toBe(200),t(a.tokens.cache_read_tokens).toBe(300),t(a.tokens.total_tokens).toBe(2e3)}),e("calculates cost in calculate mode",()=>{let a=$(n(),"calculate");t(a.cost.input_cost).toBeCloseTo(1e3*(3/1e6),10),t(a.cost.output_cost).toBeCloseTo(500*(15/1e6),10),t(a.cost.cache_write_cost).toBeCloseTo(200*(3.75/1e6),10),t(a.cost.cache_read_cost).toBeCloseTo(300*(.3/1e6),10)}),e("uses embedded cost in display mode",()=>{let a=$(n({costUSD:.42}),"display");t(a.cost.total_cost).toBe(.42),t(a.cost.input_cost).toBe(0)}),e("uses 0 in display mode when no embedded cost",()=>{let a=$(n(),"display");t(a.cost.total_cost).toBe(0)}),e("provides both calculated and display costs in compare mode data",()=>{let a=n({costUSD:.42}),i=$(a,"compare");t(i.calculatedCost.total_cost).toBeGreaterThan(0),t(i.displayCost).toBe(.42)})}),o("addTokens",()=>{e("sums token breakdowns",()=>{let d=Tt({input_tokens:10,output_tokens:5,cache_write_tokens:2,cache_read_tokens:3,total_tokens:20},{input_tokens:20,output_tokens:10,cache_write_tokens:4,cache_read_tokens:6,total_tokens:40});t(d.input_tokens).toBe(30),t(d.output_tokens).toBe(15),t(d.total_tokens).toBe(60)})}),o("addCosts",()=>{e("sums cost breakdowns",()=>{let d=Dt({input_cost:1,output_cost:2,cache_write_cost:.5,cache_read_cost:.1,total_cost:3.6},{input_cost:3,output_cost:4,cache_write_cost:1.5,cache_read_cost:.2,total_cost:8.7});t(d.input_cost).toBe(4),t(d.total_cost).toBeCloseTo(12.3,6)})}),o("processEntry with missing model",()=>{e("returns zero cost when model is undefined",()=>{let a=n({message:{usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}),i=$(a);t(i.cost.total_cost).toBe(0),t(i.tokens.input_tokens).toBe(100)})}),o("emptyTokens and emptyCost",()=>{e("emptyTokens returns all zeros",()=>{let a=re();t(a.input_tokens).toBe(0),t(a.output_tokens).toBe(0),t(a.cache_write_tokens).toBe(0),t(a.cache_read_tokens).toBe(0),t(a.total_tokens).toBe(0)}),e("emptyCost returns all zeros",()=>{let a=ae();t(a.input_cost).toBe(0),t(a.output_cost).toBe(0),t(a.cache_write_cost).toBe(0),t(a.cache_read_cost).toBe(0),t(a.total_cost).toBe(0)})})}function yt(o,e){let t=new Date(o);return e?t.toLocaleDateString("en-CA",{timeZone:e}):t.toISOString().slice(0,10)}function Ut(o,e){return yt(o,e).slice(0,7)}function mt(o,e){let t=new Date(o);if(e){let r=new Intl.DateTimeFormat("en-US",{timeZone:e,hour:"numeric",hour12:!1,weekday:"short"}).formatToParts(t),c=r.find(a=>a.type==="hour"),s=r.find(a=>a.type==="weekday"),n={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6};return{hour:parseInt(c?.value??"0",10),day:n[s?.value??"Sun"]??0}}return{hour:t.getUTCHours(),day:t.getUTCDay()}}function G(o,e,t){let r=o.slice(0,10);return!(e&&r<e||t&&r>t)}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest;o("toDateString",()=>{e("returns YYYY-MM-DD for UTC",()=>{t(yt("2025-03-25T10:00:00Z")).toBe("2025-03-25")}),e("respects timezone",()=>{t(yt("2025-03-25T23:00:00Z","Asia/Kolkata")).toBe("2025-03-26")}),e("handles midnight boundary",()=>{t(yt("2025-03-25T00:00:00Z")).toBe("2025-03-25")})}),o("toMonthString",()=>{e("returns YYYY-MM",()=>{t(Ut("2025-03-25T10:00:00Z")).toBe("2025-03")}),e("respects timezone for month boundary",()=>{t(Ut("2025-03-31T23:00:00Z","Asia/Kolkata")).toBe("2025-04")})}),o("getHourAndDay",()=>{e("returns UTC hour and day by default",()=>{let r=mt("2025-03-25T14:30:00Z");t(r.hour).toBe(14),t(r.day).toBe(2)}),e("respects timezone",()=>{let r=mt("2025-03-25T14:00:00Z","Asia/Kolkata");t(r.hour).toBe(19),t(r.day).toBe(2)}),e("handles day rollover with timezone",()=>{let r=mt("2025-03-25T20:00:00Z","Asia/Kolkata");t(r.hour).toBe(1),t(r.day).toBe(3)}),e("handles Sunday correctly",()=>{let r=mt("2025-03-23T10:00:00Z");t(r.day).toBe(0)})}),o("isInRange",()=>{e("returns true when no filters",()=>{t(G("2025-03-25T10:00:00Z")).toBe(!0)}),e("filters by since",()=>{t(G("2025-03-24T10:00:00Z","2025-03-25")).toBe(!1),t(G("2025-03-25T10:00:00Z","2025-03-25")).toBe(!0),t(G("2025-03-26T10:00:00Z","2025-03-25")).toBe(!0)}),e("filters by until",()=>{t(G("2025-03-26T10:00:00Z",void 0,"2025-03-25")).toBe(!1),t(G("2025-03-25T10:00:00Z",void 0,"2025-03-25")).toBe(!0)}),e("filters by both since and until",()=>{t(G("2025-03-25T10:00:00Z","2025-03-25","2025-03-25")).toBe(!0),t(G("2025-03-24T10:00:00Z","2025-03-25","2025-03-26")).toBe(!1),t(G("2025-03-27T10:00:00Z","2025-03-25","2025-03-26")).toBe(!1)}),e("includes entry at 23:59 on the same day",()=>{t(G("2025-03-25T23:59:59Z","2025-03-25","2025-03-25")).toBe(!0)}),e("returns false when since > until (impossible range)",()=>{t(G("2025-03-25T10:00:00Z","2025-03-26","2025-03-24")).toBe(!1)})})}function T(){return{tokens:re(),cost:ae(),request_count:0}}function O(o,e){o.tokens=Tt(o.tokens,e.tokens),o.cost=Dt(o.cost,e.cost),o.request_count++}function D(o,e){return o.filter(t=>!(!G(t.timestamp,e.since,e.until)||e.project&&(!t.cwd||!tt(t.cwd).toLowerCase().includes(e.project.toLowerCase()))))}function st(o,e="calculate",t){let r=new Map;for(let c of o){let s=yt(c.timestamp,t),n=c.message.model??"unknown",a=c.cwd?tt(c.cwd):"unknown";r.has(s)||r.set(s,{date:s,...T(),models:{},projects:{}});let i=r.get(s),d=$(c,e);O(i,d),i.models[n]||(i.models[n]=T()),O(i.models[n],d),i.projects[a]||(i.projects[a]=T()),O(i.projects[a],d)}return[...r.values()].sort((c,s)=>c.date.localeCompare(s.date))}function Yt(o,e="calculate",t){let r=new Map;for(let c of o){let s=Ut(c.timestamp,t),n=c.message.model??"unknown";r.has(s)||r.set(s,{month:s,...T(),models:{}});let a=r.get(s),i=$(c,e);O(a,i),a.models[n]||(a.models[n]=T()),O(a.models[n],i)}return[...r.values()].sort((c,s)=>c.month.localeCompare(s.month))}function ct(o,e="calculate"){let t=new Map;for(let r of o){let c=r.sessionId??"unknown",s=r.message.model??"unknown",n=r.cwd?tt(r.cwd):"unknown";t.has(c)||t.set(c,{sessionId:c,project:n,startTime:r.timestamp,endTime:r.timestamp,primaryModel:s,...T(),models:{}});let a=t.get(c),i=$(r,e);O(a,i),r.timestamp<a.startTime&&(a.startTime=r.timestamp),r.timestamp>a.endTime&&(a.endTime=r.timestamp),a.models[s]||(a.models[s]=T()),O(a.models[s],i);let d=a.models[s].request_count,m=a.models[a.primaryModel]?.request_count??0;d>=m&&(a.primaryModel=s)}return[...t.values()].sort((r,c)=>c.startTime.localeCompare(r.startTime))}function ie(o,e="calculate"){let t=new Map;for(let r of o){let c=r.cwd?tt(r.cwd):"unknown",s=r.message.model??"unknown";t.has(c)||t.set(c,{project:c,...T(),models:{}});let n=t.get(c),a=$(r,e);O(n,a),n.models[s]||(n.models[s]=T()),O(n.models[s],a)}return[...t.values()].sort((r,c)=>c.cost.total_cost-r.cost.total_cost)}function Te(o,e="calculate"){let t={};for(let r of o){let c=r.message.model??"unknown";t[c]||(t[c]=T()),O(t[c],$(r,e))}return t}function De(o,e){let t=Array.from({length:7},()=>Array(24).fill(0));for(let r of o){let{hour:c,day:s}=mt(r.timestamp,e);t[s][c]+=r.message.usage.input_tokens+r.message.usage.output_tokens}return t}function pt(o,e="calculate",t){let r=[...o].sort((u,f)=>u.timestamp.localeCompare(f.timestamp)),c=T(),s=new Map,n=new Map,a=new Map,i=new Map,d={},m=Array.from({length:7},()=>Array(24).fill(0)),l={};for(let u of r){let f=$(u,e),y=yt(u.timestamp,t),g=y.slice(0,7),p=u.message.model??"unknown",b=u.sessionId??"unknown",E=u.cwd?tt(u.cwd):"unknown",{hour:X,day:U}=mt(u.timestamp,t),Q=u.message.usage.input_tokens+u.message.usage.output_tokens;O(c,f),s.has(y)||s.set(y,{date:y,...T(),models:{},projects:{}});let L=s.get(y);O(L,f),L.models[p]||(L.models[p]=T()),O(L.models[p],f),L.projects[E]||(L.projects[E]=T()),O(L.projects[E],f),n.has(g)||n.set(g,{month:g,...T(),models:{}});let P=n.get(g);O(P,f),P.models[p]||(P.models[p]=T()),O(P.models[p],f),a.has(b)||a.set(b,{sessionId:b,project:E,startTime:u.timestamp,endTime:u.timestamp,primaryModel:p,...T(),models:{}});let C=a.get(b);O(C,f),u.timestamp<C.startTime&&(C.startTime=u.timestamp),u.timestamp>C.endTime&&(C.endTime=u.timestamp),C.models[p]||(C.models[p]=T()),O(C.models[p],f);let Y=C.models[p].request_count,w=C.models[C.primaryModel]?.request_count??0;Y>=w&&(C.primaryModel=p),i.has(E)||i.set(E,{project:E,...T(),models:{}});let Ct=i.get(E);O(Ct,f),Ct.models[p]||(Ct.models[p]=T()),O(Ct.models[p],f),d[p]||(d[p]=T()),O(d[p],f),m[U][X]+=Q,l[E]||(l[E]=Array.from({length:7},()=>Array(24).fill(0))),l[E][U][X]+=Q}return{generated_at:new Date().toISOString(),date_range:{start:r[0]?.timestamp??"",end:r[r.length-1]?.timestamp??""},totals:c,daily:[...s.values()].sort((u,f)=>u.date.localeCompare(f.date)),monthly:[...n.values()].sort((u,f)=>u.month.localeCompare(f.month)),sessions:[...a.values()].sort((u,f)=>f.startTime.localeCompare(u.startTime)),projects:[...i.values()].sort((u,f)=>f.cost.total_cost-u.cost.total_cost),models:d,heatmap:m,project_heatmaps:l}}function Me(o,e){let t={};for(let r of o){let c=r.cwd?tt(r.cwd):"unknown";t[c]||(t[c]=Array.from({length:7},()=>Array(24).fill(0)));let{hour:s,day:n}=mt(r.timestamp,e);t[c][n][s]+=r.message.usage.input_tokens+r.message.usage.output_tokens}return t}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Zt)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let{makeEntry:n}=await Promise.resolve().then(()=>(St(),Bt));o("aggregateDaily",()=>{e("groups entries by date",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T14:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"})],i=st(a);t(i).toHaveLength(2),t(i[0].date).toBe("2025-03-25"),t(i[0].request_count).toBe(2),t(i[1].date).toBe("2025-03-26")}),e("tracks per-model breakdown",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z",message:{model:"other-model",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=st(a);t(Object.keys(i[0].models)).toHaveLength(2)}),e("respects timezone for date grouping",()=>{let a=[n({timestamp:"2025-03-25T23:00:00Z"})],i=st(a,"calculate","Asia/Kolkata");t(i[0].date).toBe("2025-03-26")})}),o("aggregateSessions",()=>{e("groups entries by sessionId",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T10:05:00Z"}),n({sessionId:"s2",timestamp:"2025-03-25T11:00:00Z"})],i=ct(a);t(i).toHaveLength(2)}),e("tracks session time range",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T10:30:00Z"})],i=ct(a);t(i[0].startTime).toBe("2025-03-25T10:00:00Z"),t(i[0].endTime).toBe("2025-03-25T10:30:00Z")})}),o("buildHeatmap",()=>{e("creates 7\xD724 grid",()=>{let a=De([]);t(a).toHaveLength(7),t(a[0]).toHaveLength(24)}),e("accumulates tokens in correct cell",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"})],i=De(a);t(i[2][10]).toBe(1500)})}),o("aggregateMonthly",()=>{e("groups entries by month",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"}),n({timestamp:"2025-04-01T10:00:00Z"})],i=Yt(a);t(i).toHaveLength(2),t(i[0].month).toBe("2025-03"),t(i[0].request_count).toBe(2),t(i[1].month).toBe("2025-04"),t(i[1].request_count).toBe(1)}),e("tracks per-model breakdown in monthly",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z",message:{model:"other-model",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Yt(a);t(Object.keys(i[0].models)).toHaveLength(2)})}),o("aggregateProjects",()=>{e("groups entries by project (cwd)",()=>{let a=[n({cwd:"/home/.claude/projects/-Users-me-proj1/session.jsonl"}),n({cwd:"/home/.claude/projects/-Users-me-proj1/session.jsonl"}),n({cwd:"/home/.claude/projects/-Users-me-proj2/session.jsonl"})],i=ie(a);t(i).toHaveLength(2)}),e("sorts by cost descending",()=>{let a=[n({cwd:"/home/.claude/projects/cheap/f.jsonl",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}),n({cwd:"/home/.claude/projects/expensive/f.jsonl",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e5,output_tokens:5e4,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=ie(a);t(i[0].cost.total_cost).toBeGreaterThan(i[1].cost.total_cost)}),e("uses unknown for entries without cwd",()=>{let a=[n()],i=ie(a);t(i[0].project).toBe("unknown")})}),o("aggregateModels",()=>{e("groups entries by model name",()=>{let a=[n(),n({message:{model:"other-model",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Te(a);t(Object.keys(i)).toHaveLength(2),t(i["claude-sonnet-4-20250514"]).toBeDefined(),t(i["other-model"]).toBeDefined()}),e("uses unknown for entries without model",()=>{let a=[n({message:{usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Te(a);t(i.unknown).toBeDefined()})}),o("aggregateSessions (extended)",()=>{e("detects primary model by request count",()=>{let a=[n({sessionId:"s1"}),n({sessionId:"s1"}),n({sessionId:"s1",message:{model:"other-model",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=ct(a);t(i[0].primaryModel).toBe("claude-sonnet-4-20250514")}),e("uses unknown sessionId when missing",()=>{let a=[n()],i=ct(a);t(i[0].sessionId).toBe("unknown")}),e("sorts sessions by startTime descending (newest first)",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T08:00:00Z"}),n({sessionId:"s2",timestamp:"2025-03-25T12:00:00Z"})],i=ct(a);t(i[0].sessionId).toBe("s2"),t(i[1].sessionId).toBe("s1")})}),o("buildDashboardData",()=>{e("returns all required fields",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T11:00:00Z"})],i=pt(a);t(i.generated_at).toBeTruthy(),t(i.date_range.start).toBe("2025-03-25T10:00:00Z"),t(i.date_range.end).toBe("2025-03-25T11:00:00Z"),t(i.totals.request_count).toBe(2),t(i.daily).toHaveLength(1),t(i.monthly).toHaveLength(1),t(i.sessions).toHaveLength(1),t(i.heatmap).toHaveLength(7),t(Object.keys(i.models)).toHaveLength(1)}),e("handles empty entries",()=>{let a=pt([]);t(a.totals.request_count).toBe(0),t(a.daily).toHaveLength(0),t(a.date_range.start).toBe("")})}),o("filterEntries",()=>{e("filters by date range",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"})],i=D(a,{since:"2025-03-25",until:"2025-03-25"});t(i).toHaveLength(1)}),e("returns all entries with no filters",()=>{let a=[n(),n()];t(D(a,{})).toHaveLength(2)}),e("filters by since only",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})];t(D(a,{since:"2025-03-25"})).toHaveLength(1)}),e("filters by until only",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})];t(D(a,{until:"2025-03-24"})).toHaveLength(1)}),e("filters by project name (case-insensitive substring)",()=>{let a=[n({cwd:"tradeforge"}),n({cwd:"cctrack"}),n({cwd:"TradeForge"})],i=D(a,{project:"trade"});t(i).toHaveLength(2)}),e("excludes entries without cwd when project filter is set",()=>{let a=[n(),n({cwd:"cctrack"})];t(D(a,{project:"cctrack"})).toHaveLength(1)})}),o("aggregateDaily (project breakdown)",()=>{e("tracks per-project breakdown in daily",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T11:00:00Z",cwd:"proj-b"})],i=st(a);t(Object.keys(i[0].projects)).toHaveLength(2),t(i[0].projects["proj-a"].request_count).toBe(1),t(i[0].projects["proj-b"].request_count).toBe(1)}),e("per-project daily data has complete cost and token fields",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"})],d=st(a)[0].projects["proj-a"];t(d.request_count).toBe(1),t(d.cost.input_cost).toBeDefined(),t(d.cost.output_cost).toBeDefined(),t(d.cost.cache_write_cost).toBeDefined(),t(d.cost.cache_read_cost).toBeDefined(),t(d.cost.total_cost).toBeDefined(),t(typeof d.cost.cache_read_cost).toBe("number"),t(d.tokens.input_tokens).toBeDefined(),t(d.tokens.output_tokens).toBeDefined(),t(d.tokens.cache_write_tokens).toBeDefined(),t(d.tokens.cache_read_tokens).toBeDefined(),t(d.tokens.total_tokens).toBeDefined()}),e("per-project data produces non-zero ROI when summed (catches the $0.00 bug)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T11:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T12:00:00Z",cwd:"proj-b"})],d=st(a)[0].projects["proj-a"],m=d.cost.total_cost,l=d.request_count,u=d.cost.cache_read_cost,f=l>0?m/l:0,y=u*9;t(l).toBe(2),t(f).toBeGreaterThan(0),t(typeof u).toBe("number")})}),o("buildProjectHeatmaps",()=>{e("creates separate heatmaps per project",()=>{let a=[n({cwd:"proj-a",timestamp:"2025-03-25T10:00:00Z"}),n({cwd:"proj-b",timestamp:"2025-03-25T14:00:00Z"})],i=Me(a);t(Object.keys(i)).toHaveLength(2),t(i["proj-a"]).toHaveLength(7),t(i["proj-a"][2][10]).toBe(1500)}),e("returns empty object for no entries",()=>{t(Me([])).toEqual({})})}),o("buildDashboardData (single-pass)",()=>{e("includes project_heatmaps",()=>{let a=[n({cwd:"proj-a",sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"})],i=pt(a);t(i.project_heatmaps).toBeDefined(),t(i.project_heatmaps["proj-a"]).toHaveLength(7)}),e("produces consistent totals across aggregations",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T11:00:00Z"}),n({sessionId:"s2",timestamp:"2025-03-26T10:00:00Z"})],i=pt(a);t(i.totals.request_count).toBe(3),t(i.daily.reduce((d,m)=>d+m.request_count,0)).toBe(3),t(i.sessions.reduce((d,m)=>d+m.request_count,0)).toBe(3)})})}function h(o){return o<0?`-$${Math.abs(o).toFixed(2)}`:o<.01&&o>0?`$${o.toFixed(4)}`:`$${o.toFixed(2)}`}function _(o){return o>=1e6?`${(o/1e6).toFixed(1)}M`:o>=1e3?`${(o/1e3).toFixed(1)}K`:o.toString()}function R(o){let e=Math.floor(o/1e3);if(e<60)return`${e}s`;let t=Math.floor(e/60);return t<60?`${t}m ${e%60}s`:`${Math.floor(t/60)}h ${t%60}m`}function S(o){let e=o??"calculate";if(e==="calculate"||e==="display"||e==="compare")return e;console.error(`Invalid mode: "${e}". Choose from: calculate, display, compare`),process.exit(1)}function nt(o){return o.includes(",")||o.includes('"')||o.includes(`
23
+ `)),b};Oo=l;let{describe:o,it:e,expect:t,afterAll:r}=import.meta.vitest,{writeFileSync:c,unlinkSync:s,mkdirSync:n,rmSync:a}=await import("fs"),{join:i}=await import("path"),{tmpdir:d}=await import("os"),m=i(d(),"cctrack-test-parser");r(()=>{try{a(m,{recursive:!0,force:!0})}catch{}});let u=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{id:"msg_1",model:"claude-sonnet-4-20250514",usage:{input_tokens:100,output_tokens:50}},requestId:"req_1"}),f=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:0,output_tokens:0}},isApiErrorMessage:!0}),y=JSON.stringify({timestamp:"2025-03-25T10:00:00Z",message:{model:"<synthetic>",usage:{input_tokens:50,output_tokens:20}}});o("parseJsonlFile",()=>{e("parses valid entries",async()=>{let g=l("valid.jsonl",[u]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.entries[0].message.usage.input_tokens).toBe(100),t(p.errors).toBe(0),s(g)}),e("filters API error entries",async()=>{let g=l("api-err.jsonl",[u,f]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.skipped.apiErrors).toBe(1),s(g)}),e("filters synthetic model entries",async()=>{let g=l("synthetic.jsonl",[u,y]),p=await ut(g);t(p.entries).toHaveLength(1),t(p.skipped.synthetic).toBe(1),s(g)}),e("counts invalid JSON as errors",async()=>{let g=l("invalid.jsonl",[u,"not json at all",'{"broken']),p=await ut(g);t(p.entries).toHaveLength(1),t(p.errors).toBe(2),s(g)}),e("counts schema validation failures as errors",async()=>{let g=JSON.stringify({timestamp:"not-a-date",message:{usage:{input_tokens:1,output_tokens:1}}}),p=l("bad-schema.jsonl",[u,g]),b=await ut(p);t(b.entries).toHaveLength(1),t(b.errors).toBe(1),s(p)}),e("defaults cache tokens to 0",async()=>{let g=l("no-cache.jsonl",[u]),p=await ut(g);t(p.entries[0].message.usage.cache_creation_input_tokens).toBe(0),t(p.entries[0].message.usage.cache_read_input_tokens).toBe(0),s(g)}),e("handles empty files",async()=>{let g=l("empty.jsonl",[""]),p=await ut(g);t(p.entries).toHaveLength(0),s(g)})}),o("parseAllFiles",()=>{e("combines entries from multiple files",async()=>{let g=l("multi1.jsonl",[u]),p=l("multi2.jsonl",[u]),b=await j([g,p]);t(b.entries).toHaveLength(2),t(b.errors).toBe(0),s(g),s(p)}),e("combines errors and skipped counts",async()=>{let g=l("comb1.jsonl",[u,f]),p=l("comb2.jsonl",[y,"bad json"]),b=await j([g,p]);t(b.entries).toHaveLength(1),t(b.skipped.apiErrors).toBe(1),t(b.skipped.synthetic).toBe(1),t(b.errors).toBe(1),s(g),s(p)}),e("handles empty file list",async()=>{let g=await j([]);t(g.entries).toHaveLength(0),t(g.errors).toBe(0)}),e("handles more than BATCH_SIZE (20) files",async()=>{let g=[];for(let b=0;b<25;b++)g.push(l(`batch-${b}.jsonl`,[u]));let p=await j(g);t(p.entries).toHaveLength(25),t(p.errors).toBe(0);for(let b of g)s(b)})})}var Oo;import{createHash as Lo}from"crypto";function rt(o){return o.requestId?`req:${o.requestId}`:o.message.id?`msg:${o.message.id}`:`hash:${Lo("sha256").update(`${o.timestamp}|${o.message.model}|${o.message.usage.input_tokens}|${o.message.usage.output_tokens}`).digest("hex").slice(0,16)}`}function x(o){let e=new Set,t=[];for(let r of o){let c=rt(r);e.has(c)||(e.add(c),t.push(r))}return t}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest,{makeEntry:r}=await Promise.resolve().then(()=>(St(),Bt));o("createDedupKey",()=>{e("uses requestId when available (highest priority)",()=>{let c=r({requestId:"req_123",message:{...r().message,id:"msg_456"}});t(rt(c)).toBe("req:req_123")}),e("falls back to message.id when no requestId",()=>{let c=r({message:{...r().message,id:"msg_456"}});t(rt(c)).toBe("msg:msg_456")}),e("falls back to hash when no requestId or message.id",()=>{let c=r(),s=rt(c);t(s).toMatch(/^hash:[a-f0-9]{16}$/)}),e("produces same hash for identical entries",()=>{let c=r(),s=r();t(rt(c)).toBe(rt(s))}),e("produces different hash for different token counts",()=>{let c=r(),s=r({message:{...r().message,usage:{...r().message.usage,input_tokens:999}}});t(rt(c)).not.toBe(rt(s))}),e("treats empty-string requestId as falsy (falls through to msg or hash)",()=>{let c=r({requestId:""}),s=rt(c);t(s).not.toBe("req:"),t(s).toMatch(/^(msg:|hash:)/)})}),o("deduplicateEntries",()=>{e("removes duplicates by requestId",()=>{let c=[r({requestId:"r1"}),r({requestId:"r1"}),r({requestId:"r2"})];t(x(c)).toHaveLength(2)}),e("removes duplicates by message.id",()=>{let c=[r({message:{...r().message,id:"m1"}}),r({message:{...r().message,id:"m1"}})];t(x(c)).toHaveLength(1)}),e("removes duplicates by hash fallback",()=>{let c=[r(),r()];t(x(c)).toHaveLength(1)}),e("preserves insertion order",()=>{let c=[r({requestId:"r1"}),r({requestId:"r2"}),r({requestId:"r1"})],s=x(c);t(s[0].requestId).toBe("r1"),t(s[1].requestId).toBe("r2")}),e("handles cross-file dedup (same requestId different entries)",()=>{let c=r({requestId:"r1",cwd:"/project-a"}),s=r({requestId:"r1",cwd:"/project-b"});t(x([c,s])).toHaveLength(1)}),e("returns empty array for empty input",()=>{t(x([])).toHaveLength(0)})})}_t();function $(o,e="calculate"){let t=o.message.usage,r=o.message.model??"unknown",c={input_tokens:t.input_tokens,output_tokens:t.output_tokens,cache_write_tokens:t.cache_creation_input_tokens??0,cache_read_tokens:t.cache_read_input_tokens??0,total_tokens:t.input_tokens+t.output_tokens+(t.cache_creation_input_tokens??0)+(t.cache_read_input_tokens??0)},s=it(r,c.input_tokens,c.output_tokens,c.cache_write_tokens,c.cache_read_tokens,o.costUSD),n={input_cost:s.input,output_cost:s.output,cache_write_cost:s.cacheWrite,cache_read_cost:s.cacheRead,total_cost:s.total},a;return e==="display"?a={input_cost:0,output_cost:0,cache_write_cost:0,cache_read_cost:0,total_cost:o.costUSD??0}:a=n,{tokens:c,cost:a,calculatedCost:n,displayCost:o.costUSD}}function re(){return{input_tokens:0,output_tokens:0,cache_write_tokens:0,cache_read_tokens:0,total_tokens:0}}function ae(){return{input_cost:0,output_cost:0,cache_write_cost:0,cache_read_cost:0,total_cost:0}}function Tt(o,e){return{input_tokens:o.input_tokens+e.input_tokens,output_tokens:o.output_tokens+e.output_tokens,cache_write_tokens:o.cache_write_tokens+e.cache_write_tokens,cache_read_tokens:o.cache_read_tokens+e.cache_read_tokens,total_tokens:o.total_tokens+e.total_tokens}}function Dt(o,e){return{input_cost:o.input_cost+e.input_cost,output_cost:o.output_cost+e.output_cost,cache_write_cost:o.cache_write_cost+e.cache_write_cost,cache_read_cost:o.cache_read_cost+e.cache_read_cost,total_cost:o.total_cost+e.total_cost}}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Ht)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let n=(a={})=>({timestamp:"2025-03-25T10:00:00Z",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e3,output_tokens:500,cache_creation_input_tokens:200,cache_read_input_tokens:300}},...a});o("processEntry",()=>{e("extracts correct token breakdown",()=>{let a=$(n());t(a.tokens.input_tokens).toBe(1e3),t(a.tokens.output_tokens).toBe(500),t(a.tokens.cache_write_tokens).toBe(200),t(a.tokens.cache_read_tokens).toBe(300),t(a.tokens.total_tokens).toBe(2e3)}),e("calculates cost in calculate mode",()=>{let a=$(n(),"calculate");t(a.cost.input_cost).toBeCloseTo(1e3*(3/1e6),10),t(a.cost.output_cost).toBeCloseTo(500*(15/1e6),10),t(a.cost.cache_write_cost).toBeCloseTo(200*(3.75/1e6),10),t(a.cost.cache_read_cost).toBeCloseTo(300*(.3/1e6),10)}),e("uses embedded cost in display mode",()=>{let a=$(n({costUSD:.42}),"display");t(a.cost.total_cost).toBe(.42),t(a.cost.input_cost).toBe(0)}),e("uses 0 in display mode when no embedded cost",()=>{let a=$(n(),"display");t(a.cost.total_cost).toBe(0)}),e("provides both calculated and display costs in compare mode data",()=>{let a=n({costUSD:.42}),i=$(a,"compare");t(i.calculatedCost.total_cost).toBeGreaterThan(0),t(i.displayCost).toBe(.42)})}),o("addTokens",()=>{e("sums token breakdowns",()=>{let d=Tt({input_tokens:10,output_tokens:5,cache_write_tokens:2,cache_read_tokens:3,total_tokens:20},{input_tokens:20,output_tokens:10,cache_write_tokens:4,cache_read_tokens:6,total_tokens:40});t(d.input_tokens).toBe(30),t(d.output_tokens).toBe(15),t(d.total_tokens).toBe(60)})}),o("addCosts",()=>{e("sums cost breakdowns",()=>{let d=Dt({input_cost:1,output_cost:2,cache_write_cost:.5,cache_read_cost:.1,total_cost:3.6},{input_cost:3,output_cost:4,cache_write_cost:1.5,cache_read_cost:.2,total_cost:8.7});t(d.input_cost).toBe(4),t(d.total_cost).toBeCloseTo(12.3,6)})}),o("processEntry with missing model",()=>{e("returns zero cost when model is undefined",()=>{let a=n({message:{usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}),i=$(a);t(i.cost.total_cost).toBe(0),t(i.tokens.input_tokens).toBe(100)})}),o("emptyTokens and emptyCost",()=>{e("emptyTokens returns all zeros",()=>{let a=re();t(a.input_tokens).toBe(0),t(a.output_tokens).toBe(0),t(a.cache_write_tokens).toBe(0),t(a.cache_read_tokens).toBe(0),t(a.total_tokens).toBe(0)}),e("emptyCost returns all zeros",()=>{let a=ae();t(a.input_cost).toBe(0),t(a.output_cost).toBe(0),t(a.cache_write_cost).toBe(0),t(a.cache_read_cost).toBe(0),t(a.total_cost).toBe(0)})})}function yt(o,e){let t=new Date(o);return e?t.toLocaleDateString("en-CA",{timeZone:e}):t.toISOString().slice(0,10)}function Zt(o,e){return yt(o,e).slice(0,7)}function mt(o,e){let t=new Date(o);if(e){let r=new Intl.DateTimeFormat("en-US",{timeZone:e,hour:"numeric",hour12:!1,weekday:"short"}).formatToParts(t),c=r.find(a=>a.type==="hour"),s=r.find(a=>a.type==="weekday"),n={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6};return{hour:parseInt(c?.value??"0",10),day:n[s?.value??"Sun"]??0}}return{hour:t.getUTCHours(),day:t.getUTCDay()}}function G(o,e,t){let r=o.slice(0,10);return!(e&&r<e||t&&r>t)}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest;o("toDateString",()=>{e("returns YYYY-MM-DD for UTC",()=>{t(yt("2025-03-25T10:00:00Z")).toBe("2025-03-25")}),e("respects timezone",()=>{t(yt("2025-03-25T23:00:00Z","Asia/Kolkata")).toBe("2025-03-26")}),e("handles midnight boundary",()=>{t(yt("2025-03-25T00:00:00Z")).toBe("2025-03-25")})}),o("toMonthString",()=>{e("returns YYYY-MM",()=>{t(Zt("2025-03-25T10:00:00Z")).toBe("2025-03")}),e("respects timezone for month boundary",()=>{t(Zt("2025-03-31T23:00:00Z","Asia/Kolkata")).toBe("2025-04")})}),o("getHourAndDay",()=>{e("returns UTC hour and day by default",()=>{let r=mt("2025-03-25T14:30:00Z");t(r.hour).toBe(14),t(r.day).toBe(2)}),e("respects timezone",()=>{let r=mt("2025-03-25T14:00:00Z","Asia/Kolkata");t(r.hour).toBe(19),t(r.day).toBe(2)}),e("handles day rollover with timezone",()=>{let r=mt("2025-03-25T20:00:00Z","Asia/Kolkata");t(r.hour).toBe(1),t(r.day).toBe(3)}),e("handles Sunday correctly",()=>{let r=mt("2025-03-23T10:00:00Z");t(r.day).toBe(0)})}),o("isInRange",()=>{e("returns true when no filters",()=>{t(G("2025-03-25T10:00:00Z")).toBe(!0)}),e("filters by since",()=>{t(G("2025-03-24T10:00:00Z","2025-03-25")).toBe(!1),t(G("2025-03-25T10:00:00Z","2025-03-25")).toBe(!0),t(G("2025-03-26T10:00:00Z","2025-03-25")).toBe(!0)}),e("filters by until",()=>{t(G("2025-03-26T10:00:00Z",void 0,"2025-03-25")).toBe(!1),t(G("2025-03-25T10:00:00Z",void 0,"2025-03-25")).toBe(!0)}),e("filters by both since and until",()=>{t(G("2025-03-25T10:00:00Z","2025-03-25","2025-03-25")).toBe(!0),t(G("2025-03-24T10:00:00Z","2025-03-25","2025-03-26")).toBe(!1),t(G("2025-03-27T10:00:00Z","2025-03-25","2025-03-26")).toBe(!1)}),e("includes entry at 23:59 on the same day",()=>{t(G("2025-03-25T23:59:59Z","2025-03-25","2025-03-25")).toBe(!0)}),e("returns false when since > until (impossible range)",()=>{t(G("2025-03-25T10:00:00Z","2025-03-26","2025-03-24")).toBe(!1)})})}function T(){return{tokens:re(),cost:ae(),request_count:0}}function O(o,e){o.tokens=Tt(o.tokens,e.tokens),o.cost=Dt(o.cost,e.cost),o.request_count++}function D(o,e){return o.filter(t=>!(!G(t.timestamp,e.since,e.until)||e.project&&(!t.cwd||!tt(t.cwd).toLowerCase().includes(e.project.toLowerCase()))))}function st(o,e="calculate",t){let r=new Map;for(let c of o){let s=yt(c.timestamp,t),n=c.message.model??"unknown",a=c.cwd?tt(c.cwd):"unknown";r.has(s)||r.set(s,{date:s,...T(),models:{},projects:{}});let i=r.get(s),d=$(c,e);O(i,d),i.models[n]||(i.models[n]=T()),O(i.models[n],d),i.projects[a]||(i.projects[a]=T()),O(i.projects[a],d)}return[...r.values()].sort((c,s)=>c.date.localeCompare(s.date))}function Ut(o,e="calculate",t){let r=new Map;for(let c of o){let s=Zt(c.timestamp,t),n=c.message.model??"unknown";r.has(s)||r.set(s,{month:s,...T(),models:{}});let a=r.get(s),i=$(c,e);O(a,i),a.models[n]||(a.models[n]=T()),O(a.models[n],i)}return[...r.values()].sort((c,s)=>c.month.localeCompare(s.month))}function ct(o,e="calculate"){let t=new Map;for(let r of o){let c=r.sessionId??"unknown",s=r.message.model??"unknown",n=r.cwd?tt(r.cwd):"unknown";t.has(c)||t.set(c,{sessionId:c,project:n,startTime:r.timestamp,endTime:r.timestamp,primaryModel:s,...T(),models:{}});let a=t.get(c),i=$(r,e);O(a,i),r.timestamp<a.startTime&&(a.startTime=r.timestamp),r.timestamp>a.endTime&&(a.endTime=r.timestamp),a.models[s]||(a.models[s]=T()),O(a.models[s],i);let d=a.models[s].request_count,m=a.models[a.primaryModel]?.request_count??0;d>=m&&(a.primaryModel=s)}return[...t.values()].sort((r,c)=>c.startTime.localeCompare(r.startTime))}function ie(o,e="calculate"){let t=new Map;for(let r of o){let c=r.cwd?tt(r.cwd):"unknown",s=r.message.model??"unknown";t.has(c)||t.set(c,{project:c,...T(),models:{}});let n=t.get(c),a=$(r,e);O(n,a),n.models[s]||(n.models[s]=T()),O(n.models[s],a)}return[...t.values()].sort((r,c)=>c.cost.total_cost-r.cost.total_cost)}function Te(o,e="calculate"){let t={};for(let r of o){let c=r.message.model??"unknown";t[c]||(t[c]=T()),O(t[c],$(r,e))}return t}function De(o,e){let t=Array.from({length:7},()=>Array(24).fill(0));for(let r of o){let{hour:c,day:s}=mt(r.timestamp,e);t[s][c]+=r.message.usage.input_tokens+r.message.usage.output_tokens}return t}function pt(o,e="calculate",t){let r=[...o].sort((u,f)=>u.timestamp.localeCompare(f.timestamp)),c=T(),s=new Map,n=new Map,a=new Map,i=new Map,d={},m=Array.from({length:7},()=>Array(24).fill(0)),l={};for(let u of r){let f=$(u,e),y=yt(u.timestamp,t),g=y.slice(0,7),p=u.message.model??"unknown",b=u.sessionId??"unknown",E=u.cwd?tt(u.cwd):"unknown",{hour:X,day:U}=mt(u.timestamp,t),Q=u.message.usage.input_tokens+u.message.usage.output_tokens;O(c,f),s.has(y)||s.set(y,{date:y,...T(),models:{},projects:{}});let L=s.get(y);O(L,f),L.models[p]||(L.models[p]=T()),O(L.models[p],f),L.projects[E]||(L.projects[E]=T()),O(L.projects[E],f),n.has(g)||n.set(g,{month:g,...T(),models:{}});let P=n.get(g);O(P,f),P.models[p]||(P.models[p]=T()),O(P.models[p],f),a.has(b)||a.set(b,{sessionId:b,project:E,startTime:u.timestamp,endTime:u.timestamp,primaryModel:p,...T(),models:{}});let C=a.get(b);O(C,f),u.timestamp<C.startTime&&(C.startTime=u.timestamp),u.timestamp>C.endTime&&(C.endTime=u.timestamp),C.models[p]||(C.models[p]=T()),O(C.models[p],f);let Y=C.models[p].request_count,w=C.models[C.primaryModel]?.request_count??0;Y>=w&&(C.primaryModel=p),i.has(E)||i.set(E,{project:E,...T(),models:{}});let Ct=i.get(E);O(Ct,f),Ct.models[p]||(Ct.models[p]=T()),O(Ct.models[p],f),d[p]||(d[p]=T()),O(d[p],f),m[U][X]+=Q,l[E]||(l[E]=Array.from({length:7},()=>Array(24).fill(0))),l[E][U][X]+=Q}return{generated_at:new Date().toISOString(),date_range:{start:r[0]?.timestamp??"",end:r[r.length-1]?.timestamp??""},totals:c,daily:[...s.values()].sort((u,f)=>u.date.localeCompare(f.date)),monthly:[...n.values()].sort((u,f)=>u.month.localeCompare(f.month)),sessions:[...a.values()].sort((u,f)=>f.startTime.localeCompare(u.startTime)),projects:[...i.values()].sort((u,f)=>f.cost.total_cost-u.cost.total_cost),models:d,heatmap:m,project_heatmaps:l}}function Me(o,e){let t={};for(let r of o){let c=r.cwd?tt(r.cwd):"unknown";t[c]||(t[c]=Array.from({length:7},()=>Array(24).fill(0)));let{hour:s,day:n}=mt(r.timestamp,e);t[c][n][s]+=r.message.usage.input_tokens+r.message.usage.output_tokens}return t}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Ht)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let{makeEntry:n}=await Promise.resolve().then(()=>(St(),Bt));o("aggregateDaily",()=>{e("groups entries by date",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T14:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"})],i=st(a);t(i).toHaveLength(2),t(i[0].date).toBe("2025-03-25"),t(i[0].request_count).toBe(2),t(i[1].date).toBe("2025-03-26")}),e("tracks per-model breakdown",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z",message:{model:"other-model",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=st(a);t(Object.keys(i[0].models)).toHaveLength(2)}),e("respects timezone for date grouping",()=>{let a=[n({timestamp:"2025-03-25T23:00:00Z"})],i=st(a,"calculate","Asia/Kolkata");t(i[0].date).toBe("2025-03-26")})}),o("aggregateSessions",()=>{e("groups entries by sessionId",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T10:05:00Z"}),n({sessionId:"s2",timestamp:"2025-03-25T11:00:00Z"})],i=ct(a);t(i).toHaveLength(2)}),e("tracks session time range",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T10:30:00Z"})],i=ct(a);t(i[0].startTime).toBe("2025-03-25T10:00:00Z"),t(i[0].endTime).toBe("2025-03-25T10:30:00Z")})}),o("buildHeatmap",()=>{e("creates 7\xD724 grid",()=>{let a=De([]);t(a).toHaveLength(7),t(a[0]).toHaveLength(24)}),e("accumulates tokens in correct cell",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"})],i=De(a);t(i[2][10]).toBe(1500)})}),o("aggregateMonthly",()=>{e("groups entries by month",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"}),n({timestamp:"2025-04-01T10:00:00Z"})],i=Ut(a);t(i).toHaveLength(2),t(i[0].month).toBe("2025-03"),t(i[0].request_count).toBe(2),t(i[1].month).toBe("2025-04"),t(i[1].request_count).toBe(1)}),e("tracks per-model breakdown in monthly",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z",message:{model:"other-model",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Ut(a);t(Object.keys(i[0].models)).toHaveLength(2)})}),o("aggregateProjects",()=>{e("groups entries by project (cwd)",()=>{let a=[n({cwd:"/home/.claude/projects/-Users-me-proj1/session.jsonl"}),n({cwd:"/home/.claude/projects/-Users-me-proj1/session.jsonl"}),n({cwd:"/home/.claude/projects/-Users-me-proj2/session.jsonl"})],i=ie(a);t(i).toHaveLength(2)}),e("sorts by cost descending",()=>{let a=[n({cwd:"/home/.claude/projects/cheap/f.jsonl",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}),n({cwd:"/home/.claude/projects/expensive/f.jsonl",message:{model:"claude-sonnet-4-20250514",usage:{input_tokens:1e5,output_tokens:5e4,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=ie(a);t(i[0].cost.total_cost).toBeGreaterThan(i[1].cost.total_cost)}),e("uses unknown for entries without cwd",()=>{let a=[n()],i=ie(a);t(i[0].project).toBe("unknown")})}),o("aggregateModels",()=>{e("groups entries by model name",()=>{let a=[n(),n({message:{model:"other-model",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Te(a);t(Object.keys(i)).toHaveLength(2),t(i["claude-sonnet-4-20250514"]).toBeDefined(),t(i["other-model"]).toBeDefined()}),e("uses unknown for entries without model",()=>{let a=[n({message:{usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=Te(a);t(i.unknown).toBeDefined()})}),o("aggregateSessions (extended)",()=>{e("detects primary model by request count",()=>{let a=[n({sessionId:"s1"}),n({sessionId:"s1"}),n({sessionId:"s1",message:{model:"other-model",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:0,cache_read_input_tokens:0}}})],i=ct(a);t(i[0].primaryModel).toBe("claude-sonnet-4-20250514")}),e("uses unknown sessionId when missing",()=>{let a=[n()],i=ct(a);t(i[0].sessionId).toBe("unknown")}),e("sorts sessions by startTime descending (newest first)",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T08:00:00Z"}),n({sessionId:"s2",timestamp:"2025-03-25T12:00:00Z"})],i=ct(a);t(i[0].sessionId).toBe("s2"),t(i[1].sessionId).toBe("s1")})}),o("buildDashboardData",()=>{e("returns all required fields",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T11:00:00Z"})],i=pt(a);t(i.generated_at).toBeTruthy(),t(i.date_range.start).toBe("2025-03-25T10:00:00Z"),t(i.date_range.end).toBe("2025-03-25T11:00:00Z"),t(i.totals.request_count).toBe(2),t(i.daily).toHaveLength(1),t(i.monthly).toHaveLength(1),t(i.sessions).toHaveLength(1),t(i.heatmap).toHaveLength(7),t(Object.keys(i.models)).toHaveLength(1)}),e("handles empty entries",()=>{let a=pt([]);t(a.totals.request_count).toBe(0),t(a.daily).toHaveLength(0),t(a.date_range.start).toBe("")})}),o("filterEntries",()=>{e("filters by date range",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"})],i=D(a,{since:"2025-03-25",until:"2025-03-25"});t(i).toHaveLength(1)}),e("returns all entries with no filters",()=>{let a=[n(),n()];t(D(a,{})).toHaveLength(2)}),e("filters by since only",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})];t(D(a,{since:"2025-03-25"})).toHaveLength(1)}),e("filters by until only",()=>{let a=[n({timestamp:"2025-03-24T10:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})];t(D(a,{until:"2025-03-24"})).toHaveLength(1)}),e("filters by project name (case-insensitive substring)",()=>{let a=[n({cwd:"tradeforge"}),n({cwd:"cctrack"}),n({cwd:"TradeForge"})],i=D(a,{project:"trade"});t(i).toHaveLength(2)}),e("excludes entries without cwd when project filter is set",()=>{let a=[n(),n({cwd:"cctrack"})];t(D(a,{project:"cctrack"})).toHaveLength(1)})}),o("aggregateDaily (project breakdown)",()=>{e("tracks per-project breakdown in daily",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T11:00:00Z",cwd:"proj-b"})],i=st(a);t(Object.keys(i[0].projects)).toHaveLength(2),t(i[0].projects["proj-a"].request_count).toBe(1),t(i[0].projects["proj-b"].request_count).toBe(1)}),e("per-project daily data has complete cost and token fields",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"})],d=st(a)[0].projects["proj-a"];t(d.request_count).toBe(1),t(d.cost.input_cost).toBeDefined(),t(d.cost.output_cost).toBeDefined(),t(d.cost.cache_write_cost).toBeDefined(),t(d.cost.cache_read_cost).toBeDefined(),t(d.cost.total_cost).toBeDefined(),t(typeof d.cost.cache_read_cost).toBe("number"),t(d.tokens.input_tokens).toBeDefined(),t(d.tokens.output_tokens).toBeDefined(),t(d.tokens.cache_write_tokens).toBeDefined(),t(d.tokens.cache_read_tokens).toBeDefined(),t(d.tokens.total_tokens).toBeDefined()}),e("per-project data produces non-zero ROI when summed (catches the $0.00 bug)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T11:00:00Z",cwd:"proj-a"}),n({timestamp:"2025-03-25T12:00:00Z",cwd:"proj-b"})],d=st(a)[0].projects["proj-a"],m=d.cost.total_cost,l=d.request_count,u=d.cost.cache_read_cost,f=l>0?m/l:0,y=u*9;t(l).toBe(2),t(f).toBeGreaterThan(0),t(typeof u).toBe("number")})}),o("buildProjectHeatmaps",()=>{e("creates separate heatmaps per project",()=>{let a=[n({cwd:"proj-a",timestamp:"2025-03-25T10:00:00Z"}),n({cwd:"proj-b",timestamp:"2025-03-25T14:00:00Z"})],i=Me(a);t(Object.keys(i)).toHaveLength(2),t(i["proj-a"]).toHaveLength(7),t(i["proj-a"][2][10]).toBe(1500)}),e("returns empty object for no entries",()=>{t(Me([])).toEqual({})})}),o("buildDashboardData (single-pass)",()=>{e("includes project_heatmaps",()=>{let a=[n({cwd:"proj-a",sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"})],i=pt(a);t(i.project_heatmaps).toBeDefined(),t(i.project_heatmaps["proj-a"]).toHaveLength(7)}),e("produces consistent totals across aggregations",()=>{let a=[n({sessionId:"s1",timestamp:"2025-03-25T10:00:00Z"}),n({sessionId:"s1",timestamp:"2025-03-25T11:00:00Z"}),n({sessionId:"s2",timestamp:"2025-03-26T10:00:00Z"})],i=pt(a);t(i.totals.request_count).toBe(3),t(i.daily.reduce((d,m)=>d+m.request_count,0)).toBe(3),t(i.sessions.reduce((d,m)=>d+m.request_count,0)).toBe(3)})})}function h(o){return o<0?`-$${Math.abs(o).toFixed(2)}`:o<.01&&o>0?`$${o.toFixed(4)}`:`$${o.toFixed(2)}`}function _(o){return o>=1e6?`${(o/1e6).toFixed(1)}M`:o>=1e3?`${(o/1e3).toFixed(1)}K`:o.toString()}function R(o){let e=Math.floor(o/1e3);if(e<60)return`${e}s`;let t=Math.floor(e/60);return t<60?`${t}m ${e%60}s`:`${Math.floor(t/60)}h ${t%60}m`}function S(o){let e=o??"calculate";if(e==="calculate"||e==="display"||e==="compare")return e;console.error(`Invalid mode: "${e}". Choose from: calculate, display, compare`),process.exit(1)}function nt(o){return o.includes(",")||o.includes('"')||o.includes(`
24
24
  `)?'"'+o.replace(/"/g,'""')+'"':o}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest;o("formatCost",()=>{e("formats zero as $0.00",()=>t(h(0)).toBe("$0.00")),e("formats sub-cent with 4 decimals",()=>t(h(.0012)).toBe("$0.0012")),e("formats $0.0099 with 4 decimals",()=>t(h(.0099)).toBe("$0.0099")),e("formats $0.01 with 2 decimals",()=>t(h(.01)).toBe("$0.01")),e("formats $1.50 with 2 decimals",()=>t(h(1.5)).toBe("$1.50")),e("formats large cost",()=>t(h(1234.56)).toBe("$1234.56")),e("formats negative cost with minus before $",()=>t(h(-5)).toBe("-$5.00"))}),o("formatTokens",()=>{e("formats millions",()=>t(_(15e5)).toBe("1.5M")),e("formats thousands",()=>t(_(1500)).toBe("1.5K")),e("formats small numbers as-is",()=>t(_(999)).toBe("999")),e("formats zero",()=>t(_(0)).toBe("0"))}),o("formatDuration",()=>{e("formats seconds",()=>t(R(5e3)).toBe("5s")),e("formats minutes",()=>t(R(125e3)).toBe("2m 5s")),e("formats hours",()=>t(R(3725e3)).toBe("1h 2m")),e("formats zero",()=>t(R(0)).toBe("0s"))}),o("csvEscape",()=>{e("passes plain text through",()=>t(nt("hello")).toBe("hello")),e("wraps text with comma",()=>t(nt("a,b")).toBe('"a,b"')),e("escapes double quotes",()=>t(nt('say "hi"')).toBe('"say ""hi"""')),e("wraps text with newline",()=>t(nt(`a
25
25
  b`)).toBe(`"a
26
- b"`))}),o("shortenModelName",()=>{e("shortens opus-4.6",()=>t(ot("claude-opus-4-6-20260205")).toBe("opus-4.6")),e("shortens sonnet-4.6",()=>t(ot("claude-sonnet-4-6-20260217")).toBe("sonnet-4.6")),e("shortens opus-4",()=>t(ot("claude-opus-4-20250514")).toBe("opus-4")),e("shortens haiku-4.5",()=>t(ot("claude-haiku-4-5-20251001")).toBe("haiku-4.5")),e("shortens legacy sonnet-3.5",()=>t(ot("claude-3-5-sonnet-20241022")).toBe("sonnet-3.5")),e("strips claude- prefix for unknown",()=>t(ot("claude-custom-model")).toBe("custom-model")),e("returns non-claude model as-is",()=>t(ot("gpt-4")).toBe("gpt-4"))}),o("parseCostMode",()=>{e("accepts calculate",()=>t(S("calculate")).toBe("calculate")),e("accepts display",()=>t(S("display")).toBe("display")),e("accepts compare",()=>t(S("compare")).toBe("compare")),e("defaults to calculate",()=>t(S(void 0)).toBe("calculate"))})}function ot(o){let e={"claude-opus-4-6":"opus-4.6","claude-sonnet-4-6":"sonnet-4.6","claude-opus-4-5":"opus-4.5","claude-sonnet-4-5":"sonnet-4.5","claude-haiku-4-5":"haiku-4.5","claude-opus-4":"opus-4","claude-sonnet-4":"sonnet-4","claude-3-7-sonnet":"sonnet-3.7","claude-3-5-sonnet":"sonnet-3.5","claude-3-5-haiku":"haiku-3.5","claude-3-opus":"opus-3","claude-3-sonnet":"sonnet-3","claude-3-haiku":"haiku-3"};for(let[t,r]of Object.entries(e))if(o.startsWith(t))return r;return o.replace(/^claude-/,"").replace(/-\d{8}$/,"")}import{readFileSync as ce,writeFileSync as Ho,mkdirSync as Zo,unlinkSync as Uo,existsSync as Yo}from"fs";import{join as Ee}from"path";import{homedir as Jo}from"os";import Jt from"chalk";var $e=Ee(Jo(),".cctrack"),bt=Ee($e,"config.json");function lt(){try{let o=ce(bt,"utf-8");return JSON.parse(o).budget??{}}catch{return{}}}function Oe(o){Zo($e,{recursive:!0});let e={};try{e=JSON.parse(ce(bt,"utf-8"))}catch{}e.budget=o,Ho(bt,JSON.stringify(e,null,2)+`
27
- `,"utf-8")}function Ie(){try{let o=ce(bt,"utf-8");return JSON.parse(o)}catch{return{}}}function Le(){Yo(bt)&&Uo(bt)}function Ae(){return bt}function K(o){return o>=Ft.exceeded?"exceeded":o>=Ft.critical?"critical":o>=Ft.warning?"warning":"safe"}function N(o,e){let t=e===0?o>0?100:0:o/e*100,r=K(t),c=Math.max(0,e-o);return{level:r,budget:e,spent:o,remaining:c,percentage:t}}function Mt(o){switch(o){case"safe":return Jt.green;case"warning":return Jt.yellow;case"critical":return Jt.red;case"exceeded":return Jt.bgRed.white}}function V(o,e=20){let t=Math.min(Math.max(o,0),100),r=Math.round(t/100*e),c=e-r,s="\u2588".repeat(r)+"\u2591".repeat(c),n=K(o),a=Mt(n),i=`${Math.round(o)}%`;return`${a(s)} ${i}`}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r,afterEach:c}=import.meta.vitest,{mkdtempSync:s,writeFileSync:n,readFileSync:a,rmSync:i}=await import("fs"),{tmpdir:d}=await import("os"),{join:m}=await import("path");o("loadBudgetConfig",()=>{e("returns empty object when no config file exists",()=>{let l=lt();t(typeof l).toBe("object"),t(l).toBeDefined()})}),o("getBudgetLevel",()=>{e("returns safe for 0%",()=>{t(K(0)).toBe("safe")}),e("returns safe for 25%",()=>{t(K(25)).toBe("safe")}),e("returns safe for 49.9%",()=>{t(K(49.9)).toBe("safe")}),e("returns warning at exactly 50%",()=>{t(K(50)).toBe("warning")}),e("returns warning for 75%",()=>{t(K(75)).toBe("warning")}),e("returns critical at exactly 80%",()=>{t(K(80)).toBe("critical")}),e("returns critical for 99%",()=>{t(K(99)).toBe("critical")}),e("returns exceeded at exactly 100%",()=>{t(K(100)).toBe("exceeded")}),e("returns exceeded for 150%",()=>{t(K(150)).toBe("exceeded")})}),o("calculateBudgetStatus",()=>{e("calculates 0% when nothing spent",()=>{let l=N(0,100);t(l.percentage).toBe(0),t(l.level).toBe("safe"),t(l.remaining).toBe(100),t(l.spent).toBe(0),t(l.budget).toBe(100)}),e("calculates 25% spent",()=>{let l=N(25,100);t(l.percentage).toBe(25),t(l.level).toBe("safe"),t(l.remaining).toBe(75)}),e("calculates 50% (warning threshold)",()=>{let l=N(50,100);t(l.percentage).toBe(50),t(l.level).toBe("warning"),t(l.remaining).toBe(50)}),e("calculates 75% (still warning)",()=>{let l=N(75,100);t(l.percentage).toBe(75),t(l.level).toBe("warning"),t(l.remaining).toBe(25)}),e("calculates 99% (critical)",()=>{let l=N(99,100);t(l.percentage).toBe(99),t(l.level).toBe("critical"),t(l.remaining).toBe(1)}),e("calculates 100% (exceeded)",()=>{let l=N(100,100);t(l.percentage).toBe(100),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)}),e("calculates 150% (exceeded, remaining clamped to 0)",()=>{let l=N(150,100);t(l.percentage).toBe(150),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)})}),o("formatBudgetBar",()=>{let l=u=>u.replace(/\x1B\[[0-9;]*m/g,"");e("shows empty bar at 0%",()=>{let u=l(V(0,20));t(u).toContain("\u2591".repeat(20)),t(u).toContain("0%")}),e("shows half-filled bar at 50%",()=>{let u=l(V(50,20));t(u).toContain("\u2588".repeat(10)),t(u).toContain("\u2591".repeat(10)),t(u).toContain("50%")}),e("shows full bar at 100%",()=>{let u=l(V(100,20));t(u).toContain("\u2588".repeat(20)),t(u).toContain("100%")}),e("clamps bar fill at 100% for values over 100%",()=>{let u=l(V(150,20));t(u).toContain("\u2588".repeat(20)),t(u).toContain("150%")}),e("uses default width of 20",()=>{let f=l(V(50)).split(" ")[0];t(f).toHaveLength(20)})}),o("budgetColor",()=>{e("returns green for safe",()=>{let l=Mt("safe");t(l("test")).toContain("test")}),e("returns yellow for warning",()=>{let l=Mt("warning");t(l("test")).toContain("test")}),e("returns red for critical",()=>{let l=Mt("critical");t(l("test")).toContain("test")}),e("returns bgRed.white for exceeded",()=>{let l=Mt("exceeded");t(l("test")).toContain("test")})}),o("calculateBudgetStatus edge cases",()=>{e("handles zero budget with zero spending",()=>{let l=N(0,0);t(l.percentage).toBe(0),t(l.level).toBe("safe"),t(l.remaining).toBe(0)}),e("handles spending against zero budget",()=>{let l=N(10,0);t(l.percentage).toBe(100),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)}),e("handles fractional dollar amounts without precision errors",()=>{let l=N(.30000000000000004,1);t(l.percentage).toBeCloseTo(30,10),t(l.remaining).toBeCloseTo(.7,10),t(l.level).toBe("safe")})}),o("getBudgetLevel edge cases",()=>{e("returns safe for negative percentage",()=>{t(K(-10)).toBe("safe")}),e("returns safe for NaN (documents behavior)",()=>{t(K(NaN)).toBe("safe")})}),o("formatBudgetBar edge cases",()=>{e("handles negative percentage without throwing",()=>{try{let l=V(-50,20);t(typeof l).toBe("string")}catch(l){t(l).toBeInstanceOf(RangeError)}}),e("bar at exactly 80% shows 16 filled blocks",()=>{let u=(f=>f.replace(/\x1B\[[0-9;]*m/g,""))(V(80,20));t(u).toContain("\u2588".repeat(16)),t(u).toContain("80%")})})}function q(o,e,t){if(o.length===0)return{hourly_cost:0,daily_cost:0,projected_monthly:0,hours_analyzed:0,insufficient_data:!0};let r=[...o].sort((y,g)=>y.timestamp.localeCompare(g.timestamp)),c=new Date(r[0].timestamp).getTime(),s=new Date(r[r.length-1].timestamp).getTime(),n=0;for(let y of r){let g=$(y,e);n+=g.cost.total_cost}let a=s-c,i=Math.max(a/(1e3*60*60),1),d=n/i,m=i/24,l=m>=1?n/m:d*24,u=l*30,f={hourly_cost:d,daily_cost:l,projected_monthly:u,hours_analyzed:i,insufficient_data:a<3600*1e3};if(t?.monthly!==void 0&&d>0){let y=t.monthly-n;y<=0?f.time_until_budget_exhausted_ms=0:f.time_until_budget_exhausted_ms=y/d*60*60*1e3}return f}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Zt)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let{makeEntry:n}=await Promise.resolve().then(()=>(St(),Bt));o("calculateBurnRate",()=>{e("returns zero rates for zero entries",()=>{let a=q([],"calculate");t(a.hourly_cost).toBe(0),t(a.daily_cost).toBe(0),t(a.projected_monthly).toBe(0),t(a.hours_analyzed).toBe(0),t(a.time_until_budget_exhausted_ms).toBeUndefined()}),e("calculates burn rate with 1 hour of data",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(1,2),t(i.hourly_cost).toBeGreaterThan(0),t(i.daily_cost).toBeCloseTo(i.hourly_cost*24,6),t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6)}),e("calculates burn rate with multiple days of data",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"}),n({timestamp:"2025-03-27T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(48,2);let d=i.hours_analyzed/24;t(d).toBeGreaterThanOrEqual(1),t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6)}),e("projected monthly calculation is accurate",()=>{let a=[n({timestamp:"2025-03-25T00:00:00Z"}),n({timestamp:"2025-03-26T00:00:00Z"})],i=q(a,"calculate");t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6),t(i.projected_monthly).toBeGreaterThan(0)}),e("calculates time_until_budget_exhausted with budget config",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate",{monthly:100});t(i.time_until_budget_exhausted_ms).toBeDefined(),t(i.time_until_budget_exhausted_ms).toBeGreaterThan(0)}),e("returns 0 exhaustion time when budget already exceeded",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate",{monthly:0});t(i.time_until_budget_exhausted_ms).toBe(0)}),e("does not include exhaustion time without budget config",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate");t(i.time_until_budget_exhausted_ms).toBeUndefined()}),e("clamps to minimum 1 hour for a single entry (avoids infinite rates)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBe(1),t(Number.isFinite(i.hourly_cost)).toBe(!0),t(i.hourly_cost).toBeGreaterThan(0),t(i.daily_cost).toBeCloseTo(i.hourly_cost*24,6)}),e("hourly cost matches hand-calculated value",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate");t(i.hourly_cost).toBeCloseTo(.0105,6),t(i.hours_analyzed).toBeCloseTo(2,2)}),e("time_until_budget_exhausted_ms matches expected value",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate",{monthly:1}),u=(1-.021)/.0105*60*60*1e3;t(i.time_until_budget_exhausted_ms).toBeDefined(),t(i.time_until_budget_exhausted_ms).toBeCloseTo(u,-1),t(i.time_until_budget_exhausted_ms).toBeGreaterThan(0)}),e("returns 0 exhaustion time when spending exceeds budget (realistic overspend)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate",{monthly:.01});t(i.time_until_budget_exhausted_ms).toBe(0)}),e("handles unsorted entries correctly",()=>{let a=[n({timestamp:"2025-03-25T12:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(2,2),t(i.hourly_cost).toBeCloseTo(.0105,6)})})}function Wo(o,e){let t=[];if(e){t.push("date,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)for(let[c,s]of Object.entries(r.models))t.push([r.date,c,s.tokens.input_tokens,s.tokens.output_tokens,s.tokens.cache_write_tokens,s.tokens.cache_read_tokens,s.tokens.total_tokens,s.cost.total_cost.toFixed(6)].join(","))}else{t.push("date,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)t.push([r.date,r.tokens.input_tokens,r.tokens.output_tokens,r.tokens.cache_write_tokens,r.tokens.cache_read_tokens,r.tokens.total_tokens,r.cost.total_cost.toFixed(6)].join(","))}return t.join(`
26
+ b"`))}),o("shortenModelName",()=>{e("shortens opus-4.6",()=>t(ot("claude-opus-4-6-20260205")).toBe("opus-4.6")),e("shortens sonnet-4.6",()=>t(ot("claude-sonnet-4-6-20260217")).toBe("sonnet-4.6")),e("shortens opus-4",()=>t(ot("claude-opus-4-20250514")).toBe("opus-4")),e("shortens haiku-4.5",()=>t(ot("claude-haiku-4-5-20251001")).toBe("haiku-4.5")),e("shortens legacy sonnet-3.5",()=>t(ot("claude-3-5-sonnet-20241022")).toBe("sonnet-3.5")),e("strips claude- prefix for unknown",()=>t(ot("claude-custom-model")).toBe("custom-model")),e("returns non-claude model as-is",()=>t(ot("gpt-4")).toBe("gpt-4"))}),o("parseCostMode",()=>{e("accepts calculate",()=>t(S("calculate")).toBe("calculate")),e("accepts display",()=>t(S("display")).toBe("display")),e("accepts compare",()=>t(S("compare")).toBe("compare")),e("defaults to calculate",()=>t(S(void 0)).toBe("calculate"))})}function ot(o){let e={"claude-opus-4-6":"opus-4.6","claude-sonnet-4-6":"sonnet-4.6","claude-opus-4-5":"opus-4.5","claude-sonnet-4-5":"sonnet-4.5","claude-haiku-4-5":"haiku-4.5","claude-opus-4":"opus-4","claude-sonnet-4":"sonnet-4","claude-3-7-sonnet":"sonnet-3.7","claude-3-5-sonnet":"sonnet-3.5","claude-3-5-haiku":"haiku-3.5","claude-3-opus":"opus-3","claude-3-sonnet":"sonnet-3","claude-3-haiku":"haiku-3"};for(let[t,r]of Object.entries(e))if(o.startsWith(t))return r;return o.replace(/^claude-/,"").replace(/-\d{8}$/,"")}import{readFileSync as ce,writeFileSync as Ho,mkdirSync as Zo,unlinkSync as Uo,existsSync as Yo}from"fs";import{join as Ee}from"path";import{homedir as Jo}from"os";import Yt from"chalk";var $e=Ee(Jo(),".cctrack"),bt=Ee($e,"config.json");function lt(){try{let o=ce(bt,"utf-8");return JSON.parse(o).budget??{}}catch{return{}}}function Oe(o){Zo($e,{recursive:!0});let e={};try{e=JSON.parse(ce(bt,"utf-8"))}catch{}e.budget=o,Ho(bt,JSON.stringify(e,null,2)+`
27
+ `,"utf-8")}function Ie(){try{let o=ce(bt,"utf-8");return JSON.parse(o)}catch{return{}}}function Le(){Yo(bt)&&Uo(bt)}function Ae(){return bt}function K(o){return o>=Ft.exceeded?"exceeded":o>=Ft.critical?"critical":o>=Ft.warning?"warning":"safe"}function N(o,e){let t=e===0?o>0?100:0:o/e*100,r=K(t),c=Math.max(0,e-o);return{level:r,budget:e,spent:o,remaining:c,percentage:t}}function Mt(o){switch(o){case"safe":return Yt.green;case"warning":return Yt.yellow;case"critical":return Yt.red;case"exceeded":return Yt.bgRed.white}}function V(o,e=20){let t=Math.min(Math.max(o,0),100),r=Math.round(t/100*e),c=e-r,s="\u2588".repeat(r)+"\u2591".repeat(c),n=K(o),a=Mt(n),i=`${Math.round(o)}%`;return`${a(s)} ${i}`}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r,afterEach:c}=import.meta.vitest,{mkdtempSync:s,writeFileSync:n,readFileSync:a,rmSync:i}=await import("fs"),{tmpdir:d}=await import("os"),{join:m}=await import("path");o("loadBudgetConfig",()=>{e("returns empty object when no config file exists",()=>{let l=lt();t(typeof l).toBe("object"),t(l).toBeDefined()})}),o("getBudgetLevel",()=>{e("returns safe for 0%",()=>{t(K(0)).toBe("safe")}),e("returns safe for 25%",()=>{t(K(25)).toBe("safe")}),e("returns safe for 49.9%",()=>{t(K(49.9)).toBe("safe")}),e("returns warning at exactly 50%",()=>{t(K(50)).toBe("warning")}),e("returns warning for 75%",()=>{t(K(75)).toBe("warning")}),e("returns critical at exactly 80%",()=>{t(K(80)).toBe("critical")}),e("returns critical for 99%",()=>{t(K(99)).toBe("critical")}),e("returns exceeded at exactly 100%",()=>{t(K(100)).toBe("exceeded")}),e("returns exceeded for 150%",()=>{t(K(150)).toBe("exceeded")})}),o("calculateBudgetStatus",()=>{e("calculates 0% when nothing spent",()=>{let l=N(0,100);t(l.percentage).toBe(0),t(l.level).toBe("safe"),t(l.remaining).toBe(100),t(l.spent).toBe(0),t(l.budget).toBe(100)}),e("calculates 25% spent",()=>{let l=N(25,100);t(l.percentage).toBe(25),t(l.level).toBe("safe"),t(l.remaining).toBe(75)}),e("calculates 50% (warning threshold)",()=>{let l=N(50,100);t(l.percentage).toBe(50),t(l.level).toBe("warning"),t(l.remaining).toBe(50)}),e("calculates 75% (still warning)",()=>{let l=N(75,100);t(l.percentage).toBe(75),t(l.level).toBe("warning"),t(l.remaining).toBe(25)}),e("calculates 99% (critical)",()=>{let l=N(99,100);t(l.percentage).toBe(99),t(l.level).toBe("critical"),t(l.remaining).toBe(1)}),e("calculates 100% (exceeded)",()=>{let l=N(100,100);t(l.percentage).toBe(100),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)}),e("calculates 150% (exceeded, remaining clamped to 0)",()=>{let l=N(150,100);t(l.percentage).toBe(150),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)})}),o("formatBudgetBar",()=>{let l=u=>u.replace(/\x1B\[[0-9;]*m/g,"");e("shows empty bar at 0%",()=>{let u=l(V(0,20));t(u).toContain("\u2591".repeat(20)),t(u).toContain("0%")}),e("shows half-filled bar at 50%",()=>{let u=l(V(50,20));t(u).toContain("\u2588".repeat(10)),t(u).toContain("\u2591".repeat(10)),t(u).toContain("50%")}),e("shows full bar at 100%",()=>{let u=l(V(100,20));t(u).toContain("\u2588".repeat(20)),t(u).toContain("100%")}),e("clamps bar fill at 100% for values over 100%",()=>{let u=l(V(150,20));t(u).toContain("\u2588".repeat(20)),t(u).toContain("150%")}),e("uses default width of 20",()=>{let f=l(V(50)).split(" ")[0];t(f).toHaveLength(20)})}),o("budgetColor",()=>{e("returns green for safe",()=>{let l=Mt("safe");t(l("test")).toContain("test")}),e("returns yellow for warning",()=>{let l=Mt("warning");t(l("test")).toContain("test")}),e("returns red for critical",()=>{let l=Mt("critical");t(l("test")).toContain("test")}),e("returns bgRed.white for exceeded",()=>{let l=Mt("exceeded");t(l("test")).toContain("test")})}),o("calculateBudgetStatus edge cases",()=>{e("handles zero budget with zero spending",()=>{let l=N(0,0);t(l.percentage).toBe(0),t(l.level).toBe("safe"),t(l.remaining).toBe(0)}),e("handles spending against zero budget",()=>{let l=N(10,0);t(l.percentage).toBe(100),t(l.level).toBe("exceeded"),t(l.remaining).toBe(0)}),e("handles fractional dollar amounts without precision errors",()=>{let l=N(.30000000000000004,1);t(l.percentage).toBeCloseTo(30,10),t(l.remaining).toBeCloseTo(.7,10),t(l.level).toBe("safe")})}),o("getBudgetLevel edge cases",()=>{e("returns safe for negative percentage",()=>{t(K(-10)).toBe("safe")}),e("returns safe for NaN (documents behavior)",()=>{t(K(NaN)).toBe("safe")})}),o("formatBudgetBar edge cases",()=>{e("handles negative percentage without throwing",()=>{try{let l=V(-50,20);t(typeof l).toBe("string")}catch(l){t(l).toBeInstanceOf(RangeError)}}),e("bar at exactly 80% shows 16 filled blocks",()=>{let u=(f=>f.replace(/\x1B\[[0-9;]*m/g,""))(V(80,20));t(u).toContain("\u2588".repeat(16)),t(u).toContain("80%")})})}function q(o,e,t){if(o.length===0)return{hourly_cost:0,daily_cost:0,projected_monthly:0,hours_analyzed:0,insufficient_data:!0};let r=[...o].sort((y,g)=>y.timestamp.localeCompare(g.timestamp)),c=new Date(r[0].timestamp).getTime(),s=new Date(r[r.length-1].timestamp).getTime(),n=0;for(let y of r){let g=$(y,e);n+=g.cost.total_cost}let a=s-c,i=Math.max(a/(1e3*60*60),1),d=n/i,m=i/24,l=m>=1?n/m:d*24,u=l*30,f={hourly_cost:d,daily_cost:l,projected_monthly:u,hours_analyzed:i,insufficient_data:a<3600*1e3};if(t?.monthly!==void 0&&d>0){let y=t.monthly-n;y<=0?f.time_until_budget_exhausted_ms=0:f.time_until_budget_exhausted_ms=y/d*60*60*1e3}return f}if(import.meta.vitest){let{describe:o,it:e,expect:t,beforeEach:r}=import.meta.vitest,{setPricingData:c}=await Promise.resolve().then(()=>(_t(),Ht)),s={version:"test",models:{"claude-sonnet-4-20250514":{input_cost_per_million:3,output_cost_per_million:15,cache_creation_cost_per_million:3.75,cache_read_cost_per_million:.3,context_window:2e5}},aliases:{}};r(()=>{c(s)});let{makeEntry:n}=await Promise.resolve().then(()=>(St(),Bt));o("calculateBurnRate",()=>{e("returns zero rates for zero entries",()=>{let a=q([],"calculate");t(a.hourly_cost).toBe(0),t(a.daily_cost).toBe(0),t(a.projected_monthly).toBe(0),t(a.hours_analyzed).toBe(0),t(a.time_until_budget_exhausted_ms).toBeUndefined()}),e("calculates burn rate with 1 hour of data",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(1,2),t(i.hourly_cost).toBeGreaterThan(0),t(i.daily_cost).toBeCloseTo(i.hourly_cost*24,6),t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6)}),e("calculates burn rate with multiple days of data",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-26T10:00:00Z"}),n({timestamp:"2025-03-27T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(48,2);let d=i.hours_analyzed/24;t(d).toBeGreaterThanOrEqual(1),t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6)}),e("projected monthly calculation is accurate",()=>{let a=[n({timestamp:"2025-03-25T00:00:00Z"}),n({timestamp:"2025-03-26T00:00:00Z"})],i=q(a,"calculate");t(i.projected_monthly).toBeCloseTo(i.daily_cost*30,6),t(i.projected_monthly).toBeGreaterThan(0)}),e("calculates time_until_budget_exhausted with budget config",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate",{monthly:100});t(i.time_until_budget_exhausted_ms).toBeDefined(),t(i.time_until_budget_exhausted_ms).toBeGreaterThan(0)}),e("returns 0 exhaustion time when budget already exceeded",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate",{monthly:0});t(i.time_until_budget_exhausted_ms).toBe(0)}),e("does not include exhaustion time without budget config",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T11:00:00Z"})],i=q(a,"calculate");t(i.time_until_budget_exhausted_ms).toBeUndefined()}),e("clamps to minimum 1 hour for a single entry (avoids infinite rates)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBe(1),t(Number.isFinite(i.hourly_cost)).toBe(!0),t(i.hourly_cost).toBeGreaterThan(0),t(i.daily_cost).toBeCloseTo(i.hourly_cost*24,6)}),e("hourly cost matches hand-calculated value",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate");t(i.hourly_cost).toBeCloseTo(.0105,6),t(i.hours_analyzed).toBeCloseTo(2,2)}),e("time_until_budget_exhausted_ms matches expected value",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate",{monthly:1}),u=(1-.021)/.0105*60*60*1e3;t(i.time_until_budget_exhausted_ms).toBeDefined(),t(i.time_until_budget_exhausted_ms).toBeCloseTo(u,-1),t(i.time_until_budget_exhausted_ms).toBeGreaterThan(0)}),e("returns 0 exhaustion time when spending exceeds budget (realistic overspend)",()=>{let a=[n({timestamp:"2025-03-25T10:00:00Z"}),n({timestamp:"2025-03-25T12:00:00Z"})],i=q(a,"calculate",{monthly:.01});t(i.time_until_budget_exhausted_ms).toBe(0)}),e("handles unsorted entries correctly",()=>{let a=[n({timestamp:"2025-03-25T12:00:00Z"}),n({timestamp:"2025-03-25T10:00:00Z"})],i=q(a,"calculate");t(i.hours_analyzed).toBeCloseTo(2,2),t(i.hourly_cost).toBeCloseTo(.0105,6)})})}function Wo(o,e){let t=[];if(e){t.push("date,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)for(let[c,s]of Object.entries(r.models))t.push([r.date,c,s.tokens.input_tokens,s.tokens.output_tokens,s.tokens.cache_write_tokens,s.tokens.cache_read_tokens,s.tokens.total_tokens,s.cost.total_cost.toFixed(6)].join(","))}else{t.push("date,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)t.push([r.date,r.tokens.input_tokens,r.tokens.output_tokens,r.tokens.cache_write_tokens,r.tokens.cache_read_tokens,r.tokens.total_tokens,r.cost.total_cost.toFixed(6)].join(","))}return t.join(`
28
28
  `)}function Go(o,e){if(o.length===0){console.log(kt.yellow("No data found for the specified range."));return}if(e){let s=new Pe({head:["Date","Model","Input","Output","Cache Write","Cache Read","Total","Cost"].map(n=>kt.cyan(n)),colAligns:["left","left","right","right","right","right","right","right"],style:{head:[],border:[]}});for(let n of o)for(let[a,i]of Object.entries(n.models))s.push([n.date,a,_(i.tokens.input_tokens),_(i.tokens.output_tokens),_(i.tokens.cache_write_tokens),_(i.tokens.cache_read_tokens),_(i.tokens.total_tokens),h(i.cost.total_cost)]);console.log(s.toString())}else{let s=new Pe({head:["Date","Input","Output","Cache Write","Cache Read","Total","Cost"].map(a=>kt.cyan(a)),colAligns:["left","right","right","right","right","right","right"],style:{head:[],border:[]}}),n=Math.max(...o.map(a=>a.cost.total_cost),1);for(let a of o){let i=Math.round(a.cost.total_cost/n*8),d=kt.dim("\u2588".repeat(i)+"\u2591".repeat(8-i));s.push([a.date,_(a.tokens.input_tokens),_(a.tokens.output_tokens),_(a.tokens.cache_write_tokens),_(a.tokens.cache_read_tokens),_(a.tokens.total_tokens),h(a.cost.total_cost)+" "+d])}console.log(s.toString())}let t=lt();if(t.daily!=null){let s=new Date().toISOString().slice(0,10),a=o.find(d=>d.date===s)?.cost.total_cost??0,i=N(a,t.daily);console.log(),console.log(`Daily Budget: ${V(i.percentage)} (${h(i.spent)} / ${h(i.budget)})`)}let r=o.reduce((s,n)=>s+n.cost.total_cost,0),c=o.reduce((s,n)=>s+n.tokens.total_tokens,0);console.log(kt.dim("\u2500".repeat(60))),console.log(kt.bold(`Total: ${_(c)} tokens, ${h(r)}`))}function Ko(o,e){let t=q(o,e);t.insufficient_data||console.log(kt.dim(`Burn rate: ${h(t.hourly_cost)}/hr, ${h(t.daily_cost)}/day \u2192 projected ${h(t.projected_monthly)}/month`))}function Re(o){o.command("daily").description("Show daily usage breakdown").option("--json","Output as JSON").option("--csv","Output as CSV").option("--since <date>","Start date (YYYY-MM-DD)").option("--until <date>","End date (YYYY-MM-DD)").option("--project <name>","Filter by project name").option("--breakdown","Show per-model breakdown").option("--mode <mode>","Cost mode: calculate|display|compare","calculate").option("--timezone <tz>","Timezone for date grouping (e.g. America/New_York)").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=D(s,{since:e.since,until:e.until,project:e.project,timezone:e.timezone}),a=S(e.mode),i=st(n,a,e.timezone);e.json?console.log(JSON.stringify(i,null,2)):e.csv?console.log(Wo(i,e.breakdown)):(Go(i,e.breakdown),Ko(n,a))})}import Et from"chalk";import ze from"cli-table3";function Vo(o,e){let t=[];if(e){t.push("month,model,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)for(let[c,s]of Object.entries(r.models))t.push([r.month,c,s.tokens.input_tokens,s.tokens.output_tokens,s.tokens.cache_write_tokens,s.tokens.cache_read_tokens,s.tokens.total_tokens,s.cost.total_cost.toFixed(6)].join(","))}else{t.push("month,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let r of o)t.push([r.month,r.tokens.input_tokens,r.tokens.output_tokens,r.tokens.cache_write_tokens,r.tokens.cache_read_tokens,r.tokens.total_tokens,r.cost.total_cost.toFixed(6)].join(","))}return t.join(`
29
- `)}function Xo(o,e){if(o.length===0){console.log(Et.yellow("No data found for the specified range."));return}if(e){let s=new ze({head:["Month","Model","Input","Output","Cache Write","Cache Read","Total","Cost"].map(n=>Et.cyan(n)),colAligns:["left","left","right","right","right","right","right","right"],style:{head:[],border:[]}});for(let n of o)for(let[a,i]of Object.entries(n.models))s.push([n.month,a,_(i.tokens.input_tokens),_(i.tokens.output_tokens),_(i.tokens.cache_write_tokens),_(i.tokens.cache_read_tokens),_(i.tokens.total_tokens),h(i.cost.total_cost)]);console.log(s.toString())}else{let s=new ze({head:["Month","Input","Output","Cache Write","Cache Read","Total","Cost"].map(n=>Et.cyan(n)),colAligns:["left","right","right","right","right","right","right"],style:{head:[],border:[]}});for(let n of o)s.push([n.month,_(n.tokens.input_tokens),_(n.tokens.output_tokens),_(n.tokens.cache_write_tokens),_(n.tokens.cache_read_tokens),_(n.tokens.total_tokens),h(n.cost.total_cost)]);console.log(s.toString())}let t=lt();if(t.monthly!=null){let s=new Date().toISOString().slice(0,7),a=o.find(d=>d.month===s)?.cost.total_cost??0,i=N(a,t.monthly);console.log(),console.log(`Monthly Budget: ${V(i.percentage)} (${h(i.spent)} / ${h(i.budget)})`)}let r=o.reduce((s,n)=>s+n.cost.total_cost,0),c=o.reduce((s,n)=>s+n.tokens.total_tokens,0);console.log(Et.dim("\u2500".repeat(60))),console.log(Et.bold(`Total: ${_(c)} tokens, ${h(r)}`))}function Fe(o){o.command("monthly").description("Show monthly usage breakdown").option("--json","Output as JSON").option("--csv","Output as CSV").option("--since <date>","Start date (YYYY-MM-DD)").option("--until <date>","End date (YYYY-MM-DD)").option("--project <name>","Filter by project name").option("--breakdown","Show per-model breakdown").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--timezone <tz>","Timezone for date grouping (e.g. America/New_York)").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=D(s,{since:e.since,until:e.until,project:e.project,timezone:e.timezone}),a=S(e.mode),i=Yt(n,a,e.timezone);e.json?console.log(JSON.stringify(i,null,2)):e.csv?console.log(Vo(i,e.breakdown)):Xo(i,e.breakdown)})}import Wt from"chalk";import Qo from"cli-table3";function qe(o){return new Date(o.endTime).getTime()-new Date(o.startTime).getTime()}function Ne(o,e=12){return o.length<=e?o:o.slice(0,e)+"..."}function tn(o){let e=[];e.push("session_id,project,model,duration_ms,requests,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let t of o){let r=qe(t);e.push([nt(t.sessionId),nt(t.project),nt(t.primaryModel),r,t.request_count,t.tokens.input_tokens,t.tokens.output_tokens,t.tokens.cache_write_tokens,t.tokens.cache_read_tokens,t.tokens.total_tokens,t.cost.total_cost.toFixed(6)].join(","))}return e.join(`
30
- `)}function en(o,e=!1){if(o.length===0){console.log(Wt.yellow("No sessions found for the specified range."));return}let t=new Qo({head:["Session ID","Project","Model","Duration","Requests","Tokens","Cost"].map(n=>Wt.cyan(n)),colAligns:["left","left","left","right","right","right","right"],style:{head:[],border:[]}});for(let n of o){let a=qe(n);t.push([e?n.sessionId:Ne(n.sessionId),e?n.project:Ne(n.project,20),ot(n.primaryModel)+(Object.keys(n.models||{}).length>1?Wt.dim(` +${Object.keys(n.models).length-1}`):""),R(a),n.request_count.toString(),_(n.tokens.total_tokens),h(n.cost.total_cost)])}console.log(t.toString());let r=o.reduce((n,a)=>n+a.cost.total_cost,0),c=o.reduce((n,a)=>n+a.tokens.total_tokens,0),s=o.reduce((n,a)=>n+a.request_count,0);console.log(Wt.bold(`
29
+ `)}function Xo(o,e){if(o.length===0){console.log(Et.yellow("No data found for the specified range."));return}if(e){let s=new ze({head:["Month","Model","Input","Output","Cache Write","Cache Read","Total","Cost"].map(n=>Et.cyan(n)),colAligns:["left","left","right","right","right","right","right","right"],style:{head:[],border:[]}});for(let n of o)for(let[a,i]of Object.entries(n.models))s.push([n.month,a,_(i.tokens.input_tokens),_(i.tokens.output_tokens),_(i.tokens.cache_write_tokens),_(i.tokens.cache_read_tokens),_(i.tokens.total_tokens),h(i.cost.total_cost)]);console.log(s.toString())}else{let s=new ze({head:["Month","Input","Output","Cache Write","Cache Read","Total","Cost"].map(n=>Et.cyan(n)),colAligns:["left","right","right","right","right","right","right"],style:{head:[],border:[]}});for(let n of o)s.push([n.month,_(n.tokens.input_tokens),_(n.tokens.output_tokens),_(n.tokens.cache_write_tokens),_(n.tokens.cache_read_tokens),_(n.tokens.total_tokens),h(n.cost.total_cost)]);console.log(s.toString())}let t=lt();if(t.monthly!=null){let s=new Date().toISOString().slice(0,7),a=o.find(d=>d.month===s)?.cost.total_cost??0,i=N(a,t.monthly);console.log(),console.log(`Monthly Budget: ${V(i.percentage)} (${h(i.spent)} / ${h(i.budget)})`)}let r=o.reduce((s,n)=>s+n.cost.total_cost,0),c=o.reduce((s,n)=>s+n.tokens.total_tokens,0);console.log(Et.dim("\u2500".repeat(60))),console.log(Et.bold(`Total: ${_(c)} tokens, ${h(r)}`))}function Fe(o){o.command("monthly").description("Show monthly usage breakdown").option("--json","Output as JSON").option("--csv","Output as CSV").option("--since <date>","Start date (YYYY-MM-DD)").option("--until <date>","End date (YYYY-MM-DD)").option("--project <name>","Filter by project name").option("--breakdown","Show per-model breakdown").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--timezone <tz>","Timezone for date grouping (e.g. America/New_York)").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=D(s,{since:e.since,until:e.until,project:e.project,timezone:e.timezone}),a=S(e.mode),i=Ut(n,a,e.timezone);e.json?console.log(JSON.stringify(i,null,2)):e.csv?console.log(Vo(i,e.breakdown)):Xo(i,e.breakdown)})}import Jt from"chalk";import Qo from"cli-table3";function qe(o){return new Date(o.endTime).getTime()-new Date(o.startTime).getTime()}function Ne(o,e=12){return o.length<=e?o:o.slice(0,e)+"..."}function tn(o){let e=[];e.push("session_id,project,model,duration_ms,requests,input_tokens,output_tokens,cache_write_tokens,cache_read_tokens,total_tokens,cost");for(let t of o){let r=qe(t);e.push([nt(t.sessionId),nt(t.project),nt(t.primaryModel),r,t.request_count,t.tokens.input_tokens,t.tokens.output_tokens,t.tokens.cache_write_tokens,t.tokens.cache_read_tokens,t.tokens.total_tokens,t.cost.total_cost.toFixed(6)].join(","))}return e.join(`
30
+ `)}function en(o,e=!1){if(o.length===0){console.log(Jt.yellow("No sessions found for the specified range."));return}let t=new Qo({head:["Session ID","Project","Model","Duration","Requests","Tokens","Cost"].map(n=>Jt.cyan(n)),colAligns:["left","left","left","right","right","right","right"],style:{head:[],border:[]}});for(let n of o){let a=qe(n);t.push([e?n.sessionId:Ne(n.sessionId),e?n.project:Ne(n.project,20),ot(n.primaryModel)+(Object.keys(n.models||{}).length>1?Jt.dim(` +${Object.keys(n.models).length-1}`):""),R(a),n.request_count.toString(),_(n.tokens.total_tokens),h(n.cost.total_cost)])}console.log(t.toString());let r=o.reduce((n,a)=>n+a.cost.total_cost,0),c=o.reduce((n,a)=>n+a.tokens.total_tokens,0),s=o.reduce((n,a)=>n+a.request_count,0);console.log(Jt.bold(`
31
31
  ${o.length} sessions, ${s} requests, ${_(c)} tokens, ${h(r)}`))}function He(o){o.command("session").description("Show session-level usage").option("--json","Output as JSON").option("--csv","Output as CSV").option("--since <date>","Start date (YYYY-MM-DD)").option("--until <date>","End date (YYYY-MM-DD)").option("--project <name>","Filter by project name").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--timezone <tz>","Timezone for filtering").option("--full","Show full session IDs and project names (no truncation)").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=D(s,{since:e.since,until:e.until,project:e.project,timezone:e.timezone}),a=S(e.mode),i=ct(n,a);e.json?console.log(JSON.stringify(i,null,2)):e.csv?console.log(tn(i)):en(i,!!e.full)})}import{writeFileSync as Ze,mkdirSync as Ue,existsSync as on}from"fs";import{join as Ye,dirname as Je}from"path";import{homedir as We}from"os";import $t from"chalk";function nn(o){let e=JSON.stringify(o),t=JSON.stringify(e),r=a=>a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),c=o.projects.map(a=>`<option value="${r(a.project)}">${r(a.project)}</option>`).join(`
32
32
  `),s=o.date_range.start?o.date_range.start.slice(0,10):"",n=o.date_range.end?o.date_range.end.slice(0,10):"";return`<!DOCTYPE html>
33
33
  <html lang="en">
@@ -556,27 +556,27 @@ td{padding:8px 12px;border-bottom:1px solid var(--border);font-size:.8rem;font-v
556
556
  Models Today`));for(let[g,p]of Object.entries(l.models))console.log(` ${k.white(g)} ${k.dim("|")} ${p.request_count} reqs ${k.dim("|")} ${_(p.tokens.total_tokens)} ${k.dim("|")} ${k.white(h(p.cost.total_cost))}`)}let f=c.reduce((g,p)=>{try{let b=an(p).mtimeMs;return b>g.time?{path:p,time:b}:g}catch{return g}},{path:"",time:0});if(f.path){let g=Date.now()-f.time;console.log(k.dim(`
557
557
  Last file update: ${R(g)} ago`))}let y=lt();if(y.daily&&l){let g=N(l.cost.total_cost,y.daily);console.log(k.bold(`
558
558
  Budget`)),console.log(` Daily: ${V(g.percentage)} (${h(g.spent)} / ${h(g.budget)})`)}console.log(k.dim(`
559
- Press Ctrl+C to exit`))}function to(o){o.command("live").description("Real-time terminal monitor").option("--interval <seconds>","Refresh interval in seconds","5").option("--project <name>","Filter by project name").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--timezone <tz>","Timezone for date grouping").action(async e=>{let t=Math.max(1,parseInt(e.interval??"5",10))*1e3,r=S(e.mode);await Xe(r,e.project,e.timezone);let c=!1;async function s(){if(!c&&(await new Promise(n=>setTimeout(n,t)),!c)){try{await Xe(r,e.project,e.timezone)}catch{}s()}}s(),process.on("SIGINT",()=>{c=!0,Qe(),console.log(k.dim("Live monitor stopped.")),process.exit(0)})})}_t();import F from"chalk";import cn from"cli-table3";function eo(o){let e=o.command("pricing").description("View and update model pricing data");e.command("list").description("List all known model prices").option("--json","Output as JSON").action(async t=>{let r=ht(),c=Ht();if(t.json){console.log(JSON.stringify(c,null,2));return}console.log(F.bold("Model Pricing")),console.log(F.dim(`Source: ${r.source} | ${r.modelCount} models | version: ${r.version} | cache: ${r.cacheAge}
559
+ Press Ctrl+C to exit`))}function to(o){o.command("live").description("Real-time terminal monitor").option("--interval <seconds>","Refresh interval in seconds","5").option("--project <name>","Filter by project name").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--timezone <tz>","Timezone for date grouping").action(async e=>{let t=Math.max(1,parseInt(e.interval??"5",10))*1e3,r=S(e.mode);await Xe(r,e.project,e.timezone);let c=!1;async function s(){if(!c&&(await new Promise(n=>setTimeout(n,t)),!c)){try{await Xe(r,e.project,e.timezone)}catch{}s()}}s(),process.on("SIGINT",()=>{c=!0,Qe(),console.log(k.dim("Live monitor stopped.")),process.exit(0)})})}_t();import F from"chalk";import cn from"cli-table3";function eo(o){let e=o.command("pricing").description("View and update model pricing data");e.command("list").description("List all known model prices").option("--json","Output as JSON").action(async t=>{let r=ht(),c=qt();if(t.json){console.log(JSON.stringify(c,null,2));return}console.log(F.bold("Model Pricing")),console.log(F.dim(`Source: ${r.source} | ${r.modelCount} models | version: ${r.version} | cache: ${r.cacheAge}
560
560
  `));let s=Object.entries(c.models).sort(([i],[d])=>i.localeCompare(d)),n=new cn({head:["Model","Input/M","Output/M","Cache W/M","Cache R/M","Context"].map(i=>F.cyan(i)),colAligns:["left","right","right","right","right","right"],style:{head:[],border:[]}});for(let[i,d]of s){let m=d.input_cost_per_million_above_200k?F.yellow(" *"):"";n.push([i+m,h(d.input_cost_per_million),h(d.output_cost_per_million),h(d.cache_creation_cost_per_million),h(d.cache_read_cost_per_million),`${(d.context_window/1e3).toFixed(0)}K`])}console.log(n.toString()),console.log(F.dim(`
561
561
  * = tiered pricing above 200K context`)),console.log(F.bold(`
562
562
  Aliases`));let a=Object.entries(c.aliases).sort(([i],[d])=>i.localeCompare(d));for(let[i,d]of a)console.log(` ${F.white(i)} ${F.dim("\u2192")} ${d}`)}),e.command("update").description("Fetch latest pricing from Anthropic and update cache").action(async()=>{console.log(F.dim("Fetching pricing from Anthropic..."));let{data:t,newModels:r,source:c}=await oe();if(console.log(F.green(`Pricing updated: ${Object.keys(t.models).length} models (${c})`)),r.length>0){console.log(F.yellow(`
563
563
  New models discovered:`));for(let n of r)console.log(` + ${F.cyan(n)}`)}let s=ht();console.log(F.dim(`
564
564
  Cache: ${s.cacheAge}`))}),e.command("status").description("Show pricing source and cache status").action(async()=>{let t=ht();console.log(`Source: ${F.white(t.source)}`),console.log(`Models: ${F.white(t.modelCount.toString())}`),console.log(`Version: ${F.white(t.version)}`),console.log(`Cache: ${F.white(t.cacheAge)}`)}),e.action(async()=>{await e.commands.find(t=>t.name()==="status")?.parseAsync(["node","cctrack","pricing","status"])})}import H from"chalk";function oo(o){let e=o.command("config").description("Manage cctrack configuration (budgets, etc.)");e.command("set <key> <value>").description("Set a configuration value (e.g. budget.daily 50)").action((t,r)=>{let c=Number(r);(isNaN(c)||c<0)&&(console.error(H.red(`Invalid value: ${r}. Must be a non-negative number.`)),process.exit(1));let s=t.split(".");if(s[0]==="budget"&&s.length===2){let n=s[1];["daily","monthly","block"].includes(n)||(console.error(H.red(`Unknown budget key: ${n}. Valid keys: daily, monthly, block`)),process.exit(1));let a=lt();a[n]=c,Oe(a),console.log(H.green(`Set ${t} = ${h(c)}`))}else console.error(H.red(`Unknown config key: ${t}`)),console.error(H.dim("Valid keys: budget.daily, budget.monthly, budget.block")),process.exit(1)}),e.command("get [key]").description("Show current configuration").action(t=>{let r=Ie(),c=Ae();t&&t!=="budget"&&(console.error(H.red(`Unknown config section: ${t}`)),process.exit(1)),console.log(H.bold("CCTrack Configuration")+H.dim(` (${c})`)),console.log();let s=r.budget??{};console.log(H.bold(" Budget")),console.log(` Daily: ${s.daily!=null?h(s.daily)+H.dim(" (alerts at 50%/80%/100%)"):H.dim("not set")}`),console.log(` Monthly: ${s.monthly!=null?h(s.monthly)+H.dim(" (alerts at 50%/80%/100%)"):H.dim("not set")}`),console.log(` Block: ${s.block!=null?h(s.block):H.dim("not set")}`),console.log(H.dim(`
565
- Set with: cctrack config set budget.daily <dollars>`))}),e.command("reset").description("Reset configuration to defaults").action(()=>{Le(),console.log(H.green("Configuration reset to defaults."))})}import M from"chalk";import yn from"cli-table3";import{readFileSync as Ot,writeFileSync as co,mkdirSync as lo,statSync as de,existsSync as wt,readdirSync as ln,openSync as dn,readSync as un,closeSync as mn}from"fs";import{join as gt}from"path";import{homedir as me}from"os";var Gt=gt(me(),".cctrack"),It=gt(Gt,"statusline.cache"),ue=gt(Gt,"ratelimits.json"),pn=3e4;function gn(){try{if(process.stdin.isTTY)return null;let o=Ot(0,"utf-8").trim();if(!o)return null;let e=JSON.parse(o);if(!e.rate_limits)return null;let t={source:"statusline",captured_at:new Date().toISOString()},r=e.rate_limits;r.five_hour&&(t.five_hour={used_percentage:r.five_hour.used_percentage,resets_at:r.five_hour.resets_at}),r.seven_day&&(t.seven_day={used_percentage:r.seven_day.used_percentage,resets_at:r.seven_day.resets_at}),r.seven_day_sonnet&&(t.seven_day_sonnet={used_percentage:r.seven_day_sonnet.used_percentage,resets_at:r.seven_day_sonnet.resets_at}),r.seven_day_opus&&(t.seven_day_opus={used_percentage:r.seven_day_opus.used_percentage,resets_at:r.seven_day_opus.resets_at}),r.extra_usage?.is_enabled&&(t.extra_usage={is_enabled:!0,spent:r.extra_usage.used_credits??0,limit:r.extra_usage.monthly_limit??0,utilization:r.extra_usage.utilization??0,resets_at:r.extra_usage.resets_at??0});try{lo(Gt,{recursive:!0}),co(ue,JSON.stringify(t,null,2),"utf-8")}catch{}return t}catch{return null}}function uo(){try{if(!wt(ue))return null;let o=Ot(ue,"utf-8"),e=JSON.parse(o);return Date.now()-new Date(e.captured_at).getTime()>600*1e3?null:e}catch{return null}}function mo(){return uo()}function fn(){try{if(!wt(It))return null;let o=Ot(It,"utf-8");return JSON.parse(o)}catch{return null}}function no(o){try{lo(Gt,{recursive:!0});let e={updated_at:new Date().toISOString(),data:o};co(It,JSON.stringify(e),"utf-8")}catch{}}function hn(){try{if(!wt(It))return!1;let o=de(It);return Date.now()-o.mtimeMs<pn}catch{return!1}}function so(o){let e=[];function t(r){try{let c=ln(r,{withFileTypes:!0});for(let s of c){let n=gt(r,s.name);s.isDirectory()?t(n):s.name.endsWith(".jsonl")&&e.push(n)}}catch{}}for(let r of o)t(r);return e}function ro(){let o=[],e=process.env.CLAUDE_CONFIG_DIR;if(e){let c=gt(e,"projects");wt(c)&&o.push(c)}let t=me(),r=[gt(t,".claude","projects"),gt(t,".config","claude","projects")];for(let c of r)wt(c)&&o.push(c);return[...new Set(o)]}function ao(o,e){let t=Date.now(),r=t-1440*60*1e3,c=new Date().toISOString().slice(0,10),s=t-dt,n=0,a=0,i=0,d=0,m="unknown",l="",u="",f=new Map;for(let X of o){try{if(de(X).mtimeMs<r)continue}catch{continue}let U;try{let L=de(X),P=256*1024;if(L.size>P){let C=dn(X,"r"),Y=Buffer.alloc(P);un(C,Y,0,P,L.size-P),mn(C),U=Y.toString("utf-8");let w=U.indexOf(`
565
+ Set with: cctrack config set budget.daily <dollars>`))}),e.command("reset").description("Reset configuration to defaults").action(()=>{Le(),console.log(H.green("Configuration reset to defaults."))})}import M from"chalk";import yn from"cli-table3";import{readFileSync as Ot,writeFileSync as co,mkdirSync as lo,statSync as de,existsSync as wt,readdirSync as ln,openSync as dn,readSync as un,closeSync as mn}from"fs";import{join as gt}from"path";import{homedir as me}from"os";var Wt=gt(me(),".cctrack"),It=gt(Wt,"statusline.cache"),ue=gt(Wt,"ratelimits.json"),pn=3e4;function gn(){try{if(process.stdin.isTTY)return null;let o=Ot(0,"utf-8").trim();if(!o)return null;let e=JSON.parse(o);if(!e.rate_limits)return null;let t={source:"statusline",captured_at:new Date().toISOString()},r=e.rate_limits;r.five_hour&&(t.five_hour={used_percentage:r.five_hour.used_percentage,resets_at:r.five_hour.resets_at}),r.seven_day&&(t.seven_day={used_percentage:r.seven_day.used_percentage,resets_at:r.seven_day.resets_at}),r.seven_day_sonnet&&(t.seven_day_sonnet={used_percentage:r.seven_day_sonnet.used_percentage,resets_at:r.seven_day_sonnet.resets_at}),r.seven_day_opus&&(t.seven_day_opus={used_percentage:r.seven_day_opus.used_percentage,resets_at:r.seven_day_opus.resets_at}),r.extra_usage?.is_enabled&&(t.extra_usage={is_enabled:!0,spent:r.extra_usage.used_credits??0,limit:r.extra_usage.monthly_limit??0,utilization:r.extra_usage.utilization??0,resets_at:r.extra_usage.resets_at??0});try{lo(Wt,{recursive:!0}),co(ue,JSON.stringify(t,null,2),"utf-8")}catch{}return t}catch{return null}}function uo(){try{if(!wt(ue))return null;let o=Ot(ue,"utf-8"),e=JSON.parse(o);return Date.now()-new Date(e.captured_at).getTime()>600*1e3?null:e}catch{return null}}function mo(){return uo()}function fn(){try{if(!wt(It))return null;let o=Ot(It,"utf-8");return JSON.parse(o)}catch{return null}}function no(o){try{lo(Wt,{recursive:!0});let e={updated_at:new Date().toISOString(),data:o};co(It,JSON.stringify(e),"utf-8")}catch{}}function hn(){try{if(!wt(It))return!1;let o=de(It);return Date.now()-o.mtimeMs<pn}catch{return!1}}function so(o){let e=[];function t(r){try{let c=ln(r,{withFileTypes:!0});for(let s of c){let n=gt(r,s.name);s.isDirectory()?t(n):s.name.endsWith(".jsonl")&&e.push(n)}}catch{}}for(let r of o)t(r);return e}function ro(){let o=[],e=process.env.CLAUDE_CONFIG_DIR;if(e){let c=gt(e,"projects");wt(c)&&o.push(c)}let t=me(),r=[gt(t,".claude","projects"),gt(t,".config","claude","projects")];for(let c of r)wt(c)&&o.push(c);return[...new Set(o)]}function ao(o,e){let t=Date.now(),r=t-1440*60*1e3,c=new Date().toISOString().slice(0,10),s=t-dt,n=0,a=0,i=0,d=0,m="unknown",l="",u="",f=new Map;for(let X of o){try{if(de(X).mtimeMs<r)continue}catch{continue}let U;try{let L=de(X),P=256*1024;if(L.size>P){let C=dn(X,"r"),Y=Buffer.alloc(P);un(C,Y,0,P,L.size-P),mn(C),U=Y.toString("utf-8");let w=U.indexOf(`
566
566
  `);w>=0&&(U=U.slice(w+1))}else U=Ot(X,"utf-8")}catch{continue}let Q=U.split(`
567
567
  `);for(let L of Q){let P=L.trim();if(P)try{let C=JSON.parse(P),Y=zt.safeParse(C);if(!Y.success)continue;let w=Y.data;if(w.isApiErrorMessage===!0||w.message.model==="<synthetic>"||w.timestamp.slice(0,10)!==c)continue;let At=$(w,e);n+=At.cost.total_cost,a+=At.tokens.total_tokens,w.timestamp>l&&(l=w.timestamp,m=w.message.model??"unknown",w.sessionId&&(u=w.sessionId)),w.sessionId&&f.set(w.sessionId,(f.get(w.sessionId)??0)+At.cost.total_cost),new Date(w.timestamp).getTime()>=s&&(i+=At.tokens.total_tokens,d++)}catch{continue}}}let y=u?f.get(u)??0:0,g=d>0?Math.min(d,100):0,p=t-s,b=Math.max(dt-p,0),E="safe";try{let X=gt(me(),".cctrack","config.json");if(wt(X)){let Q=JSON.parse(Ot(X,"utf-8"))?.budget?.daily;if(Q&&Q>0){let L=n/Q*100;L>=100?E="exceeded":L>=80?E="critical":L>=50&&(E="warning")}}}catch{}return{today_cost:n,session_cost:y,model:m,total_tokens:a,block_percentage:Math.round(g),block_remaining:R(b),budget_level:E,updated_at:new Date().toISOString()}}function io(o,e=8){let t=Math.min(Math.max(o,0),100),r=Math.round(t/100*e),c=e-r;return"\u2588".repeat(r)+"\u2591".repeat(c)}function _n(o,e){if(e)return e.replace("{cost}",h(o.today_cost)).replace("{model}",ot(o.model)).replace("{tokens}",_(o.total_tokens)).replace("{block_pct}",`${o.block_percentage}%`).replace("{block_remaining}",o.block_remaining);let t=[h(o.today_cost)+" today",ot(o.model),_(o.total_tokens)+" tok"];if(o.rate_limits?.five_hour){let r=Math.round(o.rate_limits.five_hour.used_percentage),c=io(r),s=o.rate_limits.five_hour.resets_at*1e3-Date.now(),n=s>0?R(s):"now";t.push(`${c} ${r}% 5h (${n})`)}if(o.rate_limits?.seven_day&&t.push(`7d: ${Math.round(o.rate_limits.seven_day.used_percentage)}%`),o.rate_limits?.extra_usage){let r=o.rate_limits.extra_usage;t.push(`extra: $${r.spent.toFixed(2)}/$${r.limit.toFixed(0)}`)}return o.rate_limits||t.push(`${io(o.block_percentage)} ~${o.block_percentage}%`),t.join(" \u2502 ")}function po(o){o.command("statusline").description("Ultra-lightweight cached output for tmux/neovim/hooks").option("--format <template>","Custom format with placeholders: {cost}, {model}, {tokens}, {block_pct}, {block_remaining}").option("--no-cache","Force fresh parse (skip cache)").option("--json","Output as JSON").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").action(e=>{let t=S(e.mode),r=e.cache!==!1,c=gn(),s;if(r&&hn()){let n=fn();if(n)s=n.data;else{let a=ro(),i=so(a);s=ao(i,t),no(s)}}else{let n=ro(),a=so(n);s=ao(a,t),r&&no(s)}if(s.rate_limits=c??uo()??void 0,s.rate_limits?.five_hour){s.block_percentage=Math.round(s.rate_limits.five_hour.used_percentage);let n=s.rate_limits.five_hour.resets_at*1e3-Date.now();s.block_remaining=R(Math.max(n,0))}e.json?process.stdout.write(JSON.stringify(s)):process.stdout.write(_n(s,e.format))})}function go(o,e,t=10){let r=Date.now(),c=r-t*dt,s=new Map;for(let n=0;n<t;n++){let a=r-n*dt,i=a-dt;s.set(n,{block_start:new Date(i).toISOString(),block_end:new Date(a).toISOString(),block_index:n,is_current:n===0,time_remaining_ms:n===0?a-r:0,...T(),models:{}})}for(let n of o){let a=new Date(n.timestamp).getTime();if(a<c||a>r)continue;let i=Math.floor((r-a)/dt);if(i<0||i>=t)continue;let d=s.get(i),m=n.message.model??"unknown",l=$(n,e);O(d,l),d.models[m]||(d.models[m]=T());let u=d.models[m];u.tokens=Tt(u.tokens,l.tokens),u.cost=Dt(u.cost,l.cost),u.request_count++}return Array.from(s.values())}function pe(){process.stdout.write("\x1B[2J\x1B[H")}function bn(o){let e=new Date(o),t=e.getFullYear(),r=String(e.getMonth()+1).padStart(2,"0"),c=String(e.getDate()).padStart(2,"0"),s=String(e.getHours()).padStart(2,"0"),n=String(e.getMinutes()).padStart(2,"0");return`${t}-${r}-${c} ${s}:${n}`}function kn(o){let e=mo();if(e){console.log(M.bold("Anthropic Rate Limits")+M.dim(` (via ${e.source}, ${e.captured_at.slice(11,19)} UTC)`));let c=(s,n)=>{if(!n)return;let a=n.used_percentage,i=n.resets_at*1e3-Date.now(),d=i>0?R(i):"now",m=a>=80?M.red:a>=50?M.yellow:M.green,l="\u2588".repeat(Math.round(a/5))+"\u2591".repeat(20-Math.round(a/5));console.log(` ${s.padEnd(16)} ${m(l)} ${m(a.toFixed(1).padStart(5)+"%")} | resets in ${d}`)};if(c("Session (5h)",e.five_hour),c("Weekly (all)",e.seven_day),c("Weekly (Sonnet)",e.seven_day_sonnet),c("Weekly (Opus)",e.seven_day_opus),e.extra_usage){let s=e.extra_usage,n=s.utilization,a=n>=80?M.red:n>=50?M.yellow:M.green,i=s.resets_at*1e3-Date.now(),d=i>0?R(i):"now";console.log(` ${"Extra usage".padEnd(16)} ${a("$"+s.spent.toFixed(2)+" / $"+s.limit.toFixed(0))} (${n.toFixed(0)}%) | resets in ${d}`)}console.log("")}if(o.length===0){console.log(M.yellow("No usage recorded in the last 50 hours.")),console.log(M.dim("Start a Claude Code session and data will appear here."));return}let t=o[0];console.log(M.bold("Last 5 Hours")),t.request_count===0?console.log(M.dim(` No requests in this window.
568
568
  `)):(console.log(` ${M.white(h(t.cost.total_cost))} spent | ${M.white(t.request_count.toString())} requests`),console.log(` Input: ${M.white(_(t.tokens.input_tokens))} | Output: ${M.white(_(t.tokens.output_tokens))} | Cache: ${M.white(_(t.tokens.cache_read_tokens))}`));let r=o.filter(c=>!c.is_current&&c.request_count>0);if(r.length>0){console.log(M.bold(`
569
569
  Previous 5-Hour Windows`));let c=new yn({head:["Window Start","Requests","Tokens","Cost"].map(s=>M.cyan(s)),colAligns:["left","right","right","right"],style:{head:[],border:[]}});for(let s of r)c.push([bn(s.block_start),s.request_count.toString(),_(s.tokens.total_tokens),h(s.cost.total_cost)]);console.log(c.toString())}console.log(M.dim(`
570
570
  Note: These are your usage patterns grouped by 5-hour windows.`)),console.log(M.dim("They do not reflect Anthropic's actual rate limit calculations."))}async function ge(o,e){let t=I(),r=B(t);if(r.length===0){console.log(M.yellow("No JSONL files found. Waiting for data..."));return}let{entries:c}=await j(r),s=x(c),n=D(s,{since:e.since,until:e.until,project:e.project}),a=go(n,o);kn(a)}function fo(o){o.command("blocks").description("Show usage grouped by 5-hour windows").option("--json","Output as JSON").option("--since <date>","Start date (YYYY-MM-DD)").option("--until <date>","End date (YYYY-MM-DD)").option("--mode <mode>","Cost mode: calculate, display, compare","calculate").option("--live","Auto-refresh every 5 seconds").action(async e=>{let t=S(e.mode);if(e.json){let r=I(),c=B(r),{entries:s}=await j(c),n=x(s),a=D(n,{since:e.since,until:e.until}),i=go(a,t);console.log(JSON.stringify(i,null,2));return}if(e.live){pe(),await ge(t,e);let r=setInterval(async()=>{try{pe(),await ge(t,e),console.log(M.dim(`
571
571
  Press Ctrl+C to exit`))}catch{}},5e3);process.on("SIGINT",()=>{clearInterval(r),pe(),console.log(M.dim("Block monitor stopped.")),process.exit(0)})}else await ge(t,e)})}import v from"chalk";import{readFileSync as yo,writeFileSync as Jr,existsSync as bo,mkdirSync as Wr,appendFileSync as Gr}from"fs";import{join as fe}from"path";import{homedir as vn}from"os";var ko=fe(vn(),".cctrack"),ho=fe(ko,"rate-events.jsonl"),_o=fe(ko,"rate-model.json"),wn=300*60*1e3;function he(){try{if(bo(_o))return JSON.parse(yo(_o,"utf-8"))}catch{}return{limits:{},updated_at:new Date().toISOString()}}function Lt(o){return o.includes("opus")?"opus":o.includes("sonnet")?"sonnet":o.includes("haiku")?"haiku":"unknown"}function vo(){try{return bo(ho)?yo(ho,"utf-8").split(`
572
- `).filter(Boolean).map(o=>JSON.parse(o)):[]}catch{return[]}}function xt(o){let e=Date.now(),t=new Date(e-wn),r=0,c=0,s=0;for(let n of o){let a=new Date(n.timestamp).getTime();if(a<t.getTime()||a>e)continue;let i=n.message.usage;r+=i.input_tokens+(i.cache_creation_input_tokens??0),c+=i.input_tokens+i.output_tokens+(i.cache_creation_input_tokens??0)+(i.cache_read_input_tokens??0),s++}return{billable_tokens:r,total_tokens:c,requests:s,window_start:t}}function Kt(o,e){let t=Lt(e),c=he().limits[t],s=xt(o);if(c&&c.sample_count>0){let n=s.billable_tokens/c.estimated_limit*100,i=(Date.now()-s.window_start.getTime())/(1e3*60*60),d=null;if(i>.1&&s.billable_tokens>0){let m=s.billable_tokens/i,l=c.estimated_limit-s.billable_tokens;l>0&&m>0?d=Math.round(l/m*60):d=0}return{model_family:t,estimated_utilization:Math.min(Math.round(n*10)/10,100),confidence:c.confidence,estimated_limit:Math.round(c.estimated_limit),current_consumption:s.billable_tokens,minutes_to_limit:d,calibration_events:c.sample_count,source:"calibrated"}}return{model_family:t,estimated_utilization:0,confidence:0,estimated_limit:0,current_consumption:s.billable_tokens,minutes_to_limit:null,calibration_events:0,source:"uncalibrated"}}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest,{makeEntry:r}=await Promise.resolve().then(()=>(St(),Bt));o("modelFamily",()=>{e("extracts opus",()=>t(Lt("claude-opus-4-6-20260205")).toBe("opus")),e("extracts sonnet",()=>t(Lt("claude-sonnet-4-20250514")).toBe("sonnet")),e("extracts haiku",()=>t(Lt("claude-haiku-4-5-20251001")).toBe("haiku")),e("returns unknown for unrecognized",()=>t(Lt("gpt-4")).toBe("unknown"))}),o("currentWindowConsumption",()=>{e("counts only entries in last 5 hours",()=>{let c=new Date,s=new Date(c.getTime()-6e4).toISOString(),n=new Date(c.getTime()-360*60*1e3).toISOString(),a=[r({timestamp:s}),r({timestamp:n})],i=xt(a);t(i.requests).toBe(1)}),e("billable tokens exclude cache_read",()=>{let c=new Date,s=new Date(c.getTime()-6e4).toISOString(),n=[r({timestamp:s,message:{model:"claude-opus-4-6",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:200,cache_read_input_tokens:5e3}}})],a=xt(n);t(a.billable_tokens).toBe(300)}),e("returns zero for empty entries",()=>{let c=xt([]);t(c.billable_tokens).toBe(0),t(c.requests).toBe(0)})}),o("predictUtilization",()=>{e("returns uncalibrated when no model data",()=>{let c=Kt([],"claude-opus-4-6");t(c.source).toBe("uncalibrated"),t(c.confidence).toBe(0),t(c.calibration_events).toBe(0)}),e("returns calibrated when model has learned limits",async()=>{let{writeFileSync:c,mkdirSync:s,readFileSync:n,existsSync:a,unlinkSync:i}=await import("fs"),{join:d}=await import("path"),{homedir:m}=await import("os"),l=d(m(),".cctrack");s(l,{recursive:!0});let u=d(l,"rate-model.json"),f=a(u)?n(u,"utf-8"):null;c(u,JSON.stringify({limits:{opus:{estimated_limit:1e6,confidence:.8,sample_count:5,last_calibration:"2026-03-25T00:00:00Z",alpha:.3}},updated_at:"2026-03-25T00:00:00Z"}));let y=new Date,g=[r({timestamp:new Date(y.getTime()-6e4).toISOString(),message:{model:"claude-opus-4-6",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:100,cache_read_input_tokens:0}}})],p=Kt(g,"claude-opus-4-6");if(t(p.source).toBe("calibrated"),t(p.confidence).toBe(.8),t(p.estimated_limit).toBe(1e6),t(p.current_consumption).toBe(600),f)c(u,f);else try{i(u)}catch{}})})}function xn(o,e=30){let t=Math.min(Math.max(o,0),100),r=Math.round(t/100*e),c="\u2588".repeat(r)+"\u2591".repeat(e-r);return o>=80?v.red(c):o>=50?v.yellow(c):v.green(c)}function wo(o){o.command("limits").description("Predict rate limit utilization from your usage patterns").option("--json","Output as JSON").option("--mode <mode>","Cost mode","calculate").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=S(e.mode),i=[...s].sort((f,y)=>y.timestamp.localeCompare(f.timestamp))[0]?.message.model??"claude-opus-4-6",d=Kt(s,i),m=xt(s),l=he(),u=vo();if(e.json){console.log(JSON.stringify({prediction:d,consumption:m,model:l,events_count:u.length},null,2));return}if(console.log(v.bold("Rate Limit Analysis")),console.log(v.dim(`Model: ${i} (${d.model_family})`)),console.log(""),console.log(v.bold("Current 5-Hour Window")),console.log(` Billable tokens: ${v.white(_(m.billable_tokens))} ${v.dim("(input + cache_creation, excludes cache_read)")}`),console.log(` Total tokens: ${v.white(_(m.total_tokens))}`),console.log(` Requests: ${v.white(m.requests.toString())}`),console.log(""),d.source==="calibrated"){if(console.log(v.bold("Estimated Utilization")+v.dim(` (${d.calibration_events} calibration events, ${Math.round(d.confidence*100)}% confidence)`)),console.log(` ${xn(d.estimated_utilization)} ${v.white(d.estimated_utilization.toFixed(1)+"%")}`),console.log(` Estimated limit: ${v.white(_(d.estimated_limit))} billable tokens / 5h`),d.minutes_to_limit!==null)if(d.minutes_to_limit===0)console.log(` Time to limit: ${v.red("NOW \u2014 you may be rate limited")}`);else{let f=d.minutes_to_limit<30?v.red:d.minutes_to_limit<60?v.yellow:v.green;console.log(` Time to limit: ${f(R(d.minutes_to_limit*60*1e3))}`)}}else console.log(v.bold("Estimated Utilization")),console.log(v.dim(" No calibration data yet. The model learns when you hit rate limits.")),console.log(v.dim(" Keep using Claude Code normally \u2014 the first time you hit a limit,")),console.log(v.dim(" cctrack will record it and start predicting.")),console.log(""),console.log(v.dim(" To see your actual limits right now, use:")),console.log(v.dim(" /usage in Claude Code"));if(console.log(""),u.length>0){console.log(v.bold("Rate Limit History"));let f=u.slice(-5).reverse();for(let y of f){let g=y.timestamp.slice(0,16).replace("T"," ");console.log(` ${v.dim(g)} ${y.model} \u2014 ${_(y.input_tokens_in_window)} billable tokens`),y.reset_time&&console.log(` ${v.dim("Reset: "+y.reset_time)}`)}}else console.log(v.dim("No rate limit events recorded yet."))})}_t();se().catch(()=>{});var Z=new Cn;Z.name("cctrack").description("Claude Code usage analytics \u2014 accurate metrics and a beautiful HTML dashboard").version("0.1.0").addHelpText("after",`
572
+ `).filter(Boolean).map(o=>JSON.parse(o)):[]}catch{return[]}}function xt(o){let e=Date.now(),t=new Date(e-wn),r=0,c=0,s=0;for(let n of o){let a=new Date(n.timestamp).getTime();if(a<t.getTime()||a>e)continue;let i=n.message.usage;r+=i.input_tokens+(i.cache_creation_input_tokens??0),c+=i.input_tokens+i.output_tokens+(i.cache_creation_input_tokens??0)+(i.cache_read_input_tokens??0),s++}return{billable_tokens:r,total_tokens:c,requests:s,window_start:t}}function Gt(o,e){let t=Lt(e),c=he().limits[t],s=xt(o);if(c&&c.sample_count>0){let n=s.billable_tokens/c.estimated_limit*100,i=(Date.now()-s.window_start.getTime())/(1e3*60*60),d=null;if(i>.1&&s.billable_tokens>0){let m=s.billable_tokens/i,l=c.estimated_limit-s.billable_tokens;l>0&&m>0?d=Math.round(l/m*60):d=0}return{model_family:t,estimated_utilization:Math.min(Math.round(n*10)/10,100),confidence:c.confidence,estimated_limit:Math.round(c.estimated_limit),current_consumption:s.billable_tokens,minutes_to_limit:d,calibration_events:c.sample_count,source:"calibrated"}}return{model_family:t,estimated_utilization:0,confidence:0,estimated_limit:0,current_consumption:s.billable_tokens,minutes_to_limit:null,calibration_events:0,source:"uncalibrated"}}if(import.meta.vitest){let{describe:o,it:e,expect:t}=import.meta.vitest,{makeEntry:r}=await Promise.resolve().then(()=>(St(),Bt));o("modelFamily",()=>{e("extracts opus",()=>t(Lt("claude-opus-4-6-20260205")).toBe("opus")),e("extracts sonnet",()=>t(Lt("claude-sonnet-4-20250514")).toBe("sonnet")),e("extracts haiku",()=>t(Lt("claude-haiku-4-5-20251001")).toBe("haiku")),e("returns unknown for unrecognized",()=>t(Lt("gpt-4")).toBe("unknown"))}),o("currentWindowConsumption",()=>{e("counts only entries in last 5 hours",()=>{let c=new Date,s=new Date(c.getTime()-6e4).toISOString(),n=new Date(c.getTime()-360*60*1e3).toISOString(),a=[r({timestamp:s}),r({timestamp:n})],i=xt(a);t(i.requests).toBe(1)}),e("billable tokens exclude cache_read",()=>{let c=new Date,s=new Date(c.getTime()-6e4).toISOString(),n=[r({timestamp:s,message:{model:"claude-opus-4-6",usage:{input_tokens:100,output_tokens:50,cache_creation_input_tokens:200,cache_read_input_tokens:5e3}}})],a=xt(n);t(a.billable_tokens).toBe(300)}),e("returns zero for empty entries",()=>{let c=xt([]);t(c.billable_tokens).toBe(0),t(c.requests).toBe(0)})}),o("predictUtilization",()=>{e("returns uncalibrated when no model data",()=>{let c=Gt([],"claude-opus-4-6");t(c.source).toBe("uncalibrated"),t(c.confidence).toBe(0),t(c.calibration_events).toBe(0)}),e("returns calibrated when model has learned limits",async()=>{let{writeFileSync:c,mkdirSync:s,readFileSync:n,existsSync:a,unlinkSync:i}=await import("fs"),{join:d}=await import("path"),{homedir:m}=await import("os"),l=d(m(),".cctrack");s(l,{recursive:!0});let u=d(l,"rate-model.json"),f=a(u)?n(u,"utf-8"):null;c(u,JSON.stringify({limits:{opus:{estimated_limit:1e6,confidence:.8,sample_count:5,last_calibration:"2026-03-25T00:00:00Z",alpha:.3}},updated_at:"2026-03-25T00:00:00Z"}));let y=new Date,g=[r({timestamp:new Date(y.getTime()-6e4).toISOString(),message:{model:"claude-opus-4-6",usage:{input_tokens:500,output_tokens:200,cache_creation_input_tokens:100,cache_read_input_tokens:0}}})],p=Gt(g,"claude-opus-4-6");if(t(p.source).toBe("calibrated"),t(p.confidence).toBe(.8),t(p.estimated_limit).toBe(1e6),t(p.current_consumption).toBe(600),f)c(u,f);else try{i(u)}catch{}})})}function xn(o,e=30){let t=Math.min(Math.max(o,0),100),r=Math.round(t/100*e),c="\u2588".repeat(r)+"\u2591".repeat(e-r);return o>=80?v.red(c):o>=50?v.yellow(c):v.green(c)}function wo(o){o.command("limits").description("Predict rate limit utilization from your usage patterns").option("--json","Output as JSON").option("--mode <mode>","Cost mode","calculate").action(async e=>{let t=I(),r=B(t),{entries:c}=await j(r),s=x(c),n=S(e.mode),i=[...s].sort((f,y)=>y.timestamp.localeCompare(f.timestamp))[0]?.message.model??"claude-opus-4-6",d=Gt(s,i),m=xt(s),l=he(),u=vo();if(e.json){console.log(JSON.stringify({prediction:d,consumption:m,model:l,events_count:u.length},null,2));return}if(console.log(v.bold("Rate Limit Analysis")),console.log(v.dim(`Model: ${i} (${d.model_family})`)),console.log(""),console.log(v.bold("Current 5-Hour Window")),console.log(` Billable tokens: ${v.white(_(m.billable_tokens))} ${v.dim("(input + cache_creation, excludes cache_read)")}`),console.log(` Total tokens: ${v.white(_(m.total_tokens))}`),console.log(` Requests: ${v.white(m.requests.toString())}`),console.log(""),d.source==="calibrated"){if(console.log(v.bold("Estimated Utilization")+v.dim(` (${d.calibration_events} calibration events, ${Math.round(d.confidence*100)}% confidence)`)),console.log(` ${xn(d.estimated_utilization)} ${v.white(d.estimated_utilization.toFixed(1)+"%")}`),console.log(` Estimated limit: ${v.white(_(d.estimated_limit))} billable tokens / 5h`),d.minutes_to_limit!==null)if(d.minutes_to_limit===0)console.log(` Time to limit: ${v.red("NOW \u2014 you may be rate limited")}`);else{let f=d.minutes_to_limit<30?v.red:d.minutes_to_limit<60?v.yellow:v.green;console.log(` Time to limit: ${f(R(d.minutes_to_limit*60*1e3))}`)}}else console.log(v.bold("Estimated Utilization")),console.log(v.dim(" No calibration data yet. The model learns when you hit rate limits.")),console.log(v.dim(" Keep using Claude Code normally \u2014 the first time you hit a limit,")),console.log(v.dim(" cctrack will record it and start predicting.")),console.log(""),console.log(v.dim(" To see your actual limits right now, use:")),console.log(v.dim(" /usage in Claude Code"));if(console.log(""),u.length>0){console.log(v.bold("Rate Limit History"));let f=u.slice(-5).reverse();for(let y of f){let g=y.timestamp.slice(0,16).replace("T"," ");console.log(` ${v.dim(g)} ${y.model} \u2014 ${_(y.input_tokens_in_window)} billable tokens`),y.reset_time&&console.log(` ${v.dim("Reset: "+y.reset_time)}`)}}else console.log(v.dim("No rate limit events recorded yet."))})}_t();se().catch(()=>{});var Z=new Cn;Z.name("cctrackr").description("Claude Code usage analytics \u2014 accurate metrics and a beautiful HTML dashboard").version("0.1.1").addHelpText("after",`
573
573
  Examples:
574
- cctrack Open interactive HTML dashboard
575
- cctrack daily --since YYYY-MM-DD Daily cost breakdown from a date
576
- cctrack blocks 5-hour rolling window usage
577
- cctrack roi --plan max20 ROI analysis vs Max 20 plan
578
- cctrack live Real-time terminal monitor
579
- cctrack statusline Compact status for tmux/editors
580
- cctrack config set budget.daily 50 Set daily budget alert at $50
574
+ cctrackr Open interactive HTML dashboard
575
+ cctrackr daily --since YYYY-MM-DD Daily cost breakdown from a date
576
+ cctrackr blocks 5-hour rolling window usage
577
+ cctrackr roi --plan max20 ROI analysis vs Max 20 plan
578
+ cctrackr live Real-time terminal monitor
579
+ cctrackr statusline Compact status for tmux/editors
580
+ cctrackr config set budget.daily 50 Set daily budget alert at $50
581
581
  `);Re(Z);Fe(Z);He(Z);Ge(Z);Ke(Z);Ve(Z);to(Z);eo(Z);oo(Z);fo(Z);po(Z);wo(Z);Z.action(async()=>{await le({})});Z.parse();
582
582
  //# sourceMappingURL=index.js.map