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,623 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from ...agent_runtimes.service import (
|
|
10
|
+
RuntimeConnectionError,
|
|
11
|
+
activate_runtime,
|
|
12
|
+
login_runtime,
|
|
13
|
+
logout_runtime,
|
|
14
|
+
runtime_connection_snapshot,
|
|
15
|
+
)
|
|
16
|
+
from ...auth_diagnostics import credential_backend_fields
|
|
17
|
+
from ...config import AppConfig, ConfigError, load_config, save_config
|
|
18
|
+
from ...profiles import (
|
|
19
|
+
SUBSCRIPTION_SELECTION_REQUIRED_KEY,
|
|
20
|
+
ProfileSpec,
|
|
21
|
+
add_profile,
|
|
22
|
+
get_active_profile,
|
|
23
|
+
get_profile,
|
|
24
|
+
set_active_profile,
|
|
25
|
+
subscription_selection_supported,
|
|
26
|
+
)
|
|
27
|
+
from ...provider_auth import (
|
|
28
|
+
ProviderAuthError,
|
|
29
|
+
create_provider_auth,
|
|
30
|
+
provider_auth_setup_options,
|
|
31
|
+
)
|
|
32
|
+
from . import _patchable
|
|
33
|
+
from ._shared import _console
|
|
34
|
+
|
|
35
|
+
auth_app = typer.Typer(
|
|
36
|
+
add_completion=False,
|
|
37
|
+
help="Manage secure AI subscription connections.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
ALYSIS_LOGIN_CONNECTION_ID = "alysis"
|
|
41
|
+
|
|
42
|
+
JSON_OPTION_HELP = (
|
|
43
|
+
"Emit one machine-readable JSON object on stdout and nothing else. "
|
|
44
|
+
"Exit code 0 means the command ran; the payload carries the auth state."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _status_payload(
|
|
49
|
+
*,
|
|
50
|
+
connection: str,
|
|
51
|
+
authenticated: bool,
|
|
52
|
+
account_label: str | None = None,
|
|
53
|
+
method: str | None = None,
|
|
54
|
+
detail: str | None = None,
|
|
55
|
+
transport: str | None = None,
|
|
56
|
+
error: str | None = None,
|
|
57
|
+
**extra: Any,
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Build the auth status contract a supervising app can read.
|
|
60
|
+
|
|
61
|
+
``authenticated`` is data, not an outcome: a false value is still a
|
|
62
|
+
successful probe, so the caller never has to disambiguate it from a failure
|
|
63
|
+
of the command itself. The keyring fields explain why a spawned process can
|
|
64
|
+
see different auth state than the terminal the user logged in from.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
payload: dict[str, Any] = {
|
|
68
|
+
"connection": connection,
|
|
69
|
+
"authenticated": bool(authenticated),
|
|
70
|
+
"account_label": account_label,
|
|
71
|
+
"method": method,
|
|
72
|
+
"detail": detail,
|
|
73
|
+
"transport": transport,
|
|
74
|
+
"error": error,
|
|
75
|
+
}
|
|
76
|
+
payload.update(credential_backend_fields())
|
|
77
|
+
payload.update(extra)
|
|
78
|
+
return payload
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _emit_json(payload: dict[str, Any]) -> None:
|
|
82
|
+
"""Write exactly one JSON object to stdout."""
|
|
83
|
+
|
|
84
|
+
typer.echo(json.dumps(payload, sort_keys=True))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _print_credential_backend_note(console: Any) -> None:
|
|
88
|
+
"""Say so when credentials are not protected by the OS keyring.
|
|
89
|
+
|
|
90
|
+
Silent degradation is what makes auth state look inconsistent between a
|
|
91
|
+
terminal and a spawned process, so an interactive user gets told too.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
fields = credential_backend_fields()
|
|
95
|
+
if fields["keyring_available"]:
|
|
96
|
+
return
|
|
97
|
+
console.print(
|
|
98
|
+
f"[yellow]OS keyring unavailable[/yellow] "
|
|
99
|
+
f"(backend: {fields['keyring_backend'] or 'unresolved'}) — credentials use the "
|
|
100
|
+
f"{fields['credential_fallback']} key store instead. "
|
|
101
|
+
"Run `alysis doctor auth` for details."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def login_connection_rows() -> list[tuple[str, str, str]]:
|
|
106
|
+
"""Return every account connection offered by the unified login picker."""
|
|
107
|
+
|
|
108
|
+
rows = [
|
|
109
|
+
(
|
|
110
|
+
ALYSIS_LOGIN_CONNECTION_ID,
|
|
111
|
+
"Alysis Code account",
|
|
112
|
+
"Connect Alysis Code-hosted model access and account entitlements.",
|
|
113
|
+
)
|
|
114
|
+
]
|
|
115
|
+
rows.extend(
|
|
116
|
+
(option.id, option.label, option.description) for option in provider_auth_setup_options()
|
|
117
|
+
)
|
|
118
|
+
return rows
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def login_connection_interactively(
|
|
122
|
+
connection_id: str,
|
|
123
|
+
*,
|
|
124
|
+
console: Any | None = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Run one connection's browser flow outside the alternate-screen TUI."""
|
|
127
|
+
|
|
128
|
+
resolved = str(connection_id or "").strip()
|
|
129
|
+
output = console or _console()
|
|
130
|
+
if resolved == ALYSIS_LOGIN_CONNECTION_ID:
|
|
131
|
+
from ... import account_login
|
|
132
|
+
|
|
133
|
+
cfg = _patchable("load_config", load_config)()
|
|
134
|
+
try:
|
|
135
|
+
result = account_login.login(
|
|
136
|
+
cfg,
|
|
137
|
+
output_write=lambda message: output.print(message, highlight=False),
|
|
138
|
+
)
|
|
139
|
+
except (account_login.AlysisLoginError, ConfigError) as exc:
|
|
140
|
+
output.print(f"[red]{exc}[/red]")
|
|
141
|
+
raise typer.Exit(code=1) from exc
|
|
142
|
+
who = f" as [bold]{result.email}[/bold]" if result.email else ""
|
|
143
|
+
output.print(f"[green]Logged in{who}.[/green] Your Alysis Code account is connected.")
|
|
144
|
+
if result.model:
|
|
145
|
+
output.print(
|
|
146
|
+
f"Active profile: [bold]{result.profile_name}[/bold] · default model: "
|
|
147
|
+
f"[bold]{result.model}[/bold]"
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
output.print(
|
|
151
|
+
f"Active profile: [bold]{result.profile_name}[/bold] · choose a model in /config."
|
|
152
|
+
)
|
|
153
|
+
return
|
|
154
|
+
known = {row[0] for row in login_connection_rows()}
|
|
155
|
+
if resolved not in known:
|
|
156
|
+
choices = ", ".join(row[0] for row in login_connection_rows())
|
|
157
|
+
output.print(
|
|
158
|
+
f"[red]Unknown login connection:[/red] {resolved or '(empty)'}. Available: {choices}."
|
|
159
|
+
)
|
|
160
|
+
raise typer.Exit(code=2)
|
|
161
|
+
login_provider_interactively(runtime_id=resolved)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@auth_app.command("list")
|
|
165
|
+
def auth_list(
|
|
166
|
+
json_output: bool = typer.Option(False, "--json", help=JSON_OPTION_HELP),
|
|
167
|
+
) -> None:
|
|
168
|
+
"""List supported subscription connections and account state."""
|
|
169
|
+
|
|
170
|
+
cfg = _patchable("load_config", load_config)()
|
|
171
|
+
if json_output is True:
|
|
172
|
+
_emit_json(_auth_list_payload(cfg))
|
|
173
|
+
return
|
|
174
|
+
table = Table(title="AI subscription connections")
|
|
175
|
+
table.add_column("connection")
|
|
176
|
+
table.add_column("availability")
|
|
177
|
+
table.add_column("account")
|
|
178
|
+
table.add_column("active")
|
|
179
|
+
for option in provider_auth_setup_options():
|
|
180
|
+
if _is_direct_provider(option.id):
|
|
181
|
+
try:
|
|
182
|
+
status = create_provider_auth(option.id).account_status()
|
|
183
|
+
installation = "built in"
|
|
184
|
+
account = _account_status_label(status.connected, status.detail)
|
|
185
|
+
except ProviderAuthError as exc:
|
|
186
|
+
installation = "built in"
|
|
187
|
+
account = str(exc)
|
|
188
|
+
active = "yes" if _active_auth_provider(cfg) == option.id else ""
|
|
189
|
+
table.add_row(option.label, installation, account, active)
|
|
190
|
+
continue
|
|
191
|
+
try:
|
|
192
|
+
snapshot = runtime_connection_snapshot(cfg, option.id)
|
|
193
|
+
installation = snapshot.probe.version or (
|
|
194
|
+
"available" if snapshot.probe.available else "missing"
|
|
195
|
+
)
|
|
196
|
+
account = _account_status_label(snapshot.account.authenticated, snapshot.account.detail)
|
|
197
|
+
except RuntimeConnectionError as exc:
|
|
198
|
+
installation = "unavailable"
|
|
199
|
+
account = str(exc)
|
|
200
|
+
active = (
|
|
201
|
+
"yes"
|
|
202
|
+
if cfg.execution.backend == "delegated" and cfg.execution.runtime == option.id
|
|
203
|
+
else ""
|
|
204
|
+
)
|
|
205
|
+
table.add_row(option.label, installation, account, active)
|
|
206
|
+
console = _console()
|
|
207
|
+
console.print(table)
|
|
208
|
+
_print_credential_backend_note(console)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _auth_list_payload(cfg: AppConfig) -> dict[str, Any]:
|
|
212
|
+
"""Describe every offered connection without printing human output."""
|
|
213
|
+
|
|
214
|
+
connections: list[dict[str, Any]] = []
|
|
215
|
+
for option in provider_auth_setup_options():
|
|
216
|
+
if _is_direct_provider(option.id):
|
|
217
|
+
active = _active_auth_provider(cfg) == option.id
|
|
218
|
+
try:
|
|
219
|
+
adapter = create_provider_auth(option.id)
|
|
220
|
+
status = adapter.account_status()
|
|
221
|
+
except ProviderAuthError as exc:
|
|
222
|
+
connections.append(
|
|
223
|
+
_status_payload(
|
|
224
|
+
connection=option.id,
|
|
225
|
+
authenticated=False,
|
|
226
|
+
error=str(exc),
|
|
227
|
+
display_name=option.label,
|
|
228
|
+
availability="built in",
|
|
229
|
+
active=active,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
continue
|
|
233
|
+
connections.append(
|
|
234
|
+
_status_payload(
|
|
235
|
+
connection=option.id,
|
|
236
|
+
authenticated=status.connected,
|
|
237
|
+
account_label=status.account_label,
|
|
238
|
+
detail=status.detail,
|
|
239
|
+
transport=f"native Alysis Code client ({adapter.protocol})",
|
|
240
|
+
display_name=option.label,
|
|
241
|
+
availability="built in",
|
|
242
|
+
verified=bool(status.verified),
|
|
243
|
+
active=active,
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
continue
|
|
247
|
+
active = (
|
|
248
|
+
cfg.execution.backend == "delegated" and str(cfg.execution.runtime or "") == option.id
|
|
249
|
+
)
|
|
250
|
+
try:
|
|
251
|
+
snapshot = runtime_connection_snapshot(cfg, option.id)
|
|
252
|
+
except RuntimeConnectionError as exc:
|
|
253
|
+
connections.append(
|
|
254
|
+
_status_payload(
|
|
255
|
+
connection=option.id,
|
|
256
|
+
authenticated=False,
|
|
257
|
+
error=str(exc),
|
|
258
|
+
display_name=option.label,
|
|
259
|
+
availability="unavailable",
|
|
260
|
+
active=active,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
continue
|
|
264
|
+
connections.append(
|
|
265
|
+
_status_payload(
|
|
266
|
+
connection=option.id,
|
|
267
|
+
authenticated=snapshot.account.authenticated,
|
|
268
|
+
account_label=snapshot.account.account_label,
|
|
269
|
+
method=snapshot.account.auth_method_id,
|
|
270
|
+
detail=snapshot.account.detail,
|
|
271
|
+
transport=f"delegated runtime ({snapshot.option.adapter})",
|
|
272
|
+
display_name=option.label,
|
|
273
|
+
availability=snapshot.probe.version
|
|
274
|
+
or ("available" if snapshot.probe.available else "missing"),
|
|
275
|
+
installed=bool(snapshot.probe.available),
|
|
276
|
+
verified=bool(snapshot.account.verified),
|
|
277
|
+
active=active,
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
payload: dict[str, Any] = {"connections": connections, "error": None}
|
|
281
|
+
payload.update(credential_backend_fields())
|
|
282
|
+
return payload
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@auth_app.command("status")
|
|
286
|
+
def auth_status(
|
|
287
|
+
runtime_id: str | None = typer.Argument(
|
|
288
|
+
None,
|
|
289
|
+
help="Connection id. Defaults to the selected connection (or the only adapter).",
|
|
290
|
+
),
|
|
291
|
+
json_output: bool = typer.Option(False, "--json", help=JSON_OPTION_HELP),
|
|
292
|
+
) -> None:
|
|
293
|
+
"""Show availability and provider-owned account status."""
|
|
294
|
+
|
|
295
|
+
emit_json = json_output is True
|
|
296
|
+
cfg = _patchable("load_config", load_config)()
|
|
297
|
+
resolved = _cli_runtime_id(cfg, runtime_id)
|
|
298
|
+
if _use_direct_provider(cfg, resolved, requested=runtime_id):
|
|
299
|
+
try:
|
|
300
|
+
adapter = create_provider_auth(resolved)
|
|
301
|
+
status = adapter.account_status()
|
|
302
|
+
except ProviderAuthError as exc:
|
|
303
|
+
if emit_json:
|
|
304
|
+
# The probe answered: the connection is not usable, and the
|
|
305
|
+
# reason is data rather than a failure of this command.
|
|
306
|
+
_emit_json(
|
|
307
|
+
_status_payload(
|
|
308
|
+
connection=resolved,
|
|
309
|
+
authenticated=False,
|
|
310
|
+
error=str(exc),
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
return
|
|
314
|
+
_console().print(f"[red]Authentication error:[/red] {exc}")
|
|
315
|
+
raise typer.Exit(code=2) from exc
|
|
316
|
+
if emit_json:
|
|
317
|
+
_emit_json(
|
|
318
|
+
_status_payload(
|
|
319
|
+
connection=resolved,
|
|
320
|
+
authenticated=status.connected,
|
|
321
|
+
account_label=status.account_label,
|
|
322
|
+
detail=status.detail,
|
|
323
|
+
transport=f"native Alysis Code client ({adapter.protocol})",
|
|
324
|
+
display_name=adapter.display_name,
|
|
325
|
+
verified=bool(status.verified),
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
return
|
|
329
|
+
console = _console()
|
|
330
|
+
console.print(f"Connection: {adapter.display_name} ({resolved})")
|
|
331
|
+
console.print(f"Transport: native Alysis Code client ({adapter.protocol})")
|
|
332
|
+
console.print(f"Authenticated: {'yes' if status.connected else 'no'}")
|
|
333
|
+
if status.account_label:
|
|
334
|
+
console.print(f"Account: {status.account_label}")
|
|
335
|
+
if status.detail:
|
|
336
|
+
console.print(f"Status: {status.detail}")
|
|
337
|
+
_print_credential_backend_note(console)
|
|
338
|
+
if not status.connected:
|
|
339
|
+
console.print(f"[dim]Run `alysis auth login {resolved}` to reconnect.[/dim]")
|
|
340
|
+
raise typer.Exit(code=1)
|
|
341
|
+
return
|
|
342
|
+
try:
|
|
343
|
+
snapshot = runtime_connection_snapshot(cfg, resolved)
|
|
344
|
+
except RuntimeConnectionError as exc:
|
|
345
|
+
if emit_json:
|
|
346
|
+
_emit_json(
|
|
347
|
+
_status_payload(
|
|
348
|
+
connection=resolved,
|
|
349
|
+
authenticated=False,
|
|
350
|
+
error=str(exc),
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
_console().print(f"[red]Runtime error:[/red] {exc}")
|
|
355
|
+
raise typer.Exit(code=2) from exc
|
|
356
|
+
if emit_json:
|
|
357
|
+
_emit_json(
|
|
358
|
+
_status_payload(
|
|
359
|
+
connection=resolved,
|
|
360
|
+
authenticated=snapshot.account.authenticated,
|
|
361
|
+
account_label=snapshot.account.account_label,
|
|
362
|
+
method=snapshot.account.auth_method_id,
|
|
363
|
+
detail=snapshot.account.detail,
|
|
364
|
+
transport=f"delegated runtime ({snapshot.option.adapter})",
|
|
365
|
+
display_name=snapshot.option.label,
|
|
366
|
+
executable=snapshot.probe.executable or snapshot.settings.executable,
|
|
367
|
+
installed=bool(snapshot.probe.available),
|
|
368
|
+
version=snapshot.probe.version,
|
|
369
|
+
verified=bool(snapshot.account.verified),
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
return
|
|
373
|
+
console = _console()
|
|
374
|
+
console.print(f"Runtime: {snapshot.option.label} ({snapshot.option.id})")
|
|
375
|
+
console.print(f"Executable: {snapshot.probe.executable or snapshot.settings.executable}")
|
|
376
|
+
console.print(f"Installed: {'yes' if snapshot.probe.available else 'no'}")
|
|
377
|
+
if snapshot.probe.version:
|
|
378
|
+
console.print(f"Version: {snapshot.probe.version}")
|
|
379
|
+
console.print(f"Authenticated: {'yes' if snapshot.account.authenticated else 'no'}")
|
|
380
|
+
if snapshot.account.auth_method_id:
|
|
381
|
+
console.print(f"Method: {snapshot.account.auth_method_id}")
|
|
382
|
+
if snapshot.account.account_label:
|
|
383
|
+
console.print(f"Account: {snapshot.account.account_label}")
|
|
384
|
+
if snapshot.account.detail:
|
|
385
|
+
console.print(f"Status: {snapshot.account.detail}")
|
|
386
|
+
_print_credential_backend_note(console)
|
|
387
|
+
if not snapshot.probe.available or not snapshot.account.authenticated:
|
|
388
|
+
raise typer.Exit(code=1)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@auth_app.command("login")
|
|
392
|
+
def auth_login(
|
|
393
|
+
runtime_id: str | None = typer.Argument(
|
|
394
|
+
None,
|
|
395
|
+
help="Connection id. Defaults to the selected connection (or the only adapter).",
|
|
396
|
+
),
|
|
397
|
+
device_code: bool = typer.Option(
|
|
398
|
+
False,
|
|
399
|
+
"--device-code",
|
|
400
|
+
help="Use the provider's device-code flow instead of browser callback login.",
|
|
401
|
+
),
|
|
402
|
+
switch_account: bool = typer.Option(
|
|
403
|
+
False,
|
|
404
|
+
"--switch-account",
|
|
405
|
+
help="Sign out from the provider before starting login.",
|
|
406
|
+
),
|
|
407
|
+
) -> None:
|
|
408
|
+
"""Connect a provider subscription and activate it in Alysis Code."""
|
|
409
|
+
|
|
410
|
+
cfg = _patchable("load_config", load_config)()
|
|
411
|
+
resolved = _cli_runtime_id(cfg, runtime_id)
|
|
412
|
+
console = _console()
|
|
413
|
+
if _use_direct_provider(cfg, resolved, requested=runtime_id):
|
|
414
|
+
try:
|
|
415
|
+
adapter = create_provider_auth(resolved)
|
|
416
|
+
if switch_account:
|
|
417
|
+
adapter.logout()
|
|
418
|
+
status = adapter.login(
|
|
419
|
+
method="device-code" if device_code else "browser",
|
|
420
|
+
output_write=lambda message: console.print(message, highlight=False),
|
|
421
|
+
)
|
|
422
|
+
if not status.connected:
|
|
423
|
+
raise ProviderAuthError(status.detail or "Authentication failed.")
|
|
424
|
+
_activate_direct_provider(cfg, resolved, adapter)
|
|
425
|
+
_patchable("save_config", save_config)(cfg)
|
|
426
|
+
except ProviderAuthError as exc:
|
|
427
|
+
console.print(f"[red]Authentication failed:[/red] {exc}")
|
|
428
|
+
raise typer.Exit(code=1) from exc
|
|
429
|
+
console.print(f"[green]Connected:[/green] {status.account_label or resolved}")
|
|
430
|
+
console.print(
|
|
431
|
+
"[dim]Alysis Code stores refreshable credentials in its encrypted provider vault "
|
|
432
|
+
"and keeps the native agent/TUI active.[/dim]"
|
|
433
|
+
)
|
|
434
|
+
console.print(
|
|
435
|
+
"[dim]Choose the subscription model and reasoning effort in "
|
|
436
|
+
"`/config` → Default model.[/dim]"
|
|
437
|
+
)
|
|
438
|
+
return
|
|
439
|
+
try:
|
|
440
|
+
activate_runtime(cfg, resolved)
|
|
441
|
+
if switch_account:
|
|
442
|
+
logout_status = logout_runtime(cfg, resolved)
|
|
443
|
+
if not logout_status.verified or logout_status.authenticated:
|
|
444
|
+
detail = logout_status.detail or "provider credentials could not be removed"
|
|
445
|
+
raise RuntimeConnectionError(f"Could not switch account: {detail}")
|
|
446
|
+
method_id = "device-code" if device_code else "browser"
|
|
447
|
+
status = login_runtime(cfg, resolved, method_id=method_id)
|
|
448
|
+
except RuntimeConnectionError as exc:
|
|
449
|
+
console.print(f"[red]Runtime error:[/red] {exc}")
|
|
450
|
+
raise typer.Exit(code=2) from exc
|
|
451
|
+
if not status.verified or not status.authenticated:
|
|
452
|
+
console.print(f"[red]Authentication failed:[/red] {status.detail or 'unknown error'}")
|
|
453
|
+
raise typer.Exit(code=1)
|
|
454
|
+
_patchable("save_config", save_config)(cfg)
|
|
455
|
+
label = status.account_label or resolved
|
|
456
|
+
console.print(f"[green]Connected:[/green] {label}")
|
|
457
|
+
console.print(
|
|
458
|
+
"[dim]The provider runtime owns and refreshes its credentials; "
|
|
459
|
+
"Alysis Code stores only the selected runtime settings.[/dim]"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@auth_app.command("logout")
|
|
464
|
+
def auth_logout(
|
|
465
|
+
runtime_id: str | None = typer.Argument(
|
|
466
|
+
None,
|
|
467
|
+
help="Connection id. Defaults to the selected connection (or the only adapter).",
|
|
468
|
+
),
|
|
469
|
+
) -> None:
|
|
470
|
+
"""Disconnect a provider subscription and remove locally stored credentials."""
|
|
471
|
+
|
|
472
|
+
cfg = _patchable("load_config", load_config)()
|
|
473
|
+
resolved = _cli_runtime_id(cfg, runtime_id)
|
|
474
|
+
if _use_direct_provider(cfg, resolved, requested=runtime_id):
|
|
475
|
+
try:
|
|
476
|
+
status = create_provider_auth(resolved).logout()
|
|
477
|
+
except ProviderAuthError as exc:
|
|
478
|
+
_console().print(f"[red]Logout failed:[/red] {exc}")
|
|
479
|
+
raise typer.Exit(code=1) from exc
|
|
480
|
+
_console().print(f"[green]Disconnected:[/green] {resolved}")
|
|
481
|
+
if not status.verified and status.detail:
|
|
482
|
+
_console().print(f"[yellow]{status.detail}[/yellow]")
|
|
483
|
+
return
|
|
484
|
+
try:
|
|
485
|
+
status = logout_runtime(cfg, resolved)
|
|
486
|
+
except RuntimeConnectionError as exc:
|
|
487
|
+
_console().print(f"[red]Runtime error:[/red] {exc}")
|
|
488
|
+
raise typer.Exit(code=2) from exc
|
|
489
|
+
if not status.verified or status.authenticated:
|
|
490
|
+
_console().print(f"[red]Logout failed:[/red] {status.detail or 'unknown error'}")
|
|
491
|
+
raise typer.Exit(code=1)
|
|
492
|
+
_console().print(f"[green]Disconnected:[/green] {resolved}")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def login_provider_interactively(
|
|
496
|
+
*,
|
|
497
|
+
runtime_id: str | None = None,
|
|
498
|
+
device_code: bool = False,
|
|
499
|
+
) -> None:
|
|
500
|
+
"""Run a provider login flow from the unified login entry point."""
|
|
501
|
+
|
|
502
|
+
auth_login(runtime_id=runtime_id, device_code=device_code, switch_account=False)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _cli_runtime_id(cfg: AppConfig, requested: str | None) -> str:
|
|
506
|
+
explicit = str(requested or "").strip()
|
|
507
|
+
if explicit:
|
|
508
|
+
return explicit
|
|
509
|
+
selected = str(cfg.execution.runtime or "").strip()
|
|
510
|
+
if selected:
|
|
511
|
+
return selected
|
|
512
|
+
active_provider = _active_auth_provider(cfg)
|
|
513
|
+
if active_provider:
|
|
514
|
+
return active_provider
|
|
515
|
+
options = provider_auth_setup_options()
|
|
516
|
+
if len(options) == 1:
|
|
517
|
+
return options[0].id
|
|
518
|
+
choices = ", ".join(option.id for option in options) or "none"
|
|
519
|
+
raise typer.BadParameter(f"Choose a connection id. Available connections: {choices}.")
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _account_status_label(authenticated: bool, detail: str | None) -> str:
|
|
523
|
+
if authenticated:
|
|
524
|
+
return detail or "connected"
|
|
525
|
+
return detail or "not connected"
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _is_direct_provider(provider_id: str) -> bool:
|
|
529
|
+
from ...provider_auth import provider_auth_ids
|
|
530
|
+
|
|
531
|
+
return str(provider_id or "").strip() in provider_auth_ids()
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _use_direct_provider(
|
|
535
|
+
cfg: AppConfig,
|
|
536
|
+
provider_id: str,
|
|
537
|
+
*,
|
|
538
|
+
requested: str | None,
|
|
539
|
+
) -> bool:
|
|
540
|
+
"""Disambiguate the native subscription adapter from the legacy runtime.
|
|
541
|
+
|
|
542
|
+
``openai-codex`` was already used by the original delegated ``codex`` CLI
|
|
543
|
+
integration. An explicit provider id now means the native subscription
|
|
544
|
+
connection, while an omitted id continues to honor an already-selected
|
|
545
|
+
delegated runtime so existing advanced configurations remain operable.
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
if not _is_direct_provider(provider_id):
|
|
549
|
+
return False
|
|
550
|
+
if str(requested or "").strip():
|
|
551
|
+
return True
|
|
552
|
+
return not (
|
|
553
|
+
cfg.execution.backend == "delegated"
|
|
554
|
+
and str(cfg.execution.runtime or "").strip() == provider_id
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _active_auth_provider(cfg: AppConfig) -> str | None:
|
|
559
|
+
try:
|
|
560
|
+
return get_active_profile(cfg).auth_provider
|
|
561
|
+
except Exception:
|
|
562
|
+
return None
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _activate_direct_provider(cfg: AppConfig, provider_id: str, adapter: object) -> None:
|
|
566
|
+
models = adapter.list_models(refresh=True) # type: ignore[attr-defined]
|
|
567
|
+
if not models:
|
|
568
|
+
raise ProviderAuthError("The connected account did not advertise any available models.")
|
|
569
|
+
existing_profile = get_profile(cfg, str(adapter.profile_name)) # type: ignore[attr-defined]
|
|
570
|
+
preserve_existing = bool(
|
|
571
|
+
existing_profile is not None and existing_profile.auth_provider == provider_id
|
|
572
|
+
)
|
|
573
|
+
profile = ProfileSpec(
|
|
574
|
+
name=str(adapter.profile_name), # type: ignore[attr-defined]
|
|
575
|
+
protocol=str(adapter.protocol), # type: ignore[attr-defined]
|
|
576
|
+
base_url=str(adapter.base_url), # type: ignore[attr-defined]
|
|
577
|
+
api_key_env=(existing_profile.api_key_env if preserve_existing else None),
|
|
578
|
+
auth_provider=provider_id,
|
|
579
|
+
extra_headers=(dict(existing_profile.extra_headers) if preserve_existing else {}),
|
|
580
|
+
default_model=(existing_profile.default_model if preserve_existing else ""),
|
|
581
|
+
reasoning_effort=(existing_profile.reasoning_effort if preserve_existing else None),
|
|
582
|
+
reasoning_trace_adapter=(
|
|
583
|
+
existing_profile.reasoning_trace_adapter if preserve_existing else "auto"
|
|
584
|
+
),
|
|
585
|
+
web_search_adapter=(existing_profile.web_search_adapter if preserve_existing else "auto"),
|
|
586
|
+
web_search_model=(existing_profile.web_search_model if preserve_existing else ""),
|
|
587
|
+
notes=(
|
|
588
|
+
existing_profile.notes
|
|
589
|
+
if preserve_existing
|
|
590
|
+
else f"{adapter.display_name}. Uses Alysis Code's native agent loop." # type: ignore[attr-defined]
|
|
591
|
+
),
|
|
592
|
+
cache_capability=(existing_profile.cache_capability if preserve_existing else None),
|
|
593
|
+
)
|
|
594
|
+
add_profile(cfg, profile, allow_auth_profile_update=True)
|
|
595
|
+
set_active_profile(cfg, profile.name)
|
|
596
|
+
cfg.execution.backend = "native"
|
|
597
|
+
cfg.execution.runtime = None
|
|
598
|
+
cfg.model = profile.default_model
|
|
599
|
+
effective_effort = (
|
|
600
|
+
None if profile.reasoning_effort in {None, "auto"} else profile.reasoning_effort
|
|
601
|
+
)
|
|
602
|
+
cfg.llm_reasoning_effort = effective_effort
|
|
603
|
+
cfg.llm_enable_thinking = None if effective_effort is None else effective_effort != "none"
|
|
604
|
+
confirmation_pending = str(
|
|
605
|
+
cfg.extra_fields.get(SUBSCRIPTION_SELECTION_REQUIRED_KEY) or ""
|
|
606
|
+
).strip().lower() in {"true", provider_id}
|
|
607
|
+
selection_ready = subscription_selection_supported(profile, models) and not confirmation_pending
|
|
608
|
+
cfg.extra_fields["onboarded"] = selection_ready
|
|
609
|
+
cfg.extra_fields.pop("subscription_reconnect_required", None)
|
|
610
|
+
if not selection_ready:
|
|
611
|
+
cfg.extra_fields[SUBSCRIPTION_SELECTION_REQUIRED_KEY] = provider_id
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
__all__ = [
|
|
615
|
+
"auth_app",
|
|
616
|
+
"auth_list",
|
|
617
|
+
"auth_login",
|
|
618
|
+
"auth_logout",
|
|
619
|
+
"auth_status",
|
|
620
|
+
"login_connection_interactively",
|
|
621
|
+
"login_connection_rows",
|
|
622
|
+
"login_provider_interactively",
|
|
623
|
+
]
|