webscout 8.2.7__py3-none-any.whl → 8.2.9__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 +33 -15
- webscout/AIbase.py +96 -37
- webscout/AIutel.py +703 -250
- webscout/Bard.py +441 -323
- webscout/Extra/Act.md +309 -0
- webscout/Extra/GitToolkit/__init__.py +10 -0
- webscout/Extra/GitToolkit/gitapi/README.md +110 -0
- webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
- webscout/Extra/GitToolkit/gitapi/user.py +96 -0
- webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
- webscout/Extra/YTToolkit/README.md +375 -0
- webscout/Extra/YTToolkit/YTdownloader.py +957 -0
- webscout/Extra/YTToolkit/__init__.py +3 -0
- webscout/Extra/YTToolkit/transcriber.py +476 -0
- webscout/Extra/YTToolkit/ytapi/README.md +44 -0
- webscout/Extra/YTToolkit/ytapi/__init__.py +6 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -0
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -0
- webscout/Extra/YTToolkit/ytapi/extras.py +118 -0
- webscout/Extra/YTToolkit/ytapi/https.py +88 -0
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -0
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -0
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -0
- webscout/Extra/YTToolkit/ytapi/query.py +40 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +63 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -0
- webscout/Extra/YTToolkit/ytapi/video.py +232 -0
- webscout/Extra/__init__.py +7 -0
- webscout/Extra/autocoder/__init__.py +9 -0
- webscout/Extra/autocoder/autocoder.py +1105 -0
- webscout/Extra/autocoder/autocoder_utiles.py +332 -0
- webscout/Extra/gguf.md +430 -0
- webscout/Extra/gguf.py +684 -0
- webscout/Extra/tempmail/README.md +488 -0
- webscout/Extra/tempmail/__init__.py +28 -0
- webscout/Extra/tempmail/async_utils.py +141 -0
- webscout/Extra/tempmail/base.py +161 -0
- webscout/Extra/tempmail/cli.py +187 -0
- webscout/Extra/tempmail/emailnator.py +84 -0
- webscout/Extra/tempmail/mail_tm.py +361 -0
- webscout/Extra/tempmail/temp_mail_io.py +292 -0
- webscout/Extra/weather.md +281 -0
- webscout/Extra/weather.py +194 -0
- webscout/Extra/weather_ascii.py +76 -0
- webscout/Litlogger/README.md +10 -0
- webscout/Litlogger/__init__.py +15 -0
- webscout/Litlogger/formats.py +4 -0
- webscout/Litlogger/handlers.py +103 -0
- webscout/Litlogger/levels.py +13 -0
- webscout/Litlogger/logger.py +92 -0
- webscout/Provider/AI21.py +177 -0
- webscout/Provider/AISEARCH/DeepFind.py +254 -0
- webscout/Provider/AISEARCH/Perplexity.py +333 -0
- webscout/Provider/AISEARCH/README.md +279 -0
- webscout/Provider/AISEARCH/__init__.py +9 -0
- webscout/Provider/AISEARCH/felo_search.py +202 -0
- webscout/Provider/AISEARCH/genspark_search.py +324 -0
- webscout/Provider/AISEARCH/hika_search.py +186 -0
- webscout/Provider/AISEARCH/iask_search.py +410 -0
- webscout/Provider/AISEARCH/monica_search.py +220 -0
- webscout/Provider/AISEARCH/scira_search.py +298 -0
- webscout/Provider/AISEARCH/webpilotai_search.py +255 -0
- webscout/Provider/Aitopia.py +316 -0
- webscout/Provider/AllenAI.py +440 -0
- webscout/Provider/Andi.py +228 -0
- webscout/Provider/Blackboxai.py +791 -0
- webscout/Provider/ChatGPTClone.py +237 -0
- webscout/Provider/ChatGPTGratis.py +194 -0
- webscout/Provider/ChatSandbox.py +342 -0
- webscout/Provider/Cloudflare.py +324 -0
- webscout/Provider/Cohere.py +208 -0
- webscout/Provider/Deepinfra.py +340 -0
- webscout/Provider/ExaAI.py +261 -0
- webscout/Provider/ExaChat.py +358 -0
- webscout/Provider/Flowith.py +217 -0
- webscout/Provider/FreeGemini.py +250 -0
- webscout/Provider/Gemini.py +169 -0
- webscout/Provider/GithubChat.py +369 -0
- webscout/Provider/GizAI.py +295 -0
- webscout/Provider/Glider.py +225 -0
- webscout/Provider/Groq.py +801 -0
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +206 -0
- webscout/Provider/HeckAI.py +375 -0
- webscout/Provider/HuggingFaceChat.py +469 -0
- webscout/Provider/Hunyuan.py +283 -0
- webscout/Provider/Jadve.py +291 -0
- webscout/Provider/Koboldai.py +384 -0
- webscout/Provider/LambdaChat.py +411 -0
- webscout/Provider/Llama3.py +259 -0
- webscout/Provider/MCPCore.py +315 -0
- webscout/Provider/Marcus.py +198 -0
- webscout/Provider/Nemotron.py +218 -0
- webscout/Provider/Netwrck.py +270 -0
- webscout/Provider/OLLAMA.py +396 -0
- webscout/Provider/OPENAI/BLACKBOXAI.py +766 -0
- webscout/Provider/OPENAI/Cloudflare.py +378 -0
- webscout/Provider/OPENAI/FreeGemini.py +283 -0
- webscout/Provider/OPENAI/NEMOTRON.py +232 -0
- webscout/Provider/OPENAI/Qwen3.py +283 -0
- webscout/Provider/OPENAI/README.md +952 -0
- webscout/Provider/OPENAI/TwoAI.py +357 -0
- webscout/Provider/OPENAI/__init__.py +40 -0
- webscout/Provider/OPENAI/ai4chat.py +293 -0
- webscout/Provider/OPENAI/api.py +969 -0
- webscout/Provider/OPENAI/base.py +249 -0
- webscout/Provider/OPENAI/c4ai.py +373 -0
- webscout/Provider/OPENAI/chatgpt.py +556 -0
- webscout/Provider/OPENAI/chatgptclone.py +494 -0
- webscout/Provider/OPENAI/chatsandbox.py +173 -0
- webscout/Provider/OPENAI/copilot.py +242 -0
- webscout/Provider/OPENAI/deepinfra.py +322 -0
- webscout/Provider/OPENAI/e2b.py +1414 -0
- webscout/Provider/OPENAI/exaai.py +417 -0
- webscout/Provider/OPENAI/exachat.py +444 -0
- webscout/Provider/OPENAI/flowith.py +162 -0
- webscout/Provider/OPENAI/freeaichat.py +359 -0
- webscout/Provider/OPENAI/glider.py +326 -0
- webscout/Provider/OPENAI/groq.py +364 -0
- webscout/Provider/OPENAI/heckai.py +308 -0
- webscout/Provider/OPENAI/llmchatco.py +335 -0
- webscout/Provider/OPENAI/mcpcore.py +389 -0
- webscout/Provider/OPENAI/multichat.py +376 -0
- webscout/Provider/OPENAI/netwrck.py +357 -0
- webscout/Provider/OPENAI/oivscode.py +287 -0
- webscout/Provider/OPENAI/opkfc.py +496 -0
- webscout/Provider/OPENAI/pydantic_imports.py +172 -0
- webscout/Provider/OPENAI/scirachat.py +477 -0
- webscout/Provider/OPENAI/sonus.py +304 -0
- webscout/Provider/OPENAI/standardinput.py +433 -0
- webscout/Provider/OPENAI/textpollinations.py +339 -0
- webscout/Provider/OPENAI/toolbaz.py +413 -0
- webscout/Provider/OPENAI/typefully.py +355 -0
- webscout/Provider/OPENAI/typegpt.py +364 -0
- webscout/Provider/OPENAI/uncovrAI.py +463 -0
- webscout/Provider/OPENAI/utils.py +318 -0
- webscout/Provider/OPENAI/venice.py +431 -0
- webscout/Provider/OPENAI/wisecat.py +387 -0
- webscout/Provider/OPENAI/writecream.py +163 -0
- webscout/Provider/OPENAI/x0gpt.py +365 -0
- webscout/Provider/OPENAI/yep.py +382 -0
- webscout/Provider/OpenGPT.py +209 -0
- webscout/Provider/Openai.py +496 -0
- webscout/Provider/PI.py +429 -0
- webscout/Provider/Perplexitylabs.py +415 -0
- webscout/Provider/QwenLM.py +254 -0
- webscout/Provider/Reka.py +214 -0
- webscout/Provider/StandardInput.py +290 -0
- webscout/Provider/TTI/README.md +82 -0
- webscout/Provider/TTI/__init__.py +7 -0
- webscout/Provider/TTI/aiarta.py +365 -0
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/base.py +64 -0
- webscout/Provider/TTI/fastflux.py +200 -0
- webscout/Provider/TTI/magicstudio.py +201 -0
- webscout/Provider/TTI/piclumen.py +203 -0
- webscout/Provider/TTI/pixelmuse.py +225 -0
- webscout/Provider/TTI/pollinations.py +221 -0
- webscout/Provider/TTI/utils.py +11 -0
- webscout/Provider/TTS/README.md +192 -0
- webscout/Provider/TTS/__init__.py +10 -0
- webscout/Provider/TTS/base.py +159 -0
- webscout/Provider/TTS/deepgram.py +156 -0
- webscout/Provider/TTS/elevenlabs.py +111 -0
- webscout/Provider/TTS/gesserit.py +128 -0
- webscout/Provider/TTS/murfai.py +113 -0
- webscout/Provider/TTS/openai_fm.py +129 -0
- webscout/Provider/TTS/parler.py +111 -0
- webscout/Provider/TTS/speechma.py +580 -0
- webscout/Provider/TTS/sthir.py +94 -0
- webscout/Provider/TTS/streamElements.py +333 -0
- webscout/Provider/TTS/utils.py +280 -0
- webscout/Provider/TeachAnything.py +229 -0
- webscout/Provider/TextPollinationsAI.py +308 -0
- webscout/Provider/TwoAI.py +475 -0
- webscout/Provider/TypliAI.py +305 -0
- webscout/Provider/UNFINISHED/ChatHub.py +209 -0
- webscout/Provider/UNFINISHED/Youchat.py +330 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
- webscout/Provider/UNFINISHED/puterjs.py +635 -0
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
- webscout/Provider/Venice.py +258 -0
- webscout/Provider/VercelAI.py +253 -0
- webscout/Provider/WiseCat.py +233 -0
- webscout/Provider/WrDoChat.py +370 -0
- webscout/Provider/Writecream.py +246 -0
- webscout/Provider/WritingMate.py +269 -0
- webscout/Provider/__init__.py +174 -0
- webscout/Provider/ai4chat.py +174 -0
- webscout/Provider/akashgpt.py +335 -0
- webscout/Provider/asksteve.py +220 -0
- webscout/Provider/cerebras.py +290 -0
- webscout/Provider/chatglm.py +215 -0
- webscout/Provider/cleeai.py +213 -0
- webscout/Provider/copilot.py +425 -0
- webscout/Provider/elmo.py +283 -0
- webscout/Provider/freeaichat.py +285 -0
- webscout/Provider/geminiapi.py +208 -0
- webscout/Provider/granite.py +235 -0
- webscout/Provider/hermes.py +266 -0
- webscout/Provider/julius.py +223 -0
- webscout/Provider/koala.py +170 -0
- webscout/Provider/learnfastai.py +325 -0
- webscout/Provider/llama3mitril.py +215 -0
- webscout/Provider/llmchat.py +258 -0
- webscout/Provider/llmchatco.py +306 -0
- webscout/Provider/lmarena.py +198 -0
- webscout/Provider/meta.py +801 -0
- webscout/Provider/multichat.py +364 -0
- webscout/Provider/oivscode.py +309 -0
- webscout/Provider/samurai.py +224 -0
- webscout/Provider/scira_chat.py +299 -0
- webscout/Provider/scnet.py +243 -0
- webscout/Provider/searchchat.py +292 -0
- webscout/Provider/sonus.py +258 -0
- webscout/Provider/talkai.py +194 -0
- webscout/Provider/toolbaz.py +353 -0
- webscout/Provider/turboseek.py +266 -0
- webscout/Provider/typefully.py +202 -0
- webscout/Provider/typegpt.py +289 -0
- webscout/Provider/uncovr.py +368 -0
- webscout/Provider/x0gpt.py +299 -0
- webscout/Provider/yep.py +389 -0
- webscout/__init__.py +4 -2
- webscout/cli.py +3 -28
- webscout/client.py +70 -0
- webscout/conversation.py +35 -35
- webscout/litagent/Readme.md +276 -0
- webscout/litagent/__init__.py +29 -0
- webscout/litagent/agent.py +455 -0
- webscout/litagent/constants.py +60 -0
- webscout/litprinter/__init__.py +59 -0
- webscout/optimizers.py +419 -419
- webscout/scout/README.md +404 -0
- webscout/scout/__init__.py +8 -0
- webscout/scout/core/__init__.py +7 -0
- webscout/scout/core/crawler.py +210 -0
- webscout/scout/core/scout.py +607 -0
- webscout/scout/core/search_result.py +96 -0
- webscout/scout/core/text_analyzer.py +63 -0
- webscout/scout/core/text_utils.py +277 -0
- webscout/scout/core/web_analyzer.py +52 -0
- webscout/scout/element.py +478 -0
- webscout/scout/parsers/__init__.py +69 -0
- webscout/scout/parsers/html5lib_parser.py +172 -0
- webscout/scout/parsers/html_parser.py +236 -0
- webscout/scout/parsers/lxml_parser.py +178 -0
- webscout/scout/utils.py +37 -0
- webscout/swiftcli/Readme.md +323 -0
- webscout/swiftcli/__init__.py +95 -0
- webscout/swiftcli/core/__init__.py +7 -0
- webscout/swiftcli/core/cli.py +297 -0
- webscout/swiftcli/core/context.py +104 -0
- webscout/swiftcli/core/group.py +241 -0
- webscout/swiftcli/decorators/__init__.py +28 -0
- webscout/swiftcli/decorators/command.py +221 -0
- webscout/swiftcli/decorators/options.py +220 -0
- webscout/swiftcli/decorators/output.py +252 -0
- webscout/swiftcli/exceptions.py +21 -0
- webscout/swiftcli/plugins/__init__.py +9 -0
- webscout/swiftcli/plugins/base.py +135 -0
- webscout/swiftcli/plugins/manager.py +269 -0
- webscout/swiftcli/utils/__init__.py +59 -0
- webscout/swiftcli/utils/formatting.py +252 -0
- webscout/swiftcli/utils/parsing.py +267 -0
- webscout/version.py +1 -1
- webscout/webscout_search.py +2 -182
- webscout/webscout_search_async.py +1 -179
- webscout/zeroart/README.md +89 -0
- webscout/zeroart/__init__.py +135 -0
- webscout/zeroart/base.py +66 -0
- webscout/zeroart/effects.py +101 -0
- webscout/zeroart/fonts.py +1239 -0
- {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/METADATA +262 -83
- webscout-8.2.9.dist-info/RECORD +289 -0
- {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
- {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
- webscout-8.2.7.dist-info/RECORD +0 -26
- {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Main CLI application class."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
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, format_success
|
|
11
|
+
from .context import Context
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
class CLI:
|
|
16
|
+
"""
|
|
17
|
+
Main CLI application class.
|
|
18
|
+
|
|
19
|
+
The CLI class is the core of SwiftCLI. It handles command registration,
|
|
20
|
+
argument parsing, and command execution. It also manages plugins and
|
|
21
|
+
provides the main entry point for CLI applications.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name: Application name
|
|
25
|
+
help: Application description
|
|
26
|
+
version: Application version
|
|
27
|
+
debug: Debug mode flag
|
|
28
|
+
commands: Registered commands
|
|
29
|
+
groups: Command groups
|
|
30
|
+
plugin_manager: Plugin manager instance
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> app = CLI(name="myapp", version="1.0.0")
|
|
34
|
+
>>> @app.command()
|
|
35
|
+
... def greet(name: str):
|
|
36
|
+
... '''Greet someone'''
|
|
37
|
+
... print(f"Hello {name}!")
|
|
38
|
+
>>> app.run()
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
name: str,
|
|
44
|
+
help: Optional[str] = None,
|
|
45
|
+
version: Optional[str] = None,
|
|
46
|
+
debug: bool = False
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Initialize CLI application.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name: Application name
|
|
53
|
+
help: Application description
|
|
54
|
+
version: Application version
|
|
55
|
+
debug: Enable debug mode
|
|
56
|
+
"""
|
|
57
|
+
self.name = name
|
|
58
|
+
self.help = help
|
|
59
|
+
self.version = version
|
|
60
|
+
self.debug = debug
|
|
61
|
+
|
|
62
|
+
self.commands: Dict[str, Dict[str, Any]] = {}
|
|
63
|
+
self.groups: Dict[str, 'Group'] = {} # type: ignore
|
|
64
|
+
self.plugin_manager = PluginManager()
|
|
65
|
+
|
|
66
|
+
# Initialize plugin manager with this CLI instance
|
|
67
|
+
self.plugin_manager.init_plugins(self)
|
|
68
|
+
|
|
69
|
+
def command(
|
|
70
|
+
self,
|
|
71
|
+
name: Optional[str] = None,
|
|
72
|
+
help: Optional[str] = None,
|
|
73
|
+
aliases: Optional[List[str]] = None,
|
|
74
|
+
hidden: bool = False
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
Decorator to register a command.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name: Command name (defaults to function name)
|
|
81
|
+
help: Command help text
|
|
82
|
+
aliases: Alternative command names
|
|
83
|
+
hidden: Hide from help output
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
@app.command()
|
|
87
|
+
def hello(name: str):
|
|
88
|
+
'''Say hello'''
|
|
89
|
+
print(f"Hello {name}!")
|
|
90
|
+
"""
|
|
91
|
+
def decorator(f):
|
|
92
|
+
cmd_name = name or f.__name__
|
|
93
|
+
self.commands[cmd_name] = {
|
|
94
|
+
'func': f,
|
|
95
|
+
'help': help or f.__doc__,
|
|
96
|
+
'aliases': aliases or [],
|
|
97
|
+
'hidden': hidden
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Register aliases
|
|
101
|
+
for alias in (aliases or []):
|
|
102
|
+
self.commands[alias] = self.commands[cmd_name]
|
|
103
|
+
|
|
104
|
+
return f
|
|
105
|
+
return decorator
|
|
106
|
+
|
|
107
|
+
def group(
|
|
108
|
+
self,
|
|
109
|
+
name: Optional[str] = None,
|
|
110
|
+
help: Optional[str] = None,
|
|
111
|
+
**kwargs
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
Create a command group.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: Group name
|
|
118
|
+
help: Group help text
|
|
119
|
+
**kwargs: Additional group options
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
@app.group()
|
|
123
|
+
def db():
|
|
124
|
+
'''Database commands'''
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@db.command()
|
|
128
|
+
def migrate():
|
|
129
|
+
'''Run migrations'''
|
|
130
|
+
pass
|
|
131
|
+
"""
|
|
132
|
+
from .group import Group # Import here to avoid circular dependency
|
|
133
|
+
|
|
134
|
+
def decorator(f):
|
|
135
|
+
group_name = name or f.__name__
|
|
136
|
+
group = Group(
|
|
137
|
+
name=group_name,
|
|
138
|
+
help=help or f.__doc__,
|
|
139
|
+
parent=self,
|
|
140
|
+
**kwargs
|
|
141
|
+
)
|
|
142
|
+
self.groups[group_name] = group
|
|
143
|
+
return group
|
|
144
|
+
return decorator
|
|
145
|
+
|
|
146
|
+
def run(self, args: Optional[List[str]] = None) -> int:
|
|
147
|
+
"""
|
|
148
|
+
Run the CLI application.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
args: Command line arguments (defaults to sys.argv[1:])
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Exit code (0 for success, non-zero for error)
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
args = args or sys.argv[1:]
|
|
158
|
+
|
|
159
|
+
# Show help if no arguments
|
|
160
|
+
if not args or args[0] in ['-h', '--help']:
|
|
161
|
+
self._print_help()
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
# Show version if requested
|
|
165
|
+
if args[0] in ['-v', '--version'] and self.version:
|
|
166
|
+
console.print(self.version)
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
command_name = args[0]
|
|
170
|
+
command_args = args[1:]
|
|
171
|
+
|
|
172
|
+
# Check if it's a group command
|
|
173
|
+
if command_name in self.groups:
|
|
174
|
+
return self.groups[command_name].run(command_args)
|
|
175
|
+
|
|
176
|
+
# Check if it's a regular command
|
|
177
|
+
if command_name not in self.commands:
|
|
178
|
+
format_error(f"Unknown command: {command_name}")
|
|
179
|
+
self._print_help()
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
# Create command context
|
|
183
|
+
ctx = Context(self, command=command_name, debug=self.debug)
|
|
184
|
+
|
|
185
|
+
# Run command through plugin system
|
|
186
|
+
if not self.plugin_manager.before_command(command_name, command_args):
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
command = self.commands[command_name]
|
|
191
|
+
result = command['func'](**self._parse_args(command, command_args))
|
|
192
|
+
self.plugin_manager.after_command(command_name, command_args, result)
|
|
193
|
+
return 0
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self.plugin_manager.on_error(command_name, e)
|
|
196
|
+
if self.debug:
|
|
197
|
+
raise
|
|
198
|
+
format_error(str(e))
|
|
199
|
+
return 1
|
|
200
|
+
|
|
201
|
+
except KeyboardInterrupt:
|
|
202
|
+
console.print("\nOperation cancelled by user")
|
|
203
|
+
return 130
|
|
204
|
+
except Exception as e:
|
|
205
|
+
if self.debug:
|
|
206
|
+
raise
|
|
207
|
+
format_error(str(e))
|
|
208
|
+
return 1
|
|
209
|
+
|
|
210
|
+
def _parse_args(self, command: Dict[str, Any], args: List[str]) -> Dict[str, Any]:
|
|
211
|
+
"""Parse command arguments."""
|
|
212
|
+
from ..utils.parsing import (
|
|
213
|
+
parse_args, validate_required, convert_type,
|
|
214
|
+
validate_choice, get_env_var
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
params = {}
|
|
218
|
+
func = command['func']
|
|
219
|
+
|
|
220
|
+
# Parse command-line arguments
|
|
221
|
+
parsed_args = parse_args(args)
|
|
222
|
+
|
|
223
|
+
# Handle options
|
|
224
|
+
if hasattr(func, '_options'):
|
|
225
|
+
for opt in func._options:
|
|
226
|
+
name = opt['param_decls'][0].lstrip('-').replace('-', '_')
|
|
227
|
+
if name in parsed_args:
|
|
228
|
+
value = parsed_args[name]
|
|
229
|
+
if 'type' in opt:
|
|
230
|
+
value = convert_type(value, opt['type'], name)
|
|
231
|
+
if 'choices' in opt and opt['choices']:
|
|
232
|
+
validate_choice(
|
|
233
|
+
value,
|
|
234
|
+
opt['choices'],
|
|
235
|
+
name,
|
|
236
|
+
opt.get('case_sensitive', True)
|
|
237
|
+
)
|
|
238
|
+
params[name] = value
|
|
239
|
+
elif opt.get('required', False):
|
|
240
|
+
raise UsageError(f"Missing required option: {name}")
|
|
241
|
+
elif 'default' in opt:
|
|
242
|
+
params[name] = opt['default']
|
|
243
|
+
|
|
244
|
+
# Handle arguments
|
|
245
|
+
if hasattr(func, '_arguments'):
|
|
246
|
+
for i, arg in enumerate(func._arguments):
|
|
247
|
+
name = arg['name']
|
|
248
|
+
if f'arg{i}' in parsed_args:
|
|
249
|
+
value = parsed_args[f'arg{i}']
|
|
250
|
+
if 'type' in arg:
|
|
251
|
+
value = convert_type(value, arg['type'], name)
|
|
252
|
+
params[name] = value
|
|
253
|
+
elif arg.get('required', True):
|
|
254
|
+
raise UsageError(f"Missing required argument: {name}")
|
|
255
|
+
elif 'default' in arg:
|
|
256
|
+
params[name] = arg['default']
|
|
257
|
+
|
|
258
|
+
# Handle environment variables
|
|
259
|
+
if hasattr(func, '_envvars'):
|
|
260
|
+
for env in func._envvars:
|
|
261
|
+
name = env['name'].lower()
|
|
262
|
+
value = get_env_var(
|
|
263
|
+
env['name'],
|
|
264
|
+
env.get('type', str),
|
|
265
|
+
env.get('required', False),
|
|
266
|
+
env.get('default')
|
|
267
|
+
)
|
|
268
|
+
if value is not None:
|
|
269
|
+
params[name] = value
|
|
270
|
+
|
|
271
|
+
return params
|
|
272
|
+
|
|
273
|
+
def _print_help(self) -> None:
|
|
274
|
+
"""Print application help message."""
|
|
275
|
+
console.print(f"\n[bold]{self.name}[/]")
|
|
276
|
+
if self.help:
|
|
277
|
+
console.print(f"\n{self.help}")
|
|
278
|
+
|
|
279
|
+
# Show commands
|
|
280
|
+
console.print("\n[bold]Commands:[/]")
|
|
281
|
+
for name, cmd in self.commands.items():
|
|
282
|
+
if not cmd.get('hidden', False):
|
|
283
|
+
console.print(f" {name:20} {cmd['help'] or ''}")
|
|
284
|
+
|
|
285
|
+
# Show command groups
|
|
286
|
+
for name, group in self.groups.items():
|
|
287
|
+
console.print(f"\n[bold]{name} commands:[/]")
|
|
288
|
+
for cmd_name, cmd in group.commands.items():
|
|
289
|
+
if not cmd.get('hidden', False):
|
|
290
|
+
console.print(f" {cmd_name:20} {cmd['help'] or ''}")
|
|
291
|
+
|
|
292
|
+
console.print("\nUse -h or --help with any command for more info")
|
|
293
|
+
if self.version:
|
|
294
|
+
console.print("Use -v or --version to show version")
|
|
295
|
+
|
|
296
|
+
def __repr__(self) -> str:
|
|
297
|
+
return f"<CLI name={self.name}>"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Context handling for SwiftCLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
class Context:
|
|
6
|
+
"""
|
|
7
|
+
Context object that holds state for the CLI app.
|
|
8
|
+
|
|
9
|
+
The Context class provides access to the CLI application instance and maintains
|
|
10
|
+
state throughout command execution. It can be used to pass data between
|
|
11
|
+
commands and access global configuration.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
cli: The CLI application instance.
|
|
15
|
+
parent: The parent context if this is a subcommand.
|
|
16
|
+
command: The current command name.
|
|
17
|
+
obj: An object that can be used to store arbitrary data.
|
|
18
|
+
params: Dictionary of current command parameters.
|
|
19
|
+
debug: Debug mode flag.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
@app.command()
|
|
23
|
+
@pass_context
|
|
24
|
+
def status(ctx):
|
|
25
|
+
'''Show application status'''
|
|
26
|
+
print(f"App: {ctx.cli.name}")
|
|
27
|
+
print(f"Debug: {ctx.debug}")
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
cli: 'CLI', # type: ignore
|
|
33
|
+
parent: Optional['Context'] = None,
|
|
34
|
+
command: Optional[str] = None,
|
|
35
|
+
obj: Any = None,
|
|
36
|
+
debug: bool = False
|
|
37
|
+
):
|
|
38
|
+
self.cli = cli
|
|
39
|
+
self.parent = parent
|
|
40
|
+
self.command = command
|
|
41
|
+
self.obj = obj
|
|
42
|
+
self.params: Dict[str, Any] = {}
|
|
43
|
+
self.debug = debug
|
|
44
|
+
|
|
45
|
+
def get_parameter(self, name: str, default: Any = None) -> Any:
|
|
46
|
+
"""
|
|
47
|
+
Get a parameter value from the context.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: Parameter name
|
|
51
|
+
default: Default value if parameter not found
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Parameter value or default
|
|
55
|
+
"""
|
|
56
|
+
return self.params.get(name, default)
|
|
57
|
+
|
|
58
|
+
def set_parameter(self, name: str, value: Any) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Set a parameter value in the context.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Parameter name
|
|
64
|
+
value: Parameter value
|
|
65
|
+
"""
|
|
66
|
+
self.params[name] = value
|
|
67
|
+
|
|
68
|
+
def get_parent_context(self) -> Optional['Context']:
|
|
69
|
+
"""Get the parent context if it exists."""
|
|
70
|
+
return self.parent
|
|
71
|
+
|
|
72
|
+
def create_child_context(
|
|
73
|
+
self,
|
|
74
|
+
command: Optional[str] = None,
|
|
75
|
+
obj: Any = None
|
|
76
|
+
) -> 'Context':
|
|
77
|
+
"""
|
|
78
|
+
Create a new child context.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
command: Command name for the child context
|
|
82
|
+
obj: Object to store in child context
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
New child context
|
|
86
|
+
"""
|
|
87
|
+
return Context(
|
|
88
|
+
cli=self.cli,
|
|
89
|
+
parent=self,
|
|
90
|
+
command=command,
|
|
91
|
+
obj=obj,
|
|
92
|
+
debug=self.debug
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def root_context(self) -> 'Context':
|
|
97
|
+
"""Get the root context by traversing up the parent chain."""
|
|
98
|
+
ctx = self
|
|
99
|
+
while ctx.parent is not None:
|
|
100
|
+
ctx = ctx.parent
|
|
101
|
+
return ctx
|
|
102
|
+
|
|
103
|
+
def __repr__(self) -> str:
|
|
104
|
+
return f"<Context command={self.command}>"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Command group handling for SwiftCLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from ..exceptions import UsageError
|
|
8
|
+
from ..utils.formatting import format_error
|
|
9
|
+
from .context import Context
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .cli import CLI
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
class Group:
|
|
17
|
+
"""
|
|
18
|
+
Command group that can contain subcommands.
|
|
19
|
+
|
|
20
|
+
Groups allow organizing related commands together and support command
|
|
21
|
+
chaining for building command pipelines.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name: Group name
|
|
25
|
+
help: Group description
|
|
26
|
+
commands: Registered commands
|
|
27
|
+
parent: Parent CLI instance
|
|
28
|
+
chain: Enable command chaining
|
|
29
|
+
invoke_without_command: Allow invoking group without subcommand
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
@app.group()
|
|
33
|
+
def db():
|
|
34
|
+
'''Database commands'''
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@db.command()
|
|
38
|
+
def migrate():
|
|
39
|
+
'''Run database migrations'''
|
|
40
|
+
print("Running migrations...")
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
name: str,
|
|
46
|
+
help: Optional[str] = None,
|
|
47
|
+
parent: Optional['CLI'] = None,
|
|
48
|
+
chain: bool = False,
|
|
49
|
+
invoke_without_command: bool = False
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Initialize command group.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: Group name
|
|
56
|
+
help: Group description
|
|
57
|
+
parent: Parent CLI instance
|
|
58
|
+
chain: Enable command chaining
|
|
59
|
+
invoke_without_command: Allow invoking group without subcommand
|
|
60
|
+
"""
|
|
61
|
+
self.name = name
|
|
62
|
+
self.help = help
|
|
63
|
+
self.parent = parent
|
|
64
|
+
self.chain = chain
|
|
65
|
+
self.invoke_without_command = invoke_without_command
|
|
66
|
+
self.commands: Dict[str, Dict[str, Any]] = {}
|
|
67
|
+
|
|
68
|
+
def command(
|
|
69
|
+
self,
|
|
70
|
+
name: Optional[str] = None,
|
|
71
|
+
help: Optional[str] = None,
|
|
72
|
+
aliases: Optional[List[str]] = None,
|
|
73
|
+
hidden: bool = False
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Decorator to register a command in this group.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: Command name (defaults to function name)
|
|
80
|
+
help: Command help text
|
|
81
|
+
aliases: Alternative command names
|
|
82
|
+
hidden: Hide from help output
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
@group.command()
|
|
86
|
+
def status():
|
|
87
|
+
'''Show status'''
|
|
88
|
+
print("Status: OK")
|
|
89
|
+
"""
|
|
90
|
+
def decorator(f):
|
|
91
|
+
cmd_name = name or f.__name__
|
|
92
|
+
self.commands[cmd_name] = {
|
|
93
|
+
'func': f,
|
|
94
|
+
'help': help or f.__doc__,
|
|
95
|
+
'aliases': aliases or [],
|
|
96
|
+
'hidden': hidden
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Register aliases
|
|
100
|
+
for alias in (aliases or []):
|
|
101
|
+
self.commands[alias] = self.commands[cmd_name]
|
|
102
|
+
|
|
103
|
+
return f
|
|
104
|
+
return decorator
|
|
105
|
+
|
|
106
|
+
def group(
|
|
107
|
+
self,
|
|
108
|
+
name: Optional[str] = None,
|
|
109
|
+
help: Optional[str] = None,
|
|
110
|
+
**kwargs
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Create a subgroup within this group.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Subgroup name
|
|
117
|
+
help: Subgroup help text
|
|
118
|
+
**kwargs: Additional group options
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
@group.group()
|
|
122
|
+
def config():
|
|
123
|
+
'''Configuration commands'''
|
|
124
|
+
pass
|
|
125
|
+
"""
|
|
126
|
+
def decorator(f):
|
|
127
|
+
subgroup = Group(
|
|
128
|
+
name=name or f.__name__,
|
|
129
|
+
help=help or f.__doc__,
|
|
130
|
+
parent=self.parent,
|
|
131
|
+
**kwargs
|
|
132
|
+
)
|
|
133
|
+
self.commands[subgroup.name] = subgroup
|
|
134
|
+
return subgroup
|
|
135
|
+
return decorator
|
|
136
|
+
|
|
137
|
+
def run(self, args: List[str]) -> int:
|
|
138
|
+
"""
|
|
139
|
+
Run a command in this group.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
args: Command arguments
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Exit code (0 for success, non-zero for error)
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
# Show help if no arguments or help requested
|
|
149
|
+
if not args or args[0] in ['-h', '--help']:
|
|
150
|
+
self._print_help()
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
command_name = args[0]
|
|
154
|
+
command_args = args[1:]
|
|
155
|
+
|
|
156
|
+
# Check if command exists
|
|
157
|
+
if command_name not in self.commands:
|
|
158
|
+
format_error(f"Unknown command: {self.name} {command_name}")
|
|
159
|
+
self._print_help()
|
|
160
|
+
return 1
|
|
161
|
+
|
|
162
|
+
command = self.commands[command_name]
|
|
163
|
+
|
|
164
|
+
# Handle nested groups
|
|
165
|
+
if isinstance(command, Group):
|
|
166
|
+
return command.run(command_args)
|
|
167
|
+
|
|
168
|
+
# Create command context
|
|
169
|
+
ctx = Context(
|
|
170
|
+
self.parent,
|
|
171
|
+
command=f"{self.name} {command_name}",
|
|
172
|
+
debug=getattr(self.parent, 'debug', False)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Run command through plugin system
|
|
176
|
+
if self.parent and not self.parent.plugin_manager.before_command(
|
|
177
|
+
f"{self.name} {command_name}",
|
|
178
|
+
command_args
|
|
179
|
+
):
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
result = command['func'](**self._parse_args(command, command_args))
|
|
184
|
+
|
|
185
|
+
if self.parent:
|
|
186
|
+
self.parent.plugin_manager.after_command(
|
|
187
|
+
f"{self.name} {command_name}",
|
|
188
|
+
command_args,
|
|
189
|
+
result
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Handle command chaining
|
|
193
|
+
if self.chain and result is not None:
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
if self.parent:
|
|
200
|
+
self.parent.plugin_manager.on_error(
|
|
201
|
+
f"{self.name} {command_name}",
|
|
202
|
+
e
|
|
203
|
+
)
|
|
204
|
+
if getattr(self.parent, 'debug', False):
|
|
205
|
+
raise
|
|
206
|
+
format_error(str(e))
|
|
207
|
+
return 1
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
if getattr(self.parent, 'debug', False):
|
|
211
|
+
raise
|
|
212
|
+
format_error(str(e))
|
|
213
|
+
return 1
|
|
214
|
+
|
|
215
|
+
def _parse_args(self, command: Dict[str, Any], args: List[str]) -> Dict[str, Any]:
|
|
216
|
+
"""Parse command arguments."""
|
|
217
|
+
# Use parent CLI's argument parser if available
|
|
218
|
+
if self.parent:
|
|
219
|
+
return self.parent._parse_args(command, args)
|
|
220
|
+
|
|
221
|
+
# Fallback to basic argument parsing
|
|
222
|
+
from ..utils.parsing import parse_args
|
|
223
|
+
return parse_args(args)
|
|
224
|
+
|
|
225
|
+
def _print_help(self) -> None:
|
|
226
|
+
"""Print group help message."""
|
|
227
|
+
console.print(f"\n[bold]{self.name}[/] - {self.help or ''}")
|
|
228
|
+
|
|
229
|
+
console.print("\n[bold]Commands:[/]")
|
|
230
|
+
for name, cmd in self.commands.items():
|
|
231
|
+
if isinstance(cmd, Group):
|
|
232
|
+
console.print(f" {name} [group]")
|
|
233
|
+
if cmd.help:
|
|
234
|
+
console.print(f" {cmd.help}")
|
|
235
|
+
elif not cmd.get('hidden', False):
|
|
236
|
+
console.print(f" {name:20} {cmd['help'] or ''}")
|
|
237
|
+
|
|
238
|
+
console.print("\nUse -h or --help with any command for more info")
|
|
239
|
+
|
|
240
|
+
def __repr__(self) -> str:
|
|
241
|
+
return f"<Group name={self.name}>"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Decorators for SwiftCLI."""
|
|
2
|
+
|
|
3
|
+
from .command import command, group, argument, flag, pass_context
|
|
4
|
+
from .options import option, envvar, config_file, version_option, help_option
|
|
5
|
+
from .output import table_output, progress, panel_output, format_output, pager_output
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
# Command decorators
|
|
9
|
+
'command',
|
|
10
|
+
'group',
|
|
11
|
+
'argument',
|
|
12
|
+
'flag',
|
|
13
|
+
'pass_context',
|
|
14
|
+
|
|
15
|
+
# Option decorators
|
|
16
|
+
'option',
|
|
17
|
+
'envvar',
|
|
18
|
+
'config_file',
|
|
19
|
+
'version_option',
|
|
20
|
+
'help_option',
|
|
21
|
+
|
|
22
|
+
# Output decorators
|
|
23
|
+
'table_output',
|
|
24
|
+
'progress',
|
|
25
|
+
'panel_output',
|
|
26
|
+
'format_output',
|
|
27
|
+
'pager_output'
|
|
28
|
+
]
|