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.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,1118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Annotated, Any
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from ... import __version__
|
|
14
|
+
from ...auth_diagnostics import auth_doctor_payload
|
|
15
|
+
from ...build_identity import decide_clean_build, version_line
|
|
16
|
+
from ...config import (
|
|
17
|
+
AppConfig,
|
|
18
|
+
ConfigError,
|
|
19
|
+
config_path,
|
|
20
|
+
load_config,
|
|
21
|
+
resolve_model_access_api_key,
|
|
22
|
+
resolve_web_search_policy,
|
|
23
|
+
)
|
|
24
|
+
from ...provider_diagnostics import (
|
|
25
|
+
build_provider_diagnostics,
|
|
26
|
+
probe_reasoning_suppression_live,
|
|
27
|
+
validate_active_provider_live,
|
|
28
|
+
validate_web_search_live,
|
|
29
|
+
)
|
|
30
|
+
from ...provider_telemetry import (
|
|
31
|
+
diagnostic_bundle_payload,
|
|
32
|
+
last_provider_call_summary,
|
|
33
|
+
last_web_search_summary,
|
|
34
|
+
)
|
|
35
|
+
from ...sandbox_doctor import diagnose_sandbox
|
|
36
|
+
from ...skills import resolve_skills_enabled
|
|
37
|
+
from ...step_budget import normalize_step_budget_policy
|
|
38
|
+
from ...tools.availability import get_tool_availability
|
|
39
|
+
from ...tools.registry import iter_builtin_tool_metadata
|
|
40
|
+
from ...tools.web_search import resolve_web_search_runtime_status
|
|
41
|
+
from ..assets_cli import assets_app as forge_assets_app
|
|
42
|
+
from . import _patchable
|
|
43
|
+
from ._shared import (
|
|
44
|
+
Mode,
|
|
45
|
+
_console,
|
|
46
|
+
_Table,
|
|
47
|
+
)
|
|
48
|
+
from .auth import JSON_OPTION_HELP as _JSON_OPTION_HELP
|
|
49
|
+
from .auth import _status_payload as _auth_status_payload
|
|
50
|
+
from .auth import auth_app
|
|
51
|
+
from .config import config_app
|
|
52
|
+
from .conventions import conventions_app
|
|
53
|
+
from .extensions import ext_app
|
|
54
|
+
from .forge import forge_app
|
|
55
|
+
from .hooks import hooks_app
|
|
56
|
+
from .ide_bridge import ide_bridge_app
|
|
57
|
+
from .mcp import mcp_app, mcp_auth_app, mcp_prompts_app
|
|
58
|
+
from .profile import profile_app
|
|
59
|
+
from .report import report_app
|
|
60
|
+
from .sandbox import sandbox_app
|
|
61
|
+
from .server import server_app
|
|
62
|
+
from .sessions import sessions_app
|
|
63
|
+
from .skills import skill_app
|
|
64
|
+
from .tools import tool_app
|
|
65
|
+
from .update import (
|
|
66
|
+
_BACKGROUND_UPDATE_SUBCOMMANDS,
|
|
67
|
+
_cached_update_status_summary,
|
|
68
|
+
_start_background_update_check,
|
|
69
|
+
maybe_prompt_update_at_startup,
|
|
70
|
+
update_app,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if TYPE_CHECKING:
|
|
74
|
+
from rich.table import Table
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _cli_module() -> Any:
|
|
78
|
+
module = sys.modules.get("alysis_code.cli")
|
|
79
|
+
if module is not None:
|
|
80
|
+
return module
|
|
81
|
+
from ... import cli
|
|
82
|
+
|
|
83
|
+
return cli
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
app = typer.Typer(add_completion=False, help="Local CLI coding agent (multi-provider).")
|
|
87
|
+
app.add_typer(auth_app, name="auth")
|
|
88
|
+
app.add_typer(config_app, name="config")
|
|
89
|
+
app.add_typer(profile_app, name="profile")
|
|
90
|
+
app.add_typer(update_app, name="update")
|
|
91
|
+
app.add_typer(sessions_app, name="sessions")
|
|
92
|
+
app.add_typer(forge_app, name="forge")
|
|
93
|
+
forge_app.add_typer(forge_assets_app, name="assets")
|
|
94
|
+
app.add_typer(server_app, name="server")
|
|
95
|
+
app.add_typer(ext_app, name="ext")
|
|
96
|
+
app.add_typer(report_app, name="report")
|
|
97
|
+
app.add_typer(mcp_app, name="mcp")
|
|
98
|
+
app.add_typer(skill_app, name="skill")
|
|
99
|
+
app.add_typer(conventions_app, name="conventions")
|
|
100
|
+
app.add_typer(tool_app, name="tool")
|
|
101
|
+
app.add_typer(hooks_app, name="hooks")
|
|
102
|
+
app.add_typer(sandbox_app, name="sandbox")
|
|
103
|
+
app.add_typer(ide_bridge_app, name="ide-bridge")
|
|
104
|
+
mcp_app.add_typer(mcp_prompts_app, name="prompts")
|
|
105
|
+
mcp_app.add_typer(mcp_auth_app, name="auth")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
#: A build that cannot identify itself is a configuration problem, not a task
|
|
109
|
+
#: failure, so it gets its own code rather than the generic 1.
|
|
110
|
+
UNIDENTIFIABLE_BUILD_EXIT_CODE = 2
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _show_version(value: bool) -> None:
|
|
114
|
+
if not value:
|
|
115
|
+
return
|
|
116
|
+
# Version, commit, build timestamp and dirty flag on one line, version
|
|
117
|
+
# first. Three behaviourally different builds once all reported "0.9.8",
|
|
118
|
+
# which made every score they produced unattributable; the bare version is
|
|
119
|
+
# kept as the leading token because the VS Code extension, the managed-CLI
|
|
120
|
+
# smoke test and the release distribution validator all read this output.
|
|
121
|
+
typer.echo(version_line(__version__))
|
|
122
|
+
raise typer.Exit()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _enforce_clean_build(flag: bool) -> None:
|
|
126
|
+
"""Refuse to start when the build cannot prove what it is.
|
|
127
|
+
|
|
128
|
+
Benchmark harnesses set ``ALYSIS_REQUIRE_CLEAN_BUILD=1`` so a campaign
|
|
129
|
+
cannot silently run against an unpinned or locally-modified build. One
|
|
130
|
+
earlier campaign did exactly that -- it ran against "latest main" -- and
|
|
131
|
+
none of its results can now be tied to a source tree.
|
|
132
|
+
|
|
133
|
+
Deliberately after ``--version``, which is eager: diagnosing a refusal
|
|
134
|
+
starts by asking the binary what it thinks it is, and that has to keep
|
|
135
|
+
working.
|
|
136
|
+
"""
|
|
137
|
+
decision = decide_clean_build(flag=flag)
|
|
138
|
+
if decision.allowed:
|
|
139
|
+
return
|
|
140
|
+
typer.echo(f"alysis: {decision.message}", err=True)
|
|
141
|
+
raise typer.Exit(code=UNIDENTIFIABLE_BUILD_EXIT_CODE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@app.callback(invoke_without_command=True)
|
|
145
|
+
def main(
|
|
146
|
+
ctx: typer.Context,
|
|
147
|
+
version: Annotated[
|
|
148
|
+
bool,
|
|
149
|
+
typer.Option(
|
|
150
|
+
"--version",
|
|
151
|
+
callback=_show_version,
|
|
152
|
+
is_eager=True,
|
|
153
|
+
help="Show the installed Alysis Code version, commit and build stamp, then exit.",
|
|
154
|
+
),
|
|
155
|
+
] = False,
|
|
156
|
+
require_clean_build: Annotated[
|
|
157
|
+
bool,
|
|
158
|
+
typer.Option(
|
|
159
|
+
"--require-clean-build",
|
|
160
|
+
help=(
|
|
161
|
+
"Refuse to run unless this build recorded a commit and a clean tree at "
|
|
162
|
+
"build time. Also enabled by ALYSIS_REQUIRE_CLEAN_BUILD=1."
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
] = False,
|
|
166
|
+
) -> None:
|
|
167
|
+
# Before anything else, and before the subcommand dispatch below, so that
|
|
168
|
+
# `alysis run ...` under a benchmark harness is refused just as the bare
|
|
169
|
+
# invocation is.
|
|
170
|
+
_enforce_clean_build(require_clean_build)
|
|
171
|
+
cli = _cli_module()
|
|
172
|
+
if ctx.invoked_subcommand is not None:
|
|
173
|
+
if ctx.invoked_subcommand in _BACKGROUND_UPDATE_SUBCOMMANDS:
|
|
174
|
+
_start_background_update_check()
|
|
175
|
+
return
|
|
176
|
+
console = cli._console()
|
|
177
|
+
if cli._is_interactive_terminal():
|
|
178
|
+
_start_background_update_check()
|
|
179
|
+
if cli._is_non_interactive_terminal():
|
|
180
|
+
console.print(cli._home_panel())
|
|
181
|
+
return
|
|
182
|
+
maybe_prompt_update_at_startup()
|
|
183
|
+
if not cli._home_prompt_enabled():
|
|
184
|
+
if not cli._maybe_run_first_run_setup_wizard():
|
|
185
|
+
return
|
|
186
|
+
cli._maybe_run_startup_config_menu()
|
|
187
|
+
cli._run_default_chat_action()
|
|
188
|
+
return
|
|
189
|
+
console.print(cli._home_panel())
|
|
190
|
+
try:
|
|
191
|
+
action = (
|
|
192
|
+
typer.prompt(
|
|
193
|
+
"Action [1=chat|2=run|3=setup|4=doctor|5=plan|6=quit]",
|
|
194
|
+
default="1",
|
|
195
|
+
)
|
|
196
|
+
.strip()
|
|
197
|
+
.lower()
|
|
198
|
+
)
|
|
199
|
+
except (EOFError, KeyboardInterrupt):
|
|
200
|
+
console.print("")
|
|
201
|
+
return
|
|
202
|
+
action = cli._HOME_ACTION_ALIASES.get(action, action)
|
|
203
|
+
|
|
204
|
+
if action in {"quit", "q", "exit"}:
|
|
205
|
+
return
|
|
206
|
+
if action in {"chat", "c"}:
|
|
207
|
+
cli._maybe_run_startup_config_menu()
|
|
208
|
+
cli._run_default_chat_action()
|
|
209
|
+
return
|
|
210
|
+
if action in {"run", "r"}:
|
|
211
|
+
instruction = typer.prompt("Instruction").strip()
|
|
212
|
+
if instruction:
|
|
213
|
+
cli._run_default_run_action(instruction)
|
|
214
|
+
return
|
|
215
|
+
if action in {"setup", "s"}:
|
|
216
|
+
cli.setup()
|
|
217
|
+
return
|
|
218
|
+
if action in {"doctor", "d"}:
|
|
219
|
+
cli.doctor()
|
|
220
|
+
return
|
|
221
|
+
if action in {"plan", "p"}:
|
|
222
|
+
cli.forge_plan(path=Path("."))
|
|
223
|
+
return
|
|
224
|
+
console.print("[yellow]Unknown action.[/yellow] Run `alysis --help`.")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _require_active_subscription_ready(
|
|
228
|
+
*,
|
|
229
|
+
model: str | None,
|
|
230
|
+
base_url: str | None,
|
|
231
|
+
require_ready: bool = True,
|
|
232
|
+
) -> None:
|
|
233
|
+
_cli_module()._require_active_subscription_ready(
|
|
234
|
+
model=model,
|
|
235
|
+
base_url=base_url,
|
|
236
|
+
require_ready=require_ready,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@app.command()
|
|
241
|
+
def run(
|
|
242
|
+
ctx: typer.Context = None,
|
|
243
|
+
instruction: str = typer.Argument(..., help="What you want the agent to do."),
|
|
244
|
+
path: Path = typer.Option(Path("."), "--path", help="Working directory/root."),
|
|
245
|
+
create_path: bool = typer.Option(
|
|
246
|
+
False,
|
|
247
|
+
"--create-path",
|
|
248
|
+
help="Create --path if it does not exist before binding the workspace.",
|
|
249
|
+
),
|
|
250
|
+
allow_broad_workspace: bool = typer.Option(
|
|
251
|
+
False,
|
|
252
|
+
"--allow-broad-workspace",
|
|
253
|
+
help="Allow guarded broad workspaces in non-interactive startup flows.",
|
|
254
|
+
),
|
|
255
|
+
image: list[Path] | None = typer.Option(
|
|
256
|
+
None,
|
|
257
|
+
"--image",
|
|
258
|
+
help="Attach image path(s). Repeat --image for multiple files.",
|
|
259
|
+
),
|
|
260
|
+
mode: Mode | None = typer.Option(None, "--mode", help="Mode override."),
|
|
261
|
+
persona: str | None = typer.Option(
|
|
262
|
+
None,
|
|
263
|
+
"--persona",
|
|
264
|
+
help=("Persona: code, architect, ask, debug, or a custom persona from .alysis_personas."),
|
|
265
|
+
),
|
|
266
|
+
model: str | None = typer.Option(None, "--model", help="Model override."),
|
|
267
|
+
base_url: str | None = typer.Option(None, "--base-url", help="Base URL override."),
|
|
268
|
+
temperature: float | None = typer.Option(None, "--temperature", help="Sampling temperature."),
|
|
269
|
+
stream: bool | None = typer.Option(
|
|
270
|
+
None,
|
|
271
|
+
"--stream/--no-stream",
|
|
272
|
+
help="Enable streamed assistant output.",
|
|
273
|
+
),
|
|
274
|
+
max_steps: int | None = typer.Option(
|
|
275
|
+
None,
|
|
276
|
+
"--max-steps",
|
|
277
|
+
help="Optional safety limit on agent iterations.",
|
|
278
|
+
),
|
|
279
|
+
subagents: bool | None = typer.Option(
|
|
280
|
+
None,
|
|
281
|
+
"--subagents/--no-subagents",
|
|
282
|
+
help="Enable or disable subagent delegation for this session.",
|
|
283
|
+
),
|
|
284
|
+
no_log: bool = typer.Option(False, "--no-log", help="Disable JSONL session logging."),
|
|
285
|
+
verify_cmd: list[str] | None = typer.Option(
|
|
286
|
+
None,
|
|
287
|
+
"--verify-cmd",
|
|
288
|
+
help="Override verification command for this run (repeatable).",
|
|
289
|
+
),
|
|
290
|
+
api_key_env: str | None = typer.Option(
|
|
291
|
+
None,
|
|
292
|
+
"--api-key-env",
|
|
293
|
+
help=(
|
|
294
|
+
"Read API key from this environment variable (overrides ALYSIS_API_KEY/OPENAI_API_KEY)."
|
|
295
|
+
),
|
|
296
|
+
),
|
|
297
|
+
api_key_stdin: bool = typer.Option(
|
|
298
|
+
False,
|
|
299
|
+
"--api-key-stdin",
|
|
300
|
+
help="Prompt for API key (hidden input). Key is kept in memory for this run only.",
|
|
301
|
+
),
|
|
302
|
+
api_key: str | None = typer.Option(
|
|
303
|
+
None,
|
|
304
|
+
"--api-key",
|
|
305
|
+
help=(
|
|
306
|
+
"UNSAFE: Provide API key via CLI argument (may leak via shell history / process list). "
|
|
307
|
+
"Prefer --api-key-stdin or --api-key-env."
|
|
308
|
+
),
|
|
309
|
+
),
|
|
310
|
+
yes: bool = typer.Option(
|
|
311
|
+
False,
|
|
312
|
+
"--yes",
|
|
313
|
+
help="In auto mode, skip confirmations for sensitive commands (hard blocks still apply).",
|
|
314
|
+
),
|
|
315
|
+
benchmark: bool = typer.Option(
|
|
316
|
+
False,
|
|
317
|
+
"--benchmark",
|
|
318
|
+
help=(
|
|
319
|
+
"Use the raw benchmark/autonomy run profile: auto mode, code-only routing, "
|
|
320
|
+
"longer fixed step budget, and no subagents/skills/custom tools/web by default."
|
|
321
|
+
),
|
|
322
|
+
),
|
|
323
|
+
deadline_seconds: float | None = typer.Option(
|
|
324
|
+
None,
|
|
325
|
+
"--deadline-seconds",
|
|
326
|
+
help=(
|
|
327
|
+
"Stop this one-shot run after the given invocation-wide wall-clock seconds. "
|
|
328
|
+
"Defaults to 3600 (60 min); use --no-deadline to run unbounded."
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
no_deadline: bool = typer.Option(
|
|
332
|
+
False,
|
|
333
|
+
"--no-deadline",
|
|
334
|
+
help="Run without any wall-clock budget, overriding the default one-shot deadline.",
|
|
335
|
+
),
|
|
336
|
+
require_deadline: bool = typer.Option(
|
|
337
|
+
False,
|
|
338
|
+
"--require-deadline",
|
|
339
|
+
help=(
|
|
340
|
+
"Require a finite one-shot run deadline from CLI, environment, or config. "
|
|
341
|
+
"Intended for managed hosts."
|
|
342
|
+
),
|
|
343
|
+
),
|
|
344
|
+
diagnostic_log: Path | None = typer.Option(
|
|
345
|
+
None,
|
|
346
|
+
"--diagnostic-log",
|
|
347
|
+
help="Append minimal crash-safe diagnostic events to this JSONL path.",
|
|
348
|
+
),
|
|
349
|
+
) -> None:
|
|
350
|
+
if not instruction.strip():
|
|
351
|
+
typer.echo("Missing argument 'INSTRUCTION': instruction is empty.", err=True)
|
|
352
|
+
raise typer.Exit(1)
|
|
353
|
+
_require_active_subscription_ready(model=model, base_url=base_url)
|
|
354
|
+
|
|
355
|
+
from ..chat import run_impl
|
|
356
|
+
|
|
357
|
+
return run_impl(
|
|
358
|
+
_cli_module(),
|
|
359
|
+
instruction,
|
|
360
|
+
path,
|
|
361
|
+
create_path,
|
|
362
|
+
allow_broad_workspace,
|
|
363
|
+
image,
|
|
364
|
+
mode,
|
|
365
|
+
persona,
|
|
366
|
+
model,
|
|
367
|
+
base_url,
|
|
368
|
+
temperature,
|
|
369
|
+
stream,
|
|
370
|
+
max_steps,
|
|
371
|
+
subagents,
|
|
372
|
+
no_log,
|
|
373
|
+
verify_cmd,
|
|
374
|
+
api_key_env,
|
|
375
|
+
api_key_stdin,
|
|
376
|
+
api_key,
|
|
377
|
+
yes,
|
|
378
|
+
benchmark,
|
|
379
|
+
deadline_seconds,
|
|
380
|
+
no_deadline,
|
|
381
|
+
require_deadline,
|
|
382
|
+
diagnostic_log,
|
|
383
|
+
cli_ctx=ctx,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@app.command()
|
|
388
|
+
def chat(
|
|
389
|
+
ctx: typer.Context = None,
|
|
390
|
+
path: Path = typer.Option(Path("."), "--path", help="Working directory/root."),
|
|
391
|
+
create_path: bool = typer.Option(
|
|
392
|
+
False,
|
|
393
|
+
"--create-path",
|
|
394
|
+
help="Create --path if it does not exist before binding the workspace.",
|
|
395
|
+
),
|
|
396
|
+
allow_broad_workspace: bool = typer.Option(
|
|
397
|
+
False,
|
|
398
|
+
"--allow-broad-workspace",
|
|
399
|
+
help="Allow guarded broad workspaces in non-interactive startup flows.",
|
|
400
|
+
),
|
|
401
|
+
image: list[Path] | None = typer.Option(
|
|
402
|
+
None,
|
|
403
|
+
"--image",
|
|
404
|
+
help="Queue image path(s) for the next message. Repeat --image for multiple files.",
|
|
405
|
+
),
|
|
406
|
+
mode: Mode | None = typer.Option(None, "--mode", help="Mode override."),
|
|
407
|
+
persona: str | None = typer.Option(
|
|
408
|
+
None,
|
|
409
|
+
"--persona",
|
|
410
|
+
help=("Persona: code, architect, ask, debug, or a custom persona from .alysis_personas."),
|
|
411
|
+
),
|
|
412
|
+
model: str | None = typer.Option(None, "--model", help="Model override."),
|
|
413
|
+
base_url: str | None = typer.Option(None, "--base-url", help="Base URL override."),
|
|
414
|
+
temperature: float | None = typer.Option(None, "--temperature", help="Sampling temperature."),
|
|
415
|
+
stream: bool | None = typer.Option(
|
|
416
|
+
None,
|
|
417
|
+
"--stream/--no-stream",
|
|
418
|
+
help="Enable streamed assistant output.",
|
|
419
|
+
),
|
|
420
|
+
max_steps: int | None = typer.Option(
|
|
421
|
+
None,
|
|
422
|
+
"--max-steps",
|
|
423
|
+
help="Optional safety limit on agent iterations for each user turn.",
|
|
424
|
+
),
|
|
425
|
+
subagents: bool | None = typer.Option(
|
|
426
|
+
None,
|
|
427
|
+
"--subagents/--no-subagents",
|
|
428
|
+
help="Enable or disable subagent delegation for this session.",
|
|
429
|
+
),
|
|
430
|
+
no_log: bool = typer.Option(False, "--no-log", help="Disable JSONL session logging."),
|
|
431
|
+
verify_cmd: list[str] | None = typer.Option(
|
|
432
|
+
None,
|
|
433
|
+
"--verify-cmd",
|
|
434
|
+
help="Override verification command for this chat session (repeatable).",
|
|
435
|
+
),
|
|
436
|
+
api_key_env: str | None = typer.Option(
|
|
437
|
+
None,
|
|
438
|
+
"--api-key-env",
|
|
439
|
+
help=(
|
|
440
|
+
"Read API key from this environment variable (overrides ALYSIS_API_KEY/OPENAI_API_KEY)."
|
|
441
|
+
),
|
|
442
|
+
),
|
|
443
|
+
api_key_stdin: bool = typer.Option(
|
|
444
|
+
False,
|
|
445
|
+
"--api-key-stdin",
|
|
446
|
+
help="Prompt for API key (hidden input). Key is kept in memory for this run only.",
|
|
447
|
+
),
|
|
448
|
+
api_key: str | None = typer.Option(
|
|
449
|
+
None,
|
|
450
|
+
"--api-key",
|
|
451
|
+
help=(
|
|
452
|
+
"UNSAFE: Provide API key via CLI argument (may leak via shell history / process list). "
|
|
453
|
+
"Prefer --api-key-stdin or --api-key-env."
|
|
454
|
+
),
|
|
455
|
+
),
|
|
456
|
+
yes: bool = typer.Option(
|
|
457
|
+
False,
|
|
458
|
+
"--yes",
|
|
459
|
+
help="In auto mode, skip confirmations for sensitive commands.",
|
|
460
|
+
),
|
|
461
|
+
diagnostic_log: Path | None = typer.Option(
|
|
462
|
+
None,
|
|
463
|
+
"--diagnostic-log",
|
|
464
|
+
help="Append minimal crash-safe diagnostic events to this JSONL path.",
|
|
465
|
+
),
|
|
466
|
+
) -> None:
|
|
467
|
+
_require_active_subscription_ready(
|
|
468
|
+
model=model,
|
|
469
|
+
base_url=base_url,
|
|
470
|
+
require_ready=False,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
from ..chat import chat_impl
|
|
474
|
+
|
|
475
|
+
return chat_impl(
|
|
476
|
+
_cli_module(),
|
|
477
|
+
path,
|
|
478
|
+
create_path,
|
|
479
|
+
allow_broad_workspace,
|
|
480
|
+
image,
|
|
481
|
+
mode,
|
|
482
|
+
persona,
|
|
483
|
+
model,
|
|
484
|
+
base_url,
|
|
485
|
+
temperature,
|
|
486
|
+
stream,
|
|
487
|
+
max_steps,
|
|
488
|
+
subagents,
|
|
489
|
+
no_log,
|
|
490
|
+
verify_cmd,
|
|
491
|
+
api_key_env,
|
|
492
|
+
api_key_stdin,
|
|
493
|
+
api_key,
|
|
494
|
+
yes,
|
|
495
|
+
diagnostic_log,
|
|
496
|
+
cli_ctx=ctx,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _doctor_table(cfg: AppConfig) -> Table:
|
|
501
|
+
table = _Table(title="alysis doctor")
|
|
502
|
+
table.add_column("check")
|
|
503
|
+
table.add_column("result")
|
|
504
|
+
api_key = _cli_module()._resolved_api_key_value()
|
|
505
|
+
|
|
506
|
+
table.add_row("python", sys.version.split()[0])
|
|
507
|
+
|
|
508
|
+
def _cmd_exists(cmd: str) -> bool:
|
|
509
|
+
return shutil.which(cmd) is not None
|
|
510
|
+
|
|
511
|
+
table.add_row("git", "ok" if _cmd_exists("git") else "missing")
|
|
512
|
+
table.add_row(
|
|
513
|
+
"rg",
|
|
514
|
+
"ok" if _cmd_exists("rg") else "missing (search fallback will be slower)",
|
|
515
|
+
)
|
|
516
|
+
table.add_row("config_path", os.fspath(config_path()))
|
|
517
|
+
table.add_row(
|
|
518
|
+
"model_set",
|
|
519
|
+
"yes" if bool(cfg.model) else "no (run: alysis config set model <MODEL>)",
|
|
520
|
+
)
|
|
521
|
+
table.add_row(
|
|
522
|
+
"api_key_set",
|
|
523
|
+
(
|
|
524
|
+
f"yes ({_cli_module()._api_key_source_label(api_key.source)})"
|
|
525
|
+
if api_key.key
|
|
526
|
+
else "no (run: alysis config set-api-key)"
|
|
527
|
+
),
|
|
528
|
+
)
|
|
529
|
+
table.add_row("base_url", cfg.base_url)
|
|
530
|
+
table.add_row("update", _cached_update_status_summary(cfg))
|
|
531
|
+
web_search_status = resolve_web_search_runtime_status(cfg=cfg, api_key=api_key.key)
|
|
532
|
+
web_search_policy = resolve_web_search_policy(cfg)
|
|
533
|
+
web_search_label = (
|
|
534
|
+
"policy-disabled" if web_search_policy == "off" else web_search_status.availability_label
|
|
535
|
+
)
|
|
536
|
+
web_search_setup = (
|
|
537
|
+
"Set `alysis config set web_search_policy auto` to expose search to the model."
|
|
538
|
+
if web_search_policy == "off"
|
|
539
|
+
else web_search_status.setup_hint
|
|
540
|
+
)
|
|
541
|
+
table.add_row("web_search", web_search_label)
|
|
542
|
+
table.add_row("web_search_policy", web_search_policy)
|
|
543
|
+
table.add_row("web_search_provider", web_search_status.provider or "(none)")
|
|
544
|
+
table.add_row("web_search_setup", web_search_setup)
|
|
545
|
+
try:
|
|
546
|
+
sandbox_status = _patchable("diagnose_sandbox", diagnose_sandbox)(
|
|
547
|
+
cfg, include_smoke=False, include_server_image=False
|
|
548
|
+
)
|
|
549
|
+
sandbox_label = (
|
|
550
|
+
"ready" if sandbox_status.ready else "not ready (run: alysis doctor sandbox)"
|
|
551
|
+
)
|
|
552
|
+
table.add_row("sandbox", sandbox_label)
|
|
553
|
+
table.add_row("sandbox_backend", sandbox_status.selected_backend or "(none)")
|
|
554
|
+
except ConfigError as exc:
|
|
555
|
+
table.add_row("sandbox", f"config error ({exc})")
|
|
556
|
+
table.add_row("temperature", str(cfg.temperature))
|
|
557
|
+
table.add_row("coding_temperature", str(cfg.coding_temperature))
|
|
558
|
+
table.add_row("chat_temperature", str(cfg.chat_temperature))
|
|
559
|
+
execution_policy = normalize_step_budget_policy(cfg.step_budget_policy)
|
|
560
|
+
table.add_row("execution_policy", execution_policy)
|
|
561
|
+
if execution_policy == "autonomous":
|
|
562
|
+
table.add_row("step_limit", "unlimited")
|
|
563
|
+
else:
|
|
564
|
+
table.add_row("chat_step_limit", str(cfg.max_steps))
|
|
565
|
+
table.add_row("task_step_limit", str(cfg.task_max_steps))
|
|
566
|
+
table.add_row("subagent_step_limit", str(cfg.subagent_max_steps))
|
|
567
|
+
table.add_row("custom_tools_enabled", "yes" if cfg.custom_tools_enabled else "no")
|
|
568
|
+
table.add_row("stream", "yes" if cfg.stream else "no")
|
|
569
|
+
return table
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _provider_doctor_table(cfg: AppConfig) -> Table:
|
|
573
|
+
diagnostics = build_provider_diagnostics(cfg)
|
|
574
|
+
table = _Table(title="alysis doctor providers")
|
|
575
|
+
table.add_column("field")
|
|
576
|
+
table.add_column("value")
|
|
577
|
+
for key, value in diagnostics.rows():
|
|
578
|
+
table.add_row(key, value)
|
|
579
|
+
last_call = last_provider_call_summary()
|
|
580
|
+
if last_call:
|
|
581
|
+
table.add_row("last_call_provider", str(last_call.get("provider_key") or "(unknown)"))
|
|
582
|
+
table.add_row("last_call_protocol", str(last_call.get("protocol") or "(unknown)"))
|
|
583
|
+
table.add_row("last_call_status", str(last_call.get("status_category") or "(unknown)"))
|
|
584
|
+
table.add_row("last_call_latency_ms", str(last_call.get("latency_ms") or 0))
|
|
585
|
+
table.add_row("last_call_stream", "yes" if last_call.get("stream") else "no")
|
|
586
|
+
table.add_row(
|
|
587
|
+
"last_call_web_search",
|
|
588
|
+
str((last_call.get("web_search") or {}).get("backend_kind") or "off"),
|
|
589
|
+
)
|
|
590
|
+
last_search = last_web_search_summary()
|
|
591
|
+
if last_search:
|
|
592
|
+
table.add_row("last_web_search_adapter", str(last_search.get("web_search_adapter") or ""))
|
|
593
|
+
table.add_row(
|
|
594
|
+
"last_web_search_hosted",
|
|
595
|
+
"yes" if last_search.get("provider_hosted_search") else "no",
|
|
596
|
+
)
|
|
597
|
+
table.add_row("last_web_search_sources", str(last_search.get("source_count") or 0))
|
|
598
|
+
return table
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _auth_doctor_table() -> Table:
|
|
602
|
+
payload = auth_doctor_payload()
|
|
603
|
+
context = payload["context"]
|
|
604
|
+
environment = payload["environment"]
|
|
605
|
+
path = environment["path"]
|
|
606
|
+
keyring = payload["keyring"]
|
|
607
|
+
table = _Table(title="alysis doctor auth")
|
|
608
|
+
table.add_column("check")
|
|
609
|
+
table.add_column("result")
|
|
610
|
+
table.add_row("context", str(context["kind"]))
|
|
611
|
+
table.add_row(
|
|
612
|
+
"tty",
|
|
613
|
+
f"stdin={_yes_no(context['stdin_tty'])} stdout={_yes_no(context['stdout_tty'])} "
|
|
614
|
+
f"stderr={_yes_no(context['stderr_tty'])}",
|
|
615
|
+
)
|
|
616
|
+
table.add_row("term", str(context["term"] or "(unset)"))
|
|
617
|
+
table.add_row("ci_env", ", ".join(context["ci_env"]) or "(none)")
|
|
618
|
+
table.add_row("home", str(environment["home"]))
|
|
619
|
+
table.add_row("home_env", str(environment["home_env"] or "(unset)"))
|
|
620
|
+
table.add_row("config_dir", str(environment["config_dir"]))
|
|
621
|
+
table.add_row("config_dir_override", str(environment["config_dir_override"] or "(unset)"))
|
|
622
|
+
table.add_row("config_exists", _yes_no(environment["config_exists"]))
|
|
623
|
+
table.add_row(
|
|
624
|
+
"path",
|
|
625
|
+
f"{path['entry_count']} entries"
|
|
626
|
+
+ (" (looks minimal)" if path["looks_minimal"] else "")
|
|
627
|
+
+ f" · cli={path['resolved_cli'] or '(not on PATH)'}",
|
|
628
|
+
)
|
|
629
|
+
table.add_row("keyring_backend", str(keyring["backend"] or "(unresolved)"))
|
|
630
|
+
table.add_row("keyring_available", _yes_no(keyring["available"]))
|
|
631
|
+
table.add_row("keyring_env_override", str(keyring["env_override"] or "(unset)"))
|
|
632
|
+
if not keyring["available"]:
|
|
633
|
+
table.add_row("keyring_reason", str(keyring["reason"] or "(unknown)"))
|
|
634
|
+
for store in payload["credential_stores"]:
|
|
635
|
+
summary = "missing (no credentials stored yet)" if not store["exists"] else "readable"
|
|
636
|
+
if store["exists"] and not store["readable"]:
|
|
637
|
+
summary = f"UNREADABLE — {store['error']}"
|
|
638
|
+
elif store["exists"]:
|
|
639
|
+
summary = (
|
|
640
|
+
f"readable · {store['entry_count']} entries · "
|
|
641
|
+
f"key_source={store['key_source'] or '(unknown)'}"
|
|
642
|
+
)
|
|
643
|
+
table.add_row(f"store:{store['name']}", summary)
|
|
644
|
+
table.add_row(f"store:{store['name']}:next_write", str(store["planned_key_source"]))
|
|
645
|
+
return table
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _yes_no(value: object) -> str:
|
|
649
|
+
return "yes" if value else "no"
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _doctor_bundle_payload(cfg: AppConfig) -> dict[str, Any]:
|
|
653
|
+
diagnostics = build_provider_diagnostics(cfg)
|
|
654
|
+
return diagnostic_bundle_payload(
|
|
655
|
+
provider_diagnostics={key: value for key, value in diagnostics.rows()}
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _provider_live_validation_table(cfg: AppConfig, *, timeout_s: float = 15.0) -> Table:
|
|
660
|
+
validation = _patchable("validate_active_provider_live", validate_active_provider_live)(
|
|
661
|
+
cfg,
|
|
662
|
+
timeout_s=timeout_s,
|
|
663
|
+
)
|
|
664
|
+
table = _Table(title="alysis doctor providers --live")
|
|
665
|
+
table.add_column("field")
|
|
666
|
+
table.add_column("value")
|
|
667
|
+
for key, value in validation.rows():
|
|
668
|
+
table.add_row(key, value)
|
|
669
|
+
return table
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _web_search_live_validation_table(cfg: AppConfig, *, timeout_s: float = 15.0) -> Table:
|
|
673
|
+
validation = _patchable("validate_web_search_live", validate_web_search_live)(
|
|
674
|
+
cfg,
|
|
675
|
+
timeout_s=timeout_s,
|
|
676
|
+
)
|
|
677
|
+
table = _Table(title="web_search live check")
|
|
678
|
+
table.add_column("field")
|
|
679
|
+
table.add_column("value")
|
|
680
|
+
for key, value in validation.rows():
|
|
681
|
+
table.add_row(key, value)
|
|
682
|
+
return table
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def _reasoning_suppression_table(cfg: AppConfig, *, timeout_s: float = 15.0) -> Table:
|
|
686
|
+
report = _patchable("probe_reasoning_suppression_live", probe_reasoning_suppression_live)(
|
|
687
|
+
cfg,
|
|
688
|
+
timeout_s=timeout_s,
|
|
689
|
+
)
|
|
690
|
+
table = _Table(title="reasoning-off live check")
|
|
691
|
+
table.add_column("field")
|
|
692
|
+
table.add_column("value")
|
|
693
|
+
for key, value in report.rows():
|
|
694
|
+
table.add_row(key, value)
|
|
695
|
+
return table
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
@dataclass(frozen=True)
|
|
699
|
+
class _ToolAvailabilityRow:
|
|
700
|
+
name: str
|
|
701
|
+
categories: str
|
|
702
|
+
status: str
|
|
703
|
+
purpose: str
|
|
704
|
+
notes: str
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _default_session_api_key(cfg: AppConfig | None = None) -> str | None:
|
|
708
|
+
if cfg is not None:
|
|
709
|
+
try:
|
|
710
|
+
return resolve_model_access_api_key(cfg)
|
|
711
|
+
except ConfigError:
|
|
712
|
+
return None
|
|
713
|
+
return _cli_module()._resolved_api_key_value().key
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _tool_availability_rows(cfg: AppConfig) -> list[_ToolAvailabilityRow]:
|
|
717
|
+
rows: list[_ToolAvailabilityRow] = []
|
|
718
|
+
main_api_key = _default_session_api_key(cfg)
|
|
719
|
+
|
|
720
|
+
for spec in iter_builtin_tool_metadata():
|
|
721
|
+
status = "available"
|
|
722
|
+
notes: list[str] = []
|
|
723
|
+
|
|
724
|
+
availability = get_tool_availability(spec.name)
|
|
725
|
+
if spec.optional and availability is not None and availability.unavailable_reason:
|
|
726
|
+
status = "optional-unavailable"
|
|
727
|
+
notes.append(f"reason={availability.unavailable_reason}")
|
|
728
|
+
|
|
729
|
+
if spec.name == "web_search":
|
|
730
|
+
runtime = resolve_web_search_runtime_status(cfg=cfg, api_key=main_api_key)
|
|
731
|
+
policy = resolve_web_search_policy(cfg)
|
|
732
|
+
status = "policy-disabled" if policy == "off" else runtime.availability_label
|
|
733
|
+
notes.append(f"policy={policy}")
|
|
734
|
+
notes.append(f"mode={runtime.mode}")
|
|
735
|
+
notes.append(f"provider={runtime.provider or '(none)'}")
|
|
736
|
+
if runtime.registration_ready and policy != "off":
|
|
737
|
+
notes.append("ready for registration in main agent sessions")
|
|
738
|
+
elif runtime.registration_ready:
|
|
739
|
+
notes.append("backend ready but web_search_policy=off prevents registration")
|
|
740
|
+
if runtime.provider == "openai_responses":
|
|
741
|
+
notes.append(
|
|
742
|
+
"OpenAI Responses readiness is conservative: explicit web_search_base_url or first-party OpenAI base_url"
|
|
743
|
+
)
|
|
744
|
+
elif runtime.provider in {
|
|
745
|
+
"xai_responses",
|
|
746
|
+
"anthropic_messages",
|
|
747
|
+
"gemini_grounding",
|
|
748
|
+
"openrouter_web",
|
|
749
|
+
"perplexity_sonar",
|
|
750
|
+
"groq_compound",
|
|
751
|
+
"mistral_conversations",
|
|
752
|
+
"moonshot_kimi",
|
|
753
|
+
"zhipu_web_search",
|
|
754
|
+
"volcengine_web_search",
|
|
755
|
+
"minimax_coding_plan",
|
|
756
|
+
"cohere_web_search",
|
|
757
|
+
}:
|
|
758
|
+
notes.append(f"available via {runtime.provider} provider adapter")
|
|
759
|
+
elif runtime.provider == "dashscope_chat":
|
|
760
|
+
notes.append(
|
|
761
|
+
"available via DashScope Chat Completions enable_search or Responses web_search"
|
|
762
|
+
)
|
|
763
|
+
elif runtime.provider == "tavily":
|
|
764
|
+
notes.append("available via model-independent Tavily adapter")
|
|
765
|
+
elif runtime.provider == "ddgs":
|
|
766
|
+
notes.append("available via keyless DuckDuckGo metasearch (ddgs) adapter")
|
|
767
|
+
notes.extend(runtime.notes)
|
|
768
|
+
if policy == "off":
|
|
769
|
+
notes.append("setup: set web_search_policy=auto to expose search to the model")
|
|
770
|
+
elif not runtime.registration_ready:
|
|
771
|
+
notes.append(f"setup: {runtime.setup_hint}")
|
|
772
|
+
elif spec.name == "skill_read":
|
|
773
|
+
if not resolve_skills_enabled(cfg):
|
|
774
|
+
status = "optional-disabled"
|
|
775
|
+
notes.append("set skills_enabled=true to advertise skills and register skill_read")
|
|
776
|
+
else:
|
|
777
|
+
status = "contextual"
|
|
778
|
+
notes.append("registered only when discovered skill bundles are available")
|
|
779
|
+
elif spec.name == "subagent_run" and not bool(getattr(cfg, "subagents_enabled", False)):
|
|
780
|
+
status = "optional-disabled"
|
|
781
|
+
notes.append("set subagents_enabled=true or use --subagents for top-level runs")
|
|
782
|
+
|
|
783
|
+
if spec.built_in_subagent_exposure.strip().lower() == "hidden":
|
|
784
|
+
notes.append("hidden from built-in readonly subagents")
|
|
785
|
+
|
|
786
|
+
rows.append(
|
|
787
|
+
_ToolAvailabilityRow(
|
|
788
|
+
name=spec.name,
|
|
789
|
+
categories=", ".join(spec.categories),
|
|
790
|
+
status=status,
|
|
791
|
+
purpose=spec.description.strip(),
|
|
792
|
+
notes="; ".join(note for note in notes if note.strip()) or "-",
|
|
793
|
+
)
|
|
794
|
+
)
|
|
795
|
+
return rows
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _tools_table(cfg: AppConfig) -> Table:
|
|
799
|
+
table = _Table(title="alysis tools")
|
|
800
|
+
table.add_column("tool")
|
|
801
|
+
table.add_column("categories")
|
|
802
|
+
table.add_column("status")
|
|
803
|
+
table.add_column("purpose")
|
|
804
|
+
table.add_column("notes")
|
|
805
|
+
|
|
806
|
+
for row in _tool_availability_rows(cfg):
|
|
807
|
+
table.add_row(
|
|
808
|
+
row.name,
|
|
809
|
+
row.categories,
|
|
810
|
+
row.status,
|
|
811
|
+
row.purpose,
|
|
812
|
+
row.notes,
|
|
813
|
+
)
|
|
814
|
+
return table
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
@app.command()
|
|
818
|
+
def setup(
|
|
819
|
+
section: str | None = typer.Argument(
|
|
820
|
+
None,
|
|
821
|
+
help="Optional setup target. Use `sandbox` to prepare the safe command runner.",
|
|
822
|
+
),
|
|
823
|
+
) -> None:
|
|
824
|
+
"""Run first-time setup, or prepare a named setup target."""
|
|
825
|
+
if section is not None:
|
|
826
|
+
target = section.strip().lower()
|
|
827
|
+
if target != "sandbox":
|
|
828
|
+
_console().print("[red]Unknown setup target.[/red] Use: alysis setup sandbox")
|
|
829
|
+
raise typer.Exit(code=2)
|
|
830
|
+
_cli_module()._run_sandbox_setup_command(pull=True)
|
|
831
|
+
return
|
|
832
|
+
|
|
833
|
+
from ..setup_wizard import run_setup_wizard
|
|
834
|
+
|
|
835
|
+
# `alysis setup` defaults to the interactive alt-screen wizard (arrow-key
|
|
836
|
+
# selection) regardless of ALYSIS_TUI; it only falls back to the classic
|
|
837
|
+
# Rich wizard when the TUI can't run (and says why).
|
|
838
|
+
tui_result = _cli_module()._try_setup_tui(require_flag=False, announce_fallback=True)
|
|
839
|
+
if tui_result is None:
|
|
840
|
+
# Classic Rich wizard (non-interactive terminal or TUI failure): configure
|
|
841
|
+
# only, then surface any remaining sandbox setup.
|
|
842
|
+
if run_setup_wizard():
|
|
843
|
+
console = _console()
|
|
844
|
+
configured = _patchable("load_config", load_config)()
|
|
845
|
+
if configured.execution.backend == "delegated":
|
|
846
|
+
return
|
|
847
|
+
_cli_module()._maybe_run_startup_config_menu()
|
|
848
|
+
configured = _patchable("load_config", load_config)()
|
|
849
|
+
try:
|
|
850
|
+
result = _patchable("diagnose_sandbox", diagnose_sandbox)(
|
|
851
|
+
configured,
|
|
852
|
+
include_smoke=False,
|
|
853
|
+
include_server_image=False,
|
|
854
|
+
)
|
|
855
|
+
except ConfigError:
|
|
856
|
+
return
|
|
857
|
+
if not result.ready:
|
|
858
|
+
console.print()
|
|
859
|
+
console.print(
|
|
860
|
+
"[yellow]Safe runner setup is not complete yet. "
|
|
861
|
+
"Run `alysis doctor sandbox` for details or `alysis setup sandbox` "
|
|
862
|
+
"after installing/starting Docker.[/yellow]"
|
|
863
|
+
)
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
# The interactive TUI ran (it already walked the user through sandbox). On a
|
|
867
|
+
# saved setup, flow straight into chat — the final screen promised "Press
|
|
868
|
+
# Enter to start chatting" — in the workspace they just configured (so the
|
|
869
|
+
# broad-workspace guard does not re-ask about it). On cancel, back to shell.
|
|
870
|
+
if tui_result:
|
|
871
|
+
configured = _patchable("load_config", load_config)()
|
|
872
|
+
if configured.execution.backend == "delegated":
|
|
873
|
+
return
|
|
874
|
+
_cli_module()._maybe_run_startup_config_menu()
|
|
875
|
+
_cli_module()._run_chat_after_setup()
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
@app.command()
|
|
879
|
+
def doctor(
|
|
880
|
+
section: str | None = typer.Argument(
|
|
881
|
+
None,
|
|
882
|
+
help="Optional check group. Use `sandbox`, `providers`, `auth`, or `bundle`.",
|
|
883
|
+
),
|
|
884
|
+
smoke: bool = typer.Option(
|
|
885
|
+
True,
|
|
886
|
+
"--smoke/--no-smoke",
|
|
887
|
+
help="Run a sandbox smoke command when checking `doctor sandbox`.",
|
|
888
|
+
),
|
|
889
|
+
env: bool = typer.Option(
|
|
890
|
+
False,
|
|
891
|
+
"--env",
|
|
892
|
+
help="Show sandbox-related environment variable overrides.",
|
|
893
|
+
),
|
|
894
|
+
live: bool = typer.Option(
|
|
895
|
+
False,
|
|
896
|
+
"--live",
|
|
897
|
+
help="Run a minimal live text request for `doctor providers` after confirmation.",
|
|
898
|
+
),
|
|
899
|
+
yes: bool = typer.Option(
|
|
900
|
+
False,
|
|
901
|
+
"--yes",
|
|
902
|
+
help="Confirm live provider validation without prompting.",
|
|
903
|
+
),
|
|
904
|
+
live_timeout: float = typer.Option(
|
|
905
|
+
15.0,
|
|
906
|
+
"--live-timeout",
|
|
907
|
+
min=1.0,
|
|
908
|
+
help="Timeout in seconds for `doctor providers --live`.",
|
|
909
|
+
),
|
|
910
|
+
redacted: bool = typer.Option(
|
|
911
|
+
True,
|
|
912
|
+
"--redacted/--no-redacted",
|
|
913
|
+
help="Emit only redacted diagnostic data for `doctor bundle`.",
|
|
914
|
+
),
|
|
915
|
+
json_output: bool = typer.Option(
|
|
916
|
+
False,
|
|
917
|
+
"--json",
|
|
918
|
+
help=(
|
|
919
|
+
"Emit `doctor auth` as one machine-readable JSON object on stdout. "
|
|
920
|
+
"Rejected for the check groups that have no JSON form."
|
|
921
|
+
),
|
|
922
|
+
),
|
|
923
|
+
) -> None:
|
|
924
|
+
if section is not None:
|
|
925
|
+
target = section.strip().lower()
|
|
926
|
+
if target == "auth":
|
|
927
|
+
if json_output is True:
|
|
928
|
+
typer.echo(json.dumps(auth_doctor_payload(), sort_keys=True))
|
|
929
|
+
return
|
|
930
|
+
_console().print(_auth_doctor_table())
|
|
931
|
+
return
|
|
932
|
+
if json_output is True and target != "bundle":
|
|
933
|
+
# Silently printing a table to a caller that asked for JSON would hand
|
|
934
|
+
# it unparseable output. `bundle` is already JSON-only.
|
|
935
|
+
_console().print(
|
|
936
|
+
"[red]--json is only supported for `doctor auth` and `doctor bundle`.[/red]"
|
|
937
|
+
)
|
|
938
|
+
raise typer.Exit(code=2)
|
|
939
|
+
if target == "sandbox":
|
|
940
|
+
_cli_module()._run_sandbox_doctor_command(include_smoke=smoke, include_env=env)
|
|
941
|
+
return
|
|
942
|
+
if target in {"provider", "providers"}:
|
|
943
|
+
cfg = _patchable("load_config", load_config)()
|
|
944
|
+
console = _console()
|
|
945
|
+
console.print(_provider_doctor_table(cfg))
|
|
946
|
+
if live:
|
|
947
|
+
_require_active_subscription_ready(model=None, base_url=None)
|
|
948
|
+
console.print(
|
|
949
|
+
"[yellow]Live provider validation sends a few minimal requests (one text "
|
|
950
|
+
"request, one reasoning-off request, and one web search when enabled) and "
|
|
951
|
+
"may incur provider cost or rate-limit usage.[/yellow]"
|
|
952
|
+
)
|
|
953
|
+
if not yes and not typer.confirm(
|
|
954
|
+
"Run live provider validation for the active profile?", default=False
|
|
955
|
+
):
|
|
956
|
+
console.print("Live provider validation cancelled.")
|
|
957
|
+
return
|
|
958
|
+
console.print(_provider_live_validation_table(cfg, timeout_s=live_timeout))
|
|
959
|
+
console.print(_reasoning_suppression_table(cfg, timeout_s=live_timeout))
|
|
960
|
+
console.print(_web_search_live_validation_table(cfg, timeout_s=live_timeout))
|
|
961
|
+
return
|
|
962
|
+
if target == "bundle":
|
|
963
|
+
if not redacted:
|
|
964
|
+
_console().print("[red]Only redacted doctor bundles are supported.[/red]")
|
|
965
|
+
raise typer.Exit(code=2)
|
|
966
|
+
cfg = _patchable("load_config", load_config)()
|
|
967
|
+
typer.echo(json.dumps(_doctor_bundle_payload(cfg), indent=2, sort_keys=True))
|
|
968
|
+
return
|
|
969
|
+
if target != "sandbox":
|
|
970
|
+
_console().print(
|
|
971
|
+
"[red]Unknown doctor target.[/red] Use: alysis doctor sandbox|providers|auth|bundle"
|
|
972
|
+
)
|
|
973
|
+
raise typer.Exit(code=2)
|
|
974
|
+
if json_output is True:
|
|
975
|
+
_console().print(
|
|
976
|
+
"[red]--json is only supported for `doctor auth` and `doctor bundle`.[/red]"
|
|
977
|
+
)
|
|
978
|
+
raise typer.Exit(code=2)
|
|
979
|
+
console = _console()
|
|
980
|
+
cfg = _patchable("load_config", load_config)()
|
|
981
|
+
console.print(_doctor_table(cfg))
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
@app.command()
|
|
985
|
+
def tools() -> None:
|
|
986
|
+
console = _console()
|
|
987
|
+
cfg = _patchable("load_config", load_config)()
|
|
988
|
+
console.print(_tools_table(cfg))
|
|
989
|
+
console.print(
|
|
990
|
+
"[dim]`alysis tools` shows the built-in catalog plus config-dependent availability. "
|
|
991
|
+
"Use `/status` inside chat for session-specific details.[/dim]"
|
|
992
|
+
)
|
|
993
|
+
console.print(
|
|
994
|
+
"[dim]`web_search` discovers candidate sources; `web_fetch` retrieves a specific chosen URL.[/dim]"
|
|
995
|
+
)
|
|
996
|
+
console.print(
|
|
997
|
+
"[dim]Top-level readonly/Plan sessions can use ready web tools; nested readonly subagents keep them hidden.[/dim]"
|
|
998
|
+
)
|
|
999
|
+
console.print(
|
|
1000
|
+
"[dim]Use `web_search_policy=off|auto` for model web-search access, "
|
|
1001
|
+
"`web_search_mode=off|auto|native|external` for backend selection, and optional "
|
|
1002
|
+
"`web_search_adapter`. "
|
|
1003
|
+
"`auto` can use OpenAI Responses, xAI, Anthropic, Gemini, OpenRouter, DashScope "
|
|
1004
|
+
"Chat/Qwen, Kimi, Zhipu/GLM, Doubao, Perplexity, Groq, Mistral, or Tavily when "
|
|
1005
|
+
"`ALYSIS_WEB_SEARCH_API_KEY` or `TAVILY_API_KEY` is set. `native` never uses "
|
|
1006
|
+
"Tavily; `external` uses only external "
|
|
1007
|
+
"search adapters. Legacy `on` and `web_search_enabled` values still load as `auto`.[/dim]"
|
|
1008
|
+
)
|
|
1009
|
+
console.print(
|
|
1010
|
+
"[dim]Custom tools are managed separately via `alysis tool list|info|trust|untrust`.[/dim]"
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
@app.command()
|
|
1015
|
+
def login() -> None:
|
|
1016
|
+
"""Connect your Alysis Code account."""
|
|
1017
|
+
from ... import account_login
|
|
1018
|
+
|
|
1019
|
+
console = _console()
|
|
1020
|
+
cfg = _patchable("load_config", load_config)()
|
|
1021
|
+
try:
|
|
1022
|
+
result = account_login.login(
|
|
1023
|
+
cfg, output_write=lambda message: console.print(message, highlight=False)
|
|
1024
|
+
)
|
|
1025
|
+
except (account_login.AlysisLoginError, ConfigError) as exc:
|
|
1026
|
+
console.print(f"[red]{exc}[/red]")
|
|
1027
|
+
raise typer.Exit(code=1) from exc
|
|
1028
|
+
|
|
1029
|
+
who = f" as [bold]{result.email}[/bold]" if result.email else ""
|
|
1030
|
+
console.print(f"[green]Logged in{who}.[/green] Your Alysis Code account is connected.")
|
|
1031
|
+
if result.model:
|
|
1032
|
+
console.print(
|
|
1033
|
+
f"Active profile: [bold]{result.profile_name}[/bold] · default model: "
|
|
1034
|
+
f"[bold]{result.model}[/bold]"
|
|
1035
|
+
)
|
|
1036
|
+
console.print("[dim]Switch model anytime with /model in chat, or `alysis config`.[/dim]")
|
|
1037
|
+
else:
|
|
1038
|
+
# No model is auto-selected on first login (the free MiMo default was
|
|
1039
|
+
# removed); guide the user to pick one before the "Model is not set" guard.
|
|
1040
|
+
console.print(
|
|
1041
|
+
f"Active profile: [bold]{result.profile_name}[/bold] · no model selected yet."
|
|
1042
|
+
)
|
|
1043
|
+
console.print(
|
|
1044
|
+
"[dim]Pick one with /model in chat, or `alysis config set model <MODEL>`.[/dim]"
|
|
1045
|
+
)
|
|
1046
|
+
console.print("[dim]Run `alysis chat` to start. Use `alysis logout` to disconnect.[/dim]")
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@app.command()
|
|
1050
|
+
def logout() -> None:
|
|
1051
|
+
"""Disconnect your Alysis Code account (forgets the stored access key)."""
|
|
1052
|
+
from ... import account_login
|
|
1053
|
+
|
|
1054
|
+
console = _console()
|
|
1055
|
+
cfg = _patchable("load_config", load_config)()
|
|
1056
|
+
if account_login.logout(cfg):
|
|
1057
|
+
console.print("[green]Logged out.[/green] Your stored Alysis Code key was removed.")
|
|
1058
|
+
else:
|
|
1059
|
+
console.print("You're not logged in to an Alysis Code account.")
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def _whoami_payload(cfg: AppConfig) -> dict[str, Any]:
|
|
1063
|
+
"""Build the Alysis Code account status contract for a supervising app."""
|
|
1064
|
+
from ... import account_login
|
|
1065
|
+
|
|
1066
|
+
status = account_login.login_status(cfg)
|
|
1067
|
+
trial = account_login.fetch_trial_status(cfg) if status.logged_in else None
|
|
1068
|
+
detail: str | None = None
|
|
1069
|
+
if not status.logged_in:
|
|
1070
|
+
detail = "No Alysis Code account is connected."
|
|
1071
|
+
elif trial is None:
|
|
1072
|
+
from ...alysis_cloud import site_host
|
|
1073
|
+
|
|
1074
|
+
detail = f"Connected. Manage your plan and credits at {site_host()}/account."
|
|
1075
|
+
else:
|
|
1076
|
+
detail = account_login.format_trial_status_line(trial) or None
|
|
1077
|
+
return _auth_status_payload(
|
|
1078
|
+
connection="alysis",
|
|
1079
|
+
authenticated=status.logged_in,
|
|
1080
|
+
account_label=(trial.email if trial is not None else None),
|
|
1081
|
+
method=("access-key" if status.logged_in else None),
|
|
1082
|
+
detail=detail,
|
|
1083
|
+
transport=status.base_url,
|
|
1084
|
+
profile_name=status.profile_name,
|
|
1085
|
+
profile_active=bool(status.active),
|
|
1086
|
+
plan=(trial.plan if trial is not None else None),
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
@app.command()
|
|
1091
|
+
def whoami(
|
|
1092
|
+
json_output: bool = typer.Option(False, "--json", help=_JSON_OPTION_HELP),
|
|
1093
|
+
) -> None:
|
|
1094
|
+
"""Show your Alysis Code login status."""
|
|
1095
|
+
from ... import account_login
|
|
1096
|
+
|
|
1097
|
+
console = _console()
|
|
1098
|
+
cfg = _patchable("load_config", load_config)()
|
|
1099
|
+
if json_output is True:
|
|
1100
|
+
typer.echo(json.dumps(_whoami_payload(cfg), sort_keys=True))
|
|
1101
|
+
return
|
|
1102
|
+
status = account_login.login_status(cfg)
|
|
1103
|
+
if not status.logged_in:
|
|
1104
|
+
console.print("Not logged in. Run `alysis login` to connect your Alysis Code account.")
|
|
1105
|
+
return
|
|
1106
|
+
active = "active" if status.active else "not active"
|
|
1107
|
+
console.print("[green]Logged in[/green] to the Alysis Code MiMo trial.")
|
|
1108
|
+
console.print(
|
|
1109
|
+
f"Profile: [bold]{status.profile_name}[/bold] ({active}) · key {status.key_preview}"
|
|
1110
|
+
)
|
|
1111
|
+
console.print(f"Proxy: {status.base_url}")
|
|
1112
|
+
trial = account_login.fetch_trial_status(cfg)
|
|
1113
|
+
if trial is not None:
|
|
1114
|
+
line = account_login.format_trial_status_line(trial)
|
|
1115
|
+
if line:
|
|
1116
|
+
console.print(line)
|
|
1117
|
+
else:
|
|
1118
|
+
console.print("[dim](Could not reach the trial service for live status.)[/dim]")
|