@jnst/cursor-usage 0.1.0 → 0.2.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/README.md CHANGED
@@ -19,6 +19,8 @@ npx @jnst/cursor-usage # or: bunx @jnst/cursor-usage
19
19
 
20
20
  Starts a local server and opens your browser. Drag & drop a CSV exported from Cursor onto the page. All data is processed in the browser and never sent anywhere.
21
21
 
22
+ Click any bar in the daily cost chart to drill into that day (hourly breakdown, per-model / per-user / per-kind costs, and every event of the day). Click a user bar to filter the current analysis to that User; the selected User remains visible while other users are dimmed, and clicking the selected user again clears the filter. The selected day, user, and analysis time zone are reflected in the URL hash (`#day=YYYY-MM-DD&user=jnst%40example.jp&timezone=Asia%2FTokyo`), so the browser back button and shareable links work after loading the same CSV.
23
+
22
24
  The default port is 4321; if it is already in use, a free port is picked automatically. When `--port` is specified explicitly, that port is used as-is.
23
25
 
24
26
  ```bash
@@ -35,7 +37,7 @@ npx @jnst/cursor-usage stats team-usage-events.csv
35
37
  Cursor Usage 2026-06-01 – 2026-06-10 (610 events, 10 days)
36
38
 
37
39
  Total Cost $1446.69 Total Tokens 1.1B
38
- Avg/Day $144.67 Max Mode 96%
40
+ Avg/Active $144.67 Max Mode 96%
39
41
  Users 4 Models 8
40
42
 
41
43
  Daily Cost
@@ -54,12 +56,21 @@ Options:
54
56
  | Option | Description |
55
57
  | --- | --- |
56
58
  | `--by <day\|user\|model>` | Show a single breakdown axis |
59
+ | `--day <YYYY-MM-DD>` | Drill into a single day (hourly, model, user, kind, top events) |
60
+ | `--user <identifier>` | Filter analysis to a single User |
61
+ | `--timezone <iana-tz>` | Group days and hours in a specific analysis time zone |
57
62
  | `--json` | Output aggregated stats as JSON (pipe to jq etc.) |
58
63
  | `--include-no-charge` | Include "Errored, No Charge" events |
59
64
 
60
65
  ```bash
61
66
  # Extract key numbers
62
67
  npx @jnst/cursor-usage stats usage.csv --json | jq .summary.totalCost
