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,761 @@
1
+ #!/usr/bin/env bash
2
+ # alysis terminal owl playback.
3
+
4
+ ESC=$'\e'
5
+ RESET="${ESC}[0m"
6
+ BOLD="${ESC}[1m"
7
+ HIDE="${ESC}[?25l"
8
+ SHOW="${ESC}[?25h"
9
+ CLEAR="${ESC}[2J${ESC}[H"
10
+ EL="${ESC}[K"
11
+
12
+ DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ CANONICAL_ASCII_DIR="${OWL_ASCII_DIR:-$DIR/ascii}"
14
+ ASCII_DIR="$CANONICAL_ASCII_DIR"
15
+ SPEED="${OWL_SPEED:-0.10}"
16
+ THEME="${OWL_THEME:-auto}"
17
+ LOOPS=0
18
+ SHOW_TEXT=1
19
+ CLEAR_ON_EXIT=1
20
+
21
+ usage() {
22
+ cat <<EOF
23
+ Usage: ./show-owl.sh [options]
24
+
25
+ Options:
26
+ --speed SECONDS Delay between frames. Default: $SPEED
27
+ --loops N Play N loops, then exit. Default: forever
28
+ --once Same as --loops 1, leaving the final frame visible
29
+ --theme THEME Theme: auto, light, dark, or neutral. Default: $THEME
30
+ --no-text Show only the owl animation
31
+ -h, --help Show this help
32
+ EOF
33
+ }
34
+
35
+ while [[ $# -gt 0 ]]; do
36
+ case "$1" in
37
+ --speed)
38
+ SPEED="${2:-}"
39
+ shift 2
40
+ ;;
41
+ --loops)
42
+ LOOPS="${2:-}"
43
+ shift 2
44
+ ;;
45
+ --once)
46
+ LOOPS=1
47
+ CLEAR_ON_EXIT=0
48
+ shift
49
+ ;;
50
+ --theme)
51
+ THEME="${2:-}"
52
+ shift 2
53
+ ;;
54
+ --no-text)
55
+ SHOW_TEXT=0
56
+ shift
57
+ ;;
58
+ -h|--help)
59
+ usage
60
+ exit 0
61
+ ;;
62
+ *)
63
+ echo "Unknown option: $1" >&2
64
+ usage >&2
65
+ exit 2
66
+ ;;
67
+ esac
68
+ done
69
+
70
+ case "$SPEED" in
71
+ ''|*[!0-9.]*)
72
+ echo "--speed must be a number of seconds, for example 0.08" >&2
73
+ exit 2
74
+ ;;
75
+ esac
76
+
77
+ case "$LOOPS" in
78
+ ''|*[!0-9]*)
79
+ echo "--loops must be a positive integer" >&2
80
+ exit 2
81
+ ;;
82
+ esac
83
+
84
+ case "$THEME" in
85
+ auto|light|dark|neutral)
86
+ ;;
87
+ *)
88
+ echo "--theme must be auto, light, dark, or neutral" >&2
89
+ exit 2
90
+ ;;
91
+ esac
92
+
93
+ if [[ -n "${ALYSIS_NO_OWL:-}" || -n "${ALYSIS_NO_INTRO:-}" ]]; then
94
+ exit 0
95
+ fi
96
+ if [[ -n "${CI:-}" || -n "${ALYSIS_CI:-}" ]]; then
97
+ exit 0
98
+ fi
99
+ if [[ ! -t 1 || "${TERM:-}" == "dumb" ]]; then
100
+ exit 0
101
+ fi
102
+
103
+ hex_to_8bit() {
104
+ local h="$1"
105
+ local max=1
106
+ local i
107
+ for ((i=0; i<${#h}; i++)); do
108
+ max=$((max * 16))
109
+ done
110
+ max=$((max - 1))
111
+ printf '%s' $(( (16#$h) * 255 / max ))
112
+ }
113
+
114
+ terminal_color_count() {
115
+ local colors
116
+ colors="$(tput colors 2>/dev/null || printf '0')"
117
+ case "$colors" in
118
+ ''|*[!0-9-]*)
119
+ printf '0'
120
+ ;;
121
+ *)
122
+ printf '%s' "$colors"
123
+ ;;
124
+ esac
125
+ }
126
+
127
+ terminal_supports_truecolor() {
128
+ case "${COLORTERM:-}" in
129
+ truecolor|24bit)
130
+ return 0
131
+ ;;
132
+ *)
133
+ return 1
134
+ ;;
135
+ esac
136
+ }
137
+
138
+ is_wsl() {
139
+ if [[ -n "${WSL_DISTRO_NAME:-}" || -n "${WSL_INTEROP:-}" ]]; then
140
+ return 0
141
+ fi
142
+ if [[ -r /proc/version ]] && grep -qi microsoft /proc/version 2>/dev/null; then
143
+ return 0
144
+ fi
145
+ return 1
146
+ }
147
+
148
+ set_text_palette() {
149
+ local theme="$1"
150
+
151
+ if (( ! USE_COLOR )); then
152
+ BOLD=""
153
+ INK=""
154
+ ACC=""
155
+ TAG_INK=""
156
+ GREY=""
157
+ return 0
158
+ fi
159
+
160
+ if [[ "$theme" == "neutral" || "$theme" == "auto" ]]; then
161
+ INK=""
162
+ ACC=""
163
+ TAG_INK=""
164
+ GREY=""
165
+ return 0
166
+ fi
167
+
168
+ if terminal_supports_truecolor; then
169
+ case "$theme" in
170
+ light)
171
+ INK="${ESC}[38;2;30;30;30m"
172
+ ACC="${ESC}[38;2;230;102;32m"
173
+ TAG_INK="${ESC}[38;2;30;30;30m"
174
+ GREY="${ESC}[38;2;30;30;30m"
175
+ ;;
176
+ dark)
177
+ INK="${ESC}[38;2;230;230;230m"
178
+ ACC="${ESC}[38;2;255;145;77m"
179
+ TAG_INK="${ESC}[38;2;230;230;230m"
180
+ GREY="${ESC}[38;2;230;230;230m"
181
+ ;;
182
+ esac
183
+ return 0
184
+ fi
185
+
186
+ if (( COLOR_COUNT >= 256 )); then
187
+ case "$theme" in
188
+ light)
189
+ INK="${ESC}[38;5;16m"
190
+ ACC="${ESC}[38;5;202m"
191
+ TAG_INK="${ESC}[38;5;16m"
192
+ GREY="${ESC}[38;5;16m"
193
+ ;;
194
+ dark)
195
+ INK="${ESC}[38;5;255m"
196
+ ACC="${ESC}[38;5;215m"
197
+ TAG_INK="${ESC}[38;5;255m"
198
+ GREY="${ESC}[38;5;255m"
199
+ ;;
200
+ esac
201
+ return 0
202
+ fi
203
+
204
+ case "$theme" in
205
+ light)
206
+ INK="${ESC}[30m"
207
+ ACC="${ESC}[31m"
208
+ TAG_INK="${ESC}[30m"
209
+ GREY="${ESC}[30m"
210
+ ;;
211
+ dark)
212
+ INK="${ESC}[97m"
213
+ ACC="${ESC}[33m"
214
+ TAG_INK="${ESC}[97m"
215
+ GREY="${ESC}[97m"
216
+ ;;
217
+ esac
218
+ }
219
+
220
+ strip_ansi_file() {
221
+ python3 - "$1" <<'PY'
222
+ import re
223
+ import sys
224
+ from pathlib import Path
225
+
226
+ csi = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
227
+ print(csi.sub("", Path(sys.argv[1]).read_text(errors="replace")), end="")
228
+ PY
229
+ }
230
+
231
+ white_panel_file() {
232
+ python3 - "$1" <<'PY'
233
+ import re
234
+ import sys
235
+ from pathlib import Path
236
+
237
+ bg = "\x1b[48;5;231m"
238
+ reset = "\x1b[0m"
239
+ csi = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
240
+ lines = Path(sys.argv[1]).read_text(errors="replace").splitlines()
241
+ width = max((len(csi.sub("", line)) for line in lines), default=0)
242
+ side_pad = " "
243
+ blank = f"{bg}{' ' * (width + 4)}{reset}"
244
+ painted = [blank]
245
+
246
+ # Runtime-only contrast remap for light owl grays on the white panel.
247
+ panel_gray_map = {
248
+ "238": "16",
249
+ "239": "16",
250
+ "240": "16",
251
+ "241": "234",
252
+ "242": "234",
253
+ "243": "234",
254
+ "244": "236",
255
+ "245": "236",
256
+ "246": "236",
257
+ "247": "238",
258
+ "248": "238",
259
+ }
260
+
261
+ def remap_panel_grays(line):
262
+ return re.sub(
263
+ r"\x1b\[38;5;(238|239|240|241|242|243|244|245|246|247|248)m",
264
+ lambda match: f"\x1b[38;5;{panel_gray_map[match.group(1)]}m",
265
+ line,
266
+ )
267
+
268
+ for line in lines:
269
+ contrast = remap_panel_grays(line)
270
+ safe = contrast.replace(reset, f"{reset}{bg}")
271
+ pad = " " * max(width - len(csi.sub("", line)), 0)
272
+ painted.append(f"{bg}{side_pad}{safe}{pad}{side_pad}{reset}")
273
+ painted.append(blank)
274
+ print("\n".join(painted), end="")
275
+ PY
276
+ }
277
+
278
+ detect_terminal_theme() {
279
+ local bg response old_tty r g b brightness
280
+
281
+ # Some terminals expose foreground/background ANSI color indexes here.
282
+ if [[ -n "${COLORFGBG:-}" ]]; then
283
+ bg="${COLORFGBG##*;}"
284
+ case "$bg" in
285
+ ''|*[!0-9]*)
286
+ ;;
287
+ *)
288
+ if (( bg <= 6 || bg == 8 )); then
289
+ printf 'dark'
290
+ else
291
+ printf 'light'
292
+ fi
293
+ return 0
294
+ ;;
295
+ esac
296
+ fi
297
+
298
+ # OSC 11 asks the terminal for its current background color. iTerm2,
299
+ # Apple Terminal, kitty, WezTerm, and many xterm-compatible terminals
300
+ # answer with rgb:RRRR/GGGG/BBBB.
301
+ if [[ "${ALYSIS_ENABLE_OSC11:-}" =~ ^(1|true|yes|on)$ ]] && ! is_wsl && tty -s && [[ -r /dev/tty && -w /dev/tty ]]; then
302
+ old_tty="$(stty -g < /dev/tty 2>/dev/null || true)"
303
+ if [[ -n "$old_tty" ]]; then
304
+ stty raw -echo min 0 time 5 < /dev/tty 2>/dev/null || true
305
+ printf '\033]11;?\007' > /dev/tty
306
+ response="$(dd bs=1 count=128 2>/dev/null < /dev/tty || true)"
307
+ stty "$old_tty" < /dev/tty 2>/dev/null || true
308
+ if [[ "$response" =~ rgb:([0-9A-Fa-f]+)/([0-9A-Fa-f]+)/([0-9A-Fa-f]+) ]]; then
309
+ r="$(hex_to_8bit "${BASH_REMATCH[1]}")"
310
+ g="$(hex_to_8bit "${BASH_REMATCH[2]}")"
311
+ b="$(hex_to_8bit "${BASH_REMATCH[3]}")"
312
+ brightness=$(( (r * 299 + g * 587 + b * 114) / 1000 ))
313
+ if (( brightness >= 128 )); then
314
+ printf 'light'
315
+ else
316
+ printf 'dark'
317
+ fi
318
+ return 0
319
+ fi
320
+ fi
321
+ fi
322
+
323
+ if [[ -n "${WT_SESSION:-}" || -n "${ALYSIS_WINDOWS_TERMINAL_SETTINGS:-}" ]] || is_wsl; then
324
+ local wt_theme
325
+ wt_theme="$(python3 <<'PY' 2>/dev/null || true
326
+ import glob
327
+ import json
328
+ import os
329
+ import re
330
+ from pathlib import Path
331
+
332
+
333
+ def strip_json_comments(value):
334
+ output = []
335
+ in_string = escaped = in_line = in_block = False
336
+ index = 0
337
+ while index < len(value):
338
+ char = value[index]
339
+ nxt = value[index + 1] if index + 1 < len(value) else ""
340
+ if in_line:
341
+ if char == "\n":
342
+ in_line = False
343
+ output.append(char)
344
+ index += 1
345
+ continue
346
+ if in_block:
347
+ if char == "*" and nxt == "/":
348
+ in_block = False
349
+ index += 2
350
+ else:
351
+ index += 1
352
+ continue
353
+ if in_string:
354
+ output.append(char)
355
+ if escaped:
356
+ escaped = False
357
+ elif char == "\\":
358
+ escaped = True
359
+ elif char == '"':
360
+ in_string = False
361
+ index += 1
362
+ continue
363
+ if char == '"':
364
+ in_string = True
365
+ output.append(char)
366
+ index += 1
367
+ continue
368
+ if char == "/" and nxt == "/":
369
+ in_line = True
370
+ index += 2
371
+ continue
372
+ if char == "/" and nxt == "*":
373
+ in_block = True
374
+ index += 2
375
+ continue
376
+ output.append(char)
377
+ index += 1
378
+ return re.sub(r",(\s*[}\]])", r"\1", "".join(output))
379
+
380
+
381
+ def settings_paths():
382
+ override = os.environ.get("ALYSIS_WINDOWS_TERMINAL_SETTINGS")
383
+ if override:
384
+ return [Path(override).expanduser()]
385
+ paths = []
386
+ drives = [*glob.glob("/mnt/[a-z]"), *glob.glob("/mnt/host/[a-z]")]
387
+ for drive in sorted(dict.fromkeys(drives)):
388
+ for user in sorted(glob.glob(f"{drive}/Users/*")):
389
+ local = Path(user) / "AppData" / "Local"
390
+ paths.extend(
391
+ Path(path)
392
+ for path in sorted(
393
+ glob.glob(
394
+ str(
395
+ local
396
+ / "Packages"
397
+ / "Microsoft.WindowsTerminal*_8wekyb3d8bbwe"
398
+ / "LocalState"
399
+ / "settings.json"
400
+ )
401
+ )
402
+ )
403
+ )
404
+ paths.append(local / "Microsoft" / "Windows Terminal" / "settings.json")
405
+ return paths
406
+
407
+
408
+ def background_theme(value):
409
+ if not isinstance(value, str):
410
+ return None
411
+ raw = value.strip().lstrip("#")
412
+ if len(raw) == 3:
413
+ raw = "".join(char * 2 for char in raw)
414
+ if not re.fullmatch(r"[0-9A-Fa-f]{6}", raw):
415
+ return None
416
+ red = int(raw[0:2], 16)
417
+ green = int(raw[2:4], 16)
418
+ blue = int(raw[4:6], 16)
419
+ brightness = (red * 299 + green * 587 + blue * 114) // 1000
420
+ return "light" if brightness >= 128 else "dark"
421
+
422
+
423
+ def builtin_theme(name):
424
+ known = {
425
+ "atom one dark": "dark",
426
+ "atom one light": "light",
427
+ "ayu dark": "dark",
428
+ "ayu light": "light",
429
+ "ayu mirage": "dark",
430
+ "campbell": "dark",
431
+ "campbell powershell": "dark",
432
+ "dark+": "dark",
433
+ "dracula": "dark",
434
+ "dracula+": "dark",
435
+ "github dark": "dark",
436
+ "github light": "light",
437
+ "gruvbox dark": "dark",
438
+ "gruvbox light": "light",
439
+ "light+": "light",
440
+ "material dark": "dark",
441
+ "material light": "light",
442
+ "monokai": "dark",
443
+ "monokai pro": "dark",
444
+ "nord": "dark",
445
+ "one half dark": "dark",
446
+ "one half light": "light",
447
+ "powershell": "dark",
448
+ "solarized dark": "dark",
449
+ "solarized light": "light",
450
+ "tango dark": "dark",
451
+ "tango light": "light",
452
+ "tokyo night": "dark",
453
+ "tokyo night light": "light",
454
+ "tokyo night storm": "dark",
455
+ "ubuntu": "dark",
456
+ "vintage": "dark",
457
+ }
458
+ normalized = re.sub(r"[^a-z0-9+]+", " ", name.lower()).strip()
459
+ normalized = re.sub(r"\s+", " ", normalized)
460
+ return known.get(normalized)
461
+
462
+
463
+ def scheme_name(settings):
464
+ profiles = settings.get("profiles") if isinstance(settings.get("profiles"), dict) else {}
465
+ profile_list = profiles.get("list") if isinstance(profiles.get("list"), list) else []
466
+ defaults = profiles.get("defaults") if isinstance(profiles.get("defaults"), dict) else {}
467
+ profile_id = os.environ.get("WT_PROFILE_ID", "").strip().strip("{}").lower()
468
+ profile = None
469
+ if profile_id:
470
+ for candidate in profile_list:
471
+ if not isinstance(candidate, dict):
472
+ continue
473
+ if str(candidate.get("guid", "")).strip().strip("{}").lower() == profile_id:
474
+ profile = candidate
475
+ break
476
+ if profile is None:
477
+ default_profile = str(settings.get("defaultProfile", "")).strip().strip("{}").lower()
478
+ for candidate in profile_list:
479
+ if not isinstance(candidate, dict):
480
+ continue
481
+ if str(candidate.get("guid", "")).strip().strip("{}").lower() == default_profile:
482
+ profile = candidate
483
+ break
484
+ value = (profile or {}).get("colorScheme")
485
+ if isinstance(value, str) and value.strip():
486
+ return value.strip()
487
+ value = defaults.get("colorScheme")
488
+ if isinstance(value, str) and value.strip():
489
+ return value.strip()
490
+ return "Campbell"
491
+
492
+
493
+ for path in settings_paths():
494
+ try:
495
+ settings = json.loads(strip_json_comments(path.read_text(encoding="utf-8")))
496
+ except Exception:
497
+ continue
498
+ name = scheme_name(settings)
499
+ if not name:
500
+ continue
501
+ schemes = settings.get("schemes") if isinstance(settings.get("schemes"), list) else []
502
+ for scheme in schemes:
503
+ if not isinstance(scheme, dict):
504
+ continue
505
+ if str(scheme.get("name", "")).strip().lower() != name.lower():
506
+ continue
507
+ theme = background_theme(scheme.get("background"))
508
+ if theme:
509
+ print(theme)
510
+ raise SystemExit
511
+ theme = builtin_theme(name)
512
+ if theme:
513
+ print(theme)
514
+ raise SystemExit
515
+ PY
516
+ )"
517
+ case "$wt_theme" in
518
+ light|dark)
519
+ printf '%s' "$wt_theme"
520
+ return 0
521
+ ;;
522
+ esac
523
+ if [[ -n "${WT_SESSION:-}" ]]; then
524
+ printf 'dark'
525
+ return 0
526
+ fi
527
+ fi
528
+
529
+ if [[ "${TERM_PROGRAM:-}" == "Apple_Terminal" ]] && command -v defaults >/dev/null 2>&1; then
530
+ local apple_style
531
+ apple_style="$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)"
532
+ if [[ "$apple_style" == "Dark" ]]; then
533
+ printf 'dark'
534
+ else
535
+ printf 'light'
536
+ fi
537
+ return 0
538
+ fi
539
+
540
+ case "${ALYSIS_FALLBACK_THEME:-}" in
541
+ light|dark)
542
+ printf '%s' "$ALYSIS_FALLBACK_THEME"
543
+ return 0
544
+ ;;
545
+ esac
546
+ case "${OWL_FALLBACK_THEME:-}" in
547
+ light|dark)
548
+ printf '%s' "$OWL_FALLBACK_THEME"
549
+ return 0
550
+ ;;
551
+ esac
552
+ printf 'neutral'
553
+ }
554
+
555
+ case "$THEME" in
556
+ auto)
557
+ THEME="$(detect_terminal_theme)"
558
+ ;;
559
+ light|dark|neutral)
560
+ ;;
561
+ *)
562
+ echo "--theme must be auto, light, dark, or neutral" >&2
563
+ exit 2
564
+ ;;
565
+ esac
566
+
567
+ USE_COLOR=1
568
+ STRIP_FRAME_COLORS=0
569
+ COLOR_COUNT="$(terminal_color_count)"
570
+ if [[ -n "${NO_COLOR:-}" ]]; then
571
+ USE_COLOR=0
572
+ STRIP_FRAME_COLORS=1
573
+ elif ! terminal_supports_truecolor && (( COLOR_COUNT < 256 )); then
574
+ STRIP_FRAME_COLORS=1
575
+ fi
576
+
577
+ case "$THEME" in
578
+ light)
579
+ ASCII_DIR="$CANONICAL_ASCII_DIR"
580
+ ;;
581
+ dark)
582
+ ASCII_DIR="$CANONICAL_ASCII_DIR"
583
+ ;;
584
+ neutral)
585
+ ASCII_DIR="$CANONICAL_ASCII_DIR"
586
+ ;;
587
+ esac
588
+ set_text_palette "$THEME"
589
+ WHITE_OWL_PANEL=0
590
+ if [[ "$THEME" == "dark" ]] && (( USE_COLOR )) && (( ! STRIP_FRAME_COLORS )); then
591
+ WHITE_OWL_PANEL=1
592
+ fi
593
+
594
+ cleanup() {
595
+ printf '%s%s' "$RESET" "$SHOW"
596
+ if (( CLEAR_ON_EXIT )); then
597
+ printf '%s' "$CLEAR"
598
+ fi
599
+ }
600
+ trap cleanup EXIT
601
+ trap 'trap - EXIT; cleanup; exit 130' INT TERM
602
+ RESIZED=0
603
+ trap 'RESIZED=1' WINCH
604
+
605
+ if [[ ! -d "$ASCII_DIR" ]] || ! ls "$ASCII_DIR"/f-*.txt >/dev/null 2>&1; then
606
+ echo "Pre-rendered frames not found in $ASCII_DIR" >&2
607
+ echo "Run ./build_terminal_owl.sh first." >&2
608
+ exit 1
609
+ fi
610
+
611
+ # Pre-load frames into array (bash 3.2-compatible)
612
+ FRAMES=()
613
+ FRAME_ROWS=0
614
+ FRAME_COLS=0
615
+ for f in "$ASCII_DIR"/f-*.txt; do
616
+ if (( STRIP_FRAME_COLORS )); then
617
+ FRAMES+=("$(strip_ansi_file "$f")")
618
+ elif (( WHITE_OWL_PANEL )); then
619
+ FRAMES+=("$(white_panel_file "$f")")
620
+ else
621
+ FRAMES+=("$(cat "$f")")
622
+ fi
623
+ rows=$(awk 'END { print NR }' "$f")
624
+ if (( rows > FRAME_ROWS )); then
625
+ FRAME_ROWS=$rows
626
+ fi
627
+ done
628
+
629
+ read frame_cols frame_rows <<EOF
630
+ $(python3 - "$ASCII_DIR" <<'PY'
631
+ import re
632
+ import sys
633
+ from pathlib import Path
634
+
635
+ csi = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
636
+ cols = rows = 0
637
+ for path in sorted(Path(sys.argv[1]).glob("f-*.txt"))[:1]:
638
+ lines = csi.sub("", path.read_text(errors="replace")).splitlines()
639
+ rows = len(lines)
640
+ cols = max((len(line) for line in lines), default=0)
641
+ print(cols or 50, rows or 13)
642
+ PY
643
+ )
644
+ EOF
645
+
646
+ FRAME_COLS="${frame_cols:-50}"
647
+ FRAME_ROWS="${frame_rows:-$FRAME_ROWS}"
648
+ if (( WHITE_OWL_PANEL )); then
649
+ FRAME_COLS=$((FRAME_COLS + 4))
650
+ FRAME_ROWS=$((FRAME_ROWS + 2))
651
+ fi
652
+ TEXT_WIDTH=35
653
+ TEXT_ROWS=4
654
+ TEXT_GAP=2
655
+
656
+ term_cols() { tput cols 2>/dev/null || printf '80'; }
657
+ term_rows() { tput lines 2>/dev/null || printf '24'; }
658
+
659
+ compute_layout() {
660
+ COLS="$(term_cols)"
661
+ ROWS="$(term_rows)"
662
+
663
+ if (( SHOW_TEXT && ROWS < FRAME_ROWS + TEXT_GAP + TEXT_ROWS )); then
664
+ TEXT_VISIBLE=0
665
+ else
666
+ TEXT_VISIBLE="$SHOW_TEXT"
667
+ fi
668
+
669
+ if (( TEXT_VISIBLE )); then
670
+ CANVAS_WIDTH=$(( FRAME_COLS > TEXT_WIDTH ? FRAME_COLS : TEXT_WIDTH ))
671
+ CANVAS_HEIGHT=$(( FRAME_ROWS + TEXT_GAP + TEXT_ROWS ))
672
+ else
673
+ CANVAS_WIDTH="$FRAME_COLS"
674
+ CANVAS_HEIGHT="$FRAME_ROWS"
675
+ fi
676
+
677
+ if (( COLS > CANVAS_WIDTH )); then
678
+ CANVAS_LEFT=$(( (COLS - CANVAS_WIDTH) / 2 + 1 ))
679
+ else
680
+ CANVAS_LEFT=1
681
+ fi
682
+
683
+ if (( ROWS > CANVAS_HEIGHT )); then
684
+ CANVAS_TOP=$(( (ROWS - CANVAS_HEIGHT) / 2 + 1 ))
685
+ else
686
+ CANVAS_TOP=1
687
+ fi
688
+
689
+ FRAME_LEFT=$(( CANVAS_LEFT + (CANVAS_WIDTH - FRAME_COLS) / 2 ))
690
+ FRAME_TOP="$CANVAS_TOP"
691
+ TEXT_LEFT=$(( CANVAS_LEFT + (CANVAS_WIDTH - TEXT_WIDTH) / 2 ))
692
+ TEXT_TOP=$(( FRAME_TOP + FRAME_ROWS + TEXT_GAP ))
693
+ LAYOUT_KEY="${COLS}:${ROWS}:${CANVAS_LEFT}:${CANVAS_TOP}:${CANVAS_WIDTH}:${CANVAS_HEIGHT}:${TEXT_VISIBLE}"
694
+ }
695
+
696
+ clear_canvas() {
697
+ local width=$1
698
+ local height=$2
699
+ local row=$3
700
+ local left=$4
701
+ local i
702
+ for ((i=0; i<height; i++)); do
703
+ printf "${ESC}[%s;%sH%*s" "$((row + i))" "$left" "$width" ""
704
+ done
705
+ }
706
+
707
+ draw_text() {
708
+ (( TEXT_VISIBLE )) || return 0
709
+ printf "${ESC}[%s;%sH%*s" "$TEXT_TOP" "$TEXT_LEFT" "$TEXT_WIDTH" ""
710
+ printf "${ESC}[%s;%sH ${BOLD}${ACC}■${RESET} ${BOLD}${INK}alysis${RESET}" "$TEXT_TOP" "$TEXT_LEFT"
711
+ printf "${ESC}[%s;%sH%*s" "$((TEXT_TOP + 1))" "$TEXT_LEFT" "$TEXT_WIDTH" ""
712
+ printf "${ESC}[%s;%sH ${TAG_INK}the autonomous coding agent${RESET}" "$((TEXT_TOP + 1))" "$TEXT_LEFT"
713
+ printf "${ESC}[%s;%sH%*s" "$((TEXT_TOP + 2))" "$TEXT_LEFT" "$TEXT_WIDTH" ""
714
+ printf "${ESC}[%s;%sH ${GREY}. ${ACC}alysis ai${GREY} .${RESET}" "$((TEXT_TOP + 2))" "$TEXT_LEFT"
715
+ printf "${ESC}[%s;%sH%*s" "$((TEXT_TOP + 3))" "$TEXT_LEFT" "$TEXT_WIDTH" ""
716
+ printf "${ESC}[%s;%sH ${GREY}-----------------------------${RESET}" "$((TEXT_TOP + 3))" "$TEXT_LEFT"
717
+ }
718
+
719
+ draw_frame() {
720
+ local frame=$1
721
+ local row="$FRAME_TOP"
722
+ local line
723
+ while IFS= read -r line || [[ -n "$line" ]]; do
724
+ printf "${ESC}[%s;%sH%*s" "$row" "$FRAME_LEFT" "$FRAME_COLS" ""
725
+ printf "${ESC}[%s;%sH%s${RESET}" "$row" "$FRAME_LEFT" "$line"
726
+ row=$((row + 1))
727
+ done <<< "$frame"
728
+ }
729
+
730
+ # Setup screen
731
+ printf '%s%s' "$HIDE" "$CLEAR"
732
+ compute_layout
733
+ clear_canvas "$CANVAS_WIDTH" "$CANVAS_HEIGHT" "$CANVAS_TOP" "$CANVAS_LEFT"
734
+ draw_text
735
+ LAST_LAYOUT_KEY="$LAYOUT_KEY"
736
+
737
+ # Animation loop: redraw frame at absolute positions each tick.
738
+ # GIF is ~10fps, so sleep 0.1s per frame.
739
+ loop=0
740
+ while true; do
741
+ for frame in "${FRAMES[@]}"; do
742
+ compute_layout
743
+ if [[ "${LAYOUT_KEY}" != "${LAST_LAYOUT_KEY:-}" ]] || (( RESIZED )); then
744
+ printf '%s' "$CLEAR"
745
+ clear_canvas "$CANVAS_WIDTH" "$CANVAS_HEIGHT" "$CANVAS_TOP" "$CANVAS_LEFT"
746
+ LAST_LAYOUT_KEY="$LAYOUT_KEY"
747
+ RESIZED=0
748
+ else
749
+ clear_canvas "$CANVAS_WIDTH" "$FRAME_ROWS" "$FRAME_TOP" "$CANVAS_LEFT"
750
+ fi
751
+ draw_frame "$frame"
752
+ draw_text
753
+ sleep "$SPEED"
754
+ done
755
+ if (( LOOPS > 0 )); then
756
+ loop=$((loop + 1))
757
+ if (( loop >= LOOPS )); then
758
+ break
759
+ fi
760
+ fi
761
+ done