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,519 @@
1
+ """Full-screen TUI front-end for the first-run setup wizard.
2
+
3
+ A prompt_toolkit ``Application`` that walks a :class:`SetupFlow` through the
4
+ onboarding steps using the same visual grammar as the chat TUI: the white owl on
5
+ the welcome/finish screens, the green ``#3fb950`` accent, the reusable picker
6
+ renderer, and a pinned footer. The flow owns all of the wizard *logic*; this
7
+ module only paints its :class:`Screen` and routes key events back into it.
8
+
9
+ Focus is held permanently on a single hidden ``TextArea`` (shown only on input
10
+ steps). Navigation in list/confirm/message screens runs through eager global key
11
+ bindings — exactly how the chat TUI's picker works — so we never juggle focus
12
+ across the worker thread that runs the network/validation steps.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import threading
18
+ import time
19
+ from typing import Any
20
+
21
+ from prompt_toolkit.application import Application, run_in_terminal
22
+ from prompt_toolkit.application.current import get_app
23
+ from prompt_toolkit.cursor_shapes import CursorShape
24
+ from prompt_toolkit.data_structures import Point
25
+ from prompt_toolkit.filters import Condition
26
+ from prompt_toolkit.formatted_text import FormattedText, to_formatted_text
27
+ from prompt_toolkit.key_binding import KeyBindings
28
+ from prompt_toolkit.layout import Layout, WindowAlign
29
+ from prompt_toolkit.layout.containers import ConditionalContainer, HSplit, ScrollOffsets, Window
30
+ from prompt_toolkit.layout.controls import FormattedTextControl
31
+ from prompt_toolkit.layout.margins import ScrollbarMargin
32
+ from prompt_toolkit.layout.processors import (
33
+ BeforeInput,
34
+ ConditionalProcessor,
35
+ PasswordProcessor,
36
+ )
37
+ from prompt_toolkit.styles import Style, merge_styles
38
+ from prompt_toolkit.widgets import Frame, TextArea
39
+
40
+ from .app import _ACCENT, _SPINNER_FRAMES, _render_picker_rows, _wrap_line
41
+ from .app import _STYLE as _CHAT_STYLE
42
+ from .owl import load_owl_animation
43
+ from .setup_flow import Row, SetupFlow
44
+
45
+ # Tones map to concrete styles. Green is the only accent — degraded/neutral
46
+ # states stay plain or amber so the accent reads clearly (same discipline as the
47
+ # chat TUI's panels).
48
+ _TONE_STYLE = {
49
+ "ok": "class:setup.ok",
50
+ "warn": "class:setup.warn",
51
+ "err": "class:setup.err",
52
+ "dim": "class:setup.dim",
53
+ "plain": "class:setup.text",
54
+ }
55
+
56
+ _SETUP_STYLE = Style.from_dict(
57
+ {
58
+ "setup.header": f"bold {_ACCENT}",
59
+ "setup.headerdim": "#6e7681",
60
+ "setup.title": f"bold {_ACCENT}",
61
+ "setup.subtitle": "#8b949e",
62
+ "setup.text": "#c9d1d9",
63
+ "setup.dim": "#6e7681",
64
+ # Prominent call-to-action (e.g. "Press Enter to begin") on message screens.
65
+ "setup.cta": f"bold {_ACCENT}",
66
+ "setup.ok": f"bold {_ACCENT}",
67
+ "setup.warn": "#d19a66",
68
+ "setup.err": "#e06c75",
69
+ "setup.spinner": f"bold {_ACCENT}",
70
+ "setup.footer.brand": f"bold {_ACCENT}",
71
+ "setup.footer.dim": "#6e7681",
72
+ "setup.footer.progress": "#8b949e",
73
+ }
74
+ )
75
+
76
+ _STYLE = merge_styles([_CHAT_STYLE, _SETUP_STYLE])
77
+
78
+
79
+ def _row_to_dict(row: Row) -> dict[str, Any]:
80
+ return {
81
+ "label": row.label,
82
+ "description": row.description,
83
+ "value": row.value,
84
+ "current": row.current,
85
+ "kind": row.kind,
86
+ "tone": row.tone,
87
+ }
88
+
89
+
90
+ def run_setup_tui(
91
+ *,
92
+ owl_color: bool = True,
93
+ input: Any | None = None,
94
+ output: Any | None = None,
95
+ inline_busy: bool = False,
96
+ ) -> bool:
97
+ """Run the alt-screen setup wizard.
98
+
99
+ Returns ``True`` when setup was saved, ``False`` when the user cancelled.
100
+ Raises on infrastructure failure (no usable terminal, prompt_toolkit error)
101
+ so the caller can fall back to the classic Rich wizard.
102
+
103
+ ``inline_busy`` runs the network/validation steps synchronously inside the
104
+ key handler instead of on a worker thread; tests use it so a pre-loaded key
105
+ sequence drives the whole flow deterministically.
106
+ """
107
+ flow = SetupFlow()
108
+ owl = load_owl_animation(color_enabled=owl_color)
109
+
110
+ spinner = {"i": 0}
111
+ busy = {"running": False}
112
+ report = {"text": ""}
113
+ # Cached cursor row for the scrollable body so the selected list option stays
114
+ # in view (the chat TUI's cursor-pin trick, mirrored here).
115
+ body_cursor = {"row": 0}
116
+ # Tracks the last stage the input box was primed for, so we reset/seed the
117
+ # buffer exactly once per stage change (never mid-edit).
118
+ last_stage: dict[str, Any] = {"v": None}
119
+
120
+ def _invalidate() -> None:
121
+ try:
122
+ get_app().invalidate()
123
+ except Exception:
124
+ pass
125
+
126
+ flow.set_report(lambda msg: (report.__setitem__("text", str(msg)), _invalidate()))
127
+
128
+ # ----------------------------------------------------------- rendering
129
+ def _width(win: Window, fallback: int = 80) -> int:
130
+ info = win.render_info
131
+ if info is not None and info.window_width:
132
+ return int(info.window_width)
133
+ try:
134
+ return max(20, get_app().output.get_size().columns - 2)
135
+ except Exception:
136
+ return fallback
137
+
138
+ def _header_text() -> FormattedText:
139
+ return FormattedText(
140
+ [
141
+ ("class:setup.header", "◇ alysis"),
142
+ ("class:setup.headerdim", " · setup"),
143
+ ]
144
+ )
145
+
146
+ def _message_fragments() -> FormattedText:
147
+ scr = flow.screen()
148
+ frags: list[tuple[str, str]] = [("", "\n")]
149
+ if scr.stage == "welcome":
150
+ owl_ansi = owl.current_ansi()
151
+ if owl_ansi is not None:
152
+ frags.extend(to_formatted_text(owl_ansi))
153
+ frags.append(("", "\n\n"))
154
+ if scr.title:
155
+ frags.append(("class:setup.title", scr.title))
156
+ frags.append(("", "\n"))
157
+ if scr.subtitle:
158
+ frags.append(("class:setup.subtitle", scr.subtitle))
159
+ frags.append(("", "\n"))
160
+ frags.append(("", "\n"))
161
+ for text, tone in scr.lines:
162
+ frags.append((_TONE_STYLE.get(tone, "class:setup.text"), text))
163
+ frags.append(("", "\n"))
164
+ if scr.hint:
165
+ # The call-to-action stands out in bold green with a blank line above.
166
+ frags.append(("", "\n\n"))
167
+ frags.append(("class:setup.cta", scr.hint))
168
+ frags.append(("", "\n"))
169
+ return FormattedText(frags)
170
+
171
+ def _body_fragments() -> FormattedText:
172
+ scr = flow.screen()
173
+ width = _width(body_window)
174
+ rows: list[list[tuple[str, str]]] = []
175
+ # Title + subtitle header. Both are pre-wrapped to the content width so
176
+ # header_lines equals the true screen-row count — the cursor-pin scroll
177
+ # math (which counts logical rows) requires every emitted row <= width.
178
+ if scr.title:
179
+ for line in _wrap_line(scr.title, width):
180
+ rows.append([("class:setup.title", line)])
181
+ if scr.subtitle:
182
+ for line in _wrap_line(scr.subtitle, width):
183
+ rows.append([("class:setup.subtitle", line)])
184
+ rows.append([("", "")]) # blank spacer
185
+ header_lines = len(rows)
186
+ sel_line = header_lines
187
+
188
+ if scr.mode == "list" and scr.rows:
189
+ picker = _render_picker_rows(
190
+ [_row_to_dict(r) for r in scr.rows], scr.index, width, hint=scr.hint
191
+ )
192
+ located = next(
193
+ (i for i, prow in enumerate(picker) if any("selcaret" in s for s, _ in prow)),
194
+ 0,
195
+ )
196
+ sel_line = header_lines + located
197
+ rows.extend(picker)
198
+ elif scr.mode == "confirm":
199
+ for text, tone in scr.lines:
200
+ for line in _wrap_line(text, width):
201
+ rows.append([(_TONE_STYLE.get(tone, "class:setup.text"), line)])
202
+ rows.append([("", "")])
203
+ if scr.hint:
204
+ rows.append([("class:setup.dim", scr.hint)])
205
+ elif scr.mode == "input":
206
+ if scr.input_label:
207
+ rows.append([("class:setup.text", scr.input_label + ":")])
208
+ if scr.input_default:
209
+ rows.append(
210
+ [("class:setup.dim", f"default: {scr.input_default} (Enter to accept)")]
211
+ )
212
+ elif scr.mode == "busy":
213
+ frame = _SPINNER_FRAMES[spinner["i"] % len(_SPINNER_FRAMES)]
214
+ detail = report["text"] or scr.busy_label
215
+ rows.append([("class:setup.spinner", f"{frame} "), ("class:setup.text", detail)])
216
+
217
+ body_cursor["row"] = sel_line
218
+ out: list[tuple[str, str]] = []
219
+ for row in rows:
220
+ out.extend(row)
221
+ out.append(("", "\n"))
222
+ return FormattedText(out)
223
+
224
+ def _status_fragments() -> FormattedText:
225
+ scr = flow.screen()
226
+ if scr.mode == "busy":
227
+ return FormattedText([("", "")])
228
+ if not scr.status:
229
+ return FormattedText([("", "")])
230
+ return FormattedText([(_TONE_STYLE.get(scr.status_tone, "class:setup.dim"), scr.status)])
231
+
232
+ def _footer_fragments() -> FormattedText:
233
+ scr = flow.screen()
234
+ try:
235
+ width = get_app().output.get_size().columns
236
+ except Exception:
237
+ width = 80
238
+ left: list[tuple[str, str]] = [("class:setup.footer.progress", scr.progress)]
239
+ # The per-screen hint already lives in the body (picker/confirm/input);
240
+ # keep the footer to the progress label + a global cancel hint.
241
+ right: list[tuple[str, str]] = [("class:setup.footer.dim", "Ctrl+C cancel")]
242
+ left_len = sum(len(t) for _s, t in left)
243
+ right_len = sum(len(t) for _s, t in right)
244
+ gap = max(1, width - left_len - right_len)
245
+ return FormattedText([*left, ("", " " * gap), *right])
246
+
247
+ # ----------------------------------------------------------- windows
248
+ # message_window and body_window are focusable so focus can rest on the window
249
+ # that is actually visible for the current screen (no stray cursor parked on a
250
+ # hidden input box). They render no editable cursor (message) / a cursor on the
251
+ # selected row (body), and ignore typed keys — all input is global bindings.
252
+ message_window = Window(
253
+ FormattedTextControl(_message_fragments, focusable=True),
254
+ align=WindowAlign.CENTER,
255
+ )
256
+ body_window = Window(
257
+ FormattedTextControl(
258
+ _body_fragments,
259
+ focusable=True,
260
+ get_cursor_position=lambda: Point(0, body_cursor["row"]),
261
+ ),
262
+ wrap_lines=True,
263
+ scroll_offsets=ScrollOffsets(top=1, bottom=1),
264
+ right_margins=[ScrollbarMargin(display_arrows=False)],
265
+ )
266
+ status_window = Window(FormattedTextControl(_status_fragments, focusable=False), height=1)
267
+
268
+ _is_password = Condition(lambda: flow.screen().input_password)
269
+ input_area = TextArea(
270
+ height=1,
271
+ multiline=False,
272
+ wrap_lines=False,
273
+ style="class:tui.input",
274
+ input_processors=[
275
+ ConditionalProcessor(PasswordProcessor(), filter=_is_password),
276
+ BeforeInput("> ", style="class:tui.prompt"),
277
+ ],
278
+ )
279
+ input_frame = Frame(input_area)
280
+
281
+ is_message = Condition(lambda: flow.current_mode() == "message")
282
+ is_panel = Condition(lambda: flow.current_mode() in {"list", "input", "confirm", "busy"})
283
+ is_input = Condition(lambda: flow.current_mode() == "input")
284
+ is_busy = Condition(lambda: flow.current_mode() == "busy")
285
+ is_list = Condition(lambda: flow.current_mode() == "list")
286
+ is_confirm = Condition(lambda: flow.current_mode() == "confirm")
287
+ has_status = Condition(lambda: flow.current_mode() != "busy" and bool(flow.screen().status))
288
+
289
+ # The input box (and its focus) is the ONLY focusable window, so focus rests on
290
+ # it the whole time — but it is only *shown* on input steps (a hidden
291
+ # ConditionalContainer keeps the window in the layout tree, so focus stays
292
+ # valid and the worker can flip to an input step with no cross-thread focus
293
+ # change). Welcome / picker / confirm screens therefore have no stray text box.
294
+ root = HSplit(
295
+ [
296
+ Window(FormattedTextControl(_header_text, focusable=False), height=1),
297
+ Window(height=1),
298
+ ConditionalContainer(message_window, filter=is_message),
299
+ ConditionalContainer(body_window, filter=is_panel),
300
+ ConditionalContainer(status_window, filter=has_status | is_busy),
301
+ ConditionalContainer(input_frame, filter=is_input),
302
+ Window(height=1),
303
+ Window(FormattedTextControl(_footer_fragments, focusable=False), height=1),
304
+ ]
305
+ )
306
+
307
+ # ----------------------------------------------------------- focus / pump
308
+ def _reconcile_focus() -> None:
309
+ # Keep focus on the window that is actually visible for the current
310
+ # screen, so the cursor never parks on a hidden input box.
311
+ mode = flow.current_mode()
312
+ if mode == "input":
313
+ target_window = input_area.window
314
+ elif mode == "message":
315
+ target_window = message_window
316
+ else:
317
+ target_window = body_window
318
+ try:
319
+ app = get_app()
320
+ if app.layout.current_window is not target_window:
321
+ app.layout.focus(target_window)
322
+ except Exception:
323
+ pass
324
+
325
+ def _schedule_focus() -> None:
326
+ # Move focus from a worker thread safely (only app.invalidate() and
327
+ # loop.call_soon_threadsafe are thread-safe in prompt_toolkit).
328
+ try:
329
+ loop = getattr(get_app(), "loop", None)
330
+ if loop is not None:
331
+ loop.call_soon_threadsafe(_reconcile_focus)
332
+ except Exception:
333
+ pass
334
+
335
+ def _busy_worker() -> None:
336
+ while True:
337
+ if flow.current_mode() != "busy" or flow.busy_kind() != "thread":
338
+ break
339
+ try:
340
+ flow.run_busy()
341
+ except BaseException as exc: # noqa: BLE001 - surface any failure as fatal
342
+ flow.fatal_error = f"{type(exc).__name__}: {exc}"
343
+ flow.stage = "fatal"
344
+ report["text"] = ""
345
+ _invalidate()
346
+ busy["running"] = False
347
+ _schedule_focus()
348
+ _invalidate()
349
+
350
+ async def _terminal_busy() -> None:
351
+ try:
352
+ await run_in_terminal(flow.run_busy, in_executor=True)
353
+ except BaseException as exc: # noqa: BLE001
354
+ flow.fatal_error = f"{type(exc).__name__}: {exc}"
355
+ flow.stage = "fatal"
356
+ report["text"] = ""
357
+ busy["running"] = False
358
+ _invalidate()
359
+ _pump()
360
+
361
+ def _pump() -> None:
362
+ # Exit once the flow terminates.
363
+ if flow.stage == "done":
364
+ try:
365
+ get_app().exit(result=bool(flow.success))
366
+ except Exception:
367
+ pass
368
+ return
369
+ mode = flow.current_mode()
370
+ # Clear the input box once per stage change (UI thread only) so a prior
371
+ # step's text never lingers. We never *seed* the buffer — defaults are
372
+ # shown as a hint and the flow treats empty input as the default — so a
373
+ # worker flipping to an input step (key-retry / workspace) needs no
374
+ # cross-thread buffer write: the box was already cleared when its busy
375
+ # step began.
376
+ if flow.stage != last_stage["v"]:
377
+ if mode != "input" or not input_area.text:
378
+ input_area.text = ""
379
+ last_stage["v"] = flow.stage
380
+ _reconcile_focus()
381
+ # Dispatch a busy step to the right executor.
382
+ if mode == "busy" and not busy["running"]:
383
+ busy["running"] = True
384
+ report["text"] = ""
385
+ if inline_busy:
386
+ while flow.current_mode() == "busy":
387
+ try:
388
+ flow.run_busy()
389
+ except BaseException as exc: # noqa: BLE001
390
+ flow.fatal_error = f"{type(exc).__name__}: {exc}"
391
+ flow.stage = "fatal"
392
+ report["text"] = ""
393
+ busy["running"] = False
394
+ _pump() # handle whatever stage the chain landed on
395
+ return
396
+ if flow.busy_kind() == "thread":
397
+ threading.Thread(target=_busy_worker, daemon=True).start()
398
+ else:
399
+ try:
400
+ get_app().create_background_task(_terminal_busy())
401
+ except Exception:
402
+ # No running loop — run inline as a fallback, then re-pump so
403
+ # the stage it lands on (input prep / exit / a chained busy
404
+ # step) is handled, exactly like the other two busy paths.
405
+ try:
406
+ flow.run_busy()
407
+ except BaseException as exc: # noqa: BLE001
408
+ flow.fatal_error = f"{type(exc).__name__}: {exc}"
409
+ flow.stage = "fatal"
410
+ busy["running"] = False
411
+ _pump()
412
+ return
413
+ _invalidate()
414
+
415
+ # ----------------------------------------------------------- key bindings
416
+ kb = KeyBindings()
417
+
418
+ @kb.add("c-c")
419
+ @kb.add("c-d")
420
+ def _cancel(event: Any) -> None:
421
+ # Always abort setup immediately so the user is never trapped — including
422
+ # mid-validation (the daemon worker, if any, dies with the process) and on
423
+ # the cancel-confirm screen. Returns ``False`` (cancelled) to the caller.
424
+ flow.success = False
425
+ try:
426
+ event.app.exit(result=False)
427
+ except Exception:
428
+ pass
429
+
430
+ @kb.add("enter", eager=True, filter=~is_busy)
431
+ def _enter(event: Any) -> None:
432
+ mode = flow.current_mode()
433
+ if mode == "input":
434
+ flow.submit_input(input_area.text)
435
+ elif mode == "list":
436
+ flow.choose_current()
437
+ elif mode == "message":
438
+ flow.advance_message()
439
+ elif mode == "confirm":
440
+ flow.confirm(flow.screen().confirm_default)
441
+ _pump()
442
+
443
+ @kb.add("escape", filter=~is_busy)
444
+ def _escape(event: Any) -> None:
445
+ flow.back()
446
+ _pump()
447
+
448
+ @kb.add("up", filter=is_list, eager=True)
449
+ @kb.add("k", filter=is_list, eager=True)
450
+ def _up(event: Any) -> None:
451
+ flow.move(-1)
452
+ _invalidate()
453
+
454
+ @kb.add("down", filter=is_list, eager=True)
455
+ @kb.add("j", filter=is_list, eager=True)
456
+ def _down(event: Any) -> None:
457
+ flow.move(1)
458
+ _invalidate()
459
+
460
+ for _digit in "123456789":
461
+
462
+ @kb.add(_digit, filter=is_list, eager=True)
463
+ def _pick(event: Any) -> None:
464
+ try:
465
+ flow.choose_index(int(event.data) - 1)
466
+ except (TypeError, ValueError):
467
+ return
468
+ _pump()
469
+
470
+ @kb.add("y", filter=is_confirm, eager=True)
471
+ @kb.add("Y", filter=is_confirm, eager=True)
472
+ def _yes(event: Any) -> None:
473
+ flow.confirm(True)
474
+ _pump()
475
+
476
+ @kb.add("n", filter=is_confirm, eager=True)
477
+ @kb.add("N", filter=is_confirm, eager=True)
478
+ def _no(event: Any) -> None:
479
+ flow.confirm(False)
480
+ _pump()
481
+
482
+ app: Application = Application(
483
+ layout=Layout(root, focused_element=message_window),
484
+ key_bindings=kb,
485
+ style=_STYLE,
486
+ full_screen=True,
487
+ mouse_support=False,
488
+ cursor=CursorShape.BEAM,
489
+ input=input,
490
+ output=output,
491
+ )
492
+
493
+ def _pre_run() -> None:
494
+ # Prime the very first screen (start any immediate busy step / input box).
495
+ _pump()
496
+
497
+ def _spin() -> None:
498
+ while getattr(app, "is_running", False):
499
+ time.sleep(0.1)
500
+ animated = False
501
+ if owl.available and flow.current_mode() == "message" and flow.stage == "welcome":
502
+ owl.advance()
503
+ animated = True
504
+ if flow.current_mode() == "busy":
505
+ spinner["i"] = (spinner["i"] + 1) % len(_SPINNER_FRAMES)
506
+ animated = True
507
+ if animated:
508
+ try:
509
+ app.invalidate()
510
+ except Exception:
511
+ break
512
+
513
+ threading.Thread(target=_spin, daemon=True).start()
514
+
515
+ result = app.run(pre_run=_pre_run)
516
+ return bool(result)
517
+
518
+
519
+ __all__ = ["run_setup_tui"]