1- import { BailianError , ExitCode , defineCommand , unwrapResponse } from "bailian-cli-core" ;
2- import { ansi , displayWidth , emitResult , type TextStyle } from "bailian-cli-runtime" ;
1+ import { defineCommand , detectOutputFormat , unwrapResponse } from "bailian-cli-core" ;
2+ import {
3+ ansi ,
4+ displayWidth ,
5+ emitResult ,
6+ type AnsiStyles ,
7+ type TextStyle ,
8+ } from "bailian-cli-runtime" ;
9+ import { formatDateTime } from "./shared.ts" ;
310
411const TOKEN_PLAN_USAGE_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage" ;
512const BOX_WIDTH = 76 ;
@@ -12,50 +19,36 @@ interface TokenPlanUsage {
1219 per1WeekResetTime ?: number ;
1320}
1421
15- function readUsage ( result : unknown ) : TokenPlanUsage {
16- const response = unwrapResponse ( result as Record < string , unknown > ) ;
17- const usage = {
18- per5HourPercentage : response . per5HourPercentage ,
19- per5HourResetTime : response . per5HourResetTime ,
20- per1WeekPercentage : response . per1WeekPercentage ,
21- per1WeekResetTime : response . per1WeekResetTime ,
22- } ;
23-
24- const quotas = [
25- [ usage . per5HourPercentage , usage . per5HourResetTime ] ,
26- [ usage . per1WeekPercentage , usage . per1WeekResetTime ] ,
27- ] ;
28- const hasValidQuotas = quotas . every (
29- ( [ percentage , resetTime ] ) =>
30- ( percentage === undefined && resetTime === undefined ) ||
31- ( typeof percentage === "number" &&
32- Number . isFinite ( percentage ) &&
33- ( ( percentage === 0 && resetTime === undefined ) ||
34- ( typeof resetTime === "number" && Number . isFinite ( resetTime ) ) ) ) ,
35- ) ;
22+ interface QuotaWindow {
23+ percentage ?: number ;
24+ resetTime ?: number ;
25+ }
3626
37- if ( ! hasValidQuotas ) {
38- throw new BailianError ( "Token Plan usage response has an unexpected format." , ExitCode . GENERAL ) ;
39- }
27+ /** Accept only finite numbers; anything else counts as absent (possibly unlimited). */
28+ function readNumber ( value : unknown ) : number | undefined {
29+ return typeof value === "number" && Number . isFinite ( value ) ? value : undefined ;
30+ }
4031
41- return usage as TokenPlanUsage ;
32+ function readUsage ( result : unknown ) : TokenPlanUsage {
33+ const response = unwrapResponse ( result as Record < string , unknown > ) ;
34+ const usage : TokenPlanUsage = { } ;
35+
36+ const per5HourPercentage = readNumber ( response . per5HourPercentage ) ;
37+ if ( per5HourPercentage !== undefined ) usage . per5HourPercentage = per5HourPercentage ;
38+ const per5HourResetTime = readNumber ( response . per5HourResetTime ) ;
39+ if ( per5HourResetTime !== undefined ) usage . per5HourResetTime = per5HourResetTime ;
40+ const per1WeekPercentage = readNumber ( response . per1WeekPercentage ) ;
41+ if ( per1WeekPercentage !== undefined ) usage . per1WeekPercentage = per1WeekPercentage ;
42+ const per1WeekResetTime = readNumber ( response . per1WeekResetTime ) ;
43+ if ( per1WeekResetTime !== undefined ) usage . per1WeekResetTime = per1WeekResetTime ;
44+
45+ return usage ;
4246}
4347
4448function formatPercentage ( ratio : number ) : string {
4549 return `${ ( ratio * 100 ) . toFixed ( 2 ) } %` ;
4650}
4751
48- function formatDateTime ( timestamp : number ) : string {
49- const date = new Date ( timestamp ) ;
50- const year = date . getFullYear ( ) ;
51- const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) ;
52- const day = String ( date . getDate ( ) ) . padStart ( 2 , "0" ) ;
53- const hour = String ( date . getHours ( ) ) . padStart ( 2 , "0" ) ;
54- const minute = String ( date . getMinutes ( ) ) . padStart ( 2 , "0" ) ;
55- const second = String ( date . getSeconds ( ) ) . padStart ( 2 , "0" ) ;
56- return `${ year } -${ month } -${ day } ${ hour } :${ minute } :${ second } ` ;
57- }
58-
5952function formatRemainingTime ( resetTime : number , now : number ) : string {
6053 const remainingMs = Math . max ( 0 , resetTime - now ) ;
6154 const totalMinutes = Math . floor ( remainingMs / 60_000 ) ;
@@ -77,102 +70,74 @@ function progressBar(ratio: number): string {
7770 return `[${ "█" . repeat ( filled ) } ${ "░" . repeat ( PROGRESS_WIDTH - filled ) } ]` ;
7871}
7972
80- function progressStyle (
81- percentage : number ,
82- green : TextStyle ,
83- yellow : TextStyle ,
84- red : TextStyle ,
85- ) : TextStyle {
86- if ( percentage >= 0.9 ) return red ;
87- if ( percentage >= 0.75 ) return yellow ;
88- return green ;
73+ function progressStyle ( percentage : number , color : AnsiStyles ) : TextStyle {
74+ if ( percentage >= 0.9 ) return color . red ;
75+ if ( percentage >= 0.75 ) return color . yellow ;
76+ return color . green ;
8977}
9078
9179function printView ( usage : TokenPlanUsage , generatedAt : number ) : void {
9280 const color = ansi ( process . stdout ) ;
93- const writeLine = ( content = "" , visibleContent = content ) => {
94- const padding = Math . max ( 0 , BOX_WIDTH - displayWidth ( ` ${ visibleContent } ` ) ) ;
95- process . stdout . write ( `│ ${ content } ${ " " . repeat ( padding ) } │\n` ) ;
81+ const writeLine = ( text = "" , style ?: TextStyle ) => {
82+ const padding = Math . max ( 0 , BOX_WIDTH - displayWidth ( ` ${ text } ` ) ) ;
83+ process . stdout . write ( `│ ${ style ? style ( text ) : text } ${ " " . repeat ( padding ) } │\n` ) ;
9684 } ;
97- const writeQuota = (
98- label : string ,
99- unlimitedMessage : string ,
100- percentage : number | undefined ,
101- resetTime : number | undefined ,
102- ) => {
103- writeLine ( color . bold ( label ) , label ) ;
104- if ( percentage === undefined ) {
105- writeLine ( color . dim ( unlimitedMessage ) , unlimitedMessage ) ;
85+ const writeQuota = ( label : string , unlimitedMessage : string , window : QuotaWindow ) => {
86+ writeLine ( label , color . bold ) ;
87+ if ( window . percentage === undefined ) {
88+ writeLine ( unlimitedMessage , color . dim ) ;
10689 return ;
10790 }
10891
109- const percentageText = formatPercentage ( percentage ) ;
110- const bar = progressBar ( percentage ) ;
111- const style = progressStyle ( percentage , color . green , color . yellow , color . red ) ;
112- writeLine ( `${ percentageText } used ${ style ( bar ) } ` , `${ percentageText } used ${ bar } ` ) ;
113- if ( resetTime === undefined ) {
114- writeLine (
115- color . dim ( "Resets: not applicable (no usage yet)" ) ,
116- "Resets: not applicable (no usage yet)" ,
117- ) ;
92+ const percentageText = formatPercentage ( window . percentage ) ;
93+ const bar = progressBar ( window . percentage ) ;
94+ writeLine ( `${ percentageText } used ${ bar } ` , progressStyle ( window . percentage , color ) ) ;
95+ if ( window . resetTime === undefined ) {
96+ writeLine ( "Resets: not applicable (no usage yet)" , color . dim ) ;
11897 return ;
11998 }
12099
121- const resetText = `Resets: ${ formatDateTime ( resetTime ) } (in ${ formatRemainingTime ( resetTime , generatedAt ) } )` ;
122- writeLine ( color . dim ( resetText ) , resetText ) ;
100+ const resetText = `Resets: ${ formatDateTime ( window . resetTime ) } (in ${ formatRemainingTime ( window . resetTime , generatedAt ) } )` ;
101+ writeLine ( resetText , color . dim ) ;
123102 } ;
124103
125104 process . stdout . write ( `┌${ "─" . repeat ( BOX_WIDTH ) } ┐\n` ) ;
126- writeLine ( color . cyan ( "Token Plan Usage" ) , "Token Plan Usage" ) ;
127- const generatedAtText = `Generated at: ${ formatDateTime ( generatedAt ) } (local time)` ;
128- writeLine ( color . dim ( generatedAtText ) , generatedAtText ) ;
105+ writeLine ( "Token Plan Usage" , color . cyan ) ;
106+ writeLine ( `Generated at: ${ formatDateTime ( generatedAt ) } (local time)` , color . dim ) ;
129107 process . stdout . write ( `├${ "─" . repeat ( BOX_WIDTH ) } ┤\n` ) ;
130108 writeQuota (
131109 "5-hour quota" ,
132- "5小时限额当前可能无限制,请到百炼 Token Plan 控制台核实。" ,
133- usage . per5HourPercentage ,
134- usage . per5HourResetTime ,
110+ "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console." ,
111+ { percentage : usage . per5HourPercentage , resetTime : usage . per5HourResetTime } ,
135112 ) ;
136113 process . stdout . write ( `├${ "─" . repeat ( BOX_WIDTH ) } ┤\n` ) ;
137114 writeQuota (
138115 "1-week quota" ,
139- "1周限额当前可能无限制,请到百炼 Token Plan 控制台核实。" ,
140- usage . per1WeekPercentage ,
141- usage . per1WeekResetTime ,
116+ "The 1-week limit may be unlimited; verify in the Bailian Token Plan console." ,
117+ { percentage : usage . per1WeekPercentage , resetTime : usage . per1WeekResetTime } ,
142118 ) ;
143119 process . stdout . write ( `└${ "─" . repeat ( BOX_WIDTH ) } ┘\n` ) ;
144120}
145121
146122export default defineCommand ( {
147- description : "Show Token Plan quota usage as core JSON or a human-readable view " ,
123+ description : "Show Token Plan quota usage" ,
148124 auth : "console" ,
149- usageArgs : "<--json | --view> [flags]" ,
150- flags : {
151- json : {
152- type : "switch" ,
153- description : "Output only the four core usage fields as JSON" ,
154- } ,
155- view : {
156- type : "switch" ,
157- description : "Render a compact human-readable quota view" ,
158- } ,
159- } ,
160- exampleArgs : [ "--json" , "--view" ] ,
161- validate : ( flags ) =>
162- flags . json === flags . view ? "Choose exactly one of --json or --view." : undefined ,
125+ usageArgs : "[flags]" ,
126+ exampleArgs : [ "" , "--output json" ] ,
163127 async run ( ctx ) {
164- const { flags, settings } = ctx ;
128+ const { settings } = ctx ;
129+ const format = detectOutputFormat ( settings . output ) ;
165130
166131 if ( settings . dryRun ) {
167- emitResult ( { api : TOKEN_PLAN_USAGE_API , data : { } } , "json" ) ;
132+ emitResult ( { api : TOKEN_PLAN_USAGE_API , data : { } } , format ) ;
168133 return ;
169134 }
170135
171136 const result = await ctx . client . console ( TOKEN_PLAN_USAGE_API , { } ) ;
172137 const usage = readUsage ( result ) ;
173138
174- if ( flags . json ) {
175- emitResult ( usage , "json" ) ;
139+ if ( format === " json" ) {
140+ emitResult ( usage , format ) ;
176141 return ;
177142 }
178143
0 commit comments