alysis-code 0.13.0__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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,700 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import UTC, datetime
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ import typer
9
+
10
+ from ...config import ConfigError
11
+ from ...mcp.config import load_resolved_mcp_config
12
+ from ...mcp.manager import McpManager, create_mcp_manager
13
+ from ...mcp.models import ResolvedMcpServer
14
+ from ...mcp.oauth import (
15
+ McpOAuthError,
16
+ discover_authorization_server_metadata,
17
+ discover_authorization_server_metadata_from_url,
18
+ discover_protected_resource_metadata,
19
+ resolve_requested_scopes,
20
+ )
21
+ from ...mcp.oauth_runtime import perform_authorization_code_login
22
+ from ...mcp.oauth_store import (
23
+ McpOAuthTokenStoreError,
24
+ delete_oauth_token_record,
25
+ load_oauth_token_record,
26
+ )
27
+ from ...runtime_kind import RuntimeKind, normalize_runtime_kind
28
+ from ._shared import _console, _resolve_tool_workspace_root, _Table
29
+
30
+ if TYPE_CHECKING:
31
+ from rich.console import Console
32
+
33
+
34
+ mcp_app = typer.Typer(add_completion=False, help="Manual MCP inspection commands.")
35
+ mcp_prompts_app = typer.Typer(add_completion=False, help="Manual MCP prompt commands.")
36
+ mcp_auth_app = typer.Typer(add_completion=False, help="Manual MCP OAuth commands.")
37
+
38
+
39
+ _MANUAL_MCP_PROMPT_RUNTIME_KINDS = (
40
+ RuntimeKind.INTERACTIVE_CHAT,
41
+ RuntimeKind.ONE_SHOT,
42
+ RuntimeKind.FORGE_EXEC,
43
+ )
44
+
45
+
46
+ def _parse_manual_mcp_prompt_runtime(value: RuntimeKind | str) -> RuntimeKind:
47
+ try:
48
+ runtime_kind = normalize_runtime_kind(value)
49
+ except ConfigError as exc:
50
+ raise typer.BadParameter(str(exc)) from exc
51
+ if runtime_kind not in _MANUAL_MCP_PROMPT_RUNTIME_KINDS:
52
+ allowed = ", ".join(kind.value for kind in _MANUAL_MCP_PROMPT_RUNTIME_KINDS)
53
+ raise typer.BadParameter(
54
+ f"manual MCP prompt access supports only these runtime profiles: {allowed}."
55
+ )
56
+ return runtime_kind
57
+
58
+
59
+ def _manual_mcp_manager_for_path(*, path: Path, runtime_kind: RuntimeKind | str) -> McpManager:
60
+ workspace_root = _resolve_tool_workspace_root(path=path)
61
+ return create_mcp_manager(
62
+ workspace_root=workspace_root,
63
+ runtime_kind=_parse_manual_mcp_prompt_runtime(runtime_kind),
64
+ )
65
+
66
+
67
+ def _manual_mcp_resolved_config_for_path(*, path: Path) -> Any:
68
+ workspace_root = _resolve_tool_workspace_root(path=path)
69
+ return load_resolved_mcp_config(workspace_root=workspace_root)
70
+
71
+
72
+ def _resolve_mcp_server_by_id(*, resolved_config: Any, server_id: str) -> ResolvedMcpServer:
73
+ normalized_server_id = str(server_id or "").strip().lower()
74
+ for server in resolved_config.servers:
75
+ if server.id == normalized_server_id:
76
+ return server
77
+ raise typer.BadParameter(f"Unknown MCP server id: {server_id}")
78
+
79
+
80
+ def _require_http_oauth_server_for_auth(
81
+ *, server: ResolvedMcpServer, command_name: str
82
+ ) -> ResolvedMcpServer:
83
+ if server.transport != "http":
84
+ raise typer.BadParameter(
85
+ f"MCP auth {command_name} requires an HTTP MCP server: {server.id}"
86
+ )
87
+ if server.oauth is None:
88
+ raise typer.BadParameter(
89
+ f"MCP server '{server.id}' does not have an oauth block in user config."
90
+ )
91
+ return server
92
+
93
+
94
+ def _format_mcp_oauth_timestamp(value: datetime | None) -> str:
95
+ if value is None:
96
+ return "-"
97
+ return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
98
+
99
+
100
+ def _mcp_oauth_expiry_summary(record: Any | None) -> str:
101
+ if record is None:
102
+ return "-"
103
+ expires_text = _format_mcp_oauth_timestamp(record.expires_at)
104
+ if record.expires_at <= datetime.now(UTC):
105
+ return f"expired ({expires_text})"
106
+ return expires_text
107
+
108
+
109
+ def _mcp_auth_status_table(*, rows: list[dict[str, str]]) -> Any:
110
+ table = _Table(title=f"MCP OAuth Status ({len(rows)})")
111
+ table.add_column("server_id", no_wrap=True)
112
+ table.add_column("auth_enabled", no_wrap=True)
113
+ table.add_column("token", no_wrap=True)
114
+ table.add_column("expires")
115
+ table.add_column("granted_scopes")
116
+ for row in rows:
117
+ table.add_row(
118
+ row["server_id"],
119
+ row["auth_enabled"],
120
+ row["token"],
121
+ row["expires"],
122
+ row["granted_scopes"],
123
+ )
124
+ return table
125
+
126
+
127
+ def _build_mcp_auth_status_rows(
128
+ *, resolved_config: Any, server_id: str | None
129
+ ) -> list[dict[str, str]]:
130
+ selected_servers: list[ResolvedMcpServer]
131
+ if server_id is None:
132
+ selected_servers = [
133
+ server
134
+ for server in resolved_config.servers
135
+ if server.transport == "http" and server.oauth is not None
136
+ ]
137
+ else:
138
+ selected_servers = [
139
+ _resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id)
140
+ ]
141
+ rows: list[dict[str, str]] = []
142
+ for server in selected_servers:
143
+ token_record = None
144
+ if server.transport == "http" and server.oauth is not None:
145
+ token_record = load_oauth_token_record(server.id)
146
+ rows.append(
147
+ {
148
+ "server_id": server.id,
149
+ "auth_enabled": "yes"
150
+ if server.transport == "http" and server.oauth is not None
151
+ else "no",
152
+ "token": "present" if token_record is not None else "absent",
153
+ "expires": _mcp_oauth_expiry_summary(token_record),
154
+ "granted_scopes": (
155
+ ", ".join(token_record.granted_scopes)
156
+ if token_record and token_record.granted_scopes
157
+ else "-"
158
+ ),
159
+ }
160
+ )
161
+ return rows
162
+
163
+
164
+ def _parse_prompt_cli_arguments(values: list[str]) -> dict[str, str]:
165
+ parsed: dict[str, str] = {}
166
+ for raw_item in values:
167
+ item = str(raw_item or "")
168
+ if "=" not in item:
169
+ raise typer.BadParameter("--arg values must use key=value.")
170
+ raw_key, raw_value = item.split("=", 1)
171
+ key = raw_key.strip()
172
+ if not key:
173
+ raise typer.BadParameter("--arg keys cannot be empty.")
174
+ if key in parsed:
175
+ raise typer.BadParameter(f"duplicate --arg key: {key}")
176
+ parsed[key] = raw_value
177
+ return parsed
178
+
179
+
180
+ def _print_json_payload(*, console: Console, payload: dict[str, Any]) -> None:
181
+ console.file.write(json.dumps(payload, ensure_ascii=True, indent=2, sort_keys=True) + "\n")
182
+ console.file.flush()
183
+
184
+
185
+ def _mcp_prompt_argument_label(arguments: list[dict[str, Any]]) -> str:
186
+ parts: list[str] = []
187
+ for raw_item in arguments:
188
+ if not isinstance(raw_item, dict):
189
+ continue
190
+ name = str(raw_item.get("name") or "").strip()
191
+ if not name:
192
+ continue
193
+ required = bool(raw_item.get("required"))
194
+ parts.append(f"{name}{'*' if required else ''}")
195
+ return ", ".join(parts) if parts else "-"
196
+
197
+
198
+ def _mcp_prompt_list_table(*, prompts: list[dict[str, Any]]) -> Any:
199
+ table = _Table(title=f"MCP Prompts ({len(prompts)})")
200
+ table.add_column("server_id", no_wrap=True)
201
+ table.add_column("name", no_wrap=True)
202
+ table.add_column("title", no_wrap=True)
203
+ table.add_column("arguments")
204
+ table.add_column("description")
205
+ for prompt in prompts:
206
+ title = str(prompt.get("title") or "-").strip() or "-"
207
+ description = str(prompt.get("description") or "-").strip() or "-"
208
+ table.add_row(
209
+ str(prompt.get("server_id") or "").strip(),
210
+ str(prompt.get("name") or "").strip(),
211
+ title,
212
+ _mcp_prompt_argument_label(list(prompt.get("arguments") or [])),
213
+ description,
214
+ )
215
+ return table
216
+
217
+
218
+ def _mcp_status_table(*, rows: list[dict[str, str]]) -> Any:
219
+ table = _Table(title=f"MCP Status ({len(rows)})")
220
+ table.add_column("server_id", no_wrap=True)
221
+ table.add_column("transport", no_wrap=True)
222
+ table.add_column("tools")
223
+ table.add_column("resources")
224
+ table.add_column("prompts")
225
+ table.add_column("operator_action")
226
+ for row in rows:
227
+ table.add_row(
228
+ row["server_id"],
229
+ row["transport"],
230
+ row["tools"],
231
+ row["resources"],
232
+ row["prompts"],
233
+ row["operator_action"],
234
+ )
235
+ return table
236
+
237
+
238
+ def _mcp_status_surface_label(*, loaded: bool, count: int | None, stale: bool) -> str:
239
+ if loaded:
240
+ count_label = str(count if count is not None else 0)
241
+ label = f"loaded:{count_label}"
242
+ else:
243
+ label = "not_loaded"
244
+ if stale:
245
+ label += " stale:yes"
246
+ return label
247
+
248
+
249
+ def _mcp_status_payload(
250
+ *,
251
+ manager: McpManager,
252
+ bootstrap_errors: list[str],
253
+ prompt_errors: dict[str, str],
254
+ ) -> dict[str, Any]:
255
+ metadata = manager.catalog_snapshot_metadata()
256
+ server_catalogs = {
257
+ str(entry.get("server_id") or ""): entry
258
+ for entry in metadata.get("server_catalogs") or []
259
+ if isinstance(entry, dict)
260
+ }
261
+ prompt_catalogs = {
262
+ str(entry.get("server_id") or ""): entry
263
+ for entry in metadata.get("prompt_server_catalogs") or []
264
+ if isinstance(entry, dict)
265
+ }
266
+ prompt_enabled_ids = {
267
+ str(server_id) for server_id in metadata.get("prompt_enabled_server_ids") or []
268
+ }
269
+ tool_stale_ids = {str(server_id) for server_id in metadata.get("tool_stale_server_ids") or []}
270
+ resource_stale_ids = {
271
+ str(server_id) for server_id in metadata.get("resource_stale_server_ids") or []
272
+ }
273
+ prompt_stale_ids = {
274
+ str(server_id) for server_id in metadata.get("prompt_stale_server_ids") or []
275
+ }
276
+ rows: list[dict[str, str]] = []
277
+ json_rows: list[dict[str, Any]] = []
278
+ for server in manager.active_servers:
279
+ server_catalog = server_catalogs.get(server.id)
280
+ prompt_catalog = prompt_catalogs.get(server.id)
281
+ tool_loaded = server_catalog is not None
282
+ tool_count = (
283
+ int(server_catalog.get("exposed_tool_count") or 0)
284
+ if server_catalog is not None
285
+ else None
286
+ )
287
+ resource_loaded = bool(
288
+ server_catalog is not None and server_catalog.get("resources_snapshot_loaded")
289
+ )
290
+ resource_count = (
291
+ int(server_catalog.get("snapshotted_resource_count") or 0)
292
+ if resource_loaded and server_catalog is not None
293
+ else None
294
+ )
295
+ prompt_loaded = bool(prompt_catalog and prompt_catalog.get("prompt_snapshot_loaded"))
296
+ prompt_failed = bool(prompt_catalog and prompt_catalog.get("prompt_snapshot_failed"))
297
+ prompt_count = (
298
+ int(prompt_catalog.get("snapshotted_prompt_count") or 0)
299
+ if prompt_catalog is not None
300
+ else None
301
+ )
302
+ prompt_stale = server.id in prompt_stale_ids
303
+ tool_stale = server.id in tool_stale_ids
304
+ resource_stale = server.id in resource_stale_ids
305
+ actions: list[str] = []
306
+ if tool_stale:
307
+ actions.append("new session for tools")
308
+ if resource_stale:
309
+ actions.append("new session for resources")
310
+ if prompt_stale:
311
+ actions.append("mcp prompts --refresh")
312
+ if server.id in prompt_errors:
313
+ actions.append("prompt snapshot error")
314
+ action_label = "; ".join(actions) if actions else "-"
315
+ if prompt_failed:
316
+ prompt_label = "failed"
317
+ elif server.id in prompt_enabled_ids:
318
+ prompt_label = _mcp_status_surface_label(
319
+ loaded=prompt_loaded,
320
+ count=prompt_count,
321
+ stale=prompt_stale,
322
+ )
323
+ prompt_label += " refresh:yes"
324
+ else:
325
+ prompt_label = "disabled"
326
+ row = {
327
+ "server_id": server.id,
328
+ "transport": server.transport,
329
+ "tools": _mcp_status_surface_label(
330
+ loaded=tool_loaded,
331
+ count=tool_count,
332
+ stale=tool_stale,
333
+ ),
334
+ "resources": _mcp_status_surface_label(
335
+ loaded=resource_loaded,
336
+ count=resource_count,
337
+ stale=resource_stale,
338
+ ),
339
+ "prompts": prompt_label,
340
+ "operator_action": action_label,
341
+ }
342
+ rows.append(row)
343
+ json_rows.append(
344
+ {
345
+ "server_id": server.id,
346
+ "transport": server.transport,
347
+ "tools_snapshot_loaded": tool_loaded,
348
+ "tools_snapshot_count": tool_count,
349
+ "tools_snapshot_stale": tool_stale,
350
+ "resources_snapshot_loaded": resource_loaded,
351
+ "resources_snapshot_count": resource_count,
352
+ "resources_snapshot_stale": resource_stale,
353
+ "prompts_enabled": server.id in prompt_enabled_ids,
354
+ "prompts_snapshot_loaded": prompt_loaded,
355
+ "prompts_snapshot_count": prompt_count,
356
+ "prompts_snapshot_stale": prompt_stale,
357
+ "manual_prompt_refresh_available": server.id in prompt_enabled_ids,
358
+ "operator_action": actions,
359
+ "prompt_error": prompt_errors.get(server.id),
360
+ }
361
+ )
362
+ return {
363
+ "rows": json_rows,
364
+ "table_rows": rows,
365
+ "bootstrap_errors": bootstrap_errors,
366
+ "prompt_errors": prompt_errors,
367
+ "metadata": metadata,
368
+ }
369
+
370
+
371
+ def _print_prompt_get_payload(*, console: Console, payload: dict[str, Any]) -> None:
372
+ from rich.text import Text
373
+
374
+ metadata = _Table(title=f"MCP Prompt: {payload['server_id']}/{payload['name']}")
375
+ metadata.add_column("field")
376
+ metadata.add_column("value")
377
+ metadata.add_row("server_id", str(payload.get("server_id") or ""))
378
+ metadata.add_row("name", str(payload.get("name") or ""))
379
+ metadata.add_row("title", str(payload.get("title") or "-"))
380
+ metadata.add_row("description", str(payload.get("description") or "-"))
381
+ metadata.add_row(
382
+ "arguments",
383
+ _mcp_prompt_argument_label(list(payload.get("arguments") or [])),
384
+ )
385
+ metadata.add_row("message_count", str(payload.get("message_count") or 0))
386
+ metadata.add_row("content_summary", str(payload.get("content_summary") or "-"))
387
+ if payload.get("refresh_performed"):
388
+ metadata.add_row("refresh_performed", "yes")
389
+ if payload.get("snapshot_stale"):
390
+ metadata.add_row("snapshot_stale", "yes")
391
+ if isinstance(payload.get("applied_arguments"), dict):
392
+ metadata.add_row(
393
+ "applied_arguments",
394
+ json.dumps(payload["applied_arguments"], ensure_ascii=True, sort_keys=True),
395
+ )
396
+ console.print(metadata)
397
+
398
+ message_rows = list(payload.get("messages") or [])
399
+ if not message_rows:
400
+ return
401
+ messages_table = _Table(title="Prompt Messages")
402
+ messages_table.add_column("#", no_wrap=True)
403
+ messages_table.add_column("role", no_wrap=True)
404
+ messages_table.add_column("summary")
405
+ messages_table.add_column("text")
406
+ for index, raw_message in enumerate(message_rows, start=1):
407
+ if not isinstance(raw_message, dict):
408
+ continue
409
+ messages_table.add_row(
410
+ str(index),
411
+ str(raw_message.get("role") or "").strip(),
412
+ str(raw_message.get("content_summary") or "-"),
413
+ Text(str(raw_message.get("text") or "-")),
414
+ )
415
+ console.print(messages_table)
416
+
417
+
418
+ @mcp_auth_app.command("login")
419
+ def mcp_auth_login(
420
+ server_id: str = typer.Argument(..., help="Exact MCP server id."),
421
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
422
+ ) -> None:
423
+ console = _console()
424
+ try:
425
+ resolved_config = _manual_mcp_resolved_config_for_path(path=path)
426
+ server = _require_http_oauth_server_for_auth(
427
+ server=_resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id),
428
+ command_name="login",
429
+ )
430
+ assert server.url is not None
431
+ assert server.oauth is not None
432
+ configured_scopes = tuple(server.oauth.scopes or ())
433
+ protected_metadata = None
434
+ if not configured_scopes or server.oauth.authorization_server_url is None:
435
+ protected_metadata = discover_protected_resource_metadata(
436
+ server_id=server.id,
437
+ resource_server_url=server.url,
438
+ )
439
+ if server.oauth.authorization_server_url is not None:
440
+ metadata = discover_authorization_server_metadata_from_url(
441
+ server_id=server.id,
442
+ authorization_server_url=server.oauth.authorization_server_url,
443
+ )
444
+ elif protected_metadata is not None:
445
+ metadata = discover_authorization_server_metadata_from_url(
446
+ server_id=server.id,
447
+ authorization_server_url=protected_metadata.authorization_servers[0],
448
+ )
449
+ else:
450
+ metadata = discover_authorization_server_metadata(
451
+ server_id=server.id,
452
+ resource_server_url=server.url,
453
+ )
454
+ requested_scopes = resolve_requested_scopes(
455
+ configured_scopes=configured_scopes,
456
+ challenge_scope=None,
457
+ metadata_scopes_supported=(
458
+ protected_metadata.scopes_supported if protected_metadata is not None else None
459
+ ),
460
+ existing_granted_scopes=None,
461
+ purpose="login",
462
+ )
463
+ result = perform_authorization_code_login(
464
+ server_id=server.id,
465
+ authorization_server_metadata=metadata,
466
+ resource_server_url=server.url,
467
+ client_id=server.oauth.client_id,
468
+ scopes=requested_scopes,
469
+ redirect_host=server.oauth.redirect_host,
470
+ redirect_port=server.oauth.redirect_port,
471
+ output_write=lambda message: console.print(message, highlight=False),
472
+ )
473
+ scopes_text = (
474
+ ", ".join(result.token_record.granted_scopes)
475
+ if result.token_record.granted_scopes
476
+ else "-"
477
+ )
478
+ console.print(f"OAuth login succeeded for MCP server '{server.id}'.")
479
+ console.print(f"Expires: {_format_mcp_oauth_timestamp(result.token_record.expires_at)}")
480
+ console.print(f"Granted scopes: {scopes_text}")
481
+ except (ConfigError, McpOAuthError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
482
+ message = str(exc).replace("\n", " ")
483
+ console.print(f"[red]{message}[/red]", soft_wrap=True)
484
+ raise typer.Exit(code=1) from exc
485
+
486
+
487
+ @mcp_auth_app.command("status")
488
+ def mcp_auth_status(
489
+ server: str | None = typer.Option(
490
+ None, "--server", help="Optional exact MCP server id filter."
491
+ ),
492
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
493
+ ) -> None:
494
+ console = _console()
495
+ try:
496
+ resolved_config = _manual_mcp_resolved_config_for_path(path=path)
497
+ rows = _build_mcp_auth_status_rows(resolved_config=resolved_config, server_id=server)
498
+ if not rows:
499
+ console.print("No OAuth-enabled HTTP MCP servers configured.")
500
+ return
501
+ console.print(_mcp_auth_status_table(rows=rows))
502
+ except (ConfigError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
503
+ console.print(f"[red]{exc}[/red]")
504
+ raise typer.Exit(code=1) from exc
505
+
506
+
507
+ @mcp_auth_app.command("logout")
508
+ def mcp_auth_logout(
509
+ server_id: str = typer.Argument(..., help="Exact MCP server id."),
510
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
511
+ ) -> None:
512
+ console = _console()
513
+ try:
514
+ resolved_config = _manual_mcp_resolved_config_for_path(path=path)
515
+ server = _require_http_oauth_server_for_auth(
516
+ server=_resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id),
517
+ command_name="logout",
518
+ )
519
+ removed = delete_oauth_token_record(server.id)
520
+ if removed:
521
+ console.print(f"Cleared stored OAuth tokens for MCP server '{server.id}'.")
522
+ return
523
+ console.print(f"No stored OAuth tokens found for MCP server '{server.id}'.")
524
+ except (ConfigError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
525
+ console.print(f"[red]{exc}[/red]")
526
+ raise typer.Exit(code=1) from exc
527
+
528
+
529
+ @mcp_app.command("status")
530
+ def mcp_status(
531
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
532
+ runtime: str = typer.Option(
533
+ RuntimeKind.INTERACTIVE_CHAT.value,
534
+ "--runtime",
535
+ help=(
536
+ "Runtime profile used to resolve enabled MCP servers: "
537
+ "interactive_chat, one_shot, or forge_exec."
538
+ ),
539
+ ),
540
+ as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
541
+ ) -> None:
542
+ console = _console()
543
+ manager: McpManager | None = None
544
+ bootstrap_errors: list[str] = []
545
+ prompt_errors: dict[str, str] = {}
546
+ try:
547
+ manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
548
+ try:
549
+ _ = manager.tool_bindings
550
+ except (ConfigError, RuntimeError) as exc:
551
+ bootstrap_errors.append(str(exc))
552
+ prompt_enabled_ids = [
553
+ str(server_id)
554
+ for server_id in manager.catalog_snapshot_metadata().get("prompt_enabled_server_ids")
555
+ or []
556
+ ]
557
+ for prompt_server_id in prompt_enabled_ids:
558
+ try:
559
+ manager.list_prompts(server_id=prompt_server_id, limit=1)
560
+ except (ConfigError, RuntimeError) as exc:
561
+ prompt_errors[prompt_server_id] = str(exc)
562
+ payload = _mcp_status_payload(
563
+ manager=manager,
564
+ bootstrap_errors=bootstrap_errors,
565
+ prompt_errors=prompt_errors,
566
+ )
567
+ if as_json:
568
+ _print_json_payload(
569
+ console=console,
570
+ payload={key: value for key, value in payload.items() if key != "table_rows"},
571
+ )
572
+ return
573
+ table_rows = list(payload.get("table_rows") or [])
574
+ if not table_rows:
575
+ console.print("No active MCP servers for this runtime.")
576
+ return
577
+ console.print(_mcp_status_table(rows=table_rows))
578
+ for error in bootstrap_errors:
579
+ console.print(f"[yellow]Snapshot warning:[/yellow] {error}")
580
+ for server_id, error in prompt_errors.items():
581
+ console.print(f"[yellow]Prompt warning for {server_id}:[/yellow] {error}")
582
+ except ConfigError as exc:
583
+ console.print(f"[red]{exc}[/red]")
584
+ raise typer.Exit(code=1) from exc
585
+ finally:
586
+ if manager is not None:
587
+ manager.close()
588
+
589
+
590
+ @mcp_prompts_app.command("list")
591
+ def mcp_prompts_list(
592
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
593
+ runtime: str = typer.Option(
594
+ RuntimeKind.INTERACTIVE_CHAT.value,
595
+ "--runtime",
596
+ help=(
597
+ "Runtime profile used to resolve enabled MCP servers: "
598
+ "interactive_chat, one_shot, or forge_exec."
599
+ ),
600
+ ),
601
+ server: str | None = typer.Option(
602
+ None, "--server", help="Optional exact MCP server id filter."
603
+ ),
604
+ query: str | None = typer.Option(None, "--query", help="Optional text filter."),
605
+ limit: int = typer.Option(20, "--limit", min=1, max=50, help="Maximum prompts to display."),
606
+ refresh: bool = typer.Option(
607
+ False,
608
+ "--refresh",
609
+ help="Explicitly refresh the targeted prompt snapshot before listing.",
610
+ ),
611
+ as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
612
+ ) -> None:
613
+ console = _console()
614
+ manager: McpManager | None = None
615
+ try:
616
+ manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
617
+ try:
618
+ payload = manager.list_prompts(
619
+ server_id=server,
620
+ query=query,
621
+ limit=limit,
622
+ refresh=refresh,
623
+ )
624
+ except (ConfigError, RuntimeError) as exc:
625
+ console.print(f"[red]{exc}[/red]")
626
+ raise typer.Exit(code=1) from exc
627
+ prompts = list(payload.get("prompts") or [])
628
+ if as_json:
629
+ _print_json_payload(console=console, payload=payload)
630
+ return
631
+ if not prompts:
632
+ console.print("No MCP prompts available.")
633
+ return
634
+ console.print(_mcp_prompt_list_table(prompts=prompts))
635
+ console.print(
636
+ f"[dim]Returned:[/dim] {payload['returned_count']} "
637
+ f"[dim]Matching:[/dim] {payload['matching_count']} "
638
+ f"[dim]Snapshot:[/dim] {payload['total_snapshot_count']}"
639
+ )
640
+ if payload.get("refresh_performed"):
641
+ console.print("[dim]Prompt snapshot refreshed explicitly.[/dim]")
642
+ stale_server_ids = payload.get("stale_server_ids")
643
+ if stale_server_ids:
644
+ console.print(
645
+ "[yellow]Stale prompt snapshot:[/yellow] "
646
+ f"{', '.join(str(item) for item in stale_server_ids)}"
647
+ )
648
+ except ConfigError as exc:
649
+ console.print(f"[red]{exc}[/red]")
650
+ raise typer.Exit(code=1) from exc
651
+ finally:
652
+ if manager is not None:
653
+ manager.close()
654
+
655
+
656
+ @mcp_prompts_app.command("get")
657
+ def mcp_prompts_get(
658
+ server_id: str = typer.Argument(..., help="Exact MCP server id."),
659
+ prompt_name: str = typer.Argument(..., help="Exact MCP prompt name."),
660
+ path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
661
+ runtime: str = typer.Option(
662
+ RuntimeKind.INTERACTIVE_CHAT.value,
663
+ "--runtime",
664
+ help=(
665
+ "Runtime profile used to resolve enabled MCP servers: "
666
+ "interactive_chat, one_shot, or forge_exec."
667
+ ),
668
+ ),
669
+ arg: list[str] = typer.Option(None, "--arg", help="Prompt string argument as key=value."),
670
+ refresh: bool = typer.Option(
671
+ False,
672
+ "--refresh",
673
+ help="Explicitly refresh this server's prompt snapshot before fetching.",
674
+ ),
675
+ as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
676
+ ) -> None:
677
+ console = _console()
678
+ manager: McpManager | None = None
679
+ try:
680
+ manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
681
+ try:
682
+ payload = manager.get_prompt(
683
+ server_id=server_id,
684
+ prompt_name=prompt_name,
685
+ arguments=_parse_prompt_cli_arguments(list(arg or [])),
686
+ refresh=refresh,
687
+ )
688
+ except (ConfigError, RuntimeError) as exc:
689
+ console.print(f"[red]{exc}[/red]")
690
+ raise typer.Exit(code=1) from exc
691
+ if as_json:
692
+ _print_json_payload(console=console, payload=payload)
693
+ return
694
+ _print_prompt_get_payload(console=console, payload=payload)
695
+ except ConfigError as exc:
696
+ console.print(f"[red]{exc}[/red]")
697
+ raise typer.Exit(code=1) from exc
698
+ finally:
699
+ if manager is not None:
700
+ manager.close()