@@ -0,0 +1,603 @@
/**
* Usage CLI command handler.
*
* Handles `omp usage` — fetches provider usage reports for every
* authenticated account and prints a detailed per-account breakdown
* (limits, windows, reset times, plan metadata). Accounts whose
* credentials produced no usage report are listed too, so the output
* always covers the full credential pool.
*/
import type { AuthStorage , UsageLimit , UsageReport , UsageUnit } from "@oh-my-pi/pi-ai" ;
import { formatDuration , formatNumber } from "@oh-my-pi/pi-utils" ;
import chalk from "chalk" ;
import { ModelRegistry } from "../config/model-registry" ;
import { discoverAuthStorage } from "../sdk" ;
const BAR_WIDTH = 28 ;
export interface UsageCommandArgs {
json? : boolean ;
provider? : string ;
redact? : boolean ;
}
/** Identity slice of a stored credential, for "every account" coverage. */
export interface UsageAccountIdentity {
provider : string ;
type : "api_key" | "oauth" ;
email? : string ;
accountId? : string ;
projectId? : string ;
enterpriseUrl? : string ;
}
/**
* Minimal-reveal masks for identity strings (`--redact`).
*
* Every mask shows a two-character anchor. When two identities share the
* anchor, the mask additionally reveals the shortest "middle-out"
* differentiator — the shortest substring (closest to the string's middle on
* ties) that no colliding identity contains — as `an*`, `ca*9*`, `ca*nb*`.
* Prefix growth is deliberately avoided: it leaks the start of the local
* part (`can.boluk@*`) when a couple of mid-string characters suffice.
* Duplicate strings (same account on two providers) share a mask.
*/
export function buildRedactionMap ( values : Iterable < string > ) : Map < string , string > {
const unique = [ . . . new Set ( values ) ] ;
const map = new Map < string , string > ( ) ;
const byAnchor = new Map < string , string [ ] > ( ) ;
for ( const value of unique ) {
const anchor = value . slice ( 0 , 2 ) ;
const list = byAnchor . get ( anchor ) ? ? [ ] ;
list . push ( value ) ;
byAnchor . set ( anchor , list ) ;
}
for ( const value of unique ) {
const anchor = value . slice ( 0 , 2 ) ;
const peers = ( byAnchor . get ( anchor ) ? ? [ ] ) . filter ( other = > other !== value ) ;
if ( peers . length === 0 ) {
map . set ( value , ` ${ anchor } * ` ) ;
continue ;
}
const infix = findDistinguishingInfix ( value , peers ) ;
map . set ( value , infix === undefined ? ` ${ anchor } * ` : ` ${ anchor } * ${ infix } * ` ) ;
}
// Residual collisions (a value whose every substring also occurs in a
// peer gets the bare anchor mask) fall back to prefix extension.
const byMask = new Map < string , string [ ] > ( ) ;
for ( const value of unique ) {
const mask = map . get ( value ) ! ;
const list = byMask . get ( mask ) ? ? [ ] ;
list . push ( value ) ;
byMask . set ( mask , list ) ;
}
for ( const collided of byMask . values ( ) ) {
if ( collided . length < 2 ) continue ;
for ( const value of collided ) {
let length = Math . min ( 2 , value . length ) ;
while (
length < value . length &&
collided . some ( other = > other !== value && other . startsWith ( value . slice ( 0 , length ) ) )
) {
length ++ ;
}
map . set ( value , ` ${ value . slice ( 0 , length ) } * ` ) ;
}
}
return map ;
}
/**
* Shortest substring of `value` (past the revealed two-char anchor) that no
* peer contains. Among equal-length candidates, picks the one centered
* closest to the middle of the string. Returns undefined when every
* substring also occurs in a peer (e.g. `value` is contained in a peer —
* that peer's own differentiator keeps the masks distinct).
*/
function findDistinguishingInfix ( value : string , peers : string [ ] ) : string | undefined {
const start = Math . min ( 2 , value . length ) ;
const center = value . length / 2 ;
for ( let length = 1 ; length <= value . length - start ; length ++ ) {
let best : { infix : string ; distance : number } | undefined ;
for ( let pos = start ; pos + length <= value . length ; pos ++ ) {
const candidate = value . slice ( pos , pos + length ) ;
if ( peers . some ( peer = > peer . includes ( candidate ) ) ) continue ;
const distance = Math . abs ( pos + length / 2 - center ) ;
if ( ! best || distance < best . distance ) best = { infix : candidate , distance } ;
}
if ( best ) return best . infix ;
}
return undefined ;
}
/** Every identity string the output could surface — input for {@link buildRedactionMap}. */
function collectIdentityStrings ( reports : UsageReport [ ] , accounts : UsageAccountIdentity [ ] ) : string [ ] {
const values : string [ ] = [ ] ;
const add = ( value : unknown ) : void = > {
if ( typeof value === "string" && value ) values . push ( value ) ;
} ;
for ( const report of reports ) {
const meta = report . metadata ? ? { } ;
add ( meta . email ) ;
add ( meta . accountId ) ;
add ( meta . projectId ) ;
add ( meta . orgId ) ;
for ( const limit of report . limits ) {
add ( limit . scope . accountId ) ;
add ( limit . scope . projectId ) ;
add ( limit . scope . orgId ) ;
}
}
for ( const account of accounts ) {
add ( account . email ) ;
add ( account . accountId ) ;
add ( account . projectId ) ;
add ( account . enterpriseUrl ) ;
}
return values ;
}
type LimitStatus = NonNullable < UsageLimit [ "status" ] > ;
function resolveFraction ( limit : UsageLimit ) : number | undefined {
const amount = limit . amount ;
if ( amount . usedFraction !== undefined ) return amount . usedFraction ;
if ( amount . used !== undefined && amount . limit !== undefined && amount . limit > 0 ) {
return amount . used / amount . limit ;
}
if ( amount . unit === "percent" && amount . used !== undefined ) return amount . used / 100 ;
if ( amount . remainingFraction !== undefined ) return Math . max ( 0 , 1 - amount . remainingFraction ) ;
return undefined ;
}
function resolveStatus ( limit : UsageLimit ) : LimitStatus {
if ( limit . status && limit . status !== "unknown" ) return limit . status ;
const fraction = resolveFraction ( limit ) ;
if ( fraction === undefined ) return "unknown" ;
if ( fraction >= 1 ) return "exhausted" ;
if ( fraction >= 0.8 ) return "warning" ;
return "ok" ;
}
const STATUS_COLOR : Record < LimitStatus , ( text : string ) = > string > = {
exhausted : chalk.red ,
warning : chalk.yellow ,
ok : chalk.green ,
unknown : chalk . dim ,
} ;
/** Worst-of aggregation: exhausted > warning > ok > unknown. */
function aggregateStatus ( limits : UsageLimit [ ] ) : LimitStatus {
const statuses = limits . map ( resolveStatus ) ;
if ( statuses . includes ( "exhausted" ) ) return "exhausted" ;
if ( statuses . includes ( "warning" ) ) return "warning" ;
if ( statuses . includes ( "ok" ) ) return "ok" ;
return "unknown" ;
}
function formatProviderName ( provider : string ) : string {
return provider
. split ( /[-_]/g )
. map ( part = > ( part ? part [ 0 ] . toUpperCase ( ) + part . slice ( 1 ) : "" ) )
. join ( " " ) ;
}
function formatUnitValue ( value : number , unit : UsageUnit ) : string {
if ( unit === "usd" ) return ` $ ${ value . toFixed ( 2 ) } ` ;
return formatNumber ( value ) ;
}
const UNIT_SUFFIX : Record < UsageUnit , string > = {
tokens : " tokens" ,
requests : " requests" ,
minutes : " min" ,
bytes : " bytes" ,
percent : "" ,
usd : "" ,
unknown : "" ,
} ;
function describeAmount ( limit : UsageLimit ) : string {
const amount = limit . amount ;
const parts : string [ ] = [ ] ;
const absoluteUnit = amount . unit !== "percent" && amount . unit !== "unknown" ;
if ( absoluteUnit && amount . used !== undefined && amount . limit !== undefined ) {
parts . push (
` ${ formatUnitValue ( amount . used , amount . unit ) } / ${ formatUnitValue ( amount . limit , amount . unit ) } ${ UNIT_SUFFIX [ amount . unit ] } ` ,
) ;
} else if ( absoluteUnit && amount . remaining !== undefined ) {
parts . push ( ` ${ formatUnitValue ( amount . remaining , amount . unit ) } ${ UNIT_SUFFIX [ amount . unit ] } left ` ) ;
}
const fraction = resolveFraction ( limit ) ;
if ( fraction !== undefined ) {
parts . push ( ` ${ ( fraction * 100 ) . toFixed ( 1 ) } % used ` ) ;
} else if ( amount . remainingFraction !== undefined ) {
parts . push ( ` ${ ( amount . remainingFraction * 100 ) . toFixed ( 1 ) } % left ` ) ;
}
if ( parts . length === 0 ) parts . push ( "no data" ) ;
return parts . join ( " · " ) ;
}
function renderBar ( limit : UsageLimit ) : string {
const fraction = resolveFraction ( limit ) ;
if ( fraction === undefined ) return chalk . dim ( "·" . repeat ( BAR_WIDTH ) ) ;
const clamped = Math . min ( Math . max ( fraction , 0 ) , 1 ) ;
const filled = Math . round ( clamped * BAR_WIDTH ) ;
const color = STATUS_COLOR [ resolveStatus ( limit ) ] ;
return color ( "█" . repeat ( filled ) ) + chalk . dim ( "░" . repeat ( BAR_WIDTH - filled ) ) ;
}
/** Append the window label when the limit label doesn't already carry it. */
function limitTitle ( limit : UsageLimit ) : string {
let label = limit . label ;
const tier = limit . scope . tier ;
if ( tier && ! label . toLowerCase ( ) . includes ( tier . toLowerCase ( ) ) ) label = ` ${ label } ( ${ tier } ) ` ;
const windowLabel = limit . window ? . label ? ? limit . scope . windowId ;
if ( ! windowLabel ) return label ;
if ( windowLabel . toLowerCase ( ) === "quota window" ) return label ;
if ( label . toLowerCase ( ) . includes ( windowLabel . toLowerCase ( ) ) ) return label ;
return ` ${ label } ( ${ windowLabel } ) ` ;
}
function reportAccountLabel ( report : UsageReport , index : number ) : string {
const meta = report . metadata ? ? { } ;
for ( const key of [ "email" , "accountId" , "projectId" ] as const ) {
const value = meta [ key ] ;
if ( typeof value === "string" && value ) return value ;
}
for ( const limit of report . limits ) {
const scoped = limit . scope . accountId ? ? limit . scope . projectId ;
if ( scoped ) return scoped ;
}
return ` account ${ index + 1 } ` ;
}
/** Lowercased identity strings a report can be attributed to. */
function reportIdentifiers ( report : UsageReport ) : Set < string > {
const ids = new Set < string > ( ) ;
const add = ( value : unknown ) : void = > {
if ( typeof value === "string" && value ) ids . add ( value . toLowerCase ( ) ) ;
} ;
const meta = report . metadata ? ? { } ;
add ( meta . email ) ;
add ( meta . accountId ) ;
add ( meta . projectId ) ;
add ( meta . orgId ) ;
for ( const limit of report . limits ) {
add ( limit . scope . accountId ) ;
add ( limit . scope . projectId ) ;
add ( limit . scope . orgId ) ;
}
return ids ;
}
/**
* Stored credentials that no usage report could be attributed to.
*
* Conservative on purpose: when a provider's reports carry no identity at
* all (or the credential is an API key alongside existing reports), we
* can't attribute, so we don't claim the account is missing.
*/
export function collectUnreportedAccounts (
reports : UsageReport [ ] ,
accounts : UsageAccountIdentity [ ] ,
) : UsageAccountIdentity [ ] {
const byProvider = new Map < string , UsageReport [ ] > ( ) ;
for ( const report of reports ) {
const list = byProvider . get ( report . provider ) ? ? [ ] ;
list . push ( report ) ;
byProvider . set ( report . provider , list ) ;
}
return accounts . filter ( account = > {
const providerReports = byProvider . get ( account . provider ) ? ? [ ] ;
if ( providerReports . length === 0 ) return true ;
if ( account . type === "api_key" ) return false ;
const ids = [ account . email , account . accountId , account . projectId ]
. filter ( ( value ) : value is string = > typeof value === "string" && value . length > 0 )
. map ( value = > value . toLowerCase ( ) ) ;
if ( ids . length === 0 ) return false ;
const reported = new Set < string > ( ) ;
let anyIdentified = false ;
for ( const report of providerReports ) {
const identifiers = reportIdentifiers ( report ) ;
if ( identifiers . size > 0 ) anyIdentified = true ;
for ( const id of identifiers ) reported . add ( id ) ;
}
if ( ! anyIdentified ) return false ;
return ! ids . some ( id = > reported . has ( id ) ) ;
} ) ;
}
function accountIdentityLabel ( account : UsageAccountIdentity ) : string {
if ( account . type === "api_key" ) return "API key" ;
return account . email ? ? account . accountId ? ? account . projectId ? ? account . enterpriseUrl ? ? "OAuth account" ;
}
function formatAccountHeader (
report : UsageReport ,
index : number ,
nowMs : number ,
redaction? : Map < string , string > ,
) : string {
const status = aggregateStatus ( report . limits ) ;
const icon = STATUS_COLOR [ status ] ( "●" ) ;
const label = reportAccountLabel ( report , index ) ;
let header = ` ${ icon } ${ chalk . bold ( redaction ? . get ( label ) ? ? label ) } ` ;
const planType = report . metadata ? . planType ;
if ( typeof planType === "string" && planType ) header += chalk . dim ( ` · plan: ${ planType } ` ) ;
if ( report . fetchedAt && nowMs - report . fetchedAt > 90 _000 ) {
header += chalk . dim ( ` · fetched ${ formatDuration ( nowMs - report . fetchedAt ) } ago ` ) ;
}
return header ;
}
function formatLimitLine ( limit : UsageLimit , labelWidth : number , nowMs : number ) : string [ ] {
const status = resolveStatus ( limit ) ;
const title = limitTitle ( limit ) ;
const padded = title . padEnd ( labelWidth ) ;
const details : string [ ] = [ describeAmount ( limit ) ] ;
const resetsAt = limit . window ? . resetsAt ;
if ( resetsAt !== undefined && resetsAt > nowMs ) {
details . push ( ` resets in ${ formatDuration ( resetsAt - nowMs ) } ` ) ;
}
const lines = [
` ${ STATUS_COLOR [ status ] ( "●" ) } ${ padded } ${ renderBar ( limit ) } ${ chalk . dim ( details . join ( " · " ) ) } ` ,
] ;
if ( limit . notes && limit . notes . length > 0 ) {
lines . push ( ` ${ chalk . dim ( limit . notes . join ( " · " ) ) } ` ) ;
}
return lines ;
}
/** Per-window capacity stat: how many accounts the current burn requires. */
export interface ProviderWindowStat {
/** Compact window label, e.g. "5h", "7d". */
window : string ;
durationMs? : number ;
/** Accounts reporting a limit in this window. */
accounts : number ;
/** Sum of each account's binding used fraction — accounts' worth of quota burned. */
usedAccounts : number ;
/** Accounts the current burn requires: max(1, ceil(usedAccounts)). */
needed : number ;
}
/**
* Aggregate one provider's reports into per-window "accounts needed" stats.
*
* Limits are bucketed by window duration (5h, 7d, ...). Within a bucket each
* account contributes its single highest used fraction — when an account has
* several meters on the same window (tiered/metered limits), the most-burned
* one is what binds.
*/
export function computeProviderWindowStats ( reports : UsageReport [ ] ) : ProviderWindowStat [ ] {
const buckets = new Map < string , { window : string ; durationMs ? : number ; fractions : number [ ] } > ( ) ;
for ( const report of reports ) {
const accountMax = new Map < string , number > ( ) ;
for ( const limit of report . limits ) {
const fraction = resolveFraction ( limit ) ;
if ( fraction === undefined ) continue ;
const durationMs = limit . window ? . durationMs ;
const key =
durationMs !== undefined ? ` d: ${ durationMs } ` : ( limit . scope . windowId ? ? limit . window ? . label ? ? limit . label ) ;
const previous = accountMax . get ( key ) ;
if ( previous === undefined || fraction > previous ) accountMax . set ( key , fraction ) ;
if ( ! buckets . has ( key ) ) {
const window =
durationMs !== undefined
? formatDuration ( durationMs )
: ( limit . window ? . label ? ? limit . scope . windowId ? ? limit . label ) ;
buckets . set ( key , { window , durationMs , fractions : [ ] } ) ;
}
}
for ( const [ key , fraction ] of accountMax ) buckets . get ( key ) ! . fractions . push ( fraction ) ;
}
return [ . . . buckets . values ( ) ]
. sort ( ( a , b ) = > ( a . durationMs ? ? Number . POSITIVE_INFINITY ) - ( b . durationMs ? ? Number . POSITIVE_INFINITY ) )
. map ( bucket = > {
const usedAccounts = bucket . fractions . reduce ( ( sum , fraction ) = > sum + fraction , 0 ) ;
return {
window : bucket . window ,
durationMs : bucket.durationMs ,
accounts : bucket.fractions.length ,
usedAccounts ,
needed : Math.max ( 1 , Math . ceil ( usedAccounts - 1 e - 9 ) ) ,
} ;
} ) ;
}
/**
* Render the full text breakdown: per provider, per account, every limit
* with a bar, amounts, and reset times; unattributed credentials trail
* each provider section as "no usage data" rows.
*/
export function formatUsageBreakdown (
reports : UsageReport [ ] ,
accounts : UsageAccountIdentity [ ] ,
nowMs : number ,
redaction? : Map < string , string > ,
) : string {
const reportsByProvider = new Map < string , UsageReport [ ] > ( ) ;
for ( const report of reports ) {
const list = reportsByProvider . get ( report . provider ) ? ? [ ] ;
list . push ( report ) ;
reportsByProvider . set ( report . provider , list ) ;
}
const unreported = collectUnreportedAccounts ( reports , accounts ) ;
const unreportedByProvider = new Map < string , UsageAccountIdentity [ ] > ( ) ;
for ( const account of unreported ) {
const list = unreportedByProvider . get ( account . provider ) ? ? [ ] ;
list . push ( account ) ;
unreportedByProvider . set ( account . provider , list ) ;
}
const providers = [ . . . new Set ( [ . . . reportsByProvider . keys ( ) , . . . unreportedByProvider . keys ( ) ] ) ] . sort ( ( a , b ) = >
a . localeCompare ( b ) ,
) ;
const lines : string [ ] = [ ] ;
const latestFetchedAt = Math . max ( 0 , . . . reports . map ( report = > report . fetchedAt ? ? 0 ) ) ;
const headerSuffix = latestFetchedAt ? chalk . dim ( ` · fetched ${ formatDuration ( nowMs - latestFetchedAt ) } ago ` ) : "" ;
lines . push ( ` ${ chalk . bold ( "Usage" ) } ${ headerSuffix } ` ) ;
for ( const provider of providers ) {
const providerReports = reportsByProvider . get ( provider ) ? ? [ ] ;
const providerUnreported = unreportedByProvider . get ( provider ) ? ? [ ] ;
const accountCount = providerReports . length + providerUnreported . length ;
lines . push ( "" ) ;
lines . push (
` ${ chalk . bold . cyan ( formatProviderName ( provider ) ) } ${ chalk . dim ( ` — ${ accountCount } ${ accountCount === 1 ? "account" : "accounts" } ` ) } ` ,
) ;
const labelWidth = providerReports
. flatMap ( report = > report . limits )
. reduce ( ( max , limit ) = > Math . max ( max , limitTitle ( limit ) . length ) , 0 ) ;
providerReports . forEach ( ( report , index ) = > {
lines . push ( ` ${ formatAccountHeader ( report , index , nowMs , redaction ) } ` ) ;
if ( report . limits . length === 0 ) {
lines . push ( ` ${ chalk . dim ( "no limits reported" ) } ` ) ;
return ;
}
for ( const limit of report . limits ) {
lines . push ( . . . formatLimitLine ( limit , labelWidth , nowMs ) ) ;
}
} ) ;
for ( const account of providerUnreported ) {
const label = accountIdentityLabel ( account ) ;
lines . push ( ` ${ chalk . dim ( "○" ) } ${ chalk . dim ( ` ${ redaction ? . get ( label ) ? ? label } — no usage data ` ) } ` ) ;
}
const stats = computeProviderWindowStats ( providerReports ) ;
if ( stats . length > 0 ) {
const parts = stats . map (
stat = >
` ${ stat . window } → ${ stat . needed } of ${ stat . accounts } ${ stat . accounts === 1 ? "account" : "accounts" } ( ${ stat . usedAccounts . toFixed ( 2 ) } × quota burned)` ,
) ;
lines . push ( ` ${ chalk . dim ( ` need: ${ parts . join ( " · " ) } ` ) } ` ) ;
}
}
return lines . join ( "\n" ) ;
}
function collectStoredAccounts ( authStorage : AuthStorage ) : UsageAccountIdentity [ ] {
const accounts : UsageAccountIdentity [ ] = [ ] ;
const all = authStorage . getAll ( ) ;
for ( const provider in all ) {
const entry = all [ provider ] ;
const credentials = Array . isArray ( entry ) ? entry : [ entry ] ;
for ( const credential of credentials ) {
if ( credential . type === "oauth" ) {
accounts . push ( {
provider ,
type : "oauth" ,
email : credential.email ,
accountId : credential.accountId ,
projectId : credential.projectId ,
enterpriseUrl : credential.enterpriseUrl ,
} ) ;
} else {
accounts . push ( { provider , type : "api_key" } ) ;
}
}
}
return accounts ;
}
/** Apply a redaction mask to an optional identity field. */
function maskIdentity ( redaction : Map < string , string > , value : string | undefined ) : string | undefined {
return value === undefined ? undefined : ( redaction . get ( value ) ? ? value ) ;
}
const IDENTITY_METADATA_KEYS = [ "email" , "accountId" , "projectId" , "orgId" ] as const ;
/** Mask identity fields in a raw-stripped report for `--redact --json`. */
function redactReportForJson (
report : Omit < UsageReport , "raw" > ,
redaction : Map < string , string > ,
) : Omit < UsageReport , "raw" > {
let metadata = report . metadata ;
if ( metadata ) {
metadata = { . . . metadata } ;
for ( const key of IDENTITY_METADATA_KEYS ) {
const value = metadata [ key ] ;
if ( typeof value === "string" ) metadata [ key ] = redaction . get ( value ) ? ? value ;
}
}
const limits = report . limits . map ( limit = > ( {
. . . limit ,
scope : {
. . . limit . scope ,
accountId : maskIdentity ( redaction , limit . scope . accountId ) ,
projectId : maskIdentity ( redaction , limit . scope . projectId ) ,
orgId : maskIdentity ( redaction , limit . scope . orgId ) ,
} ,
} ) ) ;
return { . . . report , metadata , limits } ;
}
export async function runUsageCommand ( cmd : UsageCommandArgs ) : Promise < void > {
const authStorage = await discoverAuthStorage ( ) ;
try {
const modelRegistry = new ModelRegistry ( authStorage ) ;
const reports =
( await authStorage . fetchUsageReports ( {
baseUrlResolver : provider = > modelRegistry . getProviderBaseUrl ( provider ) ,
} ) ) ? ? [ ] ;
let accounts = collectStoredAccounts ( authStorage ) ;
let filteredReports = reports ;
if ( cmd . provider ) {
const wanted = cmd . provider . toLowerCase ( ) ;
filteredReports = reports . filter ( report = > report . provider . toLowerCase ( ) === wanted ) ;
accounts = accounts . filter ( account = > account . provider . toLowerCase ( ) === wanted ) ;
}
const redaction = cmd . redact ? buildRedactionMap ( collectIdentityStrings ( filteredReports , accounts ) ) : undefined ;
if ( cmd . json ) {
// Drop the heavy provider-specific `raw` payload — same shape as the
// broker/gateway `/v1/usage` endpoints.
let trimmed = filteredReports . map ( ( { raw : _raw , . . . rest } ) = > rest ) ;
let unreportedAccounts = collectUnreportedAccounts ( filteredReports , accounts ) ;
if ( redaction ) {
trimmed = trimmed . map ( report = > redactReportForJson ( report , redaction ) ) ;
unreportedAccounts = unreportedAccounts . map ( account = > ( {
. . . account ,
email : maskIdentity ( redaction , account . email ) ,
accountId : maskIdentity ( redaction , account . accountId ) ,
projectId : maskIdentity ( redaction , account . projectId ) ,
enterpriseUrl : maskIdentity ( redaction , account . enterpriseUrl ) ,
} ) ) ;
}
const capacity : Record < string , ProviderWindowStat [ ] > = { } ;
for ( const report of filteredReports ) {
if ( capacity [ report . provider ] ) continue ;
const stats = computeProviderWindowStats ( filteredReports . filter ( peer = > peer . provider === report . provider ) ) ;
if ( stats . length > 0 ) capacity [ report . provider ] = stats ;
}
const payload = {
generatedAt : Date.now ( ) ,
reports : trimmed ,
accountsWithoutUsage : unreportedAccounts ,
capacity ,
} ;
process . stdout . write ( ` ${ JSON . stringify ( payload , null , 2 ) } \ n ` ) ;
return ;
}
if ( filteredReports . length === 0 && accounts . length === 0 ) {
const scope = cmd . provider ? ` for provider " ${ cmd . provider } " ` : "" ;
process . stderr . write (
chalk . yellow ( ` No credentials found ${ scope } . Run \` omp \` and use /login to add accounts. \ n ` ) ,
) ;
process . exitCode = 1 ;
return ;
}
process . stdout . write ( ` ${ formatUsageBreakdown ( filteredReports , accounts , Date . now ( ) , redaction ) } \ n ` ) ;
} finally {
authStorage . close ( ) ;
}
}