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,77 @@
1
+ # ruff: noqa: F401,F403,F405,I001
2
+ from __future__ import annotations
3
+
4
+ from . import cli_common as _cli_common
5
+ from . import chat_state as _chat_state
6
+ from . import startup as _startup
7
+ from . import chat_terminal as _chat_terminal
8
+ from . import welcome as _welcome
9
+ from . import chat_resume_helpers as _chat_resume_helpers
10
+ from . import chat_status as _chat_status
11
+ from . import forge_helpers as _forge_helpers
12
+ from . import execution_helpers as _execution_helpers
13
+ from . import prompt_helpers as _prompt_helpers
14
+ from . import update as _update
15
+
16
+ from .cli_common import *
17
+ from .chat_state import *
18
+ from .startup import *
19
+ from .chat_terminal import *
20
+ from .welcome import *
21
+ from .chat_resume_helpers import *
22
+ from .chat_status import *
23
+ from .forge_helpers import *
24
+ from .execution_helpers import *
25
+ from .prompt_helpers import *
26
+ from .update import *
27
+
28
+ _CLI_SURFACE_MODULES = (
29
+ _cli_common,
30
+ _chat_state,
31
+ _startup,
32
+ _chat_terminal,
33
+ _welcome,
34
+ _chat_resume_helpers,
35
+ _chat_status,
36
+ _forge_helpers,
37
+ _execution_helpers,
38
+ _prompt_helpers,
39
+ _update,
40
+ )
41
+
42
+ _CLI_SURFACE_INTERNAL_NAMES = {
43
+ "_CLI_SURFACE_INTERNAL_NAMES",
44
+ "_CLI_SURFACE_MODULES",
45
+ "_chat_resume_helpers",
46
+ "_chat_state",
47
+ "_chat_status",
48
+ "_chat_terminal",
49
+ "_cli_common",
50
+ "_execution_helpers",
51
+ "_forge_helpers",
52
+ "_prompt_helpers",
53
+ "_startup",
54
+ "_sync_module_globals",
55
+ "_update",
56
+ "_welcome",
57
+ }
58
+
59
+
60
+ def _sync_module_globals() -> None:
61
+ surface_globals = globals()
62
+ for module in _CLI_SURFACE_MODULES:
63
+ module_globals = module.__dict__
64
+ for name, value in surface_globals.items():
65
+ if name.startswith("__") or name in _CLI_SURFACE_INTERNAL_NAMES:
66
+ continue
67
+ module_globals[name] = value
68
+
69
+
70
+ _sync_module_globals()
71
+
72
+ __all__ = [
73
+ name
74
+ for name in globals()
75
+ if (not name.startswith("__") or name == "__version__")
76
+ and name not in _CLI_SURFACE_INTERNAL_NAMES
77
+ ]
@@ -0,0 +1,131 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+
7
+ import typer
8
+
9
+ from ...branding import env_get
10
+ from ...config import (
11
+ ConfigError,
12
+ clear_persisted_api_key,
13
+ config_path,
14
+ credentials_path,
15
+ ensure_subscription_menu_managed_key,
16
+ load_config,
17
+ resolve_api_key,
18
+ save_config,
19
+ save_persisted_api_key,
20
+ set_config_value,
21
+ )
22
+ from . import _patchable
23
+ from ._shared import _console
24
+
25
+ config_app = typer.Typer(add_completion=False, help="Configuration commands.")
26
+
27
+
28
+ @config_app.command("show")
29
+ def config_show() -> None:
30
+ console = _console()
31
+ cfg = _patchable("load_config", load_config)()
32
+ data = cfg.model_dump()
33
+ data["config_path"] = os.fspath(config_path())
34
+ api_key = _patchable("resolve_api_key", resolve_api_key)()
35
+ data["api_key_set"] = bool(api_key.key)
36
+ data["api_key_source"] = "stored" if api_key.source == "stored:legacy" else api_key.source
37
+ data["credentials_path"] = os.fspath(credentials_path())
38
+ data["active_profile"] = str((cfg.extra_fields or {}).get("active_profile") or "")
39
+ role_models = (cfg.extra_fields or {}).get("role_models")
40
+ forge_role_models = (cfg.extra_fields or {}).get("forge_role_models")
41
+ data["role_models"] = dict(role_models) if isinstance(role_models, dict) else {}
42
+ data["forge_role_models"] = (
43
+ dict(forge_role_models) if isinstance(forge_role_models, dict) else {}
44
+ )
45
+ console.print_json(json.dumps(data))
46
+
47
+
48
+ @config_app.command("menu")
49
+ def config_menu_cmd() -> None:
50
+ """Open the inline configuration menu."""
51
+ from ..config_menu import run_config_menu
52
+
53
+ result = run_config_menu()
54
+ if result.saved:
55
+ n = len(result.changes) + (1 if result.api_key_changed else 0)
56
+ typer.echo(f"Saved {n} change(s).")
57
+ return
58
+ typer.echo("Cancelled — no changes saved.")
59
+
60
+
61
+ @config_app.command("set")
62
+ def config_set(
63
+ key: str = typer.Argument(..., help="Key to set."),
64
+ value: str = typer.Argument(..., help="Value to set."),
65
+ ) -> None:
66
+ console = _console()
67
+ cfg = _patchable("load_config", load_config)()
68
+ try:
69
+ ensure_subscription_menu_managed_key(cfg, key)
70
+ cfg = set_config_value(cfg, key, value)
71
+ save_config(cfg)
72
+ except ConfigError as e:
73
+ console.print(f"[red]Config error:[/red] {e}")
74
+ raise typer.Exit(code=2) from e
75
+ console.print(f"Saved {key} in {config_path()}")
76
+
77
+
78
+ @config_app.command("set-api-key")
79
+ def config_set_api_key(
80
+ env_var: str | None = typer.Option(
81
+ None,
82
+ "--env-var",
83
+ help="Copy the API key from this environment variable.",
84
+ ),
85
+ stdin: bool = typer.Option(
86
+ False,
87
+ "--stdin",
88
+ help="Read the API key from standard input.",
89
+ ),
90
+ ) -> None:
91
+ console = _console()
92
+ if env_var and stdin:
93
+ console.print("[red]Config error:[/red] Use either --env-var or --stdin, not both.")
94
+ raise typer.Exit(code=2)
95
+
96
+ if env_var:
97
+ env_name = env_var.strip()
98
+ if not env_name:
99
+ console.print("[red]Config error:[/red] --env-var must be non-empty.")
100
+ raise typer.Exit(code=2)
101
+ key = str(env_get(env_name) or "").strip()
102
+ if not key:
103
+ console.print(f"[red]Config error:[/red] Environment variable {env_name} is not set.")
104
+ raise typer.Exit(code=2)
105
+ elif stdin:
106
+ key = sys.stdin.read().strip()
107
+ if not key:
108
+ console.print("[red]Config error:[/red] API key is empty.")
109
+ raise typer.Exit(code=2)
110
+ else:
111
+ key = typer.prompt("API key", hide_input=True).strip()
112
+ if not key:
113
+ console.print("[red]Config error:[/red] API key is empty.")
114
+ raise typer.Exit(code=2)
115
+
116
+ try:
117
+ save_persisted_api_key(key)
118
+ except ConfigError as e:
119
+ console.print(f"[red]Config error:[/red] {e}")
120
+ raise typer.Exit(code=2) from e
121
+ console.print(f"Saved API key in {credentials_path()}")
122
+
123
+
124
+ @config_app.command("clear-api-key")
125
+ def config_clear_api_key() -> None:
126
+ console = _console()
127
+ removed = clear_persisted_api_key()
128
+ if removed:
129
+ console.print(f"Removed persisted API key from {credentials_path()}")
130
+ return
131
+ console.print(f"No persisted API key found at {credentials_path()}")
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from ...skills import load_repo_conventions, render_repo_conventions_context
8
+ from ._shared import _console, _resolve_tool_workspace_root, _Table
9
+
10
+ conventions_app = typer.Typer(
11
+ add_completion=False,
12
+ help="Repo conventions diagnostics for AGENTS.md / CLAUDE.md / CONVENTIONS.md.",
13
+ )
14
+
15
+
16
+ @conventions_app.command("list")
17
+ def conventions_list(
18
+ path: Path = typer.Option(Path("."), "--path", help="Workspace, repository, or file path."),
19
+ ) -> None:
20
+ console = _console()
21
+ focus_path = path.expanduser().resolve()
22
+ workspace_root = _resolve_tool_workspace_root(
23
+ path=focus_path if focus_path.is_dir() else focus_path.parent
24
+ )
25
+ documents = load_repo_conventions(
26
+ focus_path=focus_path,
27
+ workspace_root=workspace_root,
28
+ )
29
+ console.print(f"[dim]Workspace root:[/dim] {workspace_root.as_posix()}")
30
+ console.print(f"[dim]Focus path:[/dim] {focus_path.as_posix()}")
31
+ if not documents:
32
+ console.print("No repo conventions found.")
33
+ return
34
+
35
+ table = _Table(title="Repo Conventions")
36
+ table.add_column("priority")
37
+ table.add_column("name")
38
+ table.add_column("trust")
39
+ table.add_column("chars")
40
+ table.add_column("path")
41
+ for priority, document in enumerate(documents, start=1):
42
+ try:
43
+ display_path = document.path.relative_to(workspace_root).as_posix()
44
+ except ValueError:
45
+ display_path = document.path.as_posix()
46
+ table.add_row(
47
+ str(priority),
48
+ document.name,
49
+ document.trust_level,
50
+ str(len(document.content)),
51
+ display_path,
52
+ )
53
+ console.print(table)
54
+ console.print("[dim]Priority order is highest precedence first within repo conventions.[/dim]")
55
+
56
+
57
+ @conventions_app.command("render")
58
+ def conventions_render(
59
+ path: Path = typer.Option(Path("."), "--path", help="Workspace, repository, or file path."),
60
+ max_chars: int = typer.Option(
61
+ 4000,
62
+ "--max-chars",
63
+ min=256,
64
+ help="Maximum rendered repo-conventions context size.",
65
+ ),
66
+ ) -> None:
67
+ console = _console()
68
+ focus_path = path.expanduser().resolve()
69
+ workspace_root = _resolve_tool_workspace_root(
70
+ path=focus_path if focus_path.is_dir() else focus_path.parent
71
+ )
72
+ documents = load_repo_conventions(
73
+ focus_path=focus_path,
74
+ workspace_root=workspace_root,
75
+ )
76
+ rendered = render_repo_conventions_context(
77
+ documents=documents,
78
+ max_chars=max_chars,
79
+ )
80
+ console.print(f"[dim]Workspace root:[/dim] {workspace_root.as_posix()}")
81
+ console.print(f"[dim]Focus path:[/dim] {focus_path.as_posix()}")
82
+ if rendered is None:
83
+ console.print("No repo conventions found.")
84
+ return
85
+ console.print(rendered, end="")
@@ -0,0 +1,350 @@
1
+ # ruff: noqa: F401,F403,F405,I001
2
+ # Legacy split module: dependencies are synced by cli_surface.py.
3
+ from __future__ import annotations
4
+
5
+ from ...task_dependencies import infer_ordered_predecessor_dependency
6
+ from .cli_common import *
7
+
8
+
9
+ def _snapshot_workspace_tree(root: Path) -> dict[str, str]:
10
+ return _shared_snapshot_workspace_tree(root)
11
+
12
+
13
+ def _capture_task_local_workspace_baseline(
14
+ root: Path,
15
+ *,
16
+ before_commit: str | None,
17
+ ) -> Any:
18
+ return _shared_capture_task_local_workspace_baseline(
19
+ root,
20
+ before_commit=before_commit,
21
+ )
22
+
23
+
24
+ def _cleanup_task_local_workspace_baseline(baseline: Any) -> None:
25
+ _shared_cleanup_task_local_workspace_baseline(baseline)
26
+
27
+
28
+ def _git_diff_text(root: Path) -> str | None:
29
+ if shutil.which("git") is None:
30
+ return None
31
+ proc = subprocess.run(
32
+ ["git", "-C", os.fspath(root), "diff"],
33
+ capture_output=True,
34
+ text=True,
35
+ check=False,
36
+ )
37
+ if proc.returncode != 0:
38
+ return None
39
+ return proc.stdout
40
+
41
+
42
+ def _git_changed_files(root: Path) -> list[str]:
43
+ return _shared_git_changed_files(root)
44
+
45
+
46
+ def _build_execution_reporting_diff(root: Path) -> Any:
47
+ return _shared_build_execution_reporting_diff(root)
48
+
49
+
50
+ def _build_execution_reporting_diff_with_commit_range(
51
+ root: Path,
52
+ *,
53
+ before_commit: str | None,
54
+ after_commit: str | None,
55
+ ) -> Any:
56
+ return _shared_build_execution_reporting_diff_with_commit_range(
57
+ root,
58
+ before_commit=before_commit,
59
+ after_commit=after_commit,
60
+ )
61
+
62
+
63
+ def _build_workspace_snapshot_reporting_diff(
64
+ root: Path,
65
+ *,
66
+ before_snapshot: dict[str, str],
67
+ after_snapshot: dict[str, str],
68
+ ) -> Any:
69
+ return _shared_build_workspace_snapshot_reporting_diff(
70
+ root,
71
+ before_snapshot=before_snapshot,
72
+ after_snapshot=after_snapshot,
73
+ )
74
+
75
+
76
+ def _build_task_local_workspace_reporting_diff(
77
+ root: Path,
78
+ *,
79
+ baseline: Any,
80
+ after_commit: str | None,
81
+ ) -> Any:
82
+ return _shared_build_task_local_workspace_reporting_diff(
83
+ root,
84
+ baseline=baseline,
85
+ after_commit=after_commit,
86
+ )
87
+
88
+
89
+ def _snapshot_session_logs(cfg: AppConfig) -> set[Path]:
90
+ return _shared_snapshot_session_logs(cfg)
91
+
92
+
93
+ def _execution_private_sessions_dir(
94
+ *,
95
+ cfg: AppConfig,
96
+ run_id: str,
97
+ task_id: str,
98
+ workspace_root: Path,
99
+ ) -> Path:
100
+ return _shared_execution_private_sessions_dir(
101
+ cfg=cfg,
102
+ run_id=run_id,
103
+ task_id=task_id,
104
+ workspace_root=workspace_root,
105
+ )
106
+
107
+
108
+ def _cleanup_execution_private_sessions_dir(path: Path) -> None:
109
+ _shared_cleanup_execution_private_sessions_dir(path)
110
+
111
+
112
+ def _write_exec_log_artifacts(
113
+ *,
114
+ paths: Any,
115
+ task_id: str,
116
+ cfg: AppConfig,
117
+ no_log: bool,
118
+ before_logs: set[Path] | None,
119
+ sessions_dir: Path | None = None,
120
+ expected_session_id: str | None = None,
121
+ ) -> Any:
122
+ return _shared_write_exec_log_artifacts(
123
+ paths=paths,
124
+ task_id=task_id,
125
+ cfg=cfg,
126
+ no_log=no_log,
127
+ before_logs=before_logs,
128
+ sessions_dir=sessions_dir,
129
+ expected_session_id=expected_session_id,
130
+ )
131
+
132
+
133
+ def _write_execution_context_artifact(
134
+ *,
135
+ paths: Any,
136
+ task_id: str,
137
+ context_text: str,
138
+ ) -> Path:
139
+ return _shared_write_execution_context_artifact(
140
+ run_paths=paths,
141
+ task_id=task_id,
142
+ context_text=context_text,
143
+ )
144
+
145
+
146
+ def _write_execution_budget_artifact(
147
+ *,
148
+ paths: Any,
149
+ task_id: str,
150
+ payload: dict[str, Any],
151
+ ) -> Path:
152
+ return _shared_write_execution_budget_artifact(
153
+ run_paths=paths,
154
+ task_id=task_id,
155
+ payload=payload,
156
+ )
157
+
158
+
159
+ def _resolve_managed_task_step_budget(
160
+ *,
161
+ cfg: AppConfig,
162
+ plan: dict[str, Any],
163
+ task: dict[str, Any],
164
+ kind: str = "managed_task",
165
+ mode: str | None = None,
166
+ verification_enabled: bool,
167
+ max_steps_override: int | None = None,
168
+ attempt_count: int | None = None,
169
+ image_count: int = 0,
170
+ conflict_file_count: int = 0,
171
+ ):
172
+ return _shared_resolve_managed_task_step_budget(
173
+ cfg=cfg,
174
+ plan=plan,
175
+ task=task,
176
+ kind=kind,
177
+ mode=mode,
178
+ verification_enabled=verification_enabled,
179
+ max_steps_override=max_steps_override,
180
+ attempt_count=attempt_count,
181
+ image_count=image_count,
182
+ conflict_file_count=conflict_file_count,
183
+ )
184
+
185
+
186
+ def _prepare_task_execution_knowledge(
187
+ *,
188
+ run_paths: RunPaths,
189
+ task: dict[str, Any],
190
+ selection_label: str,
191
+ extra_paths: list[str] | None = None,
192
+ limit: int = 4,
193
+ ) -> Any:
194
+ return _shared_prepare_task_execution_knowledge(
195
+ run_paths=run_paths,
196
+ task=task,
197
+ selection_label=selection_label,
198
+ extra_paths=extra_paths,
199
+ limit=limit,
200
+ )
201
+
202
+
203
+ def _mirror_selected_knowledge_into_worktree(
204
+ *,
205
+ materialized: Any,
206
+ run_paths: RunPaths,
207
+ worktree_repo_path: Path,
208
+ ) -> None:
209
+ _shared_mirror_selected_knowledge_into_worktree(
210
+ materialized=materialized,
211
+ run_paths=run_paths,
212
+ worktree_repo_path=worktree_repo_path,
213
+ )
214
+
215
+
216
+ def _task_dependency_blockers(plan: dict[str, Any], task: dict[str, Any]) -> list[str]:
217
+ blockers: list[str] = []
218
+ deps = task.get("dependencies") or []
219
+ for dep_id in deps:
220
+ dep_task = find_task(plan, str(dep_id))
221
+ if dep_task is None:
222
+ blockers.append(f"{dep_id} (missing)")
223
+ continue
224
+ dep_status = str(dep_task.get("status") or "")
225
+ if dep_status != "done":
226
+ blockers.append(f"{dep_id} ({dep_status or 'unknown'})")
227
+ tasks = [item for item in plan.get("tasks") or [] if isinstance(item, dict)]
228
+ inferred = infer_ordered_predecessor_dependency(tasks=tasks, task=task)
229
+ if inferred is not None and inferred.depends_on not in {str(dep).strip() for dep in deps}:
230
+ dep_task = find_task(plan, inferred.depends_on)
231
+ if dep_task is None:
232
+ blockers.append(f"{inferred.depends_on} (missing, inferred)")
233
+ else:
234
+ dep_status = str(dep_task.get("status") or "")
235
+ if dep_status != "done":
236
+ blockers.append(f"{inferred.depends_on} ({dep_status or 'unknown'}, inferred)")
237
+ return blockers
238
+
239
+
240
+ def _safe_task_file_component(task_id: str) -> str:
241
+ return _shared_safe_task_file_component(task_id)
242
+
243
+
244
+ def _normalize_scope_mode(scope: str) -> str:
245
+ value = scope.strip().lower()
246
+ if value not in _SCOPE_MODES:
247
+ raise ForgeError("Invalid --scope. Use one of: off, warn, strict.")
248
+ return value
249
+
250
+
251
+ def _normalize_verify_mode(value: str) -> str:
252
+ try:
253
+ return normalize_verify_mode(value)
254
+ except VerifyError as e:
255
+ raise ForgeError(str(e)) from e
256
+
257
+
258
+ def _remote_report_lines(record: dict[str, Any] | None) -> list[str]:
259
+ if not record:
260
+ return []
261
+ lines: list[str] = [
262
+ f"remote={record.get('remote') or '-'} provider={record.get('provider') or 'unknown'}",
263
+ (
264
+ f"pushed_branch={record.get('pushed_branch')} "
265
+ f"pushed_base={record.get('pushed_base')} "
266
+ f"created_pr={record.get('created_pr')}"
267
+ ),
268
+ ]
269
+ pr_url = str(record.get("pr_url") or "").strip()
270
+ if pr_url:
271
+ lines.append(f"pr_url={pr_url}")
272
+ pr_id = str(record.get("pr_number_or_iid") or "").strip()
273
+ if pr_id:
274
+ lines.append(f"pr_number_or_iid={pr_id}")
275
+
276
+ branch_push_output = str(record.get("branch_push_output") or "").strip()
277
+ if branch_push_output:
278
+ lines.append(f"branch_push_output={truncate_output(branch_push_output, max_chars=300)}")
279
+ base_push_output = str(record.get("base_push_output") or "").strip()
280
+ if base_push_output:
281
+ lines.append(f"base_push_output={truncate_output(base_push_output, max_chars=300)}")
282
+ pr_output = str(record.get("pr_output") or "").strip()
283
+ if pr_output:
284
+ lines.append(f"pr_output={truncate_output(pr_output, max_chars=300)}")
285
+
286
+ raw_errors = record.get("errors")
287
+ if isinstance(raw_errors, list):
288
+ for err in raw_errors:
289
+ msg = str(err).strip()
290
+ if msg:
291
+ lines.append(f"error={truncate_output(msg, max_chars=300)}")
292
+ return lines
293
+
294
+
295
+ def _print_usage_summary_from_logs(
296
+ *,
297
+ console: Console,
298
+ title: str,
299
+ log_paths: list[Path],
300
+ ) -> None:
301
+ summary = aggregate_usage_from_session_logs(log_paths)
302
+ rows = summary.by_model_rows()
303
+ if not rows:
304
+ return
305
+ table = _Table(title=title)
306
+ table.add_column("model")
307
+ table.add_column("prompt_tokens", justify="right")
308
+ table.add_column("completion_tokens", justify="right")
309
+ table.add_column("total_tokens", justify="right")
310
+ table.add_column("cost_usd", justify="right")
311
+ table.add_column("unknown_pricing", justify="right")
312
+ table.add_column("usage_source(api/est)", justify="right")
313
+ for row in rows:
314
+ unknown_count = int(row.get("unknown_cost_count") or 0)
315
+ cost_display = _format_cost_with_unknown(
316
+ known_cost=_known_cost_value(row),
317
+ unknown_calls=unknown_count,
318
+ style="table",
319
+ )
320
+ table.add_row(
321
+ str(row.get("model") or "-"),
322
+ str(int(row.get("prompt_tokens") or 0)),
323
+ str(int(row.get("completion_tokens") or 0)),
324
+ str(int(row.get("total_tokens") or 0)),
325
+ cost_display,
326
+ str(unknown_count),
327
+ (f"{int(row.get('api_usage_calls') or 0)}/{int(row.get('estimate_usage_calls') or 0)}"),
328
+ )
329
+ totals = summary.totals()
330
+ total_cost = _format_cost_with_unknown(
331
+ known_cost=_known_cost_value(totals),
332
+ unknown_calls=int(totals.get("unknown_cost_calls") or 0),
333
+ style="table",
334
+ )
335
+ table.add_row(
336
+ "TOTAL",
337
+ str(int(totals.get("prompt_tokens") or 0)),
338
+ str(int(totals.get("completion_tokens") or 0)),
339
+ str(int(totals.get("total_tokens") or 0)),
340
+ total_cost,
341
+ str(int(totals.get("unknown_cost_calls") or 0)),
342
+ (
343
+ f"{int(totals.get('api_usage_calls') or 0)}/"
344
+ f"{int(totals.get('estimate_usage_calls') or 0)}"
345
+ ),
346
+ )
347
+ console.print(table)
348
+
349
+
350
+ __all__ = [name for name in globals() if (not name.startswith("__") or name == "__version__")]