68
+
69
+ # Drill into the most expensive day
70
+ npx @jnst/cursor-usage stats usage.csv --day 2026-06-02 --timezone Asia/Tokyo
71
+
72
+ # Filter to a single user
73
+ npx @jnst/cursor-usage stats usage.csv --user jnst@example.jp
63
74
  ```
64
75
 
65
76
  Or install globally to use the short `cursor-usage` command:
package/dist/cli.js CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import{readFile as pn}from"node:fs/promises";import{parseArgs as E}from"node:util";var Q="Errored, No Charge";function F(n){return n.toISOString().slice(0,10)}function M(n){return n.filter((g)=>g.kind!==Q)}function Z(n){let g=0,p=0,o=0,f=new Set,c=new Set,S=new Set;for(let $ of n){if(g+=$.cost,p+=$.totalTokens,$.maxMode)o++;f.add(F($.date)),c.add($.user),S.add($.model)}let I=[...f].sort();return{totalCost:g,totalTokens:p,eventCount:n.length,firstDay:I[0]??null,lastDay:I[I.length-1]??null,dayCount:f.size,avgCostPerDay:f.size>0?g/f.size:0,maxModeRatio:n.length>0?o/n.length:0,userCount:c.size,modelCount:S.size}}function j(n,g){let p=new Map;for(let o of n){let f=g(o),c=p.get(f);if(!c)c={key:f,cost:0,totalTokens:0,inputTokens:0,outputTokens:0,cacheRead:0,eventCount:0},p.set(f,c);c.cost+=o.cost,c.totalTokens+=o.totalTokens,c.inputTokens+=o.inputWithCacheWrite+o.inputWithoutCacheWrite,c.outputTokens+=o.outputTokens,c.cacheRead+=o.cacheRead,c.eventCount++}return[...p.values()]}function N(n){return j(n,(g)=>F(g.date)).sort((g,p)=>g.key.localeCompare(p.key))}function X(n){return j(n,(g)=>g.user).sort((g,p)=>p.cost-g.cost)}function Y(n){return j(n,(g)=>g.model).sort((g,p)=>p.cost-g.cost)}function R(n){let g=[],p=[],o="",f=!1,c=0,S=()=>{p.push(o),o=""},I=()=>{S(),g.push(p),p=[]};while(c<n.length){let $=n[c];if(f){if($==='"'){if(n[c+1]==='"'){o+='"',c+=2;continue}f=!1,c++;continue}o+=$,c++;continue}if($==='"'){f=!0,c++;continue}if($===","){S(),c++;continue}if($==="\r"){c++;continue}if($===`
3
- `){I(),c++;continue}o+=$,c++}if(o.length>0||p.length>0)I();return g}var i=["Date","User","Cloud Agent ID","Automation ID","Kind","Model","Max Mode","Input (w/ Cache Write)","Input (w/o Cache Write)","Cache Read","Output Tokens","Total Tokens","Cost"];function J(n){if(!n)return 0;let g=Number(n);return Number.isFinite(g)?g:0}function L(n){let g=R(n.trim());if(g.length===0)return[];let p=g[0],o=new Map;p.forEach((S,I)=>o.set(S.trim(),I));for(let S of["Date","User","Model","Cost"])if(!o.has(S))throw Error(`Invalid CSV: missing column "${S}". Expected a Cursor usage-events export with columns: ${i.join(", ")}`);let f=(S,I)=>{let $=o.get(I);return $===void 0?"":(S[$]??"").trim()},c=[];for(let S of g.slice(1)){if(S.length===1&&S[0]==="")continue;let I=f(S,"Date"),$=new Date(I);if(Number.isNaN($.getTime()))continue;c.push({date:$,user:f(S,"User"),cloudAgentId:f(S,"Cloud Agent ID")||null,automationId:f(S,"Automation ID")||null,kind:f(S,"Kind"),model:f(S,"Model"),maxMode:f(S,"Max Mode").toLowerCase()==="yes",inputWithCacheWrite:J(f(S,"Input (w/ Cache Write)")),inputWithoutCacheWrite:J(f(S,"Input (w/o Cache Write)")),cacheRead:J(f(S,"Cache Read")),outputTokens:J(f(S,"Output Tokens")),totalTokens:J(f(S,"Total Tokens")),cost:J(f(S,"Cost"))})}return c}import{spawn as k}from"node:child_process";import{createReadStream as x,existsSync as h}from"node:fs";import{stat as l}from"node:fs/promises";import{createServer as r}from"node:http";import{dirname as b,extname as w,join as K,normalize as d}from"node:path";import{fileURLToPath as y}from"node:url";var _=4321,m={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".map":"application/json"};function v(){let n=b(y(import.meta.url)),g=[K(n,"web"),K(n,"../../dist/web")];for(let p of g)if(h(K(p,"index.html")))return p;throw Error("Dashboard assets not found. Run `bun run build` first (expected dist/web/index.html).")}function a(n){let[g,...p]=process.platform==="darwin"?["open",n]:process.platform==="win32"?["cmd","/c","start","",n]:["xdg-open",n];try{k(g,p,{stdio:"ignore",detached:!0}).unref()}catch{}}function T(n){let g=v(),p=r(async(f,c)=>{let S=decodeURIComponent(new URL(f.url??"/","http://localhost").pathname),I=d(K(g,S==="/"?"/index.html":S));if(!I.startsWith(g)){c.writeHead(403),c.end("Forbidden");return}try{let $=await l(I);if(!$.isFile())throw Error("not a file");c.writeHead(200,{"Content-Type":m[w(I)]??"application/octet-stream","Content-Length":$.size}),x(I).pipe(c)}catch{c.writeHead(404),c.end("Not Found")}}),o=()=>{let{port:f}=p.address(),c=`http://localhost:${f}`;if(console.log(`cursor-usage dashboard running at ${c}`),console.log("Drop a Cursor usage-events CSV onto the page. Ctrl+C to stop."),n.open)a(c)};if(n.port!==void 0){p.listen(n.port,o);return}p.once("error",(f)=>{if(f.code!=="EADDRINUSE")throw f;console.log(`port ${_} is in use, picked a free port instead`),p.listen(0)}),p.once("listening",o),p.listen(_)}var s=process.stdout.isTTY&&!process.env.NO_COLOR,G=(n)=>(g)=>s?`\x1B[${n}m${g}\x1B[0m`:g,z=G("1"),V=G("2"),e=G("36"),Zn=G("32"),jn=G("33");function P(n){return`$${n.toFixed(2)}`}function t(n){if(n>=1e9)return`${(n/1e9).toFixed(1)}B`;if(n>=1e6)return`${(n/1e6).toFixed(1)}M`;if(n>=1000)return`${(n/1000).toFixed(1)}K`;return String(n)}function nn(n,g,p){if(g<=0||n<=0)return"";let o=Math.round(n/g*p*8),f=Math.floor(o/8),c=o%8,S=["","▏","▎","▍","▌","▋","▊","▉"];return"█".repeat(f)+(S[c]??"")}function C(n,g){return n.length>=g?n:n+" ".repeat(g-n.length)}function gn(n){let g=n.firstDay&&n.lastDay?`${n.firstDay} – ${n.lastDay}`:"no data",p=(f)=>V(C(f,14)),o=(f)=>z(C(f,12));return[`${z("Cursor Usage")} ${g} ${V(`(${n.eventCount} events, ${n.dayCount} days)`)}`,"",` ${p("Total Cost")}${o(P(n.totalCost))} ${p("Total Tokens")}${o(t(n.totalTokens))}`,` ${p("Avg/Day")}${o(P(n.avgCostPerDay))} ${p("Max Mode")}${o(`${Math.round(n.maxModeRatio*100)}%`)}`,` ${p("Users")}${o(String(n.userCount))} ${p("Models")}${o(String(n.modelCount))}`]}function q(n,g,p={totalCost:0}){let{totalCost:o,barWidth:f=28,maxRows:c=15}=p,S=g.slice(0,c),I=Math.max(...S.map((A)=>A.key.length),4),$=Math.max(...S.map((A)=>A.cost),0),W=[z(n)];for(let A of S){let u=o>0?` ${V(`${Math.round(A.cost/o*100)}%`)}`:"";W.push(` ${C(A.key,I)} ${C(P(A.cost),8)} ${e(C(nn(A.cost,$,f),f))}${u} ${V(`${t(A.totalTokens)} tok, ${A.eventCount} ev`)}`)}if(g.length>c)W.push(V(` … and ${g.length-c} more`));return W}function B(n,g){let p=Z(n),o=[gn(p)],f={day:()=>q("Daily Cost",N(n),{totalCost:p.totalCost,maxRows:31}),model:()=>q("By Model",Y(n),{totalCost:p.totalCost}),user:()=>q("By User",X(n),{totalCost:p.totalCost})};if(g)o.push(f[g]());else o.push(f.day(),f.model(),f.user());return o.map((c)=>c.join(`
2
+ import{readFile as Wg}from"node:fs/promises";import{parseArgs as m}from"node:util";var w="Errored, No Charge";var F="UTC";function h(){return Intl.DateTimeFormat().resolvedOptions().timeZone||F}function k(g){try{return new Intl.DateTimeFormat("en-US",{timeZone:g}).format(new Date),!0}catch{return!1}}function R(g,f,$){return new Intl.DateTimeFormat("en-GB",{timeZone:f,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",hourCycle:"h23"}).formatToParts(g).find((J)=>J.type===$)?.value??""}function D(g,f=F){return[R(g,f,"year"),R(g,f,"month"),R(g,f,"day")].join("-")}function a(g,f=F){return R(g,f,"hour")}function _(g,f,$=F){return g.filter((Y)=>D(Y.date,$)===f)}function E(g){return g.filter((f)=>f.kind!==w)}function K(g,f=F){let $=0,Y=0,J=0,S=new Set,V=new Set,A=new Set;for(let G of g){if($+=G.cost,Y+=G.totalTokens,G.maxMode)J++;S.add(D(G.date,f)),V.add(G.user),A.add(G.model)}let j=[...S].sort();return{totalCost:$,totalTokens:Y,eventCount:g.length,firstDay:j[0]??null,lastDay:j[j.length-1]??null,dayCount:S.size,avgCostPerActiveDay:S.size>0?$/S.size:0,maxModeRatio:g.length>0?J/g.length:0,userCount:V.size,modelCount:A.size}}function P(g,f){let $=new Map;for(let Y of g){let J=f(Y),S=$.get(J);if(!S)S={key:J,cost:0,totalTokens:0,inputTokens:0,outputTokens:0,cacheRead:0,eventCount:0},$.set(J,S);S.cost+=Y.cost,S.totalTokens+=Y.totalTokens,S.inputTokens+=Y.inputWithCacheWrite+Y.inputWithoutCacheWrite,S.outputTokens+=Y.outputTokens,S.cacheRead+=Y.cacheRead,S.eventCount++}return[...$.values()]}function p(g,f=F){return P(g,($)=>D($.date,f)).sort(($,Y)=>$.key.localeCompare(Y.key))}function N(g){return P(g,(f)=>f.user).sort((f,$)=>$.cost-f.cost)}function c(g){return P(g,(f)=>f.model).sort((f,$)=>$.cost-f.cost)}function x(g){return P(g,(f)=>f.kind).sort((f,$)=>$.cost-f.cost)}function T(g,f=F){return P(g,($)=>a($.date,f)).sort(($,Y)=>$.key.localeCompare(Y.key))}function l(g,f){return[...g].sort(($,Y)=>Y.cost-$.cost).slice(0,f)}function t(g){let f=[],$=[],Y="",J=!1,S=0,V=()=>{$.push(Y),Y=""},A=()=>{V(),f.push($),$=[]};while(S<g.length){let j=g[S];if(J){if(j==='"'){if(g[S+1]==='"'){Y+='"',S+=2;continue}J=!1,S++;continue}Y+=j,S++;continue}if(j==='"'){J=!0,S++;continue}if(j===","){V(),S++;continue}if(j==="\r"){S++;continue}if(j===`
3
+ `){A(),S++;continue}Y+=j,S++}if(Y.length>0||$.length>0)A();return f}var e=["Date","User","Cloud Agent ID","Automation ID","Kind","Model","Max Mode","Input (w/ Cache Write)","Input (w/o Cache Write)","Cache Read","Output Tokens","Total Tokens","Cost"];function H(g){if(!g)return 0;let f=Number(g);return Number.isFinite(f)?f:0}function u(g){let f=t(g.trim());if(f.length===0)return[];let $=f[0],Y=new Map;$.forEach((V,A)=>Y.set(V.trim(),A));for(let V of["Date","User","Model","Cost"])if(!Y.has(V))throw Error(`Invalid CSV: missing column "${V}". Expected a Cursor usage-events export with columns: ${e.join(", ")}`);let J=(V,A)=>{let j=Y.get(A);return j===void 0?"":(V[j]??"").trim()},S=[];for(let V of f.slice(1)){if(V.length===1&&V[0]==="")continue;let A=J(V,"Date"),j=new Date(A);if(Number.isNaN(j.getTime()))continue;S.push({date:j,user:J(V,"User"),cloudAgentId:J(V,"Cloud Agent ID")||null,automationId:J(V,"Automation ID")||null,kind:J(V,"Kind"),model:J(V,"Model"),maxMode:J(V,"Max Mode").toLowerCase()==="yes",inputWithCacheWrite:H(J(V,"Input (w/ Cache Write)")),inputWithoutCacheWrite:H(J(V,"Input (w/o Cache Write)")),cacheRead:H(J(V,"Cache Read")),outputTokens:H(J(V,"Output Tokens")),totalTokens:H(J(V,"Total Tokens")),cost:H(J(V,"Cost"))})}return S}import{spawn as gg}from"node:child_process";import{createReadStream as fg,existsSync as $g}from"node:fs";import{stat as Sg}from"node:fs/promises";import{createServer as Vg}from"node:http";import{dirname as Jg,extname as Yg,join as M,normalize as Ag}from"node:path";import{fileURLToPath as jg}from"node:url";var Z=4321,Gg={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".map":"application/json"};function Xg(){let g=Jg(jg(import.meta.url)),f=[M(g,"web"),M(g,"../../dist/web")];for(let $ of f)if($g(M($,"index.html")))return $;throw Error("Dashboard assets not found. Run `bun run build` first (expected dist/web/index.html).")}function qg(g){let[f,...$]=process.platform==="darwin"?["open",g]:process.platform==="win32"?["cmd","/c","start","",g]:["xdg-open",g];try{gg(f,$,{stdio:"ignore",detached:!0}).unref()}catch{}}function b(g){let f=Xg(),$=Vg(async(J,S)=>{let V=decodeURIComponent(new URL(J.url??"/","http://localhost").pathname),A=Ag(M(f,V==="/"?"/index.html":V));if(!A.startsWith(f)){S.writeHead(403),S.end("Forbidden");return}try{let j=await Sg(A);if(!j.isFile())throw Error("not a file");S.writeHead(200,{"Content-Type":Gg[Yg(A)]??"application/octet-stream","Content-Length":j.size}),fg(A).pipe(S)}catch{S.writeHead(404),S.end("Not Found")}}),Y=()=>{let{port:J}=$.address(),S=`http://localhost:${J}`;if(console.log(`cursor-usage dashboard running at ${S}`),console.log("Drop a Cursor usage-events CSV onto the page. Ctrl+C to stop."),g.open)qg(S)};if(g.port!==void 0){$.listen(g.port,Y);return}$.once("error",(J)=>{if(J.code!=="EADDRINUSE")throw J;console.log(`port ${Z} is in use, picked a free port instead`),$.listen(0)}),$.once("listening",Y),$.listen(Z)}var zg=process.stdout.isTTY&&!process.env.NO_COLOR,B=(g)=>(f)=>zg?`\x1B[${g}m${f}\x1B[0m`:f,Q=B("1"),z=B("2"),v=B("36"),Cg=B("32"),wg=B("33");function I(g){return`$${g.toFixed(2)}`}function O(g){if(g>=1e9)return`${(g/1e9).toFixed(1)}B`;if(g>=1e6)return`${(g/1e6).toFixed(1)}M`;if(g>=1000)return`${(g/1000).toFixed(1)}K`;return String(g)}function y(g,f,$){if(f<=0||g<=0)return"";let Y=Math.round(g/f*$*8),J=Math.floor(Y/8),S=Y%8,V=["","▏","▎","▍","▌","▋","▊","▉"];return"█".repeat(J)+(V[S]??"")}function q(g,f){return g.length>=f?g:g+" ".repeat(f-g.length)}function C(g,f,$){return new Intl.DateTimeFormat("en-GB",{timeZone:f,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(g).find((J)=>J.type===$)?.value??""}function Fg(g,f){return[C(g,f,"hour"),C(g,f,"minute"),C(g,f,"second")].join(":")}function Kg(g,f,$){let Y=g.firstDay&&g.lastDay?`${g.firstDay} – ${g.lastDay}`:"no data",J=$?`${f}, user ${$}`:f,S=(A)=>z(q(A,14)),V=(A)=>Q(q(A,12));return[`${Q("Cursor Usage")} ${Y} ${z(`(${g.eventCount} events, ${g.dayCount} days, ${J})`)}`,"",` ${S("Total Cost")}${V(I(g.totalCost))} ${S("Total Tokens")}${V(O(g.totalTokens))}`,` ${S("Avg/Active")}${V(I(g.avgCostPerActiveDay))} ${S("Max Mode")}${V(`${Math.round(g.maxModeRatio*100)}%`)}`,` ${S("Users")}${V(String(g.userCount))} ${S("Models")}${V(String(g.modelCount))}`]}function W(g,f,$={totalCost:0}){let{totalCost:Y,barWidth:J=28,maxRows:S=15}=$,V=f.slice(0,S),A=Math.max(...V.map((X)=>X.key.length),4),j=Math.max(...V.map((X)=>X.cost),0),G=[Q(g)];for(let X of V){let U=Y>0?` ${z(`${Math.round(X.cost/Y*100)}%`)}`:"";G.push(` ${q(X.key,A)} ${q(I(X.cost),8)} ${v(q(y(X.cost,j,J),J))}${U} ${z(`${O(X.totalTokens)} tok, ${X.eventCount} ev`)}`)}if(f.length>S)G.push(z(` … and ${f.length-S} more`));return G}function i(g,f,$,Y){let J=K(g,$),S=[Kg(J,$,Y)],V={day:()=>W("Daily Cost",p(g,$),{totalCost:J.totalCost,maxRows:31}),model:()=>W("By Model",c(g),{totalCost:J.totalCost}),user:()=>W("By User",N(g),{totalCost:J.totalCost})};if(f)S.push(V[f]());else S.push(V.day(),V.model(),V.user());return S.map((A)=>A.join(`
4
4
  `)).join(`
5
5
 
6
6
  `)+`
7
- `}function D(n){return JSON.stringify({summary:Z(n),byDay:N(n),byModel:Y(n),byUser:X(n)},null,2)}var O=`cursor-usage visualize Cursor usage-events CSV
7
+ `}function r(g,f,$){return JSON.stringify({timeZone:f,filters:{user:$??null},summary:K(g,f),byDay:p(g,f),byModel:c(g),byUser:N(g)},null,2)}function Qg(g,f,$,Y,J,S){let V=K(f,$),A=Y>0?Math.round(V.totalCost/Y*100):0,j=(X)=>z(q(X,14)),G=(X)=>Q(q(X,12));return[`${Q(`Day ${g}`)} ${z(`(${V.eventCount} events, rank ${J}/${S} by cost, ${$})`)}`,"",` ${j("Cost")}${G(I(V.totalCost))} ${j("of period")}${G(`${A}%`)}`,` ${j("Total Tokens")}${G(O(V.totalTokens))} ${j("Max Mode")}${G(`${Math.round(V.maxModeRatio*100)}%`)}`,` ${j("Users")}${G(String(V.userCount))} ${j("Models")}${G(String(V.modelCount))}`]}function Lg(g,f){let $=new Map(T(g,f).map((S)=>[S.key,S])),Y=Math.max(...[...$.values()].map((S)=>S.cost),0),J=[Q(`By Hour (${f})`)];for(let S=0;S<24;S++){let V=String(S).padStart(2,"0"),A=$.get(V),j=A?.cost??0,G=A?.eventCount??0,X=G>0?z(` ${G} ev`):"";J.push(` ${V} ${q(j>0?I(j):"",8)} ${v(q(y(j,Y,24),24))}${X}`)}return J}function Hg(g,f,$){let Y=l(g,f),J=[Q(`Top Events (${Y.length} of ${g.length})`)],S=Math.max(...Y.map((A)=>A.user.length),4),V=Math.max(...Y.map((A)=>A.model.length),5);for(let A of Y){let j=Fg(A.date,$);J.push(` ${z(j)} ${q(A.user,S)} ${q(A.model,V)} ${q(I(A.cost),8)} ${z(`${O(A.totalTokens)} tok`)}`)}return J}function d(g,f,$,Y){let J=p(g,$),S=_(g,f,$);if(S.length===0){let G=J.map((U)=>U.key),X=G.length>0?`
8
+ Available days: ${G[0]} – ${G[G.length-1]}`:"";return`No billable events on ${f}.${X}
9
+ `}let V=g.reduce((G,X)=>G+X.cost,0),A=[...J].sort((G,X)=>X.cost-G.cost).findIndex((G)=>G.key===f)+1;return[Qg(f,S,$,V,A,J.length),...Y?[[z(`Filtered to user: ${Y}`)]]:[],Lg(S,$),W("By Model",c(S),{totalCost:K(S,$).totalCost}),W("By User",N(S),{totalCost:K(S,$).totalCost}),W("By Kind",x(S),{totalCost:0}),Hg(S,20,$)].map((G)=>G.join(`
10
+ `)).join(`
11
+
12
+ `)+`
13
+ `}function o(g,f,$,Y){let J=_(g,f,$);return JSON.stringify({day:f,timeZone:$,filters:{user:Y??null},summary:K(J,$),byHour:T(J,$),byModel:c(J),byUser:N(J),byKind:x(J)},null,2)}var s=`cursor-usage — visualize Cursor usage-events CSV
8
14
 
9
15
  Usage:
10
16
  cursor-usage [serve] [--port <n>] [--no-open] Start the drag & drop dashboard (default)
@@ -12,6 +18,9 @@ Usage:
12
18
 
13
19
  Stats options:
14
20
  --by <day|user|model> Show a single breakdown axis (default: all)
21
+ --day <YYYY-MM-DD> Drill into a single day (hourly, model, user, kind, top events)
22
+ --user <identifier> Filter analysis to a single User
23
+ --timezone <iana-tz> Analysis time zone (default: current environment)
15
24
  --json Output aggregated stats as JSON
16
25
  --include-no-charge Include "Errored, No Charge" events
17
26
 
@@ -21,5 +30,5 @@ Serve options:
21
30
  --no-open Do not open the browser automatically
22
31
 
23
32
  -h, --help Show this help
24
- `;function H(n){console.error(`Error: ${n}
25
- `),console.error(O),process.exit(1)}async function fn(n){let{values:g,positionals:p}=E({args:n,allowPositionals:!0,options:{by:{type:"string"},json:{type:"boolean",default:!1},"include-no-charge":{type:"boolean",default:!1}}}),o=p[0];if(!o)H("stats requires a path to a CSV file");let f;try{f=await pn(o,"utf8")}catch{H(`file not found: ${o}`)}let c=g.by;if(c&&!["day","user","model"].includes(c))H(`invalid --by value: ${c} (expected day, user or model)`);let S=L(f);if(!g["include-no-charge"])S=M(S);if(S.length===0)console.error("No usage events found in the CSV."),process.exit(1);console.log(g.json?D(S):B(S,c))}function U(n){let{values:g}=E({args:n,allowPositionals:!1,options:{port:{type:"string"},"no-open":{type:"boolean",default:!1}}}),p;if(g.port!==void 0){if(p=Number(g.port),!Number.isInteger(p)||p<0||p>65535)H(`invalid --port value: ${g.port}`)}T({port:p,open:!g["no-open"]})}async function cn(){let n=process.argv.slice(2);if(n.includes("-h")||n.includes("--help")){console.log(O);return}let[g,...p]=n;switch(g){case"stats":await fn(p);break;case"serve":U(p);break;case void 0:U([]);break;default:H(`unknown command: ${g}`)}}await cn();
33
+ `;function L(g){console.error(`Error: ${g}
34
+ `),console.error(s),process.exit(1)}async function Ig(g){let{values:f,positionals:$}=m({args:g,allowPositionals:!0,options:{by:{type:"string"},day:{type:"string"},user:{type:"string"},timezone:{type:"string"},json:{type:"boolean",default:!1},"include-no-charge":{type:"boolean",default:!1}}}),Y=$[0];if(!Y)L("stats requires a path to a CSV file");let J;try{J=await Wg(Y,"utf8")}catch{L(`file not found: ${Y}`)}let S=f.by;if(S&&!["day","user","model"].includes(S))L(`invalid --by value: ${S} (expected day, user or model)`);let V=f.day;if(V!==void 0&&!/^\d{4}-\d{2}-\d{2}$/.test(V))L(`invalid --day value: ${V} (expected YYYY-MM-DD)`);let A=f.timezone??h();if(!k(A))L(`invalid --timezone value: ${A}`);let j=u(J);if(!f["include-no-charge"])j=E(j);let G=f.user;if(G)j=j.filter((X)=>X.user===G);if(j.length===0)console.error("No usage events found for the requested filters."),process.exit(1);if(V){console.log(f.json?o(j,V,A,G):d(j,V,A,G));return}console.log(f.json?r(j,A,G):i(j,S,A,G))}function n(g){let{values:f}=m({args:g,allowPositionals:!1,options:{port:{type:"string"},"no-open":{type:"boolean",default:!1}}}),$;if(f.port!==void 0){if($=Number(f.port),!Number.isInteger($)||$<0||$>65535)L(`invalid --port value: ${f.port}`)}b({port:$,open:!f["no-open"]})}async function Pg(){let g=process.argv.slice(2);if(g.includes("-h")||g.includes("--help")){console.log(s);return}let[f,...$]=g;switch(f){case"stats":await Ig($);break;case"serve":n($);break;case void 0:n([]);break;default:L(`unknown command: ${f}`)}}await Pg();