@jnst/cursor-usage 0.4.0 → 0.5.0
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 +7 -12
- package/dist/cli.js +8 -8
- package/dist/web/{chunk-z22gajzk.js → chunk-4pkntcpj.js} +71 -71
- package/dist/web/index.html +1 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -146,32 +146,27 @@ bun scripts/generate-dummy-csv.ts > dummy-usage.csv
|
|
|
146
146
|
|
|
147
147
|
### Release
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
The release command verifies, versions, publishes, pushes commits/tags, and creates a
|
|
150
|
+
GitHub Release with generated notes:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
bun run release
|
|
154
|
-
bun run release:minor
|
|
155
|
-
bun run release:major
|
|
153
|
+
bun run release
|
|
156
154
|
```
|
|
157
155
|
|
|
158
|
-
|
|
159
|
-
Use `--publish-release` to publish the GitHub Release immediately, or
|
|
160
|
-
`--dry-run` to print mutating steps without running them:
|
|
156
|
+
Use `--dry-run` to print mutating steps without running them:
|
|
161
157
|
|
|
162
158
|
```bash
|
|
163
|
-
bun run release
|
|
164
|
-
bun run release:patch --publish-release
|
|
159
|
+
bun run release --dry-run
|
|
165
160
|
```
|
|
166
161
|
|
|
167
|
-
|
|
162
|
+
The release command is safe to rerun after a partial failure. The script checks
|
|
168
163
|
the current tag, npm package version, and GitHub Release before each publishing
|
|
169
164
|
step:
|
|
170
165
|
|
|
171
166
|
```bash
|
|
172
167
|
# If npm publish, git push, or GitHub Release creation failed midway,
|
|
173
168
|
# fix the problem and run the same command again.
|
|
174
|
-
bun run release
|
|
169
|
+
bun run release
|
|
175
170
|
```
|
|
176
171
|
|
|
177
172
|
## Architecture
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFile as
|
|
3
|
-
`){
|
|
2
|
+
import{readFile as yf}from"node:fs/promises";import{parseArgs as p}from"node:util";var u="Errored, No Charge";var F="UTC";function x(){return Intl.DateTimeFormat().resolvedOptions().timeZone||F}function h(f){try{return new Intl.DateTimeFormat("en-US",{timeZone:f}).format(new Date),!0}catch{return!1}}var l=new Map;function Af(f){let g=l.get(f);if(g)return g;let $=new Intl.DateTimeFormat("en-GB",{timeZone:f,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",hourCycle:"h23"});return l.set(f,$),$}function y(f,g){return new Map(Af(g).formatToParts(f).map(($)=>[$.type,$.value]))}function qf(f,g,$){return y(f,g).get($)??""}function E(f){return Number.isInteger(f)&&f>=0&&f<=23}function n(f){if(!E(f))throw Error(`Invalid Daily Window start hour: ${f}`)}function Gf(f,g){let $=y(f,g);return{dateKey:[$.get("year"),$.get("month"),$.get("day")].join("-"),hour:Number($.get("hour")??0)}}function Lf(f){let[g,$,Y]=f.split("-").map(Number);if(g===void 0||$===void 0||Y===void 0)throw Error(`Invalid Daily Window Key: ${f}`);return{year:g,month:$,date:Y}}function Xf(f){return f.toISOString().slice(0,10)}function Qf(f,g){let{year:$,month:Y,date:z}=Lf(f);return Xf(new Date(Date.UTC($,Y-1,z)+g*86400000))}function _(f,g=F,$=0){n($);let{dateKey:Y,hour:z}=Gf(f,g);return z<$?Qf(Y,-1):Y}function Ff(f,g=F){return qf(f,g,"hour")}function i(f=0){return n(f),Array.from({length:24},(g,$)=>String((f+$)%24).padStart(2,"0"))}function R(f,g,$=F,Y=0){return f.filter((z)=>_(z.date,$,Y)===g)}function m(f,g=F,$=0){let Y=[...f].sort((z,c)=>c.date.getTime()-z.date.getTime())[0];return Y?_(Y.date,g,$):null}function d(f){return f.filter((g)=>g.kind!==u)}function N(f,g=F,$=0){let Y=0,z=0,c=0,V=new Set,J=new Set,S=new Set;for(let q of f){if(Y+=q.cost,z+=q.totalTokens,q.maxMode)c++;V.add(_(q.date,g,$)),J.add(q.user),S.add(q.model)}let A=[...V].sort();return{totalCost:Y,totalTokens:z,eventCount:f.length,firstDailyWindow:A[0]??null,lastDailyWindow:A[A.length-1]??null,dailyWindowCount:V.size,avgCostPerActiveDailyWindow:V.size>0?Y/V.size:0,maxModeRatio:f.length>0?c/f.length:0,userCount:J.size,modelCount:S.size}}function K(f,g){let $=new Map;for(let Y of f){let z=g(Y),c=$.get(z);if(!c)c={key:z,cost:0,totalTokens:0,inputTokens:0,outputTokens:0,cacheRead:0,eventCount:0},$.set(z,c);c.cost+=Y.cost,c.totalTokens+=Y.totalTokens,c.inputTokens+=Y.inputWithCacheWrite+Y.inputWithoutCacheWrite,c.outputTokens+=Y.outputTokens,c.cacheRead+=Y.cacheRead,c.eventCount++}return[...$.values()]}function C(f,g=F,$=0){return K(f,(Y)=>_(Y.date,g,$)).sort((Y,z)=>Y.key.localeCompare(z.key))}function D(f){return K(f,(g)=>g.user).sort((g,$)=>$.cost-g.cost)}function O(f){return K(f,(g)=>g.model).sort((g,$)=>$.cost-g.cost)}function H(f){return K(f,(g)=>g.kind).sort((g,$)=>$.cost-g.cost)}function W(f,g=F){return K(f,($)=>Ff($.date,g)).sort(($,Y)=>$.key.localeCompare(Y.key))}function r(f,g){return[...f].sort(($,Y)=>Y.cost-$.cost).slice(0,g)}function jf(f){let g=[],$=[],Y="",z=!1,c=0,V=()=>{$.push(Y),Y=""},J=()=>{V(),g.push($),$=[]};while(c<f.length){let S=f[c];if(z){if(S==='"'){if(f[c+1]==='"'){Y+='"',c+=2;continue}z=!1,c++;continue}Y+=S,c++;continue}if(S==='"'){z=!0,c++;continue}if(S===","){V(),c++;continue}if(S==="\r"){c++;continue}if(S===`
|
|
3
|
+
`){J(),c++;continue}Y+=S,c++}if(Y.length>0||$.length>0)J();return g}var Nf=["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 P(f){if(!f)return 0;let g=Number(f);return Number.isFinite(g)?g:0}function a(f){let g=jf(f.trim());if(g.length===0)return[];let $=g[0],Y=new Map;$.forEach((V,J)=>Y.set(V.trim(),J));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: ${Nf.join(", ")}`);let z=(V,J)=>{let S=Y.get(J);return S===void 0?"":(V[S]??"").trim()},c=[];for(let V of g.slice(1)){if(V.length===1&&V[0]==="")continue;let J=z(V,"Date"),S=new Date(J);if(Number.isNaN(S.getTime()))continue;c.push({date:S,user:z(V,"User"),cloudAgentId:z(V,"Cloud Agent ID")||null,automationId:z(V,"Automation ID")||null,kind:z(V,"Kind"),model:z(V,"Model"),maxMode:z(V,"Max Mode").toLowerCase()==="yes",inputWithCacheWrite:P(z(V,"Input (w/ Cache Write)")),inputWithoutCacheWrite:P(z(V,"Input (w/o Cache Write)")),cacheRead:P(z(V,"Cache Read")),outputTokens:P(z(V,"Output Tokens")),totalTokens:P(z(V,"Total Tokens")),cost:P(z(V,"Cost"))})}return c}import{spawn as Pf}from"node:child_process";import{createReadStream as If,existsSync as Bf}from"node:fs";import{stat as Mf}from"node:fs/promises";import{createServer as Rf}from"node:http";import{dirname as Kf,extname as Df,join as k,normalize as Of}from"node:path";import{fileURLToPath as Uf}from"node:url";var s=4321,xf={".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 _f(){let f=Kf(Uf(import.meta.url)),g=[k(f,"web"),k(f,"../../dist/web")];for(let $ of g)if(Bf(k($,"index.html")))return $;throw Error("Dashboard assets not found. Run `bun run build` first (expected dist/web/index.html).")}function Cf(f){let[g,...$]=process.platform==="darwin"?["open",f]:process.platform==="win32"?["cmd","/c","start","",f]:["xdg-open",f];try{Pf(g,$,{stdio:"ignore",detached:!0}).unref()}catch{}}function kf(f){return Rf(async(g,$)=>{let Y=decodeURIComponent(new URL(g.url??"/","http://localhost").pathname),z=Of(k(f,Y==="/"?"/index.html":Y));if(!z.startsWith(f)){$.writeHead(403),$.end("Forbidden");return}try{let c=await Mf(z);if(!c.isFile())throw Error("not a file");$.writeHead(200,{"Content-Type":xf[Df(z)]??"application/octet-stream","Content-Length":c.size}),If(z).pipe($)}catch{$.writeHead(404),$.end("Not Found")}})}function w(f={}){let g=_f(),$=kf(g);return new Promise((Y,z)=>{let c=()=>{let{port:V}=$.address();Y({server:$,url:`http://localhost:${V}`})};if(f.port!==void 0){$.once("error",z),$.listen(f.port,c);return}$.once("error",(V)=>{if(V.code!=="EADDRINUSE"){z(V);return}console.log(`port ${s} is in use, picked a free port instead`),$.listen(0)}),$.once("listening",c),$.listen(s)})}function o(f){w({port:f.port}).then(({url:g})=>{if(console.log(`cursor-usage dashboard running at ${g}`),console.log("Drop a Cursor usage-events CSV onto the page. Ctrl+C to stop."),f.open)Cf(g)}).catch((g)=>{throw g})}var Tf=process.stdout.isTTY&&!process.env.NO_COLOR,U=(f)=>(g)=>Tf?`\x1B[${f}m${g}\x1B[0m`:g,j=U("1"),Q=U("2"),t=U("36"),Sg=U("32"),Ag=U("33");function B(f){return`$${f.toFixed(2)}`}function T(f){if(f>=1e9)return`${(f/1e9).toFixed(1)}B`;if(f>=1e6)return`${(f/1e6).toFixed(1)}M`;if(f>=1000)return`${(f/1000).toFixed(1)}K`;return String(f)}function e(f,g,$){if(g<=0||f<=0)return"";let Y=Math.round(f/g*$*8),z=Math.floor(Y/8),c=Y%8,V=["","▏","▎","▍","▌","▋","▊","▉"];return"█".repeat(z)+(V[c]??"")}function X(f,g){return f.length>=g?f:f+" ".repeat(g-f.length)}function b(f,g,$){return new Intl.DateTimeFormat("en-GB",{timeZone:g,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(f).find((z)=>z.type===$)?.value??""}function pf(f,g){return[b(f,g,"hour"),b(f,g,"minute"),b(f,g,"second")].join(":")}function hf(f,g,$,Y){let z=f.firstDailyWindow&&f.lastDailyWindow?`${f.firstDailyWindow} – ${f.lastDailyWindow}`:"no data",c=$?`${g}, start ${Y}:00, user ${$}`:`${g}, start ${Y}:00`,V=(S)=>Q(X(S,14)),J=(S)=>j(X(S,12));return[`${j("Cursor Usage")} ${z} ${Q(`(${f.eventCount} events, ${f.dailyWindowCount} daily windows, ${c})`)}`,"",` ${V("Total Cost")}${J(B(f.totalCost))} ${V("Total Tokens")}${J(T(f.totalTokens))}`,` ${V("Avg/Active")}${J(B(f.avgCostPerActiveDailyWindow))} ${V("Max Mode")}${J(`${Math.round(f.maxModeRatio*100)}%`)}`,` ${V("Users")}${J(String(f.userCount))} ${V("Models")}${J(String(f.modelCount))}`]}function I(f,g,$={totalCost:0}){let{totalCost:Y,barWidth:z=28,maxRows:c=15}=$,V=g.slice(0,c),J=Math.max(...V.map((q)=>q.key.length),4),S=Math.max(...V.map((q)=>q.cost),0),A=[j(f)];for(let q of V){let G=Y>0?` ${Q(`${Math.round(q.cost/Y*100)}%`)}`:"";A.push(` ${X(q.key,J)} ${X(B(q.cost),8)} ${t(X(e(q.cost,S,z),z))}${G} ${Q(`${T(q.totalTokens)} tok, ${q.eventCount} ev`)}`)}if(g.length>c)A.push(Q(` … and ${g.length-c} more`));return A}function ff(f,g,$,Y,z=0){let c=N(f,$,z),V=[hf(c,$,Y,z)],J={"daily-window":()=>I("Daily Window Cost",C(f,$,z),{totalCost:c.totalCost,maxRows:31}),model:()=>I("By Model",O(f),{totalCost:c.totalCost}),user:()=>I("By User",D(f),{totalCost:c.totalCost})};if(g)V.push(J[g]());else V.push(J["daily-window"](),J.model(),J.user());return V.map((S)=>S.join(`
|
|
4
4
|
`)).join(`
|
|
5
5
|
|
|
6
6
|
`)+`
|
|
7
|
-
`}function
|
|
7
|
+
`}function gf(f,g,$,Y=0){return JSON.stringify({timeZone:g,startHour:Y,filters:{user:$??null},summary:N(f,g,Y),byDailyWindow:C(f,g,Y),byModel:O(f),byUser:D(f)},null,2)}function Ef(f,g,$,Y,z,c,V){let J=N(g,$,Y),S=z>0?Math.round(J.totalCost/z*100):0,A=(G)=>Q(X(G,14)),q=(G)=>j(X(G,12));return[`${j(`Daily Window ${f}`)} ${Q(`(${J.eventCount} events, rank ${c}/${V} by cost, ${$}, start ${Y}:00)`)}`,"",` ${A("Cost")}${q(B(J.totalCost))} ${A("of period")}${q(`${S}%`)}`,` ${A("Total Tokens")}${q(T(J.totalTokens))} ${A("Max Mode")}${q(`${Math.round(J.maxModeRatio*100)}%`)}`,` ${A("Users")}${q(String(J.userCount))} ${A("Models")}${q(String(J.modelCount))}`]}function Hf(f,g,$){let Y=new Map(W(f,g).map((V)=>[V.key,V])),z=Math.max(...[...Y.values()].map((V)=>V.cost),0),c=[j(`By Hour (${g})`)];for(let V of i($)){let J=Y.get(V),S=J?.cost??0,A=J?.eventCount??0,q=A>0?Q(` ${A} ev`):"";c.push(` ${V} ${X(S>0?B(S):"",8)} ${t(X(e(S,z,24),24))}${q}`)}return c}function Wf(f,g,$){let Y=r(f,g),z=[j(`Top Events (${Y.length} of ${f.length})`)],c=Math.max(...Y.map((J)=>J.user.length),4),V=Math.max(...Y.map((J)=>J.model.length),5);for(let J of Y){let S=pf(J.date,$);z.push(` ${Q(S)} ${X(J.user,c)} ${X(J.model,V)} ${X(B(J.cost),8)} ${Q(`${T(J.totalTokens)} tok`)}`)}return z}function $f(f,g,$,Y,z=0){let c=C(f,$,z),V=R(f,g,$,z);if(V.length===0){let G=c.map((Sf)=>Sf.key),M=G.length>0?`
|
|
8
8
|
Available Daily Windows: ${G[0]} – ${G[G.length-1]}`:"";return`No billable events in Daily Window ${g}.${M}
|
|
9
|
-
`}let
|
|
9
|
+
`}let J=f.reduce((G,M)=>G+M.cost,0),S=[...c].sort((G,M)=>M.cost-G.cost).findIndex((G)=>G.key===g)+1,A=N(V,$,z).totalCost;return[Ef(g,V,$,z,J,S,c.length),...Y?[[Q(`Filtered to user: ${Y}`)]]:[],Hf(V,$,z),I("By Model",O(V),{totalCost:A}),I("By User",D(V),{totalCost:A}),I("By Kind",H(V),{totalCost:A}),Wf(V,20,$)].map((G)=>G.join(`
|
|
10
10
|
`)).join(`
|
|
11
11
|
|
|
12
12
|
`)+`
|
|
13
|
-
`}function
|
|
14
|
-
${String(g)}`)}}function
|
|
15
|
-
`))}let
|
|
13
|
+
`}function Yf(f,g,$,Y,z=0){let c=R(f,g,$,z);return JSON.stringify({dailyWindow:g,timeZone:$,startHour:z,filters:{user:Y??null},summary:N(c,$,z),byHour:W(c,$),byModel:O(c),byUser:D(c),byKind:H(c)},null,2)}import{basename as wf,dirname as bf,extname as Zf,join as Vf}from"node:path";function vf(f){let{csvPath:g,dailyWindow:$,dailyReport:Y,out:z}=f;if(z)return z;if(Y)return Vf(process.cwd(),"daily-report.png");let c=Zf(g),V=wf(g,c);return Vf(bf(g),`${V}${$?`-${$}-daily`:""}.png`)}async function uf(){let f=Function("specifier","return import(specifier)");try{let g=await f("playwright-core");if(!g.chromium)throw Error("playwright-core did not expose chromium");return g.chromium}catch(g){throw Error(`Screenshot export requires playwright-core. Install dependencies and try again.
|
|
14
|
+
${String(g)}`)}}function lf(f){return f.map((g)=>({...g,date:g.date.toISOString()}))}async function Z(f){if(f.events.length===0)throw Error("No billable usage events found in the Usage Export.");if(f.dailyWindow){if(R(f.events,f.dailyWindow,f.timeZone,f.startHour).length===0)throw Error(`No billable usage events found in Daily Window ${f.dailyWindow} (start hour ${f.startHour}, ${f.timeZone}).`)}let g=await uf(),$=process.env.CHROME_PATH!==void 0?{headless:!0,executablePath:process.env.CHROME_PATH}:{headless:!0,channel:process.env.PLAYWRIGHT_CHROME_CHANNEL??"chrome"},Y;try{Y=await g.launch($)}catch(V){throw Error(["Could not launch Chrome for screenshot export.","Install Google Chrome, set CHROME_PATH, or set PLAYWRIGHT_CHROME_CHANNEL to an installed Chromium channel.",String(V)].join(`
|
|
15
|
+
`))}let z=vf(f),c=await w({port:0});try{let V=await Y.newPage({viewport:{width:1200,height:900}});await V.addInitScript({content:`window.__CURSOR_USAGE_EVENTS__ = ${JSON.stringify(lf(f.events))};`});let J=new URL(c.url);J.hash=new URLSearchParams({timezone:f.timeZone,...f.user?{user:f.user}:{},...f.dailyWindow?{"daily-window":f.dailyWindow}:{},...f.startHour!==0?{"start-hour":String(f.startHour)}:{},...f.eventLimit!==void 0?{"event-limit":String(f.eventLimit)}:{}}).toString(),await V.goto(J.href,{waitUntil:"networkidle"}),await V.waitForSelector(".grid",{timeout:1e4}),await V.screenshot({path:z,fullPage:!0})}finally{c.server.close(),await Y.close()}return z}var cf=`cursor-usage — visualize Cursor usage-events CSV
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
18
|
cursor-usage [serve] [--port <n>] [--no-open] Start the drag & drop dashboard (default)
|
|
@@ -45,4 +45,4 @@ Serve options:
|
|
|
45
45
|
|
|
46
46
|
-h, --help Show this help
|
|
47
47
|
`;function L(f){console.error(`Error: ${f}
|
|
48
|
-
`),console.error(
|
|
48
|
+
`),console.error(cf),process.exit(1)}function Jf(f,g){if(f===void 0)return g;let $=Number(f);if(!E($))L(`invalid --start-hour value: ${f} (expected 0-23)`);return $}function nf(f){if(f===void 0)return;let g=Number(f);if(!Number.isInteger(g)||g<=0)L(`invalid --event-limit value: ${f} (expected a positive integer)`);return g}async function v(f,g,$,Y="No usage events found for the requested filters."){let z;try{z=await yf(f,"utf8")}catch{L(`file not found: ${f}`)}let c=a(z);if(!g)c=d(c);if($)c=c.filter((V)=>V.user===$);if(c.length===0)L(Y);return c}async function mf(f){let{values:g,positionals:$}=p({args:f,allowPositionals:!0,options:{by:{type:"string"},"daily-window":{type:"string"},"start-hour":{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 z=g.by;if(z&&!["daily-window","user","model"].includes(z))L(`invalid --by value: ${z} (expected daily-window, user or model)`);let c=g["daily-window"];if(c!==void 0&&!/^\d{4}-\d{2}-\d{2}$/.test(c))L(`invalid --daily-window value: ${c} (expected YYYY-MM-DD)`);let V=g.timezone??x();if(!h(V))L(`invalid --timezone value: ${V}`);let J=Jf(g["start-hour"],0),S=g.user,A=await v(Y,g["include-no-charge"],S);if(c){console.log(g.json?Yf(A,c,V,S,J):$f(A,c,V,S,J));return}console.log(g.json?gf(A,V,S,J):ff(A,z,V,S,J))}async function df(f){let{values:g,positionals:$}=p({args:f,allowPositionals:!0,options:{"daily-window":{type:"string"},"start-hour":{type:"string"},"event-limit":{type:"string"},out:{type:"string"},user:{type:"string"},timezone:{type:"string"},"include-no-charge":{type:"boolean",default:!1}}}),Y=$[0];if(!Y)L("screenshot requires a path to a CSV file");let z=g.timezone??x();if(!h(z))L(`invalid --timezone value: ${z}`);let c=g["daily-window"];if(c!==void 0&&!/^\d{4}-\d{2}-\d{2}$/.test(c))L(`invalid --daily-window value: ${c} (expected YYYY-MM-DD)`);let V=Jf(g["start-hour"],0),J=nf(g["event-limit"]),S=await v(Y,g["include-no-charge"],g.user),A=await Z({csvPath:Y,events:S,timeZone:z,dailyWindow:c,startHour:V,eventLimit:J,dailyReport:!1,out:g.out,user:g.user});console.log(`wrote ${A}`)}async function rf(f){let{positionals:g}=p({args:f,allowPositionals:!0,options:{}}),$=g[0];if(!$)L("daily-report requires a path to a CSV file");if(g.length>1)L(`unexpected argument: ${g[1]}`);let Y=x(),z=5,c=await v($,!1,void 0,"No billable usage events found in the Usage Export."),V=m(c,Y,z);if(!V)L("No billable usage events found in the Usage Export.");let J=await Z({csvPath:$,events:c,timeZone:Y,dailyWindow:V,startHour:z,eventLimit:10,dailyReport:!0});console.log(`wrote ${J}`)}function zf(f){let{values:g}=p({args:f,allowPositionals:!1,options:{port:{type:"string"},"no-open":{type:"boolean",default:!1}}}),$;if(g.port!==void 0){if($=Number(g.port),!Number.isInteger($)||$<0||$>65535)L(`invalid --port value: ${g.port}`)}o({port:$,open:!g["no-open"]})}async function af(){let f=process.argv.slice(2);if(f.includes("-h")||f.includes("--help")){console.log(cf);return}let[g,...$]=f;switch(g){case"stats":await mf($);break;case"screenshot":await df($);break;case"daily-report":await rf($);break;case"serve":zf($);break;case void 0:zf([]);break;default:L(`unknown command: ${g}`)}}await af();
|