1717"""
1818
1919import argparse
20+ import difflib
2021import getpass
2122import os
2223import subprocess
24+ import sys
2325from pathlib import Path
2426from typing import Any , Dict , List , Optional , Union
2527
5658load_swarms_env ()
5759
5860
61+ # Single source of truth for valid CLI commands. Used by argparse for
62+ # strict validation and by the typo-correction pre-check in main() to
63+ # suggest the closest match when a user mistypes a command.
64+ COMMAND_CHOICES : List [str ] = [
65+ "init" ,
66+ "onboarding" ,
67+ "get-api-key" ,
68+ "check-login" ,
69+ "run-agents" ,
70+ "load-markdown" ,
71+ "agent" ,
72+ "chat" ,
73+ "upgrade" ,
74+ "autoswarm" ,
75+ "setup-check" ,
76+ "llm-council" ,
77+ "heavy-swarm" ,
78+ "tips" ,
79+ "models" ,
80+ ]
81+
82+
83+ def _suggest_command (typed : str ) -> Optional [str ]:
84+ """Return the closest valid command, or None if nothing is close enough."""
85+ matches = difflib .get_close_matches (
86+ typed , COMMAND_CHOICES , n = 1 , cutoff = 0.5
87+ )
88+ return matches [0 ] if matches else None
89+
90+
5991def run_autoswarm (
6092 task : str ,
6193 model : str ,
@@ -942,26 +974,11 @@ def setup_argument_parser() -> argparse.ArgumentParser:
942974 action = CustomHelpAction ,
943975 help = "Show this help message and exit" ,
944976 )
945- command_choices = [
946- "init" ,
947- "onboarding" ,
948- "get-api-key" ,
949- "check-login" ,
950- "run-agents" ,
951- "load-markdown" ,
952- "agent" ,
953- "chat" ,
954- "upgrade" ,
955- "autoswarm" ,
956- "setup-check" ,
957- "llm-council" ,
958- "heavy-swarm" ,
959- ]
960977 parser .add_argument (
961978 "command" ,
962979 metavar = "COMMAND" ,
963- choices = command_choices ,
964- help = f"Command to execute. Available commands: { ', ' .join (command_choices )} " ,
980+ choices = COMMAND_CHOICES ,
981+ help = f"Command to execute. Available commands: { ', ' .join (COMMAND_CHOICES )} " ,
965982 )
966983 parser .add_argument (
967984 "--yaml-file" ,
@@ -1159,6 +1176,44 @@ def setup_argument_parser() -> argparse.ArgumentParser:
11591176 default = None ,
11601177 help = "Directory for 'swarms init' (default: prompted interactively)" ,
11611178 )
1179+ # Tips command arguments
1180+ parser .add_argument (
1181+ "--count" ,
1182+ type = int ,
1183+ default = 1 ,
1184+ help = "Number of tips to show (used with 'swarms tips')" ,
1185+ )
1186+ parser .add_argument (
1187+ "--category" ,
1188+ type = str ,
1189+ default = None ,
1190+ help = "Restrict tips to a category: commands, agents, swarms, models, pro, trivia, env, community" ,
1191+ )
1192+ parser .add_argument (
1193+ "--all" ,
1194+ dest = "all_tips" ,
1195+ action = "store_true" ,
1196+ help = "Show every tip in the selected category (or every category when no --category is given)" ,
1197+ )
1198+ # Models command arguments
1199+ parser .add_argument (
1200+ "--search" ,
1201+ type = str ,
1202+ default = None ,
1203+ help = "Fuzzy-search the model list (used with 'swarms models')" ,
1204+ )
1205+ parser .add_argument (
1206+ "--info" ,
1207+ type = str ,
1208+ default = None ,
1209+ help = "Show full metadata for a single model (used with 'swarms models')" ,
1210+ )
1211+ parser .add_argument (
1212+ "--provider" ,
1213+ type = str ,
1214+ default = None ,
1215+ help = "Restrict 'swarms models' to one provider (e.g. openai, anthropic)" ,
1216+ )
11621217 return parser
11631218
11641219
@@ -1998,6 +2053,81 @@ def handle_init(args: argparse.Namespace) -> None:
19982053 )
19992054 )
20002055
2056+ # Contextual next-step tip
2057+ from swarms .cli .tips import render_tip
2058+
2059+ console .print ()
2060+ console .print (render_tip (category = "commands" ))
2061+ console .print ()
2062+
2063+
2064+ def handle_tips (args : argparse .Namespace ) -> None :
2065+ """
2066+ Display random tips & tricks for the Swarms CLI.
2067+
2068+ Flags:
2069+ --count N Show N random tips (default: 1).
2070+ --category CAT Restrict to one category (e.g. agents, swarms, pro).
2071+ --all Show every tip in the (optionally filtered) pool.
2072+ """
2073+ from swarms .cli .tips import (
2074+ TIP_CATEGORIES ,
2075+ list_categories ,
2076+ render_random_tips ,
2077+ render_tip ,
2078+ )
2079+
2080+ category = getattr (args , "category" , None )
2081+ show_all = getattr (args , "all_tips" , False )
2082+ n = max (1 , getattr (args , "count" , 1 ) or 1 )
2083+
2084+ try :
2085+ if show_all :
2086+ categories = [category ] if category else list_categories ()
2087+ for cat in categories :
2088+ console .print (f"\n [bold red]── { cat } ──[/bold red]" )
2089+ for tip_body in TIP_CATEGORIES [cat ]:
2090+ console .print (render_tip (body = tip_body ))
2091+ return
2092+
2093+ for line in render_random_tips (n = n , category = category ):
2094+ console .print (line )
2095+ except ValueError as e :
2096+ show_error (
2097+ "Unknown tip category" ,
2098+ f"{ e } \n Run 'swarms tips' with no --category to see a random tip from any pool." ,
2099+ )
2100+
2101+
2102+ def handle_models (args : argparse .Namespace ) -> None :
2103+ """
2104+ Discover and inspect LLM models available via LiteLLM.
2105+
2106+ Flags:
2107+ --search PATTERN Fuzzy-match against the model list.
2108+ --info NAME Show full metadata for one model.
2109+ --provider NAME Filter the list to one provider.
2110+
2111+ With no flag, prints every model grouped by provider.
2112+ """
2113+ from swarms .cli .models import (
2114+ list_models ,
2115+ search_models ,
2116+ show_model_info ,
2117+ )
2118+
2119+ info_name = getattr (args , "info" , None )
2120+ search_pattern = getattr (args , "search" , None )
2121+ provider = getattr (args , "provider" , None )
2122+
2123+ if info_name :
2124+ show_model_info (console , info_name )
2125+ return
2126+ if search_pattern :
2127+ search_models (console , search_pattern )
2128+ return
2129+ list_models (console , provider = provider )
2130+
20012131
20022132def route_command (args : argparse .Namespace ) -> None :
20032133 """
@@ -2035,6 +2165,8 @@ def route_command(args: argparse.Namespace) -> None:
20352165 ),
20362166 "llm-council" : handle_llm_council ,
20372167 "heavy-swarm" : handle_heavy_swarm ,
2168+ "tips" : handle_tips ,
2169+ "models" : handle_models ,
20382170 }
20392171
20402172 handler = command_handlers .get (args .command )
@@ -2047,6 +2179,95 @@ def route_command(args: argparse.Namespace) -> None:
20472179 )
20482180
20492181
2182+ def _classify_command_error (message : str ) -> List [str ]:
2183+ """
2184+ Inspect an exception message and return a list of targeted recovery hints.
2185+
2186+ Matches are case-insensitive and additive: multiple hints can fire for a
2187+ single error (e.g. a missing-key error that also mentions a stale model).
2188+ """
2189+ err = message .lower ()
2190+ hints : List [str ] = []
2191+
2192+ if (
2193+ "401" in err
2194+ or "unauthorized" in err
2195+ or "authenticationerror" in err
2196+ or "invalid api key" in err
2197+ or "missing api key" in err
2198+ ):
2199+ hints .append (
2200+ "Authentication failed — set your provider key in [bold]swarms init[/bold] "
2201+ "or get one via [bold]swarms get-api-key[/bold]"
2202+ )
2203+
2204+ if (
2205+ "model not found" in err
2206+ or "model_not_found" in err
2207+ or ("model" in err and "does not exist" in err )
2208+ ):
2209+ hints .append (
2210+ "Find a valid model with [bold]swarms models --search <name>[/bold] "
2211+ "or list them all with [bold]swarms models[/bold]"
2212+ )
2213+
2214+ if "workspace_dir" in err or "workspace directory" in err :
2215+ hints .append (
2216+ "Workspace not configured — run [bold]swarms init[/bold] to scaffold one"
2217+ )
2218+
2219+ if "rate limit" in err or "ratelimit" in err or "429" in err :
2220+ hints .append (
2221+ "Rate-limited by provider — slow down, use a smaller model with "
2222+ "[bold]--model-name[/bold], or retry in a minute"
2223+ )
2224+
2225+ if (
2226+ "connection" in err
2227+ or "timeout" in err
2228+ or "timed out" in err
2229+ or "network" in err
2230+ ):
2231+ hints .append (
2232+ "Network issue — check connectivity, then run [bold]swarms setup-check --verbose[/bold]"
2233+ )
2234+
2235+ if "modulenotfounderror" in err or "no module named" in err :
2236+ hints .append (
2237+ "Missing dependency — try [bold]swarms upgrade[/bold] or "
2238+ "[bold]pip install -U swarms[/bold]"
2239+ )
2240+
2241+ return hints
2242+
2243+
2244+ def _show_command_error (error : Exception ) -> None :
2245+ """Render a command-execution error with classified recovery hints."""
2246+ hints = _classify_command_error (str (error ))
2247+
2248+ hint_block = ""
2249+ if hints :
2250+ hint_block = (
2251+ "[bold white]Suggested next steps:[/bold white]\n "
2252+ + "\n " .join (f" • { h } " for h in hints )
2253+ )
2254+ else :
2255+ hint_block = (
2256+ "[bold white]Troubleshooting tips:[/bold white]\n "
2257+ "- Double-check your arguments and the command structure\n "
2258+ "- Try [bold]swarms --help[/bold] for command details\n "
2259+ "- Run [bold]swarms setup-check --verbose[/bold] to validate your environment\n "
2260+ "- Report bugs at https://github.com/kyegomez/swarms/issues"
2261+ )
2262+
2263+ console .print (
2264+ f"\n [{ COLORS ['error' ]} ]Oops! An unexpected error occurred while running your command:[/{ COLORS ['error' ]} ]\n "
2265+ f"[bold]{ rich_escape (str (error ))} [/bold]\n \n "
2266+ f"{ hint_block } \n \n "
2267+ f"[dim]Traceback:[/dim]\n { rich_escape (traceback .format_exc ())} "
2268+ )
2269+
2270+
20502271def main () -> None :
20512272 """
20522273 Main entry point for the Swarms CLI.
@@ -2076,21 +2297,34 @@ def main() -> None:
20762297 try :
20772298 show_ascii_art ()
20782299
2300+ # Typo correction: catch mistyped command name before argparse
2301+ # rejects it with a generic "invalid choice" error.
2302+ if (
2303+ len (sys .argv ) > 1
2304+ and not sys .argv [1 ].startswith ("-" )
2305+ and sys .argv [1 ] not in COMMAND_CHOICES
2306+ ):
2307+ typed = sys .argv [1 ]
2308+ suggestion = _suggest_command (typed )
2309+ hint = (
2310+ f"\n Did you mean [bold white]swarms { suggestion } [/bold white]?"
2311+ if suggestion
2312+ else ""
2313+ )
2314+ show_error (
2315+ f"Unknown command '{ typed } '" ,
2316+ f"Available commands: { ', ' .join (COMMAND_CHOICES )} { hint } \n "
2317+ f"Run [bold white]swarms --help[/bold white] for full usage." ,
2318+ )
2319+ return
2320+
20792321 parser = setup_argument_parser ()
20802322 args = parser .parse_args ()
20812323
20822324 try :
20832325 route_command (args )
20842326 except Exception as e :
2085- console .print (
2086- f"\n [{ COLORS ['error' ]} ]Oops! An unexpected error occurred while running your command:[/{ COLORS ['error' ]} ]\n "
2087- f"[bold]{ rich_escape (str (e ))} [/bold]\n \n "
2088- "[bold white]Troubleshooting tips:[/bold white]\n "
2089- "- Double-check your arguments and the command structure\n "
2090- "- Try 'swarms help' for command details and examples\n "
2091- "- If the issue persists, please report it at https://github.com/OpenAgentsInc/swarms/issues\n \n "
2092- f"[dim]Traceback:[/dim]\n { rich_escape (traceback .format_exc ())} "
2093- )
2327+ _show_command_error (e )
20942328 return
20952329 except Exception as error :
20962330 formatter .print_panel (
0 commit comments