talkops-opscloud 0.3.0b1__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.
Files changed (309) hide show
  1. opscloud/__init__.py +5 -0
  2. opscloud/__main__.py +6 -0
  3. opscloud/_constants.py +53 -0
  4. opscloud/_debug.py +145 -0
  5. opscloud/_debug_buffer.py +250 -0
  6. opscloud/_textual_patches.py +367 -0
  7. opscloud/_version.py +15 -0
  8. opscloud/agent/__init__.py +27 -0
  9. opscloud/agent/config.py +168 -0
  10. opscloud/agent/factory.py +1103 -0
  11. opscloud/approval_mode.py +44 -0
  12. opscloud/backend/__init__.py +49 -0
  13. opscloud/backend/composite.py +89 -0
  14. opscloud/backend/local.py +183 -0
  15. opscloud/backend/registry.py +79 -0
  16. opscloud/backend/sandbox_config.py +107 -0
  17. opscloud/backend/sandbox_factory.py +432 -0
  18. opscloud/built_in_skills/remember/SKILL.md +124 -0
  19. opscloud/built_in_skills/skill-creator/SKILL.md +348 -0
  20. opscloud/built_in_skills/skill-creator/scripts/init_skill.py +371 -0
  21. opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +165 -0
  22. opscloud/cli/__init__.py +1 -0
  23. opscloud/cli/commands/__init__.py +15 -0
  24. opscloud/cli/commands/auth.py +45 -0
  25. opscloud/cli/commands/doctor.py +100 -0
  26. opscloud/cli/commands/mcp.py +137 -0
  27. opscloud/cli/commands/threads.py +51 -0
  28. opscloud/cli/interactive.py +154 -0
  29. opscloud/cli/main.py +903 -0
  30. opscloud/cli/non_interactive.py +425 -0
  31. opscloud/cli/server_manager.py +58 -0
  32. opscloud/commands/__init__.py +19 -0
  33. opscloud/commands/_base.py +110 -0
  34. opscloud/commands/_guards.py +52 -0
  35. opscloud/commands/_router.py +146 -0
  36. opscloud/commands/_types.py +36 -0
  37. opscloud/commands/core/__init__.py +47 -0
  38. opscloud/commands/core/auth.py +94 -0
  39. opscloud/commands/core/bug.py +46 -0
  40. opscloud/commands/core/clear.py +106 -0
  41. opscloud/commands/core/cloud.py +185 -0
  42. opscloud/commands/core/compact.py +128 -0
  43. opscloud/commands/core/config_cmd.py +120 -0
  44. opscloud/commands/core/context.py +220 -0
  45. opscloud/commands/core/cost.py +44 -0
  46. opscloud/commands/core/doctor.py +535 -0
  47. opscloud/commands/core/effort.py +108 -0
  48. opscloud/commands/core/exit_cmd.py +41 -0
  49. opscloud/commands/core/fast.py +148 -0
  50. opscloud/commands/core/help_cmd.py +94 -0
  51. opscloud/commands/core/mcp.py +134 -0
  52. opscloud/commands/core/model.py +109 -0
  53. opscloud/commands/core/permissions.py +196 -0
  54. opscloud/commands/core/plugins.py +92 -0
  55. opscloud/commands/core/pool.py +108 -0
  56. opscloud/commands/core/resume.py +97 -0
  57. opscloud/commands/core/skills.py +178 -0
  58. opscloud/commands/power/__init__.py +51 -0
  59. opscloud/commands/power/agents.py +98 -0
  60. opscloud/commands/power/btw.py +85 -0
  61. opscloud/commands/power/copy.py +97 -0
  62. opscloud/commands/power/goal.py +548 -0
  63. opscloud/commands/power/loop.py +295 -0
  64. opscloud/commands/power/memory.py +287 -0
  65. opscloud/commands/power/review.py +152 -0
  66. opscloud/commands/power/rubric.py +287 -0
  67. opscloud/commands/power/runtime.py +321 -0
  68. opscloud/commands/power/skill_creator.py +70 -0
  69. opscloud/commands/power/skill_invoke.py +165 -0
  70. opscloud/commands/power/tasks.py +209 -0
  71. opscloud/commands/power/trace.py +117 -0
  72. opscloud/commands/power/ui_toggles.py +135 -0
  73. opscloud/commands/power/version.py +52 -0
  74. opscloud/commands/registry.py +176 -0
  75. opscloud/config/__init__.py +114 -0
  76. opscloud/config/adapters/__init__.py +5 -0
  77. opscloud/config/adapters/postgres.py +776 -0
  78. opscloud/config/adapters/sqlite.py +769 -0
  79. opscloud/config/aws.py +248 -0
  80. opscloud/config/cloud_profiles.py +437 -0
  81. opscloud/config/env_vars.py +81 -0
  82. opscloud/config/langsmith.py +319 -0
  83. opscloud/config/manifest.py +817 -0
  84. opscloud/config/metadata.py +205 -0
  85. opscloud/config/paths.py +604 -0
  86. opscloud/config/plugins.py +130 -0
  87. opscloud/config/settings.py +834 -0
  88. opscloud/config/store.py +453 -0
  89. opscloud/config/store_factory.py +97 -0
  90. opscloud/config/toml_config.py +562 -0
  91. opscloud/editor.py +173 -0
  92. opscloud/exceptions.py +65 -0
  93. opscloud/file_ops.py +217 -0
  94. opscloud/hooks/__init__.py +139 -0
  95. opscloud/hooks/env.py +44 -0
  96. opscloud/hooks/interrupt.py +121 -0
  97. opscloud/hooks/models/__init__.py +109 -0
  98. opscloud/hooks/models/adapters.py +19 -0
  99. opscloud/hooks/models/config.py +79 -0
  100. opscloud/hooks/models/domain.py +434 -0
  101. opscloud/hooks/models/transport.py +37 -0
  102. opscloud/hooks/tools.py +231 -0
  103. opscloud/input.py +48 -0
  104. opscloud/integrations/__init__.py +80 -0
  105. opscloud/integrations/base.py +153 -0
  106. opscloud/integrations/content_filter.py +119 -0
  107. opscloud/integrations/event_bus.py +480 -0
  108. opscloud/integrations/hooks.py +238 -0
  109. opscloud/integrations/identity.py +181 -0
  110. opscloud/integrations/notifications.py +134 -0
  111. opscloud/integrations/stream_bridge.py +397 -0
  112. opscloud/integrations/thread_store.py +81 -0
  113. opscloud/json_types.py +20 -0
  114. opscloud/mcp/__init__.py +57 -0
  115. opscloud/mcp/config.py +203 -0
  116. opscloud/mcp/discovery.py +229 -0
  117. opscloud/mcp/mcp_info.py +109 -0
  118. opscloud/mcp/middleware.py +102 -0
  119. opscloud/mcp/preload.py +505 -0
  120. opscloud/mcp/raw_config.py +149 -0
  121. opscloud/mcp/semantic_profiler.py +305 -0
  122. opscloud/mcp/session_manager.py +727 -0
  123. opscloud/mcp/trust.py +105 -0
  124. opscloud/memory/__init__.py +27 -0
  125. opscloud/memory/branch.py +61 -0
  126. opscloud/memory/guard.py +19 -0
  127. opscloud/memory/onboarding.py +120 -0
  128. opscloud/memory/registry.py +134 -0
  129. opscloud/memory/store.py +223 -0
  130. opscloud/middleware/__init__.py +106 -0
  131. opscloud/middleware/_repository_bounds.py +459 -0
  132. opscloud/middleware/ask_user.py +152 -0
  133. opscloud/middleware/auto_mode.py +60 -0
  134. opscloud/middleware/auto_mode_hitl.py +2434 -0
  135. opscloud/middleware/compaction.py +566 -0
  136. opscloud/middleware/configurable_model.py +233 -0
  137. opscloud/middleware/cost_tracking.py +1021 -0
  138. opscloud/middleware/glm_stall_recovery.py +109 -0
  139. opscloud/middleware/goal_criteria.py +1234 -0
  140. opscloud/middleware/goal_state_notice.py +712 -0
  141. opscloud/middleware/goal_tools.py +407 -0
  142. opscloud/middleware/headless_mcp_guard.py +88 -0
  143. opscloud/middleware/jev_model_router.py +1018 -0
  144. opscloud/middleware/local_context.py +460 -0
  145. opscloud/middleware/mcp_context.py +151 -0
  146. opscloud/middleware/mcp_middleware.py +146 -0
  147. opscloud/middleware/memory_guard.py +393 -0
  148. opscloud/middleware/model_retry.py +205 -0
  149. opscloud/middleware/registry.py +137 -0
  150. opscloud/middleware/reliable_rubric.py +709 -0
  151. opscloud/middleware/resume_state.py +91 -0
  152. opscloud/middleware/server_hooks.py +1189 -0
  153. opscloud/middleware/shell_allow_list.py +90 -0
  154. opscloud/middleware/skills.py +382 -0
  155. opscloud/middleware/subagent_artifacts.py +84 -0
  156. opscloud/middleware/subagent_telemetry.py +261 -0
  157. opscloud/middleware/subagents.py +243 -0
  158. opscloud/middleware/tool_filter.py +384 -0
  159. opscloud/middleware/unified_system_message.py +62 -0
  160. opscloud/model/__init__.py +37 -0
  161. opscloud/model/config.py +2992 -0
  162. opscloud/model/factory.py +477 -0
  163. opscloud/model/pool.py +459 -0
  164. opscloud/model/reasoning.py +282 -0
  165. opscloud/offload.py +173 -0
  166. opscloud/output.py +64 -0
  167. opscloud/plugins/__init__.py +189 -0
  168. opscloud/plugins/_json.py +22 -0
  169. opscloud/plugins/adapters/__init__.py +13 -0
  170. opscloud/plugins/adapters/agents.py +112 -0
  171. opscloud/plugins/adapters/commands.py +190 -0
  172. opscloud/plugins/adapters/mcp.py +363 -0
  173. opscloud/plugins/adapters/skills.py +132 -0
  174. opscloud/plugins/commands_cli.py +541 -0
  175. opscloud/plugins/discovery.py +512 -0
  176. opscloud/plugins/manifest.py +335 -0
  177. opscloud/plugins/marketplace.py +893 -0
  178. opscloud/plugins/models.py +334 -0
  179. opscloud/plugins/project_plugins.py +317 -0
  180. opscloud/plugins/protocol.py +31 -0
  181. opscloud/plugins/store.py +905 -0
  182. opscloud/plugins/substitution.py +122 -0
  183. opscloud/project_utils.py +145 -0
  184. opscloud/prompts/__init__.py +37 -0
  185. opscloud/prompts/system.py +483 -0
  186. opscloud/prompts/templates/system_prompt.md +166 -0
  187. opscloud/prompts/types.py +54 -0
  188. opscloud/rubrics/__init__.py +42 -0
  189. opscloud/rubrics/evaluator.py +404 -0
  190. opscloud/rubrics/evidence_extractor.py +229 -0
  191. opscloud/rubrics/generator.py +239 -0
  192. opscloud/rubrics/jev_compiler.py +175 -0
  193. opscloud/rubrics/jev_grader.py +297 -0
  194. opscloud/rubrics/middleware.py +8 -0
  195. opscloud/schema/__init__.py +107 -0
  196. opscloud/schema/interrupts.py +981 -0
  197. opscloud/security/__init__.py +91 -0
  198. opscloud/security/approval_mode.py +528 -0
  199. opscloud/security/approval_mode_source.py +230 -0
  200. opscloud/security/cli_ast_evaluator.py +552 -0
  201. opscloud/security/jev_classifier.py +651 -0
  202. opscloud/security/shell_safety.py +340 -0
  203. opscloud/security/unicode_security.py +439 -0
  204. opscloud/security/url_validation.py +160 -0
  205. opscloud/server/__init__.py +13 -0
  206. opscloud/server/_server_config.py +158 -0
  207. opscloud/server/server.py +242 -0
  208. opscloud/server/server_graph.py +93 -0
  209. opscloud/skills/__init__.py +91 -0
  210. opscloud/skills/commands.py +1099 -0
  211. opscloud/skills/invocation.py +200 -0
  212. opscloud/skills/load.py +388 -0
  213. opscloud/skills/loader.py +23 -0
  214. opscloud/skills/merge.py +65 -0
  215. opscloud/skills/registry.py +263 -0
  216. opscloud/skills/sources.py +174 -0
  217. opscloud/skills/trust.py +417 -0
  218. opscloud/state/__init__.py +142 -0
  219. opscloud/state/base.py +154 -0
  220. opscloud/state/cloud_context.py +262 -0
  221. opscloud/state/goal_channels.py +124 -0
  222. opscloud/state/goal_state_limits.py +175 -0
  223. opscloud/state/resume_state.py +58 -0
  224. opscloud/state/service_context.py +281 -0
  225. opscloud/state/session.py +957 -0
  226. opscloud/state/state_migration.py +86 -0
  227. opscloud/subagents/__init__.py +30 -0
  228. opscloud/subagents/loader.py +331 -0
  229. opscloud/subagents/subagents_parser.py +117 -0
  230. opscloud/subagents/types.py +25 -0
  231. opscloud/tools/__init__.py +36 -0
  232. opscloud/tools/catalog.py +37 -0
  233. opscloud/tools/display.py +24 -0
  234. opscloud/tools/fetch_url.py +176 -0
  235. opscloud/tools/goal_tools.py +423 -0
  236. opscloud/tools/registry.py +105 -0
  237. opscloud/tools/thread.py +32 -0
  238. opscloud/tools/web_search.py +215 -0
  239. opscloud/ui/__init__.py +119 -0
  240. opscloud/ui/app.py +5400 -0
  241. opscloud/ui/app.tcss +479 -0
  242. opscloud/ui/clipboard.py +147 -0
  243. opscloud/ui/command_registry.py +482 -0
  244. opscloud/ui/message_store.py +152 -0
  245. opscloud/ui/permission_store.py +360 -0
  246. opscloud/ui/preferences.py +113 -0
  247. opscloud/ui/prompt_manager.py +96 -0
  248. opscloud/ui/remote_client.py +505 -0
  249. opscloud/ui/textual_adapter.py +1269 -0
  250. opscloud/ui/theme.py +728 -0
  251. opscloud/ui/ui_help.py +415 -0
  252. opscloud/ui/widgets/__init__.py +383 -0
  253. opscloud/ui/widgets/_ask_user_types.py +81 -0
  254. opscloud/ui/widgets/_copy_spans.py +52 -0
  255. opscloud/ui/widgets/_inline_prompt.py +290 -0
  256. opscloud/ui/widgets/_js_eval_display.py +139 -0
  257. opscloud/ui/widgets/_links.py +175 -0
  258. opscloud/ui/widgets/_tool_stream.py +308 -0
  259. opscloud/ui/widgets/agent_selector.py +297 -0
  260. opscloud/ui/widgets/approval.py +741 -0
  261. opscloud/ui/widgets/ask_user.py +539 -0
  262. opscloud/ui/widgets/auth.py +695 -0
  263. opscloud/ui/widgets/auth_manager.py +214 -0
  264. opscloud/ui/widgets/auto_mode_notice.py +146 -0
  265. opscloud/ui/widgets/autocomplete.py +709 -0
  266. opscloud/ui/widgets/chat_input.py +805 -0
  267. opscloud/ui/widgets/cloud_selector.py +528 -0
  268. opscloud/ui/widgets/config_manager.py +652 -0
  269. opscloud/ui/widgets/debug_console.py +1269 -0
  270. opscloud/ui/widgets/devops_renderers.py +184 -0
  271. opscloud/ui/widgets/diff.py +237 -0
  272. opscloud/ui/widgets/effort_selector.py +166 -0
  273. opscloud/ui/widgets/goal_review.py +612 -0
  274. opscloud/ui/widgets/goal_status.py +57 -0
  275. opscloud/ui/widgets/infra_panel.py +141 -0
  276. opscloud/ui/widgets/install_confirm.py +114 -0
  277. opscloud/ui/widgets/loading.py +170 -0
  278. opscloud/ui/widgets/mcp_viewer.py +1204 -0
  279. opscloud/ui/widgets/messages.py +4461 -0
  280. opscloud/ui/widgets/model_selector.py +863 -0
  281. opscloud/ui/widgets/notification_center.py +108 -0
  282. opscloud/ui/widgets/notification_settings.py +119 -0
  283. opscloud/ui/widgets/operation_card.py +106 -0
  284. opscloud/ui/widgets/permissions_manager.py +667 -0
  285. opscloud/ui/widgets/plugin_manager.py +1212 -0
  286. opscloud/ui/widgets/pool_selector.py +376 -0
  287. opscloud/ui/widgets/preamble.py +53 -0
  288. opscloud/ui/widgets/skills_viewer.py +432 -0
  289. opscloud/ui/widgets/status.py +1037 -0
  290. opscloud/ui/widgets/subagent_panel.py +927 -0
  291. opscloud/ui/widgets/theme_selector.py +232 -0
  292. opscloud/ui/widgets/thread_selector.py +358 -0
  293. opscloud/ui/widgets/toast.py +31 -0
  294. opscloud/ui/widgets/tool_display.py +325 -0
  295. opscloud/ui/widgets/tool_renderers.py +234 -0
  296. opscloud/ui/widgets/tool_widgets.py +278 -0
  297. opscloud/ui/widgets/welcome.py +421 -0
  298. opscloud/ui/widgets/welcome_popup.py +102 -0
  299. opscloud/utils/__init__.py +24 -0
  300. opscloud/utils/cost_estimation.py +169 -0
  301. opscloud/utils/git.py +390 -0
  302. opscloud/utils/logger.py +571 -0
  303. opscloud/utils/session_stats.py +115 -0
  304. opscloud/utils/startup_error.py +37 -0
  305. talkops_opscloud-0.3.0b1.dist-info/METADATA +449 -0
  306. talkops_opscloud-0.3.0b1.dist-info/RECORD +309 -0
  307. talkops_opscloud-0.3.0b1.dist-info/WHEEL +4 -0
  308. talkops_opscloud-0.3.0b1.dist-info/entry_points.txt +2 -0
  309. talkops_opscloud-0.3.0b1.dist-info/licenses/LICENSE +175 -0
