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
alysis_code/policy.py ADDED
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shlex
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class PolicyDecision:
10
+ allowed: bool
11
+ needs_confirm: bool
12
+ reason: str
13
+
14
+
15
+ _DANGEROUS_PATTERNS: list[tuple[re.Pattern[str], str]] = [
16
+ (re.compile(r"\bmkfs(\.\w+)?\b"), "Filesystem formatting (mkfs)"),
17
+ (re.compile(r"\bwipefs\b"), "Filesystem wipe (wipefs)"),
18
+ (re.compile(r"\bdd\b.*\bof="), "Raw disk write (dd ... of=)"),
19
+ (re.compile(r"\b(parted|fdisk|sfdisk|sgdisk)\b"), "Disk partitioning tool"),
20
+ ]
21
+
22
+ _CONFIRM_PATTERNS: list[tuple[re.Pattern[str], str]] = [
23
+ (re.compile(r"\bgit\b\s+push\b"), "git push"),
24
+ (re.compile(r"\b(curl|wget)\b.*\|\s*(sh|bash)\b"), "pipe-to-shell installer"),
25
+ (re.compile(r"\bapt(-get)?\b\s+install\b"), "system package install"),
26
+ (re.compile(r"\bbrew\b\s+install\b"), "package install"),
27
+ (re.compile(r"\bpip\b\s+install\b"), "python package install"),
28
+ (re.compile(r"\bnpm\b\s+install\b"), "node package install"),
29
+ ]
30
+
31
+
32
+ def _is_rm_command_token(token: str) -> bool:
33
+ if not token:
34
+ return False
35
+ return token.rsplit("/", 1)[-1] == "rm"
36
+
37
+
38
+ def _is_control_operator(token: str) -> bool:
39
+ return token in {"&&", "||", ";", "|"}
40
+
41
+
42
+ def _is_destructive_rm(lowered_cmd: str) -> bool:
43
+ try:
44
+ tokens = shlex.split(lowered_cmd)
45
+ except ValueError:
46
+ return False
47
+
48
+ for idx, token in enumerate(tokens):
49
+ if not _is_rm_command_token(token):
50
+ continue
51
+ has_recursive = False
52
+ has_force = False
53
+
54
+ for opt in tokens[idx + 1 :]:
55
+ if _is_control_operator(opt) or opt == "--":
56
+ break
57
+ if not opt.startswith("-") or opt == "-":
58
+ break
59
+ if opt.startswith("--"):
60
+ if opt == "--recursive" or opt.startswith("--recursive="):
61
+ has_recursive = True
62
+ if opt == "--force" or opt.startswith("--force="):
63
+ has_force = True
64
+ else:
65
+ flags = opt[1:]
66
+ if "r" in flags or "R" in flags:
67
+ has_recursive = True
68
+ if "f" in flags:
69
+ has_force = True
70
+ if has_recursive and has_force:
71
+ return True
72
+ return False
73
+
74
+
75
+ def evaluate_shell_command(cmd: str) -> PolicyDecision:
76
+ c = cmd.strip()
77
+ if not c:
78
+ return PolicyDecision(allowed=False, needs_confirm=False, reason="Empty command")
79
+
80
+ lowered = c.lower()
81
+
82
+ if _is_destructive_rm(lowered):
83
+ return PolicyDecision(
84
+ allowed=False, needs_confirm=False, reason="Destructive remove (rm -rf)"
85
+ )
86
+
87
+ for pat, reason in _DANGEROUS_PATTERNS:
88
+ if pat.search(lowered):
89
+ return PolicyDecision(allowed=False, needs_confirm=False, reason=reason)
90
+
91
+ for pat, reason in _CONFIRM_PATTERNS:
92
+ if pat.search(lowered):
93
+ return PolicyDecision(allowed=True, needs_confirm=True, reason=reason)
94
+
95
+ return PolicyDecision(allowed=True, needs_confirm=False, reason="OK")
@@ -0,0 +1,457 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import hashlib
5
+ import http.cookies
6
+ import ipaddress
7
+ import json
8
+ import os
9
+ import platform
10
+ import queue
11
+ import re
12
+ import secrets
13
+ import shutil
14
+ import socket
15
+ import subprocess
16
+ import threading
17
+ from functools import partial
18
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
19
+ from pathlib import Path
20
+ from typing import Any
21
+ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
22
+
23
+ PREVIEW_ACCESS_SCOPES = frozenset({"local", "lan"})
24
+ _TOKEN_QUERY_PARAMETER = "alysis_token"
25
+
26
+
27
+ class WorkspacePreviewHandler(SimpleHTTPRequestHandler):
28
+ """Static handler constrained to one workspace directory."""
29
+
30
+ server_version = "AlysisPreview/2"
31
+
32
+ def __init__(
33
+ self,
34
+ *args: Any,
35
+ directory: str,
36
+ auth_token: str | None = None,
37
+ **kwargs: Any,
38
+ ) -> None:
39
+ self._preview_root = Path(directory).resolve()
40
+ self._auth_token = auth_token
41
+ digest = hashlib.sha256((auth_token or "local").encode("utf-8")).hexdigest()[:12]
42
+ self._auth_cookie_name = f"alysis_preview_{digest}"
43
+ super().__init__(*args, directory=os.fspath(self._preview_root), **kwargs)
44
+
45
+ def do_GET(self) -> None: # noqa: N802 - inherited HTTP handler API
46
+ if not self._authorize_request():
47
+ return
48
+ super().do_GET()
49
+
50
+ def do_HEAD(self) -> None: # noqa: N802 - inherited HTTP handler API
51
+ if not self._authorize_request():
52
+ return
53
+ super().do_HEAD()
54
+
55
+ def _authorize_request(self) -> bool:
56
+ if self._auth_token is None:
57
+ return True
58
+
59
+ parsed = urlsplit(self.path)
60
+ query_items = parse_qsl(parsed.query, keep_blank_values=True)
61
+ supplied = next(
62
+ (value for key, value in query_items if key == _TOKEN_QUERY_PARAMETER), None
63
+ )
64
+ if supplied is not None and secrets.compare_digest(supplied, self._auth_token):
65
+ cleaned_query = urlencode(
66
+ [(key, value) for key, value in query_items if key != _TOKEN_QUERY_PARAMETER]
67
+ )
68
+ location = urlunsplit(("", "", parsed.path or "/", cleaned_query, ""))
69
+ self.send_response(303)
70
+ self.send_header("Location", location)
71
+ self.send_header(
72
+ "Set-Cookie",
73
+ f"{self._auth_cookie_name}={self._auth_token}; Path=/; HttpOnly; SameSite=Strict",
74
+ )
75
+ self.end_headers()
76
+ return False
77
+
78
+ cookies = http.cookies.SimpleCookie()
79
+ try:
80
+ cookies.load(self.headers.get("Cookie", ""))
81
+ except http.cookies.CookieError:
82
+ cookies.clear()
83
+ morsel = cookies.get(self._auth_cookie_name)
84
+ if morsel is not None and secrets.compare_digest(morsel.value, self._auth_token):
85
+ return True
86
+
87
+ body = b"Authentication is required for this network preview.\n"
88
+ self.send_response(401)
89
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
90
+ self.send_header("Content-Length", str(len(body)))
91
+ self.end_headers()
92
+ if self.command != "HEAD":
93
+ self.wfile.write(body)
94
+ return False
95
+
96
+ def send_head(self): # type: ignore[no-untyped-def]
97
+ translated = Path(self.translate_path(self.path))
98
+ try:
99
+ requested_relative = translated.relative_to(self._preview_root)
100
+ except ValueError:
101
+ self.send_error(403, "Path escapes the preview root")
102
+ return None
103
+ if any(part.startswith(".") for part in requested_relative.parts):
104
+ self.send_error(403, "Hidden files are not available in previews")
105
+ return None
106
+
107
+ candidate = translated.resolve()
108
+ try:
109
+ candidate.relative_to(self._preview_root)
110
+ except ValueError:
111
+ self.send_error(403, "Path escapes the preview root")
112
+ return None
113
+ if candidate.is_dir():
114
+ for index_name in ("index.html", "index.htm"):
115
+ index_path = candidate / index_name
116
+ if not index_path.exists():
117
+ continue
118
+ try:
119
+ index_path.resolve().relative_to(self._preview_root)
120
+ except ValueError:
121
+ self.send_error(403, "Index file escapes the preview root")
122
+ return None
123
+ break
124
+ return super().send_head()
125
+
126
+ def list_directory(self, path: str): # type: ignore[no-untyped-def]
127
+ _ = path
128
+ self.send_error(403, "Directory listing is disabled")
129
+ return None
130
+
131
+ def end_headers(self) -> None:
132
+ self.send_header("Cache-Control", "no-store")
133
+ self.send_header("X-Content-Type-Options", "nosniff")
134
+ self.send_header("Referrer-Policy", "no-referrer")
135
+ super().end_headers()
136
+
137
+ def log_message(self, format: str, *args: Any) -> None:
138
+ # Access URLs contain a temporary credential. Do not copy request targets
139
+ # into durable logs where the token could outlive the preview process.
140
+ _ = (format, args)
141
+
142
+
143
+ class WorkspacePreviewHTTPServer(ThreadingHTTPServer):
144
+ daemon_threads = True
145
+ allow_reuse_address = True
146
+
147
+
148
+ def run_preview_server(
149
+ *,
150
+ root: Path,
151
+ access: str,
152
+ port: int = 0,
153
+ ready_file: Path,
154
+ token_file: Path | None = None,
155
+ ) -> None:
156
+ preview_root = root.expanduser().resolve()
157
+ if not preview_root.is_dir():
158
+ raise ValueError(f"Preview root is not a directory: {preview_root}")
159
+ normalized_access = str(access or "").strip().lower()
160
+ if normalized_access not in PREVIEW_ACCESS_SCOPES:
161
+ raise ValueError(
162
+ f"Preview access must be one of: {', '.join(sorted(PREVIEW_ACCESS_SCOPES))}"
163
+ )
164
+ if isinstance(port, bool) or port < 0 or port > 65535:
165
+ raise ValueError("Preview port must be between 0 and 65535")
166
+
167
+ auth_token = _read_auth_token(token_file)
168
+ if normalized_access == "lan" and auth_token is None:
169
+ raise ValueError("LAN previews require a temporary authentication token")
170
+ handler = partial(
171
+ WorkspacePreviewHandler,
172
+ directory=os.fspath(preview_root),
173
+ auth_token=auth_token,
174
+ )
175
+ runtime = _runtime_environment()
176
+ server, family = _create_preview_server(
177
+ access=normalized_access,
178
+ port=port,
179
+ handler=handler,
180
+ runtime=runtime,
181
+ )
182
+ with server:
183
+ actual_port = int(server.server_address[1])
184
+ probe_host = _probe_host_for_family(family, actual_port)
185
+ preview_urls = _preview_urls(
186
+ access=normalized_access,
187
+ family=family,
188
+ port=actual_port,
189
+ probe_host=probe_host,
190
+ )
191
+ _write_ready_file(
192
+ ready_file,
193
+ {
194
+ "schema_version": 1,
195
+ "access": normalized_access,
196
+ "port": actual_port,
197
+ "probe_host": probe_host,
198
+ "preview_urls": preview_urls,
199
+ "runtime": runtime,
200
+ "authentication_required": auth_token is not None,
201
+ },
202
+ )
203
+ print(f"Alysis Code preview ready at {preview_urls[0]}", flush=True)
204
+ server.serve_forever(poll_interval=0.2)
205
+
206
+
207
+ def _create_preview_server(
208
+ *,
209
+ access: str,
210
+ port: int,
211
+ handler: Any,
212
+ runtime: str,
213
+ ) -> tuple[WorkspacePreviewHTTPServer, int]:
214
+ host: str | None = None if access == "lan" else "localhost"
215
+ flags = socket.AI_PASSIVE if access == "lan" else 0
216
+ candidates = socket.getaddrinfo(
217
+ host,
218
+ port,
219
+ family=socket.AF_UNSPEC,
220
+ type=socket.SOCK_STREAM,
221
+ proto=socket.IPPROTO_TCP,
222
+ flags=flags,
223
+ )
224
+ if access == "lan" or runtime == "wsl":
225
+ # IPv4 remains the most consistently forwarded family across WSL and
226
+ # consumer LAN routers. This orders system-provided candidates; it does
227
+ # not select or embed a machine-specific address.
228
+ candidates.sort(key=lambda candidate: candidate[0] != socket.AF_INET)
229
+ errors: list[str] = []
230
+ seen: set[tuple[int, tuple[Any, ...]]] = set()
231
+ for family, _socktype, _proto, _canonname, sockaddr in candidates:
232
+ if family not in {socket.AF_INET, socket.AF_INET6}:
233
+ continue
234
+ normalized_sockaddr = tuple(sockaddr)
235
+ key = (family, normalized_sockaddr)
236
+ if key in seen:
237
+ continue
238
+ seen.add(key)
239
+ server_type = type(
240
+ f"WorkspacePreviewHTTPServer_{family}",
241
+ (WorkspacePreviewHTTPServer,),
242
+ {"address_family": family},
243
+ )
244
+ try:
245
+ return server_type(sockaddr, handler), family
246
+ except OSError as exc:
247
+ errors.append(str(exc))
248
+ detail = "; ".join(dict.fromkeys(errors)) or "no usable TCP address was found"
249
+ raise OSError(f"Unable to bind the {access} preview server: {detail}")
250
+
251
+
252
+ def _probe_host_for_family(family: int, port: int) -> str:
253
+ candidates = socket.getaddrinfo(
254
+ "localhost",
255
+ port,
256
+ family=family,
257
+ type=socket.SOCK_STREAM,
258
+ proto=socket.IPPROTO_TCP,
259
+ )
260
+ if not candidates:
261
+ raise OSError("The operating system did not provide a loopback address")
262
+ return str(candidates[0][4][0])
263
+
264
+
265
+ def _preview_urls(*, access: str, family: int, port: int, probe_host: str) -> list[str]:
266
+ if access == "local":
267
+ hosts = [probe_host, "localhost"]
268
+ else:
269
+ hosts = _discover_lan_hosts(family)
270
+ # A verified local URL remains useful when interface discovery is limited
271
+ # by the platform (and is the correct Windows-facing path under WSL
272
+ # localhost forwarding). Network addresses, when discoverable, stay first.
273
+ hosts.append(probe_host)
274
+ urls = [_http_url(host, port) for host in dict.fromkeys(hosts) if host]
275
+ if not urls:
276
+ raise OSError("Unable to determine a reachable preview URL")
277
+ return urls
278
+
279
+
280
+ def _discover_lan_hosts(family: int) -> list[str]:
281
+ hostname = socket.gethostname().strip()
282
+ hosts: list[str] = []
283
+ if hostname:
284
+ candidates = _bounded_getaddrinfo(
285
+ hostname,
286
+ None,
287
+ family=family,
288
+ type=socket.SOCK_STREAM,
289
+ )
290
+ for candidate in candidates:
291
+ host = str(candidate[4][0]).split("%", 1)[0]
292
+ try:
293
+ address = ipaddress.ip_address(host)
294
+ except ValueError:
295
+ continue
296
+ if address.is_loopback or address.is_unspecified or address.is_link_local:
297
+ continue
298
+ hosts.append(address.compressed)
299
+ hosts.extend(_discover_platform_lan_hosts(family))
300
+ return list(dict.fromkeys(hosts))
301
+
302
+
303
+ def _discover_platform_lan_hosts(family: int) -> list[str]:
304
+ system = platform.system().strip().lower()
305
+ candidates: list[str] = []
306
+ if system == "darwin" and family == socket.AF_INET:
307
+ route = shutil.which("route")
308
+ ipconfig = shutil.which("ipconfig")
309
+ if route and ipconfig:
310
+ route_output = _run_discovery_command([route, "-n", "get", "default"])
311
+ match = re.search(r"^\s*interface:\s*(\S+)\s*$", route_output, flags=re.MULTILINE)
312
+ if match:
313
+ address = _run_discovery_command(
314
+ [ipconfig, "getifaddr", match.group(1)],
315
+ ).strip()
316
+ if address:
317
+ candidates.append(address)
318
+ elif system == "linux":
319
+ ip = shutil.which("ip")
320
+ if ip:
321
+ raw = _run_discovery_command([ip, "-j", "address", "show", "up"])
322
+ try:
323
+ interfaces = json.loads(raw)
324
+ except json.JSONDecodeError:
325
+ interfaces = []
326
+ if isinstance(interfaces, list):
327
+ expected_family = "inet" if family == socket.AF_INET else "inet6"
328
+ for interface in interfaces:
329
+ if not isinstance(interface, dict):
330
+ continue
331
+ for info in interface.get("addr_info") or []:
332
+ if not isinstance(info, dict) or info.get("family") != expected_family:
333
+ continue
334
+ if info.get("scope") != "global":
335
+ continue
336
+ candidates.append(str(info.get("local") or ""))
337
+ if not candidates:
338
+ hostname = shutil.which("hostname")
339
+ if hostname:
340
+ candidates.extend(_run_discovery_command([hostname, "-I"]).split())
341
+
342
+ discovered: list[str] = []
343
+ expected_version = 4 if family == socket.AF_INET else 6
344
+ for candidate in candidates:
345
+ try:
346
+ address = ipaddress.ip_address(candidate.split("%", 1)[0])
347
+ except ValueError:
348
+ continue
349
+ if address.version != expected_version:
350
+ continue
351
+ if address.is_loopback or address.is_unspecified or address.is_link_local:
352
+ continue
353
+ discovered.append(address.compressed)
354
+ return list(dict.fromkeys(discovered))
355
+
356
+
357
+ def _run_discovery_command(argv: list[str]) -> str:
358
+ try:
359
+ completed = subprocess.run(
360
+ argv,
361
+ check=False,
362
+ capture_output=True,
363
+ text=True,
364
+ timeout=0.75,
365
+ )
366
+ except (OSError, subprocess.SubprocessError):
367
+ return ""
368
+ return completed.stdout if completed.returncode == 0 else ""
369
+
370
+
371
+ def _bounded_getaddrinfo(
372
+ host: str,
373
+ port: int | None,
374
+ *,
375
+ family: int,
376
+ type: int,
377
+ timeout_s: float = 0.5,
378
+ ) -> list[tuple[Any, ...]]:
379
+ results: queue.SimpleQueue[list[tuple[Any, ...]]] = queue.SimpleQueue()
380
+
381
+ def _resolve() -> None:
382
+ try:
383
+ resolved = socket.getaddrinfo(host, port, family=family, type=type)
384
+ except OSError:
385
+ resolved = []
386
+ results.put(resolved)
387
+
388
+ worker = threading.Thread(target=_resolve, name="preview-address-discovery", daemon=True)
389
+ worker.start()
390
+ worker.join(max(0.01, timeout_s))
391
+ if worker.is_alive():
392
+ return []
393
+ try:
394
+ return results.get_nowait()
395
+ except queue.Empty:
396
+ return []
397
+
398
+
399
+ def _http_url(host: str, port: int) -> str:
400
+ formatted_host = f"[{host}]" if ":" in host else host
401
+ return f"http://{formatted_host}:{port}"
402
+
403
+
404
+ def _read_auth_token(path: Path | None) -> str | None:
405
+ if path is None:
406
+ return None
407
+ token = path.read_text(encoding="utf-8").strip()
408
+ if len(token) < 32:
409
+ raise ValueError("Preview authentication token is invalid")
410
+ return token
411
+
412
+
413
+ def _write_ready_file(path: Path, payload: dict[str, Any]) -> None:
414
+ target = path.expanduser().resolve()
415
+ target.parent.mkdir(parents=True, exist_ok=True)
416
+ temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
417
+ temporary.write_text(json.dumps(payload, sort_keys=True) + "\n", encoding="utf-8")
418
+ try:
419
+ temporary.chmod(0o600)
420
+ except OSError:
421
+ pass
422
+ temporary.replace(target)
423
+
424
+
425
+ def _runtime_environment() -> str:
426
+ system = platform.system().strip().lower()
427
+ if system == "linux":
428
+ release = platform.release().lower()
429
+ proc_version = ""
430
+ try:
431
+ proc_version = Path("/proc/version").read_text(encoding="utf-8").lower()
432
+ except OSError:
433
+ pass
434
+ if "microsoft" in release or "microsoft" in proc_version or os.environ.get("WSL_INTEROP"):
435
+ return "wsl"
436
+ return system or "unknown"
437
+
438
+
439
+ def main() -> None:
440
+ parser = argparse.ArgumentParser(description="Serve a workspace directory for local preview.")
441
+ parser.add_argument("--root", type=Path, required=True)
442
+ parser.add_argument("--access", choices=sorted(PREVIEW_ACCESS_SCOPES), required=True)
443
+ parser.add_argument("--port", type=int, default=0)
444
+ parser.add_argument("--ready-file", type=Path, required=True)
445
+ parser.add_argument("--token-file", type=Path)
446
+ args = parser.parse_args()
447
+ run_preview_server(
448
+ root=args.root,
449
+ access=args.access,
450
+ port=args.port,
451
+ ready_file=args.ready_file,
452
+ token_file=args.token_file,
453
+ )
454
+
455
+
456
+ if __name__ == "__main__":
457
+ main()