77from __future__ import annotations
88
99import json
10- from typing import Annotated , Optional
10+ from typing import Annotated , Any , Optional
1111
1212import click
1313import typer
1818# Root application
1919# ---------------------------------------------------------------------------
2020
21+ _DESCRIPTION = (
22+ "Build and deploy computer vision models with Roboflow. "
23+ "Manage datasets, train models, run inference, and deploy "
24+ "workflows \u2014 from the command line or via structured JSON for AI agents."
25+ )
26+
2127app = typer .Typer (
2228 name = "roboflow" ,
23- help = (
24- "Build and deploy computer vision models with Roboflow. "
25- "Manage datasets, train models, run inference, and deploy "
26- "workflows \u2014 from the command line or via structured JSON for AI agents."
27- ),
28- pretty_exceptions_enable = False , # We handle errors ourselves via output_error
29+ help = _DESCRIPTION ,
30+ pretty_exceptions_enable = False ,
2931 rich_markup_mode = "rich" ,
30- add_completion = False , # We have our own 'completion' subcommand
32+ add_completion = False ,
33+ context_settings = {"help_option_names" : ["-h" , "--help" ]},
3134)
3235
3336
3437def _version_callback (value : bool ) -> None :
3538 if value :
3639 import sys
3740
38- # Check if --json was passed (eager callback fires before other params are parsed)
3941 if "--json" in sys .argv or "-j" in sys .argv :
4042 print (json .dumps ({"version" : roboflow .__version__ }))
4143 else :
@@ -46,25 +48,31 @@ def _version_callback(value: bool) -> None:
4648@app .callback (invoke_without_command = True )
4749def _root_callback (
4850 ctx : typer .Context ,
49- json_output : Annotated [
50- bool ,
51- typer .Option ("--json" , "-j" , help = "Output results as JSON (stable schema, for agents and piping)" ),
52- ] = False ,
5351 api_key : Annotated [
5452 Optional [str ],
5553 typer .Option ("--api-key" , "-k" , help = "API key override (default: $ROBOFLOW_API_KEY or config file)" ),
5654 ] = None ,
57- workspace : Annotated [
58- Optional [ str ] ,
59- typer .Option ("--workspace " , "-w " , help = "Workspace URL or ID override (default: configured default )" ),
60- ] = None ,
55+ json_output : Annotated [
56+ bool ,
57+ typer .Option ("--json " , "-j " , help = "Output results as JSON (stable schema, for agents and piping )" ),
58+ ] = False ,
6159 quiet : Annotated [
6260 bool ,
6361 typer .Option ("--quiet" , "-q" , help = "Suppress non-essential output (progress bars, status messages)" ),
6462 ] = False ,
6563 version : Annotated [
6664 Optional [bool ],
67- typer .Option ("--version" , help = "Show package version and exit" , callback = _version_callback , is_eager = True ),
65+ typer .Option (
66+ "--version" ,
67+ "-v" ,
68+ help = "Show package version and exit" ,
69+ callback = _version_callback ,
70+ is_eager = True ,
71+ ),
72+ ] = None ,
73+ workspace : Annotated [
74+ Optional [str ],
75+ typer .Option ("--workspace" , "-w" , help = "Workspace URL or ID override (default: configured default)" ),
6876 ] = None ,
6977) -> None :
7078 """Build and deploy computer vision models with Roboflow."""
@@ -74,12 +82,69 @@ def _root_callback(
7482 ctx .obj ["workspace" ] = workspace
7583 ctx .obj ["quiet" ] = quiet
7684
77- # Show help and exit 0 when no subcommand is given
7885 if ctx .invoked_subcommand is None :
79- print ( ctx . get_help ()) # noqa: T201
86+ _print_flattened_help ()
8087 raise typer .Exit (code = 0 )
8188
8289
90+ def _print_flattened_help () -> None :
91+ """Print a custom help screen with all commands flattened and alphabetized."""
92+ import shutil
93+
94+ click_app = typer .main .get_command (app )
95+
96+ # Collect all visible commands, flattened
97+ commands : list [tuple [str , str ]] = []
98+
99+ def _walk (group : Any , prefix : str = "" ) -> None :
100+ for name in sorted (group .list_commands (None ) or []): # type: ignore[arg-type]
101+ cmd = group .get_command (None , name ) # type: ignore[arg-type]
102+ if cmd is None :
103+ continue
104+ # Skip hidden commands
105+ if getattr (cmd , "hidden" , False ):
106+ continue
107+ full = f"{ prefix } { name } " .strip () if prefix else name
108+ if hasattr (cmd , "list_commands" ) and cmd .list_commands (None ):
109+ _walk (cmd , full )
110+ else :
111+ help_text = cmd .get_short_help_str () or ""
112+ commands .append ((full , help_text ))
113+
114+ _walk (click_app )
115+ commands .sort (key = lambda x : x [0 ])
116+
117+ # Calculate column width
118+ width = shutil .get_terminal_size ((80 , 24 )).columns
119+ name_width = max ((len (c [0 ]) for c in commands ), default = 20 ) + 2
120+ desc_width = max (width - name_width - 4 , 20 )
121+
122+ # Print
123+ print (f"\n \033 [1mroboflow\033 [0m v{ roboflow .__version__ } " ) # noqa: T201
124+ print (f" { _DESCRIPTION } \n " ) # noqa: T201
125+
126+ print (" \033 [1mUsage:\033 [0m roboflow [OPTIONS] COMMAND [ARGS]\n " ) # noqa: T201
127+
128+ print (" \033 [1mOptions:\033 [0m" ) # noqa: T201
129+ options = [
130+ (" --api-key, -k <KEY>" , "API key override" ),
131+ (" --json, -j" , "Output as JSON (for agents and piping)" ),
132+ (" --quiet, -q" , "Suppress progress bars and status messages" ),
133+ (" --version, -v" , "Show version" ),
134+ (" --workspace, -w <ID>" , "Workspace override" ),
135+ (" --help, -h" , "Show this help" ),
136+ ]
137+ opt_name_width = max (len (o [0 ]) for o in options ) + 2
138+ for opt_name , opt_help in options :
139+ print (f" { opt_name :<{opt_name_width }} { opt_help } " ) # noqa: T201
140+
141+ print ("\n \033 [1mCommands:\033 [0m" ) # noqa: T201
142+ for cmd_name , help_text in commands :
143+ truncated = help_text [:desc_width ] if len (help_text ) > desc_width else help_text
144+ print (f" { cmd_name :<{name_width }} { truncated } " ) # noqa: T201
145+ print () # noqa: T201
146+
147+
83148# ---------------------------------------------------------------------------
84149# Register command groups (explicit imports — no auto-discovery needed)
85150# ---------------------------------------------------------------------------
0 commit comments