@@ -0,0 +1,100 @@
1
+ """Doctor command verifying AWS credentials, tools, and environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import sys
7
+
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from opscloud.config.aws import get_aws_context
12
+ from opscloud.output import OutputFormat, write_json
13
+
14
+
15
+ def run_doctor_command(output_format: OutputFormat = "text") -> int:
16
+ aws_ctx = get_aws_context()
17
+ tools = ["aws", "kubectl", "terraform", "helm", "docker", "git"]
18
+ tool_statuses: dict[str, str | None] = {}
19
+ for tool_name in tools:
20
+ tool_statuses[tool_name] = shutil.which(tool_name)
21
+
22
+ py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
23
+
24
+ from opscloud.utils.logger import (
25
+ get_active_log_dir,
26
+ get_active_log_file,
27
+ get_configured_log_level,
28
+ is_console_logging_enabled,
29
+ )
30
+
31
+ log_dir = str(get_active_log_dir())
32
+ log_file = str(get_active_log_file())
33
+ log_level = get_configured_log_level()
34
+ console_logging = is_console_logging_enabled()
35
+
36
+ if output_format == "json":
37
+ write_json(
38
+ "doctor",
39
+ {
40
+ "aws_sts": {
41
+ "authenticated": aws_ctx.is_authenticated,
42
+ "account_id": aws_ctx.account_id,
43
+ "region": aws_ctx.region,
44
+ "profile": aws_ctx.profile,
45
+ "error": aws_ctx.error,
46
+ },
47
+ "tools": tool_statuses,
48
+ "python": py_ver,
49
+ "logging": {
50
+ "directory": log_dir,
51
+ "file": log_file,
52
+ "level": log_level,
53
+ "console_enabled": console_logging,
54
+ },
55
+ },
56
+ )
57
+ return 0
58
+
59
+ console = Console()
60
+ console.print("\n[bold yellow]OpsCloud System & Cloud Environment Doctor[/bold yellow]\n")
61
+
62
+ table = Table(title="Environment Diagnostics", border_style="cyan")
63
+ table.add_column("Component", style="bold")
64
+ table.add_column("Status")
65
+ table.add_column("Details")
66
+
67
+ # 1. AWS Credentials
68
+ if aws_ctx.is_authenticated:
69
+ table.add_row(
70
+ "AWS STS Identity",
71
+ "[green]OK[/green]",
72
+ f"Account: {aws_ctx.account_id}, Region: {aws_ctx.region}, Profile: {aws_ctx.profile}",
73
+ )
74
+ else:
75
+ table.add_row(
76
+ "AWS STS Identity",
77
+ "[yellow]Unauthenticated[/yellow]",
78
+ aws_ctx.error or "No active AWS credentials found via STS",
79
+ )
80
+
81
+ # 2. Tooling
82
+ for tool_name, path in tool_statuses.items():
83
+ if path:
84
+ table.add_row(f"CLI: {tool_name}", "[green]Found[/green]", path)
85
+ else:
86
+ table.add_row(f"CLI: {tool_name}", "[dim]Not installed[/dim]", "Optional")
87
+
88
+ # 3. Python version
89
+ table.add_row("Python Runtime", "[green]OK[/green]", f"Python {py_ver}")
90
+
91
+ # 4. Logging subsystem
92
+ table.add_row(
93
+ "Log Destination",
94
+ "[green]Active[/green]",
95
+ f"{log_file} (level={log_level}, console={'enabled' if console_logging else 'silent'})",
96
+ )
97
+
98
+ console.print(table)
99
+ console.print()
100
+ return 0
@@ -0,0 +1,137 @@
1
+ """MCP management CLI command for opscloud."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from opscloud.mcp.discovery import MCPDiscovery
15
+ from opscloud.mcp.preload import preload_mcp_metadata
16
+
17
+
18
+ def setup_mcp_parser(subparsers: Any) -> None:
19
+ """Setup parser for `opscloud mcp`."""
20
+ mcp_parser = subparsers.add_parser(
21
+ "mcp",
22
+ help="List, probe, and inspect configured Model Context Protocol (MCP) servers.",
23
+ )
24
+ mcp_parser.add_argument(
25
+ "action",
26
+ nargs="?",
27
+ default="list",
28
+ choices=["list", "ls", "status", "probe"],
29
+ help="Action to perform (default: list).",
30
+ )
31
+ mcp_parser.add_argument(
32
+ "--json",
33
+ action="store_true",
34
+ dest="json_output",
35
+ help="Output in JSON format.",
36
+ )
37
+ mcp_parser.add_argument(
38
+ "--probe",
39
+ action="store_true",
40
+ help="Actively probe configured servers to inspect tools and health status.",
41
+ )
42
+
43
+
44
+ def run_mcp_command(args: argparse.Namespace | None = None) -> int:
45
+ """Execute the `opscloud mcp` CLI command."""
46
+ console = Console()
47
+ action = getattr(args, "action", "list") if args else "list"
48
+ json_output = getattr(args, "json_output", False) if args else False
49
+ do_probe = getattr(args, "probe", False) if args else False
50
+
51
+ if action in ("status", "probe"):
52
+ do_probe = True
53
+
54
+ discovery = MCPDiscovery()
55
+ configs = discovery.discover()
56
+
57
+ if not configs:
58
+ if json_output:
59
+ console.print(json.dumps({"mcp_servers": [], "total_servers": 0}, indent=2))
60
+ else:
61
+ console.print("\n[yellow]No MCP servers configured.[/yellow]")
62
+ console.print("Add servers in [bold].mcp.json[/bold] or [bold]~/.opscloud/.mcp.json[/bold].\n")
63
+ return 0
64
+
65
+ server_infos = None
66
+ if do_probe:
67
+ with console.status("[cyan]Probing MCP servers...[/cyan]"):
68
+ server_infos = asyncio.run(preload_mcp_metadata(configs))
69
+
70
+ if json_output:
71
+ if server_infos:
72
+ payload = {
73
+ "mcp_servers": [s.to_dict() for s in server_infos],
74
+ "total_servers": len(server_infos),
75
+ "total_tools": sum(s.tool_count for s in server_infos),
76
+ }
77
+ else:
78
+ payload = {
79
+ "mcp_servers": [
80
+ {"name": k, **v} for k, v in configs.items()
81
+ ],
82
+ "total_servers": len(configs),
83
+ }
84
+ console.print(json.dumps(payload, indent=2))
85
+ return 0
86
+
87
+ console.print("\n[bold cyan]Configured MCP Servers[/bold cyan]\n")
88
+ table = Table(show_header=True, header_style="bold magenta")
89
+ table.add_column("Status", width=8, justify="center")
90
+ table.add_column("Server Name", style="bold")
91
+ table.add_column("Transport", style="dim")
92
+ table.add_column("Command / URL")
93
+ table.add_column("Source", style="dim")
94
+ table.add_column("Tools", justify="right")
95
+
96
+ if server_infos:
97
+ for srv in server_infos:
98
+ if srv.status == "ok":
99
+ status_glyph = "[bold green]● OK[/bold green]"
100
+ elif srv.status == "disabled":
101
+ status_glyph = "[dim]○ OFF[/dim]"
102
+ elif srv.status == "unauthenticated":
103
+ status_glyph = "[bold yellow]▲ AUTH[/bold yellow]"
104
+ else:
105
+ status_glyph = "[bold red]✗ ERR[/bold red]"
106
+
107
+ cmd_or_url = srv.url or f"{srv.command} {' '.join(srv.args or [])}".strip()
108
+ table.add_row(
109
+ status_glyph,
110
+ srv.name,
111
+ srv.transport,
112
+ cmd_or_url or "-",
113
+ srv.source or "unknown",
114
+ str(srv.tool_count),
115
+ )
116
+ else:
117
+ for name, cfg in configs.items():
118
+ enabled = cfg.get("enabled", True)
119
+ status_glyph = "[green]● ON[/green]" if enabled else "[dim]○ OFF[/dim]"
120
+ transport = cfg.get("transport") or ("http" if cfg.get("url") else "stdio")
121
+ cmd_or_url = cfg.get("url") or f"{cfg.get('command', '')} {' '.join(cfg.get('args') or [])}".strip()
122
+ source = cfg.get("source", "project")
123
+ table.add_row(
124
+ status_glyph,
125
+ name,
126
+ transport,
127
+ cmd_or_url or "-",
128
+ source,
129
+ "-",
130
+ )
131
+
132
+ console.print(table)
133
+ if not do_probe:
134
+ console.print("\n[dim]Tip: Run `opscloud mcp probe` to test live server connections and tool schemas.[/dim]\n")
135
+ else:
136
+ console.print("")
137
+ return 0
@@ -0,0 +1,51 @@
1
+ """Threads management CLI command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from opscloud.output import OutputFormat, write_json
10
+ from opscloud.state.session import (
11
+ format_relative_timestamp,
12
+ format_timestamp,
13
+ list_threads,
14
+ )
15
+
16
+
17
+ def run_threads_command(output_format: OutputFormat = "text") -> int:
18
+ threads = asyncio.run(list_threads(include_message_count=True))
19
+
20
+ if output_format == "json":
21
+ write_json("threads list", threads)
22
+ return 0
23
+
24
+ console = Console()
25
+ console.print("\n[bold yellow]Saved Session Threads[/bold yellow]\n")
26
+ if not threads:
27
+ console.print("[dim]No saved sessions found in SQLite database.[/dim]\n")
28
+ return 0
29
+
30
+ table = Table(border_style="cyan", show_lines=False)
31
+ table.add_column("Thread ID", style="bold green", no_wrap=True)
32
+ table.add_column("Cloud / Env", style="bold magenta", no_wrap=True)
33
+ table.add_column("Msgs", justify="right", style="cyan", no_wrap=True)
34
+ table.add_column("Initial Prompt", style="white")
35
+ table.add_column("Updated", style="dim", no_wrap=True)
36
+
37
+ for t in threads:
38
+ tid = t.get("thread_id", "")
39
+ short_id = tid[:8] if len(tid) > 8 else tid
40
+ cloud = t.get("cloud_badge") or t.get("cloud_provider") or "-"
41
+ msg_count = str(t.get("message_count", 0))
42
+ prompt = t.get("initial_prompt") or "(No initial prompt)"
43
+ if len(prompt) > 50:
44
+ prompt = prompt[:47] + "..."
45
+ updated = format_relative_timestamp(t.get("updated_at")) or format_timestamp(t.get("updated_at"))
46
+
47
+ table.add_row(short_id, cloud, msg_count, prompt, updated)
48
+
49
+ console.print(table)
50
+ console.print("\n[dim]To resume a thread, run:[/dim] opscloud -r <thread_id>\n")
51
+ return 0
@@ -0,0 +1,154 @@
1
+ """Interactive TUI runner for OpsCloud."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from typing import Any
9
+
10
+ from opscloud.approval_mode import ApprovalMode
11
+ from opscloud.config.settings import sync_aws_env_aliases
12
+ from opscloud.server import ServerConfig, ServerProcess
13
+ from opscloud.ui.app import OpsCloudApp
14
+ from opscloud.utils.logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ def run_interactive(
20
+ *,
21
+ model: str | None = None,
22
+ approval_mode: ApprovalMode | str = ApprovalMode.MANUAL,
23
+ resume_thread: str | None = None,
24
+ cwd: str | None = None,
25
+ goal: str | None = None,
26
+ initial_prompt: str | None = None,
27
+ initial_skill: str | None = None,
28
+ startup_cmd: str | None = None,
29
+ read_only: bool = False,
30
+ aws_profile: str | None = None,
31
+ aws_region: str | None = None,
32
+ shell_allow_list: list[str] | None = None,
33
+ mcp_config_path: str | None = None,
34
+ no_mcp: bool = False,
35
+ trust_project_mcp: bool = False,
36
+ ) -> int:
37
+ """Launch interactive TUI session with background server and cloud context."""
38
+ if resume_thread:
39
+ from opscloud.utils.logger import bind_logging_to_thread
40
+
41
+ bind_logging_to_thread(resume_thread)
42
+
43
+ # 1. Startup command
44
+ if startup_cmd:
45
+ try:
46
+ print(f"[Running startup command: {startup_cmd}]", file=sys.stderr)
47
+ subprocess.run(startup_cmd, shell=True, check=False)
48
+ except Exception as e:
49
+ logger.warning("Startup command failed: %s", e)
50
+
51
+ # 2. AWS Profile & Region sync
52
+ from opscloud.config.cloud_profiles import get_active_aws_profile, get_active_aws_region
53
+
54
+ if not aws_profile:
55
+ aws_profile = get_active_aws_profile()
56
+
57
+ if not aws_region:
58
+ aws_region = get_active_aws_region()
59
+
60
+ if aws_profile:
61
+ os.environ["AWS_PROFILE"] = aws_profile
62
+ if aws_region:
63
+ os.environ["AWS_REGION"] = aws_region
64
+ if aws_profile or aws_region:
65
+ sync_aws_env_aliases()
66
+
67
+ from pathlib import Path
68
+ from opscloud.project_utils import ProjectContext
69
+
70
+ project_context = (
71
+ ProjectContext.from_user_cwd(Path(cwd))
72
+ if cwd is not None
73
+ else ProjectContext.from_user_cwd(Path.cwd())
74
+ )
75
+ resolved_cwd = str(project_context.user_cwd)
76
+
77
+ defer_server_start = False
78
+ resolved_model = model
79
+ if not resolved_model:
80
+ try:
81
+ from opscloud.model.factory import _get_default_model_spec
82
+
83
+ resolved_model = _get_default_model_spec()
84
+ except Exception:
85
+ resolved_model = None
86
+ defer_server_start = True
87
+
88
+ proc: ServerProcess | None = None
89
+ url: str | None = None
90
+ if not defer_server_start and resolved_model:
91
+ config = ServerConfig.from_cli_args(
92
+ project_context=project_context,
93
+ model=resolved_model,
94
+ interactive=True,
95
+ approval_mode=str(approval_mode),
96
+ read_only=read_only,
97
+ aws_profile=aws_profile,
98
+ aws_region=aws_region,
99
+ shell_allow_list=tuple(shell_allow_list) if shell_allow_list else None,
100
+ mcp_config_path=mcp_config_path,
101
+ no_mcp=no_mcp,
102
+ trust_project_mcp=trust_project_mcp,
103
+ )
104
+ proc = ServerProcess(config)
105
+ url = proc.start()
106
+
107
+ effective_prompt = initial_prompt
108
+ if initial_skill and not effective_prompt:
109
+ effective_prompt = f"Use skill {initial_skill}"
110
+
111
+ mcp_preload_kwargs: dict[str, Any] = {
112
+ "mcp_config_path": mcp_config_path,
113
+ "no_mcp": no_mcp,
114
+ "trust_project_mcp": trust_project_mcp,
115
+ }
116
+
117
+ try:
118
+ app = OpsCloudApp(
119
+ server_url=url,
120
+ server=proc,
121
+ server_proc=proc,
122
+ thread_id=resume_thread,
123
+ model=resolved_model,
124
+ approval_mode=approval_mode,
125
+ defer_server_start=defer_server_start,
126
+ goal=goal,
127
+ cwd=resolved_cwd,
128
+ mcp_preload_kwargs=mcp_preload_kwargs,
129
+ )
130
+ if effective_prompt and hasattr(app, "_initial_prompt"):
131
+ app._initial_prompt = effective_prompt
132
+
133
+ thread_id = app.run()
134
+ if thread_id and isinstance(thread_id, str) and thread_id != "local_session":
135
+ try:
136
+ from rich.console import Console
137
+ from rich.text import Text
138
+
139
+ console = Console()
140
+ console.print()
141
+ console.print("[dim]Resume this thread with:[/dim]")
142
+ hint = Text("opscloud -r ", style="cyan")
143
+ hint.append(thread_id, style="cyan")
144
+ console.print(hint)
145
+ except Exception:
146
+ pass
147
+ return 0
148
+ except Exception as e:
149
+ logger.critical("OpsCloud TUI run failed: %s", e, exc_info=True)
150
+ print(f"Error starting OpsCloud: {e}")
151
+ return 1
152
+ finally:
153
+ if proc is not None:
154
+ proc.stop()