@@ -7,13 +7,105 @@ import (
77 "net/url"
88 "os"
99 "strings"
10+ "text/tabwriter"
1011
1112 "github.com/peterbourgon/ff/v3/ffcli"
1213
1314 "github.com/rudrankriyam/App-Store-Connect-CLI/internal/asc"
1415 "github.com/rudrankriyam/App-Store-Connect-CLI/internal/auth"
1516)
1617
18+ // ANSI escape codes for bold text
19+ var (
20+ bold = "\033 [1m"
21+ reset = "\033 [22m"
22+ )
23+
24+ // Bold returns the string wrapped in ANSI bold codes
25+ func Bold (s string ) string {
26+ return bold + s + reset
27+ }
28+
29+ // DefaultUsageFunc returns a usage string with bold section headers
30+ func DefaultUsageFunc (c * ffcli.Command ) string {
31+ var b strings.Builder
32+
33+ shortHelp := strings .TrimSpace (c .ShortHelp )
34+ longHelp := strings .TrimSpace (c .LongHelp )
35+ if shortHelp == "" && longHelp != "" {
36+ shortHelp = longHelp
37+ longHelp = ""
38+ }
39+
40+ // DESCRIPTION
41+ if shortHelp != "" {
42+ b .WriteString (Bold ("DESCRIPTION" ))
43+ b .WriteString ("\n " )
44+ b .WriteString (" " )
45+ b .WriteString (shortHelp )
46+ b .WriteString ("\n \n " )
47+ }
48+
49+ // USAGE / ShortUsage
50+ usage := strings .TrimSpace (c .ShortUsage )
51+ if usage == "" {
52+ usage = strings .TrimSpace (c .Name )
53+ }
54+ if usage != "" {
55+ b .WriteString (Bold ("USAGE" ))
56+ b .WriteString ("\n " )
57+ b .WriteString (" " )
58+ b .WriteString (usage )
59+ b .WriteString ("\n \n " )
60+ }
61+
62+ // LongHelp (additional description)
63+ if longHelp != "" {
64+ if shortHelp != "" && strings .HasPrefix (longHelp , shortHelp ) {
65+ longHelp = strings .TrimSpace (strings .TrimPrefix (longHelp , shortHelp ))
66+ }
67+ if longHelp != "" {
68+ b .WriteString (longHelp )
69+ b .WriteString ("\n \n " )
70+ }
71+ }
72+
73+ // SUBCOMMANDS
74+ if len (c .Subcommands ) > 0 {
75+ b .WriteString (Bold ("SUBCOMMANDS" ))
76+ b .WriteString ("\n " )
77+ tw := tabwriter .NewWriter (& b , 0 , 2 , 2 , ' ' , 0 )
78+ for _ , sub := range c .Subcommands {
79+ fmt .Fprintf (tw , " %-12s %s\n " , sub .Name , sub .ShortHelp )
80+ }
81+ tw .Flush ()
82+ b .WriteString ("\n " )
83+ }
84+
85+ // FLAGS
86+ if c .FlagSet != nil {
87+ hasFlags := false
88+ c .FlagSet .VisitAll (func (* flag.Flag ) {
89+ hasFlags = true
90+ })
91+ if hasFlags {
92+ b .WriteString (Bold ("FLAGS" ))
93+ b .WriteString ("\n " )
94+ tw := tabwriter .NewWriter (& b , 0 , 2 , 2 , ' ' , 0 )
95+ c .FlagSet .VisitAll (func (f * flag.Flag ) {
96+ def := f .DefValue
97+ if def == "" {
98+ def = "\" \" "
99+ }
100+ fmt .Fprintf (tw , " --%-12s %s (default: %s)\n " , f .Name , f .Usage , def )
101+ })
102+ tw .Flush ()
103+ }
104+ }
105+
106+ return b .String ()
107+ }
108+
17109// Feedback command factory
18110func FeedbackCommand () * ffcli.Command {
19111 fs := flag .NewFlagSet ("feedback" , flag .ExitOnError )
@@ -47,7 +139,8 @@ Examples:
47139 asc feedback --app "123456789" --device-model "iPhone15,3" --os-version "17.2"
48140 asc feedback --app "123456789" --sort -createdDate --limit 5 --json
49141 asc feedback --next "<links.next>" --json` ,
50- FlagSet : fs ,
142+ FlagSet : fs ,
143+ UsageFunc : DefaultUsageFunc ,
51144 Exec : func (ctx context.Context , args []string ) error {
52145 if * limit != 0 && (* limit < 1 || * limit > 200 ) {
53146 return fmt .Errorf ("feedback: --limit must be between 1 and 200" )
@@ -138,7 +231,8 @@ Examples:
138231 asc crashes --app "123456789" --device-model "iPhone15,3" --os-version "17.2"
139232 asc crashes --app "123456789" --sort -createdDate --limit 5 --json
140233 asc crashes --next "<links.next>" --json` ,
141- FlagSet : fs ,
234+ FlagSet : fs ,
235+ UsageFunc : DefaultUsageFunc ,
142236 Exec : func (ctx context.Context , args []string ) error {
143237 if * limit != 0 && (* limit < 1 || * limit > 200 ) {
144238 return fmt .Errorf ("crashes: --limit must be between 1 and 200" )
@@ -223,7 +317,8 @@ Examples:
223317 asc reviews --app "123456789" --stars 1 --territory US --json
224318 asc reviews --app "123456789" --sort -createdDate --limit 5 --json
225319 asc reviews --next "<links.next>" --json` ,
226- FlagSet : fs ,
320+ FlagSet : fs ,
321+ UsageFunc : DefaultUsageFunc ,
227322 Exec : func (ctx context.Context , args []string ) error {
228323 if * limit != 0 && (* limit < 1 || * limit > 200 ) {
229324 return fmt .Errorf ("reviews: --limit must be between 1 and 200" )
@@ -311,7 +406,8 @@ Examples:
311406 asc apps --sort name --json
312407 asc apps --output table
313408 asc apps --next "<links.next>" --json` ,
314- FlagSet : fs ,
409+ FlagSet : fs ,
410+ UsageFunc : DefaultUsageFunc ,
315411 Exec : func (ctx context.Context , args []string ) error {
316412 if * limit != 0 && (* limit < 1 || * limit > 200 ) {
317413 return fmt .Errorf ("apps: --limit must be between 1 and 200" )
@@ -379,7 +475,8 @@ with presigned URLs. The actual file upload must be done separately.
379475Examples:
380476 asc builds upload --app "123456789" --ipa "path/to/app.ipa"
381477 asc builds upload --ipa "app.ipa" --version "1.0.0" --build-number "123"` ,
382- FlagSet : fs ,
478+ FlagSet : fs ,
479+ UsageFunc : DefaultUsageFunc ,
383480 Exec : func (ctx context.Context , args []string ) error {
384481 // Validate required flags
385482 resolvedAppID := resolveAppID (* appID )
@@ -502,18 +599,13 @@ func BuildsCommand() *ffcli.Command {
502599 ShortHelp : "Manage builds in App Store Connect." ,
503600 LongHelp : `Manage builds in App Store Connect.
504601
505- Subcommands:
506- list List builds for an app
507- info Show build details
508- expire Expire a build for TestFlight
509- upload Prepare a build upload
510-
511602Examples:
512603 asc builds list --app "123456789"
513604 asc builds info --build "BUILD_ID"
514605 asc builds expire --build "BUILD_ID"
515606 asc builds upload --app "123456789" --ipa "app.ipa"` ,
516- FlagSet : fs ,
607+ FlagSet : fs ,
608+ UsageFunc : DefaultUsageFunc ,
517609 Subcommands : []* ffcli.Command {
518610 listCmd ,
519611 BuildsInfoCommand (),
@@ -551,7 +643,8 @@ Examples:
551643 asc builds list --app "123456789"
552644 asc builds list --app "123456789" --json
553645 asc builds list --app "123456789" --limit 10 --json` ,
554- FlagSet : fs ,
646+ FlagSet : fs ,
647+ UsageFunc : DefaultUsageFunc ,
555648 Subcommands : []* ffcli.Command {
556649 BuildsInfoCommand (),
557650 BuildsExpireCommand (),
@@ -621,11 +714,11 @@ func BuildsInfoCommand() *ffcli.Command {
621714
622715Examples:
623716 asc builds info --build "BUILD_ID" --json` ,
624- FlagSet : fs ,
717+ FlagSet : fs ,
718+ UsageFunc : DefaultUsageFunc ,
625719 Exec : func (ctx context.Context , args []string ) error {
626720 if strings .TrimSpace (* buildID ) == "" {
627721 fmt .Fprintln (os .Stderr , "Error: --build is required" )
628- fs .Usage ()
629722 return flag .ErrHelp
630723 }
631724
@@ -671,11 +764,11 @@ This action is irreversible for the specified build.
671764
672765Examples:
673766 asc builds expire --build "BUILD_ID" --json` ,
674- FlagSet : fs ,
767+ FlagSet : fs ,
768+ UsageFunc : DefaultUsageFunc ,
675769 Exec : func (ctx context.Context , args []string ) error {
676770 if strings .TrimSpace (* buildID ) == "" {
677771 fmt .Fprintln (os .Stderr , "Error: --build is required" )
678- fs .Usage ()
679772 return flag .ErrHelp
680773 }
681774
@@ -724,7 +817,8 @@ a version for review on the App Store.
724817Examples:
725818 asc submit --version "VERSION_ID" --confirm
726819 asc submit --version "VERSION_ID" --confirm --json` ,
727- FlagSet : fs ,
820+ FlagSet : fs ,
821+ UsageFunc : DefaultUsageFunc ,
728822 Exec : func (ctx context.Context , args []string ) error {
729823 // Validate required flags
730824 if * versionID == "" {
@@ -782,6 +876,7 @@ func VersionCommand(version string) *ffcli.Command {
782876 Name : "version" ,
783877 ShortUsage : "asc version" ,
784878 ShortHelp : "Print version information and exit." ,
879+ UsageFunc : DefaultUsageFunc ,
785880 Exec : func (ctx context.Context , args []string ) error {
786881 fmt .Println (version )
787882 return nil
@@ -797,6 +892,7 @@ func RootCommand(version string) *ffcli.Command {
797892 ShortHelp : "A fast, AI-agent friendly CLI for App Store Connect." ,
798893 LongHelp : "ASC is a lightweight CLI for App Store Connect. Built for developers and AI agents." ,
799894 FlagSet : flag .NewFlagSet ("asc" , flag .ExitOnError ),
895+ UsageFunc : DefaultUsageFunc ,
800896 Subcommands : []* ffcli.Command {
801897 AuthCommand (),
802898 FeedbackCommand (),
0 commit comments