webscout 8.2.9__py3-none-any.whl → 2026.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -375
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -129
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -478
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
webscout/swiftcli/core/cli.py
CHANGED
|
@@ -1,297 +1,574 @@
|
|
|
1
|
-
"""Main CLI application class."""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from typing import Any, Dict, List, Optional,
|
|
5
|
-
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
|
|
8
|
-
from ..exceptions import UsageError
|
|
9
|
-
from ..plugins.manager import PluginManager
|
|
10
|
-
from ..utils.formatting import format_error
|
|
11
|
-
from .context import Context
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
...
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
self.
|
|
61
|
-
|
|
62
|
-
self.
|
|
63
|
-
|
|
64
|
-
self.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
#
|
|
186
|
-
if not
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
self.
|
|
193
|
-
return 0
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
1
|
+
"""Main CLI application class."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Dict, List, Optional, cast
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from ..exceptions import UsageError
|
|
9
|
+
from ..plugins.manager import PluginManager
|
|
10
|
+
from ..utils.formatting import format_error
|
|
11
|
+
from .context import Context
|
|
12
|
+
from .group import Group # Fix: Import Group for type checking and usage
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CLI:
|
|
18
|
+
"""
|
|
19
|
+
Main CLI application class.
|
|
20
|
+
|
|
21
|
+
The CLI class is the core of SwiftCLI. It handles command registration,
|
|
22
|
+
argument parsing, and command execution. It also manages plugins and
|
|
23
|
+
provides the main entry point for CLI applications.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
name: Application name
|
|
27
|
+
help: Application description
|
|
28
|
+
version: Application version
|
|
29
|
+
debug: Debug mode flag
|
|
30
|
+
commands: Registered commands
|
|
31
|
+
groups: Command groups
|
|
32
|
+
plugin_manager: Plugin manager instance
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
>>> app = CLI(name="myapp", version="1.0.0")
|
|
36
|
+
>>> @app.command()
|
|
37
|
+
... def greet(name: str):
|
|
38
|
+
... '''Greet someone'''
|
|
39
|
+
... print(f"Hello {name}!")
|
|
40
|
+
>>> app.run()
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
name: str,
|
|
46
|
+
help: Optional[str] = None,
|
|
47
|
+
version: Optional[str] = None,
|
|
48
|
+
debug: bool = False,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Initialize CLI application.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: Application name
|
|
55
|
+
help: Application description
|
|
56
|
+
version: Application version
|
|
57
|
+
debug: Enable debug mode
|
|
58
|
+
"""
|
|
59
|
+
self.name = name
|
|
60
|
+
self.help = help
|
|
61
|
+
self.version = version
|
|
62
|
+
self.debug = debug
|
|
63
|
+
|
|
64
|
+
self.commands: Dict[str, Dict[str, Any]] = {}
|
|
65
|
+
self.groups: Dict[str, "Group"] = {}
|
|
66
|
+
self.command_aliases: Dict[str, str] = {}
|
|
67
|
+
self.command_chain: bool = False
|
|
68
|
+
self.plugin_manager = PluginManager()
|
|
69
|
+
|
|
70
|
+
# Initialize plugin manager with this CLI instance
|
|
71
|
+
self.plugin_manager.init_plugins(self)
|
|
72
|
+
|
|
73
|
+
def command(
|
|
74
|
+
self,
|
|
75
|
+
name: Optional[str] = None,
|
|
76
|
+
help: Optional[str] = None,
|
|
77
|
+
aliases: Optional[List[str]] = None,
|
|
78
|
+
hidden: bool = False,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Decorator to register a command.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
name: Command name (defaults to function name)
|
|
85
|
+
help: Command help text
|
|
86
|
+
aliases: Alternative command names
|
|
87
|
+
hidden: Hide from help output
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
@app.command()
|
|
91
|
+
def hello(name: str):
|
|
92
|
+
'''Say hello'''
|
|
93
|
+
print(f"Hello {name}!")
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def decorator(f):
|
|
97
|
+
cmd_name = name or f.__name__
|
|
98
|
+
self.commands[cmd_name] = {
|
|
99
|
+
"name": cmd_name,
|
|
100
|
+
"func": f,
|
|
101
|
+
"help": help or f.__doc__,
|
|
102
|
+
"aliases": aliases or [],
|
|
103
|
+
"hidden": hidden,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Register aliases
|
|
107
|
+
for alias in aliases or []:
|
|
108
|
+
self.commands[alias] = self.commands[cmd_name]
|
|
109
|
+
|
|
110
|
+
return f
|
|
111
|
+
|
|
112
|
+
return decorator
|
|
113
|
+
|
|
114
|
+
def group(self, name: Optional[str] = None, help: Optional[str] = None, **kwargs):
|
|
115
|
+
"""
|
|
116
|
+
Create a command group.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
name: Group name
|
|
120
|
+
help: Group help text
|
|
121
|
+
**kwargs: Additional group options
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
@app.group()
|
|
125
|
+
def db():
|
|
126
|
+
'''Database commands'''
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@db.command()
|
|
130
|
+
def migrate():
|
|
131
|
+
'''Run migrations'''
|
|
132
|
+
pass
|
|
133
|
+
"""
|
|
134
|
+
# Group is now imported at the top
|
|
135
|
+
|
|
136
|
+
def decorator(f):
|
|
137
|
+
group_name = name or f.__name__
|
|
138
|
+
group = Group(name=group_name, help=help or f.__doc__, parent=self, **kwargs)
|
|
139
|
+
self.groups[group_name] = group
|
|
140
|
+
return group
|
|
141
|
+
|
|
142
|
+
return decorator
|
|
143
|
+
|
|
144
|
+
def alias(self, command_name: str, alias_name: str) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Add a command alias.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
command_name: Original command name
|
|
150
|
+
alias_name: Alias name
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
app.alias("list", "ls")
|
|
154
|
+
"""
|
|
155
|
+
if command_name in self.commands:
|
|
156
|
+
self.command_aliases[alias_name] = command_name
|
|
157
|
+
else:
|
|
158
|
+
raise UsageError(f"Command {command_name} not found")
|
|
159
|
+
|
|
160
|
+
def enable_chaining(self, enabled: bool = True) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Enable or disable command chaining.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
enabled: Whether to enable chaining
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
app.enable_chaining(True)
|
|
169
|
+
"""
|
|
170
|
+
self.command_chain = enabled
|
|
171
|
+
|
|
172
|
+
def run(self, args: Optional[List[str]] = None) -> int:
|
|
173
|
+
"""
|
|
174
|
+
Run the CLI application.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
args: Command line arguments (defaults to sys.argv[1:])
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Exit code (0 for success, non-zero for error)
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
args = args or sys.argv[1:]
|
|
184
|
+
|
|
185
|
+
# Show help if no arguments
|
|
186
|
+
if not args or args[0] in ["-h", "--help"]:
|
|
187
|
+
self._print_help()
|
|
188
|
+
return 0
|
|
189
|
+
|
|
190
|
+
# Show version if requested
|
|
191
|
+
if args[0] in ["-v", "--version"] and self.version:
|
|
192
|
+
console.print(self.version)
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
command_name = args[0]
|
|
196
|
+
command_args = args[1:]
|
|
197
|
+
|
|
198
|
+
# Check if it's a command alias
|
|
199
|
+
if command_name in self.command_aliases:
|
|
200
|
+
command_name = self.command_aliases[command_name]
|
|
201
|
+
|
|
202
|
+
# Check if it's a group command
|
|
203
|
+
if command_name in self.groups:
|
|
204
|
+
return self.groups[command_name].run(command_args)
|
|
205
|
+
|
|
206
|
+
# Check if it's a regular command
|
|
207
|
+
if command_name not in self.commands:
|
|
208
|
+
format_error(f"Unknown command: {command_name}")
|
|
209
|
+
self._print_help()
|
|
210
|
+
return 1
|
|
211
|
+
|
|
212
|
+
# Create command context
|
|
213
|
+
ctx = Context(self, command=command_name, debug=self.debug)
|
|
214
|
+
|
|
215
|
+
# Run command through plugin system
|
|
216
|
+
if not self.plugin_manager.before_command(command_name, command_args):
|
|
217
|
+
return 1
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
import asyncio
|
|
221
|
+
import inspect
|
|
222
|
+
|
|
223
|
+
command = self.commands[command_name]
|
|
224
|
+
func = command["func"]
|
|
225
|
+
params = self._parse_args(command, command_args)
|
|
226
|
+
|
|
227
|
+
# Inject context if function was decorated with pass_context
|
|
228
|
+
if getattr(func, "_pass_context", False):
|
|
229
|
+
# Call with ctx as first positional arg
|
|
230
|
+
call_args = (ctx,)
|
|
231
|
+
else:
|
|
232
|
+
call_args = ()
|
|
233
|
+
|
|
234
|
+
# If coroutine function, run it using asyncio
|
|
235
|
+
if inspect.iscoroutinefunction(func):
|
|
236
|
+
result = asyncio.run(func(*call_args, **params))
|
|
237
|
+
else:
|
|
238
|
+
result = func(*call_args, **params)
|
|
239
|
+
|
|
240
|
+
# If function returned a coroutine (in case wrapper returned coroutine)
|
|
241
|
+
if not inspect.iscoroutine(result) and hasattr(result, "__await__"):
|
|
242
|
+
# awaitable returned
|
|
243
|
+
result = asyncio.run(result)
|
|
244
|
+
|
|
245
|
+
self.plugin_manager.after_command(command_name, command_args, result)
|
|
246
|
+
return 0
|
|
247
|
+
except Exception as e:
|
|
248
|
+
self.plugin_manager.on_error(command_name, e)
|
|
249
|
+
if self.debug:
|
|
250
|
+
raise
|
|
251
|
+
format_error(str(e))
|
|
252
|
+
return 1
|
|
253
|
+
|
|
254
|
+
except KeyboardInterrupt:
|
|
255
|
+
console.print("\nOperation cancelled by user")
|
|
256
|
+
return 130
|
|
257
|
+
except Exception as e:
|
|
258
|
+
if self.debug:
|
|
259
|
+
raise
|
|
260
|
+
format_error(str(e))
|
|
261
|
+
return 1
|
|
262
|
+
|
|
263
|
+
def generate_completion_script(self, shell: str = "bash") -> str:
|
|
264
|
+
"""
|
|
265
|
+
Generate shell completion script.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
shell: Shell type (bash, zsh, fish)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Completion script as string
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
script = app.generate_completion_script('bash')
|
|
275
|
+
print(script)
|
|
276
|
+
"""
|
|
277
|
+
if shell == "bash":
|
|
278
|
+
return self._generate_bash_completion()
|
|
279
|
+
elif shell == "zsh":
|
|
280
|
+
return self._generate_zsh_completion()
|
|
281
|
+
elif shell == "fish":
|
|
282
|
+
return self._generate_fish_completion()
|
|
283
|
+
else:
|
|
284
|
+
raise UsageError(f"Unsupported shell: {shell}")
|
|
285
|
+
|
|
286
|
+
def _generate_bash_completion(self) -> str:
|
|
287
|
+
"""Generate bash completion script."""
|
|
288
|
+
commands = []
|
|
289
|
+
for cmd_name, cmd in self.commands.items():
|
|
290
|
+
if not cmd.get("hidden", False):
|
|
291
|
+
commands.append(cmd_name)
|
|
292
|
+
if "aliases" in cmd:
|
|
293
|
+
commands.extend(cmd["aliases"])
|
|
294
|
+
|
|
295
|
+
for group_name in self.groups:
|
|
296
|
+
commands.append(group_name)
|
|
297
|
+
|
|
298
|
+
script = f"""#!/bin/bash
|
|
299
|
+
# Bash completion for {self.name}
|
|
300
|
+
|
|
301
|
+
_{self.name}_completion() {{
|
|
302
|
+
local cur prev words cword
|
|
303
|
+
_init_completion || return
|
|
304
|
+
|
|
305
|
+
# Generate completion options
|
|
306
|
+
COMPREPLY=($(compgen -W "{" ".join(commands)}" -- "$cur"))
|
|
307
|
+
}}
|
|
308
|
+
|
|
309
|
+
complete -F _{self.name}_completion {self.name}
|
|
310
|
+
"""
|
|
311
|
+
return script
|
|
312
|
+
|
|
313
|
+
def _generate_zsh_completion(self) -> str:
|
|
314
|
+
"""Generate zsh completion script."""
|
|
315
|
+
commands = []
|
|
316
|
+
for cmd_name, cmd in self.commands.items():
|
|
317
|
+
if not cmd.get("hidden", False):
|
|
318
|
+
commands.append(cmd_name)
|
|
319
|
+
if "aliases" in cmd:
|
|
320
|
+
commands.extend(cmd["aliases"])
|
|
321
|
+
|
|
322
|
+
for group_name in self.groups:
|
|
323
|
+
commands.append(group_name)
|
|
324
|
+
|
|
325
|
+
script = f"""#compdef {self.name}
|
|
326
|
+
|
|
327
|
+
local -a commands
|
|
328
|
+
commands=({" ".join(commands)})
|
|
329
|
+
|
|
330
|
+
_arguments \
|
|
331
|
+
'1: :->cmds' \
|
|
332
|
+
'*::arg:->args'
|
|
333
|
+
|
|
334
|
+
case $state in
|
|
335
|
+
cmds)
|
|
336
|
+
_describe 'command' commands
|
|
337
|
+
;;
|
|
338
|
+
args)
|
|
339
|
+
# Add argument completion here if needed
|
|
340
|
+
;;
|
|
341
|
+
esac
|
|
342
|
+
"""
|
|
343
|
+
return script
|
|
344
|
+
|
|
345
|
+
def _generate_fish_completion(self) -> str:
|
|
346
|
+
"""Generate fish completion script."""
|
|
347
|
+
commands = []
|
|
348
|
+
for cmd_name, cmd in self.commands.items():
|
|
349
|
+
if not cmd.get("hidden", False):
|
|
350
|
+
commands.append(cmd_name)
|
|
351
|
+
if "aliases" in cmd:
|
|
352
|
+
commands.extend(cmd["aliases"])
|
|
353
|
+
|
|
354
|
+
for group_name in self.groups:
|
|
355
|
+
commands.append(group_name)
|
|
356
|
+
|
|
357
|
+
script = f"""# Fish completion for {self.name}
|
|
358
|
+
|
|
359
|
+
complete -c {self.name} -n "__fish_use_subcommand" -a "{" ".join(commands)}"
|
|
360
|
+
"""
|
|
361
|
+
return script
|
|
362
|
+
|
|
363
|
+
def _parse_args(self, command: Dict[str, Any], args: List[str]) -> Dict[str, Any]:
|
|
364
|
+
"""Parse command arguments."""
|
|
365
|
+
from ..utils.parsing import (
|
|
366
|
+
check_mutually_exclusive,
|
|
367
|
+
convert_type,
|
|
368
|
+
get_env_var,
|
|
369
|
+
parse_args,
|
|
370
|
+
validate_argument,
|
|
371
|
+
validate_choice,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
params = {}
|
|
375
|
+
func = command["func"]
|
|
376
|
+
|
|
377
|
+
# Collect mutually exclusive groups
|
|
378
|
+
exclusive_groups = []
|
|
379
|
+
|
|
380
|
+
# Parse command-line arguments
|
|
381
|
+
parsed_args = parse_args(args)
|
|
382
|
+
|
|
383
|
+
# Handle options
|
|
384
|
+
if hasattr(func, "_options"):
|
|
385
|
+
for opt in func._options:
|
|
386
|
+
# Use the longest parameter name (usually the --long-form) for the parameter name
|
|
387
|
+
param_names = [p.lstrip("-").replace("-", "_") for p in opt["param_decls"]]
|
|
388
|
+
name = cast(str, max(param_names, key=len)) # Use the longest name
|
|
389
|
+
|
|
390
|
+
# Check all possible parameter names in parsed args
|
|
391
|
+
value = None
|
|
392
|
+
found = False
|
|
393
|
+
for param_name in param_names:
|
|
394
|
+
if param_name in parsed_args:
|
|
395
|
+
value = parsed_args[param_name]
|
|
396
|
+
found = True
|
|
397
|
+
break
|
|
398
|
+
|
|
399
|
+
if found:
|
|
400
|
+
# Handle 'count' option (e.g., -v, -vv)
|
|
401
|
+
if opt.get("count", False):
|
|
402
|
+
if isinstance(value, list):
|
|
403
|
+
params[name] = len(value)
|
|
404
|
+
elif isinstance(value, bool):
|
|
405
|
+
params[name] = 1 if value else 0
|
|
406
|
+
elif value is not None:
|
|
407
|
+
try:
|
|
408
|
+
params[name] = int(value)
|
|
409
|
+
except Exception:
|
|
410
|
+
params[name] = 1
|
|
411
|
+
else:
|
|
412
|
+
params[name] = 1
|
|
413
|
+
# Handle 'multiple' option which collects multiple values
|
|
414
|
+
elif opt.get("multiple", False):
|
|
415
|
+
items = value if isinstance(value, list) else [value]
|
|
416
|
+
if "type" in opt:
|
|
417
|
+
items = [convert_type(str(v), opt["type"], name) for v in items if v is not None]
|
|
418
|
+
if "choices" in opt and opt["choices"]:
|
|
419
|
+
for v in items:
|
|
420
|
+
validate_choice(
|
|
421
|
+
str(v), opt["choices"], name, opt.get("case_sensitive", True)
|
|
422
|
+
)
|
|
423
|
+
params[name] = items
|
|
424
|
+
else:
|
|
425
|
+
# Normal option/flag handling
|
|
426
|
+
# If multiple values found but option is not 'multiple', take last
|
|
427
|
+
if isinstance(value, list):
|
|
428
|
+
value_to_convert = value[-1]
|
|
429
|
+
else:
|
|
430
|
+
value_to_convert = value
|
|
431
|
+
|
|
432
|
+
if opt.get("is_flag", False):
|
|
433
|
+
# Flags are boolean; if a string value is provided, attempt to convert
|
|
434
|
+
if isinstance(value_to_convert, str):
|
|
435
|
+
# If 'type' is provided and is bool, convert accordingly
|
|
436
|
+
if "type" in opt and opt["type"] is bool:
|
|
437
|
+
value_to_convert = convert_type(value_to_convert, bool, name)
|
|
438
|
+
else:
|
|
439
|
+
value_to_convert = True
|
|
440
|
+
elif isinstance(value_to_convert, bool):
|
|
441
|
+
# Use boolean value
|
|
442
|
+
value_to_convert = value_to_convert
|
|
443
|
+
elif value_to_convert is not None:
|
|
444
|
+
# Non-boolean provided, try convert to bool
|
|
445
|
+
value_to_convert = convert_type(str(value_to_convert), bool, name)
|
|
446
|
+
else:
|
|
447
|
+
value_to_convert = False
|
|
448
|
+
|
|
449
|
+
if "type" in opt and not opt.get("is_flag", False):
|
|
450
|
+
value_to_convert = convert_type(str(value_to_convert), opt["type"], name) if value_to_convert is not None else None
|
|
451
|
+
if "choices" in opt and opt["choices"]:
|
|
452
|
+
if value_to_convert is not None:
|
|
453
|
+
validate_choice(
|
|
454
|
+
str(value_to_convert),
|
|
455
|
+
opt["choices"],
|
|
456
|
+
name,
|
|
457
|
+
opt.get("case_sensitive", True),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Apply validation rules
|
|
461
|
+
if opt.get("validation") and value_to_convert is not None:
|
|
462
|
+
value_to_convert = validate_argument(
|
|
463
|
+
str(value_to_convert), opt["validation"], name
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
params[name] = value_to_convert
|
|
467
|
+
# Apply callback if provided
|
|
468
|
+
if opt.get("callback") and callable(opt.get("callback")):
|
|
469
|
+
params[name] = opt.get("callback")(params[name])
|
|
470
|
+
|
|
471
|
+
# Collect mutually exclusive groups
|
|
472
|
+
if opt.get("mutually_exclusive"):
|
|
473
|
+
exclusive_groups.append(opt["mutually_exclusive"])
|
|
474
|
+
elif opt.get("required", False):
|
|
475
|
+
raise UsageError(f"Missing required option: {name}")
|
|
476
|
+
elif "default" in opt:
|
|
477
|
+
params[name] = opt["default"]
|
|
478
|
+
|
|
479
|
+
# Handle arguments
|
|
480
|
+
if hasattr(func, "_arguments"):
|
|
481
|
+
for i, arg in enumerate(func._arguments):
|
|
482
|
+
name = arg["name"]
|
|
483
|
+
if f"arg{i}" in parsed_args:
|
|
484
|
+
value = parsed_args[f"arg{i}"]
|
|
485
|
+
if "type" in arg:
|
|
486
|
+
value = convert_type(value, arg["type"], name)
|
|
487
|
+
|
|
488
|
+
# Apply validation rules
|
|
489
|
+
if arg.get("validation"):
|
|
490
|
+
value = validate_argument(value, arg["validation"], name)
|
|
491
|
+
|
|
492
|
+
params[name] = value
|
|
493
|
+
|
|
494
|
+
# Collect mutually exclusive groups
|
|
495
|
+
if arg.get("mutually_exclusive"):
|
|
496
|
+
exclusive_groups.append(arg["mutually_exclusive"])
|
|
497
|
+
elif arg.get("required", True):
|
|
498
|
+
raise UsageError(f"Missing required argument: {name}")
|
|
499
|
+
elif "default" in arg:
|
|
500
|
+
params[name] = arg["default"]
|
|
501
|
+
|
|
502
|
+
# Check mutually exclusive options
|
|
503
|
+
if exclusive_groups:
|
|
504
|
+
check_mutually_exclusive(params, exclusive_groups)
|
|
505
|
+
|
|
506
|
+
# Handle environment variables
|
|
507
|
+
if hasattr(func, "_envvars"):
|
|
508
|
+
for env in func._envvars:
|
|
509
|
+
name = env["name"].lower()
|
|
510
|
+
value = get_env_var(
|
|
511
|
+
env["name"],
|
|
512
|
+
env.get("type", str),
|
|
513
|
+
env.get("required", False),
|
|
514
|
+
env.get("default"),
|
|
515
|
+
)
|
|
516
|
+
if value is not None:
|
|
517
|
+
params[name] = value
|
|
518
|
+
|
|
519
|
+
return params
|
|
520
|
+
|
|
521
|
+
def _print_help(self) -> None:
|
|
522
|
+
"""Print application help message."""
|
|
523
|
+
console.print(f"\n[bold]{self.name}[/]")
|
|
524
|
+
if self.help:
|
|
525
|
+
console.print(f"\n{self.help}")
|
|
526
|
+
|
|
527
|
+
# Show commands
|
|
528
|
+
console.print("\n[bold]Commands:[/]")
|
|
529
|
+
printed = set()
|
|
530
|
+
for name, cmd in self.commands.items():
|
|
531
|
+
primary = cmd.get("name", name)
|
|
532
|
+
if primary in printed:
|
|
533
|
+
continue
|
|
534
|
+
printed.add(primary)
|
|
535
|
+
if not cmd.get("hidden", False):
|
|
536
|
+
aliases = cmd.get("aliases", [])
|
|
537
|
+
alias_text = f" (aliases: {', '.join(aliases)})" if aliases else ""
|
|
538
|
+
console.print(f" {primary:20} {cmd['help'] or ''}{alias_text}")
|
|
539
|
+
|
|
540
|
+
# Show command groups
|
|
541
|
+
for name, group in self.groups.items():
|
|
542
|
+
console.print(f"\n[bold]{name} commands:[/]")
|
|
543
|
+
printed_group = set()
|
|
544
|
+
for cmd_name, cmd in group.commands.items():
|
|
545
|
+
# cmd can be Group or dict
|
|
546
|
+
if isinstance(cmd, dict):
|
|
547
|
+
primary = cmd.get("name", cmd_name)
|
|
548
|
+
elif hasattr(cmd, "name"):
|
|
549
|
+
primary = cmd.name
|
|
550
|
+
else:
|
|
551
|
+
primary = cmd_name
|
|
552
|
+
if primary in printed_group:
|
|
553
|
+
continue
|
|
554
|
+
printed_group.add(primary)
|
|
555
|
+
# Get help and hidden
|
|
556
|
+
help_text = ""
|
|
557
|
+
hidden = False
|
|
558
|
+
aliases = []
|
|
559
|
+
if isinstance(cmd, dict):
|
|
560
|
+
hidden = cmd.get("hidden", False)
|
|
561
|
+
help_text = cmd.get("help", "")
|
|
562
|
+
aliases = cmd.get("aliases", [])
|
|
563
|
+
elif isinstance(cmd, Group):
|
|
564
|
+
help_text = cmd.help or ""
|
|
565
|
+
if not hidden:
|
|
566
|
+
alias_text = f" (aliases: {', '.join(aliases)})" if aliases else ""
|
|
567
|
+
console.print(f" {primary:20} {help_text}{alias_text}")
|
|
568
|
+
|
|
569
|
+
console.print("\nUse -h or --help with any command for more info")
|
|
570
|
+
if self.version:
|
|
571
|
+
console.print("Use -v or --version to show version")
|
|
572
|
+
|
|
573
|
+
def __repr__(self) -> str:
|
|
574
|
+
return f"<CLI name={self.name}>"
|