sa-assistant 0.1.1__tar.gz

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.
Files changed (31) hide show
  1. sa_assistant-0.1.1/PKG-INFO +77 -0
  2. sa_assistant-0.1.1/README.md +40 -0
  3. sa_assistant-0.1.1/pyproject.toml +82 -0
  4. sa_assistant-0.1.1/setup.cfg +4 -0
  5. sa_assistant-0.1.1/src/agents/__init__.py +32 -0
  6. sa_assistant-0.1.1/src/agents/orchestrator.py +283 -0
  7. sa_assistant-0.1.1/src/agents/specialists.py +230 -0
  8. sa_assistant-0.1.1/src/agentskills/__init__.py +38 -0
  9. sa_assistant-0.1.1/src/agentskills/discovery.py +107 -0
  10. sa_assistant-0.1.1/src/agentskills/errors.py +38 -0
  11. sa_assistant-0.1.1/src/agentskills/models.py +48 -0
  12. sa_assistant-0.1.1/src/agentskills/parser.py +183 -0
  13. sa_assistant-0.1.1/src/agentskills/prompt.py +70 -0
  14. sa_assistant-0.1.1/src/agentskills/tool.py +138 -0
  15. sa_assistant-0.1.1/src/cli/__init__.py +51 -0
  16. sa_assistant-0.1.1/src/cli/callback.py +145 -0
  17. sa_assistant-0.1.1/src/cli/components.py +346 -0
  18. sa_assistant-0.1.1/src/cli/console.py +106 -0
  19. sa_assistant-0.1.1/src/cli/mdstream.py +236 -0
  20. sa_assistant-0.1.1/src/mcp_client/client.py +12 -0
  21. sa_assistant-0.1.1/src/model/__init__.py +20 -0
  22. sa_assistant-0.1.1/src/model/load.py +87 -0
  23. sa_assistant-0.1.1/src/prompts/__init__.py +65 -0
  24. sa_assistant-0.1.1/src/prompts/system_prompts.py +464 -0
  25. sa_assistant-0.1.1/src/sa_assistant.egg-info/PKG-INFO +77 -0
  26. sa_assistant-0.1.1/src/sa_assistant.egg-info/SOURCES.txt +29 -0
  27. sa_assistant-0.1.1/src/sa_assistant.egg-info/dependency_links.txt +1 -0
  28. sa_assistant-0.1.1/src/sa_assistant.egg-info/entry_points.txt +2 -0
  29. sa_assistant-0.1.1/src/sa_assistant.egg-info/requires.txt +15 -0
  30. sa_assistant-0.1.1/src/sa_assistant.egg-info/top_level.txt +7 -0
  31. sa_assistant-0.1.1/test/test_main.py +42 -0
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: sa-assistant
3
+ Version: 0.1.1
4
+ Summary: SA Assistant - AWS Solutions Architect Professional Agent with Multi-Agent Architecture
5
+ Author-email: onesuit <wltks2155@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/onesuit/SaAssistant
8
+ Project-URL: Repository, https://github.com/onesuit/SaAssistant
9
+ Project-URL: Issues, https://github.com/onesuit/SaAssistant/issues
10
+ Keywords: aws,solutions-architect,ai-agent,llm,bedrock,claude,multi-agent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: aws-opentelemetry-distro>=0.10.0
24
+ Requires-Dist: bedrock-agentcore>=1.0.3
25
+ Requires-Dist: botocore[crt]
26
+ Requires-Dist: mcp>=1.19.0
27
+ Requires-Dist: prompt-toolkit>=3.0.0
28
+ Requires-Dist: python-dotenv>=1.2.1
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: rich>=13.0.0
31
+ Requires-Dist: strands-agents>=1.13.0
32
+ Requires-Dist: strands-agents-tools>=0.2.16
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
37
+
38
+ This is a project generated by the agentcore create --template basic CLI tool!
39
+
40
+ # Layout
41
+
42
+ There are two directories generated, `src/` and `test/`. At the root, there is a `.gitignore` file, a `.bedrock_agentcore.yaml` file
43
+ which is used for other `agentcore` commands like `deploy`, `dev`, and `invoke`.
44
+
45
+ ## src/
46
+
47
+ The main entrypoint to your app is defined in `src/main.py`. Using the AgentCore SDK `@app.entrypoint` decorator, this file defines a Starlette ASGI app with the chosen Agent framework SDK
48
+ running within.
49
+
50
+ `src/mcp_client/client.py` implements an MCP client using the library from your chosen Agent framework SDK.
51
+
52
+ `src/model/load.py` instantiates your chosen model provider.
53
+
54
+ ## test/
55
+
56
+ Tests are not defined by default. Add your own pytest definitions here.
57
+
58
+ # Developing locally
59
+
60
+ If installation was successful, a virtual environment is already created with dependencies installed.
61
+
62
+ Run `source .venv/bin/activate` before developing.
63
+
64
+ `agentcore dev` will start a local server on 0.0.0.0:8080.
65
+
66
+ In a new terminal, you can invoke that server with:
67
+
68
+ `agentcore invoke --dev "What can you do"`
69
+
70
+ # Deployment
71
+
72
+ If you want to customize your project, you can first run `agentcore configure` before deploying. Otherwise, the default project settings
73
+ will work out of the box.
74
+
75
+ After providing credentials, `agentcore deploy` will deploy your project into Amazon Bedrock AgentCore.
76
+
77
+ Use `agentcore invoke` to invoke your deployed agent.
@@ -0,0 +1,40 @@
1
+ This is a project generated by the agentcore create --template basic CLI tool!
2
+
3
+ # Layout
4
+
5
+ There are two directories generated, `src/` and `test/`. At the root, there is a `.gitignore` file, a `.bedrock_agentcore.yaml` file
6
+ which is used for other `agentcore` commands like `deploy`, `dev`, and `invoke`.
7
+
8
+ ## src/
9
+
10
+ The main entrypoint to your app is defined in `src/main.py`. Using the AgentCore SDK `@app.entrypoint` decorator, this file defines a Starlette ASGI app with the chosen Agent framework SDK
11
+ running within.
12
+
13
+ `src/mcp_client/client.py` implements an MCP client using the library from your chosen Agent framework SDK.
14
+
15
+ `src/model/load.py` instantiates your chosen model provider.
16
+
17
+ ## test/
18
+
19
+ Tests are not defined by default. Add your own pytest definitions here.
20
+
21
+ # Developing locally
22
+
23
+ If installation was successful, a virtual environment is already created with dependencies installed.
24
+
25
+ Run `source .venv/bin/activate` before developing.
26
+
27
+ `agentcore dev` will start a local server on 0.0.0.0:8080.
28
+
29
+ In a new terminal, you can invoke that server with:
30
+
31
+ `agentcore invoke --dev "What can you do"`
32
+
33
+ # Deployment
34
+
35
+ If you want to customize your project, you can first run `agentcore configure` before deploying. Otherwise, the default project settings
36
+ will work out of the box.
37
+
38
+ After providing credentials, `agentcore deploy` will deploy your project into Amazon Bedrock AgentCore.
39
+
40
+ Use `agentcore invoke` to invoke your deployed agent.
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sa-assistant"
7
+ version = "0.1.1"
8
+ requires-python = ">=3.10"
9
+ description = "SA Assistant - AWS Solutions Architect Professional Agent with Multi-Agent Architecture"
10
+ readme = "README.md"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "onesuit", email = "wltks2155@gmail.com"}
14
+ ]
15
+ keywords = [
16
+ "aws",
17
+ "solutions-architect",
18
+ "ai-agent",
19
+ "llm",
20
+ "bedrock",
21
+ "claude",
22
+ "multi-agent"
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Software Development :: Libraries :: Python Modules",
34
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
35
+ ]
36
+
37
+ dependencies = [
38
+ "aws-opentelemetry-distro >= 0.10.0",
39
+ "bedrock-agentcore >= 1.0.3",
40
+ "botocore[crt]",
41
+ "mcp >= 1.19.0",
42
+ "prompt-toolkit >= 3.0.0",
43
+ "python-dotenv >= 1.2.1",
44
+ "pyyaml >= 6.0",
45
+ "rich >= 13.0.0",
46
+ "strands-agents >= 1.13.0",
47
+ "strands-agents-tools >= 0.2.16"
48
+ ]
49
+
50
+ [project.optional-dependencies]
51
+ dev = [
52
+ "pytest >= 7.0.0",
53
+ "pytest-asyncio >= 0.21.0",
54
+ "ruff >= 0.1.0",
55
+ ]
56
+
57
+ [project.urls]
58
+ Homepage = "https://github.com/onesuit/SaAssistant"
59
+ Repository = "https://github.com/onesuit/SaAssistant"
60
+ Issues = "https://github.com/onesuit/SaAssistant/issues"
61
+
62
+ [project.scripts]
63
+ sa-assistant = "main:run_cli"
64
+
65
+ [tool.setuptools]
66
+ include-package-data = true
67
+ package-dir = {"" = "src"}
68
+
69
+ [tool.setuptools.packages.find]
70
+ where = ["src"]
71
+ include = ["*"]
72
+
73
+ [tool.setuptools.package-data]
74
+ "*" = ["skills/**/*.md"]
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py310"
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I", "W"]
82
+ ignore = ["E501"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ # SA 에이전트 모듈
2
+ # Orchestrator + Guru Sub-agents + Specialist Sub-agents 아키텍처
3
+
4
+ from .orchestrator import (
5
+ create_orchestrator,
6
+ consult_guru,
7
+ list_gurus,
8
+ get_available_gurus,
9
+ )
10
+
11
+ from .specialists import (
12
+ consult_specialist,
13
+ list_specialists,
14
+ get_available_specialists,
15
+ parallel_research,
16
+ run_specialists_parallel,
17
+ )
18
+
19
+ __all__ = [
20
+ # Orchestrator
21
+ "create_orchestrator",
22
+ # Guru
23
+ "consult_guru",
24
+ "list_gurus",
25
+ "get_available_gurus",
26
+ # Specialist
27
+ "consult_specialist",
28
+ "list_specialists",
29
+ "get_available_specialists",
30
+ "parallel_research",
31
+ "run_specialists_parallel",
32
+ ]
@@ -0,0 +1,283 @@
1
+ # SA Orchestrator - AWS Solutions Architect Professional Agent
2
+ # Guru Sub-agents를 활용한 전문가 자문 시스템
3
+ # ThreadPoolExecutor를 사용하여 Sub-agent를 별도 스레드에서 실행 (OpenTelemetry 컨텍스트 격리)
4
+
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from typing import List, Any
7
+ from strands import Agent, tool
8
+
9
+ # 로컬 모듈 임포트
10
+ import sys
11
+ from pathlib import Path
12
+ sys.path.insert(0, str(Path(__file__).parent.parent))
13
+
14
+ from model.load import load_opus, load_sonnet
15
+ from prompts.system_prompts import (
16
+ ORCHESTRATOR_PROMPT,
17
+ GURU_PROMPTS,
18
+ get_orchestrator_prompt,
19
+ )
20
+
21
+
22
+ # =============================================================================
23
+ # Guru Sub-agent 도구 (ThreadPoolExecutor로 별도 스레드에서 실행)
24
+ # =============================================================================
25
+
26
+ def get_available_gurus() -> List[str]:
27
+ """사용 가능한 Guru 목록을 반환합니다."""
28
+ return list(GURU_PROMPTS.keys())
29
+
30
+
31
+ def _run_guru_in_thread(guru_name: str, question: str) -> str:
32
+ """
33
+ 별도 스레드에서 Guru Agent 실행 (OpenTelemetry 컨텍스트 격리)
34
+
35
+ ThreadPoolExecutor에서 호출되어 독립적인 스레드에서 실행됩니다.
36
+ OpenTelemetry 컨텍스트를 명시적으로 분리하여
37
+ "Failed to detach context" 오류를 방지합니다.
38
+
39
+ Args:
40
+ guru_name: Guru 이름 (소문자)
41
+ question: 자문을 구할 질문
42
+
43
+ Returns:
44
+ str: Guru의 응답 텍스트
45
+ """
46
+ # OpenTelemetry 컨텍스트 명시적 분리
47
+ try:
48
+ from opentelemetry import context as otel_context
49
+ # 새로운 빈 컨텍스트에서 실행
50
+ token = otel_context.attach(otel_context.Context())
51
+ except ImportError:
52
+ token = None
53
+
54
+ try:
55
+ guru_agent = Agent(
56
+ model=load_sonnet(enable_thinking=False),
57
+ system_prompt=GURU_PROMPTS[guru_name],
58
+ )
59
+ result = guru_agent(question)
60
+ return str(result)
61
+ finally:
62
+ # 컨텍스트 복원
63
+ if token is not None:
64
+ try:
65
+ otel_context.detach(token)
66
+ except Exception:
67
+ pass # detach 실패 무시
68
+
69
+
70
+ @tool
71
+ def consult_guru(guru_name: str, question: str) -> str:
72
+ """
73
+ 전문가 Guru에게 자문을 구합니다.
74
+
75
+ 복잡한 아키텍처 결정, 비즈니스 전략, 학습 방법 등에 대해
76
+ 저명한 전문가들의 사고방식을 기반으로 조언을 받습니다.
77
+
78
+ Args:
79
+ guru_name: Guru 이름
80
+ - "bezos": Jeff Bezos - 고객 중심 사고, 장기적 비전, Day 1 문화
81
+ - "vogels": Werner Vogels - 분산 시스템, 확장성, 운영 우수성
82
+ - "naval": Naval Ravikant - 레버리지, 비대칭적 결과, 시스템적 사고
83
+ - "feynman": Richard Feynman - 복잡한 개념 단순화, 학습 최적화
84
+ question: 자문을 구할 질문
85
+
86
+ Returns:
87
+ str: Guru의 조언
88
+
89
+ Example:
90
+ consult_guru("bezos", "이 아키텍처가 고객에게 어떤 가치를 제공하나요?")
91
+ consult_guru("vogels", "이 시스템의 확장성을 어떻게 개선할 수 있을까요?")
92
+ """
93
+ guru_name_lower = guru_name.lower()
94
+
95
+ if guru_name_lower not in GURU_PROMPTS:
96
+ available = ", ".join(get_available_gurus())
97
+ return f"알 수 없는 Guru입니다: {guru_name}. 사용 가능한 Guru: {available}"
98
+
99
+ try:
100
+ # ThreadPoolExecutor로 별도 스레드에서 Guru Agent 실행
101
+ # OpenTelemetry 컨텍스트가 스레드별로 격리되어 충돌 방지
102
+ with ThreadPoolExecutor(max_workers=1) as executor:
103
+ future = executor.submit(_run_guru_in_thread, guru_name_lower, question)
104
+ response_text = future.result()
105
+
106
+ return f"[{guru_name.upper()} GURU 조언]\n\n{response_text}"
107
+ except Exception as e:
108
+ return f"Guru 자문 중 오류 발생: {str(e)}"
109
+
110
+
111
+ @tool
112
+ def list_gurus() -> str:
113
+ """
114
+ 사용 가능한 Guru 목록과 전문 분야를 반환합니다.
115
+
116
+ Returns:
117
+ str: Guru 목록 및 설명
118
+ """
119
+ guru_descriptions = {
120
+ "bezos": "Jeff Bezos - 고객 중심 사고, 장기적 비전, Day 1 문화, 역방향 사고",
121
+ "vogels": "Werner Vogels - 분산 시스템, 확장성, 운영 우수성, 장애 대응",
122
+ "naval": "Naval Ravikant - 레버리지, 비대칭적 결과, 시스템적 사고, 복리 효과",
123
+ "feynman": "Richard Feynman - 복잡한 개념 단순화, 학습 최적화, First Principles"
124
+ }
125
+
126
+ result = "## 사용 가능한 Guru 전문가\n\n"
127
+ for name, desc in guru_descriptions.items():
128
+ result += f"- **{name}**: {desc}\n"
129
+
130
+ result += "\n`consult_guru(guru_name, question)` 도구로 자문을 구할 수 있습니다."
131
+ return result
132
+
133
+
134
+ # =============================================================================
135
+ # Orchestrator 생성
136
+ # =============================================================================
137
+
138
+ def create_orchestrator(
139
+ tools: List[Any] = None,
140
+ system_prompt: str = None,
141
+ enable_thinking: bool = True
142
+ ) -> Agent:
143
+ """
144
+ SA Orchestrator 에이전트를 생성합니다.
145
+
146
+ Args:
147
+ tools: 추가할 도구 목록 (MCP 도구 등)
148
+ system_prompt: 커스텀 시스템 프롬프트 (None이면 기본 프롬프트 사용)
149
+ enable_thinking: Extended Thinking 활성화 여부
150
+
151
+ Returns:
152
+ Agent: SA Orchestrator 에이전트
153
+ """
154
+ # 시스템 프롬프트 구성
155
+ if system_prompt is None:
156
+ system_prompt = get_orchestrator_prompt()
157
+
158
+ # 기본 도구 목록
159
+ default_tools = [
160
+ consult_guru,
161
+ list_gurus,
162
+ ]
163
+
164
+ # 추가 도구 병합
165
+ all_tools = default_tools + (tools or [])
166
+
167
+ # Conversation Manager 설정
168
+ conversation_manager = SlidingWindowConversationManager(
169
+ window_size=20 # 최근 20개 메시지 유지
170
+ )
171
+
172
+ # Orchestrator 에이전트 생성
173
+ orchestrator = Agent(
174
+ model=load_opus(enable_thinking=enable_thinking),
175
+ system_prompt=system_prompt,
176
+ tools=all_tools,
177
+ conversation_manager=conversation_manager
178
+ )
179
+
180
+ return orchestrator
181
+
182
+
183
+ # =============================================================================
184
+ # CLI 실행 지원
185
+ # =============================================================================
186
+
187
+ # ANSI 색상 코드
188
+ class Colors:
189
+ """터미널 색상 코드"""
190
+ CYAN = '\033[96m'
191
+ GREEN = '\033[92m'
192
+ YELLOW = '\033[93m'
193
+ RED = '\033[91m'
194
+ BLUE = '\033[94m'
195
+ MAGENTA = '\033[95m'
196
+ WHITE = '\033[97m'
197
+ RESET = '\033[0m'
198
+ BOLD = '\033[1m'
199
+ DIM = '\033[2m'
200
+ ORANGE = '\033[38;5;208m' # AWS Orange
201
+
202
+
203
+ def print_banner():
204
+ """SA Assistant 배너를 출력합니다."""
205
+ banner = f"""
206
+ {Colors.ORANGE}╔══════════════════════════════════════════════════════════════╗
207
+ ║ ║
208
+ ║ {Colors.WHITE}AWS SA Assistant{Colors.ORANGE} ║
209
+ ║ {Colors.DIM}Solutions Architect Professional Agent{Colors.ORANGE} ║
210
+ ║ ║
211
+ ║ {Colors.CYAN}Powered by Claude Opus 4.5 with Extended Thinking{Colors.ORANGE} ║
212
+ ║ ║
213
+ ╚══════════════════════════════════════════════════════════════╝{Colors.RESET}
214
+ """
215
+ print(banner)
216
+
217
+
218
+ def tool_callback_handler(**kwargs):
219
+ """도구 호출 콜백 핸들러"""
220
+ if "current_tool_use" in kwargs:
221
+ tool_use = kwargs["current_tool_use"]
222
+ if isinstance(tool_use, dict):
223
+ tool_name = tool_use.get("name", "")
224
+ if tool_name:
225
+ if tool_name == "consult_guru":
226
+ guru = tool_use.get("input", {}).get("guru_name", "")
227
+ print(f"\n{Colors.MAGENTA}🧙 Consulting {guru.upper()} Guru...{Colors.RESET}", flush=True)
228
+ elif tool_name == "list_gurus":
229
+ print(f"\n{Colors.BLUE}📋 Listing available Gurus...{Colors.RESET}", flush=True)
230
+ else:
231
+ print(f"\n{Colors.CYAN}🔧 [{tool_name}]{Colors.RESET}", flush=True)
232
+
233
+ if "data" in kwargs:
234
+ print(kwargs["data"], end="", flush=True)
235
+
236
+
237
+ def run_cli(tools: List[Any] = None):
238
+ """CLI 모드로 실행합니다."""
239
+ print_banner()
240
+
241
+ print(f"{Colors.WHITE}안녕하세요! AWS Solutions Architect Assistant입니다.{Colors.RESET}")
242
+ print(f"{Colors.DIM}아키텍처 설계, 비용 분석, 보안 검토 등을 도와드립니다.{Colors.RESET}")
243
+ print(f"\n{Colors.YELLOW}💡 Guru 전문가 자문: 'list_gurus'로 사용 가능한 전문가를 확인하세요.{Colors.RESET}")
244
+ print(f"{Colors.DIM}종료하려면 'exit' 또는 'quit'을 입력하세요.{Colors.RESET}\n")
245
+
246
+ # Orchestrator 생성 (Progressive Disclosure: 스킬은 main.py에서 처리)
247
+ orchestrator = create_orchestrator(
248
+ tools=tools,
249
+ enable_thinking=True
250
+ )
251
+
252
+ # 콜백 핸들러 설정
253
+ orchestrator.callback_handler = tool_callback_handler
254
+
255
+ while True:
256
+ try:
257
+ user_input = input(f"\n{Colors.GREEN}You: {Colors.RESET}").strip()
258
+
259
+ if not user_input:
260
+ continue
261
+
262
+ if user_input.lower() in ["exit", "quit", "종료"]:
263
+ print(f"\n{Colors.ORANGE}감사합니다. 좋은 하루 되세요! 👋{Colors.RESET}")
264
+ break
265
+
266
+ print(f"\n{Colors.ORANGE}{Colors.BOLD}SA Assistant:{Colors.RESET} ", end="", flush=True)
267
+
268
+ try:
269
+ response = orchestrator(user_input)
270
+ print() # 줄바꿈
271
+ except Exception as e:
272
+ print(f"\n{Colors.RED}오류 발생: {e}{Colors.RESET}")
273
+ continue
274
+
275
+ except KeyboardInterrupt:
276
+ print(f"\n\n{Colors.YELLOW}중단되었습니다.{Colors.RESET}")
277
+ break
278
+ except Exception as e:
279
+ print(f"\n{Colors.RED}오류: {e}{Colors.RESET}")
280
+
281
+
282
+ if __name__ == "__main__":
283
+ run_cli()