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,453 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ...config import (
|
|
11
|
+
AppConfig,
|
|
12
|
+
ConfigError,
|
|
13
|
+
clear_persisted_profile_key,
|
|
14
|
+
load_config,
|
|
15
|
+
rename_persisted_profile_key,
|
|
16
|
+
resolve_profile_api_key,
|
|
17
|
+
save_config,
|
|
18
|
+
save_persisted_profile_key,
|
|
19
|
+
)
|
|
20
|
+
from . import _patchable
|
|
21
|
+
from ._shared import _console, _Table
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cli_module() -> Any:
|
|
25
|
+
module = sys.modules.get("alysis_code.cli")
|
|
26
|
+
if module is not None:
|
|
27
|
+
return module
|
|
28
|
+
from ... import cli
|
|
29
|
+
|
|
30
|
+
return cli
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
profile_app = typer.Typer(add_completion=False, help="Provider profile commands.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _ensure_generic_profile_mutation_allowed(
|
|
37
|
+
cfg: AppConfig,
|
|
38
|
+
name: str,
|
|
39
|
+
*,
|
|
40
|
+
operation: str,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Keep generic profile commands from taking ownership of auth profiles."""
|
|
43
|
+
|
|
44
|
+
from ...profiles import get_profile
|
|
45
|
+
|
|
46
|
+
existing = get_profile(cfg, name)
|
|
47
|
+
if existing is None or not existing.auth_provider:
|
|
48
|
+
return
|
|
49
|
+
raise ConfigError(
|
|
50
|
+
f"Profile {existing.name!r} is managed by the {existing.auth_provider!r} "
|
|
51
|
+
f"subscription connection and cannot be {operation} through generic profile "
|
|
52
|
+
"commands. Choose a different profile name or manage the connection through auth."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_profile_headers(header_values: list[str] | None) -> dict[str, str]:
|
|
57
|
+
headers: dict[str, str] = {}
|
|
58
|
+
for raw_value in header_values or []:
|
|
59
|
+
text = str(raw_value or "").strip()
|
|
60
|
+
if not text:
|
|
61
|
+
continue
|
|
62
|
+
if "=" not in text:
|
|
63
|
+
raise ConfigError("--header values must use k=v syntax.")
|
|
64
|
+
key, value = text.split("=", 1)
|
|
65
|
+
key = key.strip()
|
|
66
|
+
value = value.strip()
|
|
67
|
+
if not key or not value:
|
|
68
|
+
raise ConfigError("--header values must use non-empty k=v syntax.")
|
|
69
|
+
headers[key] = value
|
|
70
|
+
return headers
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _profile_api_key_status(cfg: AppConfig, profile_name: str) -> str:
|
|
74
|
+
resolved = resolve_profile_api_key(cfg, profile_name)
|
|
75
|
+
if not resolved.key:
|
|
76
|
+
return "missing"
|
|
77
|
+
return _cli_module()._api_key_source_label(resolved.source)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@profile_app.command("list")
|
|
81
|
+
def profile_list() -> None:
|
|
82
|
+
"""List provider profiles."""
|
|
83
|
+
from ...profiles import list_profiles
|
|
84
|
+
|
|
85
|
+
console = _console()
|
|
86
|
+
cfg = _patchable("load_config", load_config)()
|
|
87
|
+
active = str((cfg.extra_fields or {}).get("active_profile") or "")
|
|
88
|
+
table = _Table(title="Provider Profiles")
|
|
89
|
+
table.add_column("name")
|
|
90
|
+
table.add_column("active")
|
|
91
|
+
table.add_column("protocol")
|
|
92
|
+
table.add_column("base_url")
|
|
93
|
+
table.add_column("api_key")
|
|
94
|
+
table.add_column("default_model")
|
|
95
|
+
table.add_column("web_search")
|
|
96
|
+
for profile in list_profiles(cfg):
|
|
97
|
+
table.add_row(
|
|
98
|
+
profile.name,
|
|
99
|
+
"✓" if profile.name == active else "",
|
|
100
|
+
profile.protocol,
|
|
101
|
+
profile.base_url,
|
|
102
|
+
_profile_api_key_status(cfg, profile.name),
|
|
103
|
+
profile.default_model,
|
|
104
|
+
profile.web_search_adapter,
|
|
105
|
+
)
|
|
106
|
+
console.print(table)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@profile_app.command("show")
|
|
110
|
+
def profile_show(name: str = typer.Argument(..., help="Profile name.")) -> None:
|
|
111
|
+
"""Show one provider profile."""
|
|
112
|
+
from ...profiles import get_profile
|
|
113
|
+
|
|
114
|
+
console = _console()
|
|
115
|
+
cfg = _patchable("load_config", load_config)()
|
|
116
|
+
profile = get_profile(cfg, name)
|
|
117
|
+
if profile is None:
|
|
118
|
+
console.print(f"[red]Profile not found:[/red] {name}")
|
|
119
|
+
raise typer.Exit(code=2)
|
|
120
|
+
data = profile.to_dict()
|
|
121
|
+
data["name"] = profile.name
|
|
122
|
+
data["active"] = profile.name == str((cfg.extra_fields or {}).get("active_profile") or "")
|
|
123
|
+
data["api_key"] = _profile_api_key_status(cfg, profile.name)
|
|
124
|
+
console.print_json(json.dumps(data, indent=2, sort_keys=True))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@profile_app.command("add")
|
|
128
|
+
def profile_add(
|
|
129
|
+
name: str = typer.Argument(..., help="Profile name."),
|
|
130
|
+
base_url: str = typer.Option(..., "--base-url", help="OpenAI-compatible base URL."),
|
|
131
|
+
api_key_env: str | None = typer.Option(None, "--api-key-env", help="API key env var."),
|
|
132
|
+
header: list[str] | None = typer.Option(None, "--header", help="Extra request header k=v."),
|
|
133
|
+
default_model: str = typer.Option("", "--default-model", help="Default model."),
|
|
134
|
+
reasoning_trace_adapter: str = typer.Option(
|
|
135
|
+
"auto",
|
|
136
|
+
"--reasoning-trace-adapter",
|
|
137
|
+
help="Safe reasoning-summary adapter for this provider profile.",
|
|
138
|
+
),
|
|
139
|
+
web_search_adapter: str = typer.Option(
|
|
140
|
+
"auto",
|
|
141
|
+
"--web-search-adapter",
|
|
142
|
+
help="web_search adapter for this profile.",
|
|
143
|
+
),
|
|
144
|
+
web_search_model: str = typer.Option(
|
|
145
|
+
"",
|
|
146
|
+
"--web-search-model",
|
|
147
|
+
help="Optional model used only by web_search.",
|
|
148
|
+
),
|
|
149
|
+
notes: str = typer.Option("", "--notes", help="Notes."),
|
|
150
|
+
protocol: str = typer.Option("openai_compat", "--protocol", help="Profile protocol."),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Add a custom provider profile."""
|
|
153
|
+
from ...profiles import ProfileSpec, add_profile
|
|
154
|
+
|
|
155
|
+
console = _console()
|
|
156
|
+
cfg = _patchable("load_config", load_config)()
|
|
157
|
+
try:
|
|
158
|
+
_ensure_generic_profile_mutation_allowed(cfg, name, operation="overwritten")
|
|
159
|
+
profile = ProfileSpec(
|
|
160
|
+
name=name,
|
|
161
|
+
protocol=protocol,
|
|
162
|
+
base_url=base_url,
|
|
163
|
+
api_key_env=api_key_env,
|
|
164
|
+
extra_headers=_parse_profile_headers(header),
|
|
165
|
+
default_model=default_model,
|
|
166
|
+
reasoning_trace_adapter=reasoning_trace_adapter,
|
|
167
|
+
web_search_adapter=web_search_adapter,
|
|
168
|
+
web_search_model=web_search_model,
|
|
169
|
+
notes=notes,
|
|
170
|
+
)
|
|
171
|
+
add_profile(cfg, profile)
|
|
172
|
+
save_config(cfg)
|
|
173
|
+
except ConfigError as exc:
|
|
174
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
175
|
+
raise typer.Exit(code=2) from exc
|
|
176
|
+
console.print(f"Added profile {profile.name}.")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@profile_app.command("remove")
|
|
180
|
+
def profile_remove(
|
|
181
|
+
name: str = typer.Argument(..., help="Profile name."),
|
|
182
|
+
yes: bool = typer.Option(False, "--yes", help="Skip confirmation."),
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Remove a provider profile."""
|
|
185
|
+
from ...profiles import remove_profile
|
|
186
|
+
|
|
187
|
+
console = _console()
|
|
188
|
+
if not yes and not typer.confirm(f"Remove profile {name}?", default=False):
|
|
189
|
+
console.print("Cancelled.")
|
|
190
|
+
return
|
|
191
|
+
cfg = _patchable("load_config", load_config)()
|
|
192
|
+
try:
|
|
193
|
+
remove_profile(cfg, name)
|
|
194
|
+
clear_persisted_profile_key(name)
|
|
195
|
+
save_config(cfg)
|
|
196
|
+
except ConfigError as exc:
|
|
197
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
198
|
+
raise typer.Exit(code=2) from exc
|
|
199
|
+
console.print(f"Removed profile {name}.")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@profile_app.command("use")
|
|
203
|
+
def profile_use(name: str = typer.Argument(..., help="Profile name.")) -> None:
|
|
204
|
+
"""Switch the active provider profile."""
|
|
205
|
+
from ...profiles import set_active_profile
|
|
206
|
+
|
|
207
|
+
console = _console()
|
|
208
|
+
cfg = _patchable("load_config", load_config)()
|
|
209
|
+
try:
|
|
210
|
+
set_active_profile(cfg, name)
|
|
211
|
+
save_config(cfg)
|
|
212
|
+
except ConfigError as exc:
|
|
213
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
214
|
+
raise typer.Exit(code=2) from exc
|
|
215
|
+
console.print(f"Active profile: {name}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@profile_app.command("rename")
|
|
219
|
+
def profile_rename(
|
|
220
|
+
old: str = typer.Argument(..., help="Current profile name."),
|
|
221
|
+
new: str = typer.Argument(..., help="New profile name."),
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Rename a provider profile."""
|
|
224
|
+
from ...profiles import rename_profile
|
|
225
|
+
|
|
226
|
+
console = _console()
|
|
227
|
+
cfg = _patchable("load_config", load_config)()
|
|
228
|
+
try:
|
|
229
|
+
rename_profile(cfg, old, new)
|
|
230
|
+
rename_persisted_profile_key(old, new)
|
|
231
|
+
save_config(cfg)
|
|
232
|
+
except ConfigError as exc:
|
|
233
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
234
|
+
raise typer.Exit(code=2) from exc
|
|
235
|
+
console.print(f"Renamed profile {old} to {new}.")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@profile_app.command("preset")
|
|
239
|
+
def profile_preset(
|
|
240
|
+
preset_key: str = typer.Argument(..., help="Preset key."),
|
|
241
|
+
profile_name_as: str | None = typer.Option(None, "--as", help="Profile name to create."),
|
|
242
|
+
api_key: str | None = typer.Option(None, "--api-key", help="API key to store."),
|
|
243
|
+
api_key_stdin: bool = typer.Option(False, "--api-key-stdin", help="Read API key from stdin."),
|
|
244
|
+
yes: bool = typer.Option(False, "--yes", help="Overwrite without confirmation."),
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Clone a provider preset into a profile."""
|
|
247
|
+
from ...profile_presets import get_preset, make_profile_from_preset
|
|
248
|
+
from ...profiles import add_profile, get_profile, update_profile
|
|
249
|
+
|
|
250
|
+
console = _console()
|
|
251
|
+
preset = get_preset(preset_key)
|
|
252
|
+
if preset is None:
|
|
253
|
+
console.print(f"[red]Unknown preset:[/red] {preset_key}")
|
|
254
|
+
raise typer.Exit(code=2)
|
|
255
|
+
cfg = _patchable("load_config", load_config)()
|
|
256
|
+
name = str(profile_name_as or preset.key).strip().lower()
|
|
257
|
+
existing = get_profile(cfg, name)
|
|
258
|
+
try:
|
|
259
|
+
_ensure_generic_profile_mutation_allowed(cfg, name, operation="overwritten")
|
|
260
|
+
except ConfigError as exc:
|
|
261
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
262
|
+
raise typer.Exit(code=2) from exc
|
|
263
|
+
if existing is not None and not yes:
|
|
264
|
+
if not typer.confirm(f"Overwrite profile {name}?", default=False):
|
|
265
|
+
console.print("Cancelled.")
|
|
266
|
+
return
|
|
267
|
+
base_url = preset.base_url
|
|
268
|
+
if not base_url:
|
|
269
|
+
base_url = str(typer.prompt("Base URL", default="", show_default=False)).strip()
|
|
270
|
+
try:
|
|
271
|
+
profile = make_profile_from_preset(preset, name=name)
|
|
272
|
+
if base_url != profile.base_url:
|
|
273
|
+
profile = make_profile_from_preset(preset, name=name)
|
|
274
|
+
add_profile(cfg, profile)
|
|
275
|
+
update_profile(cfg, profile.name, base_url=base_url)
|
|
276
|
+
else:
|
|
277
|
+
add_profile(cfg, profile)
|
|
278
|
+
save_config(cfg)
|
|
279
|
+
key_value = ""
|
|
280
|
+
if api_key_stdin:
|
|
281
|
+
key_value = sys.stdin.read().strip()
|
|
282
|
+
elif api_key is not None:
|
|
283
|
+
key_value = api_key.strip()
|
|
284
|
+
if key_value:
|
|
285
|
+
save_persisted_profile_key(profile.name, key_value)
|
|
286
|
+
except ConfigError as exc:
|
|
287
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
288
|
+
raise typer.Exit(code=2) from exc
|
|
289
|
+
console.print(f"Added profile {name} from preset {preset.key}.")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@profile_app.command("convert")
|
|
293
|
+
def profile_convert(
|
|
294
|
+
name: str | None = typer.Argument(
|
|
295
|
+
None,
|
|
296
|
+
help="Profile name. Defaults to the active profile.",
|
|
297
|
+
),
|
|
298
|
+
target: str = typer.Option(
|
|
299
|
+
...,
|
|
300
|
+
"--to",
|
|
301
|
+
help="Conversion target: native or compatibility.",
|
|
302
|
+
),
|
|
303
|
+
yes: bool = typer.Option(False, "--yes", help="Skip confirmation."),
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Convert a first-party profile between native and compatibility protocols."""
|
|
306
|
+
from ...profile_presets import (
|
|
307
|
+
convert_profile_to_preset,
|
|
308
|
+
normalize_conversion_target,
|
|
309
|
+
target_preset_for_profile_conversion,
|
|
310
|
+
)
|
|
311
|
+
from ...profiles import add_profile, get_profile, set_active_profile
|
|
312
|
+
|
|
313
|
+
console = _console()
|
|
314
|
+
cfg = _patchable("load_config", load_config)()
|
|
315
|
+
profile_name = str(name or (cfg.extra_fields or {}).get("active_profile") or "").strip()
|
|
316
|
+
if not profile_name:
|
|
317
|
+
console.print(
|
|
318
|
+
"[red]Profile error:[/red] No profile name supplied and no active profile set."
|
|
319
|
+
)
|
|
320
|
+
raise typer.Exit(code=2)
|
|
321
|
+
profile = get_profile(cfg, profile_name)
|
|
322
|
+
if profile is None:
|
|
323
|
+
console.print(f"[red]Profile not found:[/red] {profile_name}")
|
|
324
|
+
raise typer.Exit(code=2)
|
|
325
|
+
try:
|
|
326
|
+
_ensure_generic_profile_mutation_allowed(cfg, profile.name, operation="converted")
|
|
327
|
+
normalized_target = normalize_conversion_target(target)
|
|
328
|
+
except (ConfigError, ValueError) as exc:
|
|
329
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
330
|
+
raise typer.Exit(code=2) from exc
|
|
331
|
+
preset = target_preset_for_profile_conversion(profile, target=normalized_target)
|
|
332
|
+
if preset is None:
|
|
333
|
+
console.print(
|
|
334
|
+
"[red]Profile error:[/red] Only OpenAI, Anthropic, and Gemini profiles can be "
|
|
335
|
+
"converted between native and compatibility protocols."
|
|
336
|
+
)
|
|
337
|
+
raise typer.Exit(code=2)
|
|
338
|
+
converted = convert_profile_to_preset(profile, preset)
|
|
339
|
+
if converted.to_dict() == profile.to_dict():
|
|
340
|
+
console.print(
|
|
341
|
+
f"Profile {profile.name} is already using the {normalized_target} preset "
|
|
342
|
+
f"({preset.key})."
|
|
343
|
+
)
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
table = _Table(title=f"Convert profile {profile.name}")
|
|
347
|
+
table.add_column("field")
|
|
348
|
+
table.add_column("before")
|
|
349
|
+
table.add_column("after")
|
|
350
|
+
table.add_row("preset", profile.name, preset.key)
|
|
351
|
+
table.add_row("protocol", profile.protocol, converted.protocol)
|
|
352
|
+
table.add_row("base_url", profile.base_url, converted.base_url)
|
|
353
|
+
table.add_row(
|
|
354
|
+
"default_model", profile.default_model or "(empty)", converted.default_model or "(empty)"
|
|
355
|
+
)
|
|
356
|
+
table.add_row("web_search_adapter", profile.web_search_adapter, converted.web_search_adapter)
|
|
357
|
+
table.add_row(
|
|
358
|
+
"web_search_model",
|
|
359
|
+
profile.web_search_model or "(empty)",
|
|
360
|
+
converted.web_search_model or "(empty)",
|
|
361
|
+
)
|
|
362
|
+
table.add_row(
|
|
363
|
+
"api_key",
|
|
364
|
+
_profile_api_key_status(cfg, profile.name),
|
|
365
|
+
_profile_api_key_status(cfg, profile.name),
|
|
366
|
+
)
|
|
367
|
+
console.print(table)
|
|
368
|
+
|
|
369
|
+
if not yes and not typer.confirm(
|
|
370
|
+
f"Convert profile {profile.name} to {normalized_target}?", default=False
|
|
371
|
+
):
|
|
372
|
+
console.print("Cancelled.")
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
add_profile(cfg, converted)
|
|
376
|
+
if str((cfg.extra_fields or {}).get("active_profile") or "") == profile.name:
|
|
377
|
+
set_active_profile(cfg, profile.name)
|
|
378
|
+
save_config(cfg)
|
|
379
|
+
console.print(f"Converted profile {profile.name} to {normalized_target} ({preset.key}).")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@profile_app.command("presets")
|
|
383
|
+
def profile_presets() -> None:
|
|
384
|
+
"""List available provider presets."""
|
|
385
|
+
from ...profile_presets import PROFILE_PRESETS, preset_protocol_kind, preset_selection_label
|
|
386
|
+
|
|
387
|
+
table = _Table(title="Provider Presets")
|
|
388
|
+
table.add_column("key")
|
|
389
|
+
table.add_column("label")
|
|
390
|
+
table.add_column("kind")
|
|
391
|
+
table.add_column("protocol")
|
|
392
|
+
table.add_column("base_url")
|
|
393
|
+
table.add_column("web_search")
|
|
394
|
+
for preset in PROFILE_PRESETS:
|
|
395
|
+
table.add_row(
|
|
396
|
+
preset.key,
|
|
397
|
+
preset_selection_label(preset),
|
|
398
|
+
preset_protocol_kind(preset),
|
|
399
|
+
preset.protocol,
|
|
400
|
+
preset.base_url,
|
|
401
|
+
preset.web_search_adapter,
|
|
402
|
+
)
|
|
403
|
+
_console().print(table)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@profile_app.command("set-key")
|
|
407
|
+
def profile_set_key(
|
|
408
|
+
name: str = typer.Argument(..., help="Profile name."),
|
|
409
|
+
stdin: bool = typer.Option(False, "--stdin", help="Read API key from stdin."),
|
|
410
|
+
from_env: str | None = typer.Option(None, "--from-env", help="Copy key from env var."),
|
|
411
|
+
key: str | None = typer.Option(None, "--key", help="Inline key; avoid shell history."),
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Store an API key for one profile."""
|
|
414
|
+
from ...profiles import get_profile
|
|
415
|
+
|
|
416
|
+
console = _console()
|
|
417
|
+
cfg = _patchable("load_config", load_config)()
|
|
418
|
+
profile = get_profile(cfg, name)
|
|
419
|
+
if profile is None:
|
|
420
|
+
console.print(f"[red]Profile not found:[/red] {name}")
|
|
421
|
+
raise typer.Exit(code=2)
|
|
422
|
+
sources = sum(1 for value in (stdin, bool(from_env), key is not None) if value)
|
|
423
|
+
if sources > 1:
|
|
424
|
+
console.print("[red]Profile error:[/red] Use only one key source.")
|
|
425
|
+
raise typer.Exit(code=2)
|
|
426
|
+
if stdin:
|
|
427
|
+
key_value = sys.stdin.read().strip()
|
|
428
|
+
elif from_env:
|
|
429
|
+
key_value = str(os.environ.get(from_env) or "").strip()
|
|
430
|
+
if not key_value:
|
|
431
|
+
console.print(f"[red]Profile error:[/red] Environment variable {from_env} is not set.")
|
|
432
|
+
raise typer.Exit(code=2)
|
|
433
|
+
elif key is not None:
|
|
434
|
+
key_value = key.strip()
|
|
435
|
+
console.print("[yellow]Warning:[/yellow] inline keys may be stored in shell history.")
|
|
436
|
+
else:
|
|
437
|
+
key_value = typer.prompt("API key", hide_input=True).strip()
|
|
438
|
+
try:
|
|
439
|
+
save_persisted_profile_key(profile.name, key_value)
|
|
440
|
+
except ConfigError as exc:
|
|
441
|
+
console.print(f"[red]Profile error:[/red] {exc}")
|
|
442
|
+
raise typer.Exit(code=2) from exc
|
|
443
|
+
console.print(f"Saved API key for profile {profile.name}.")
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@profile_app.command("clear-key")
|
|
447
|
+
def profile_clear_key(name: str = typer.Argument(..., help="Profile name.")) -> None:
|
|
448
|
+
"""Remove a stored API key for one profile."""
|
|
449
|
+
removed = clear_persisted_profile_key(name)
|
|
450
|
+
if removed:
|
|
451
|
+
_console().print(f"Removed API key for profile {name}.")
|
|
452
|
+
return
|
|
453
|
+
_console().print(f"No stored API key found for profile {name}.")
|