PraisonAI 3.0.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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatters for the Doctor CLI module.
|
|
3
|
+
|
|
4
|
+
Provides text and JSON formatting with secret redaction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any, Dict, List, Optional, TextIO
|
|
11
|
+
from .models import CheckResult, CheckStatus, DoctorReport, ReportSummary
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Patterns for detecting secrets
|
|
15
|
+
SECRET_PATTERNS = [
|
|
16
|
+
re.compile(r'(sk-[a-zA-Z0-9]{20,})', re.IGNORECASE), # OpenAI
|
|
17
|
+
re.compile(r'(sk-ant-[a-zA-Z0-9-]{20,})', re.IGNORECASE), # Anthropic
|
|
18
|
+
re.compile(r'(AIza[a-zA-Z0-9_-]{35})', re.IGNORECASE), # Google
|
|
19
|
+
re.compile(r'(tvly-[a-zA-Z0-9]{20,})', re.IGNORECASE), # Tavily
|
|
20
|
+
re.compile(r'(xai-[a-zA-Z0-9]{20,})', re.IGNORECASE), # xAI
|
|
21
|
+
re.compile(r'([a-zA-Z0-9_-]*api[_-]?key[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
|
|
22
|
+
re.compile(r'([a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
|
|
23
|
+
re.compile(r'([a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
|
|
24
|
+
re.compile(r'(password\s*=\s*["\']?[^\s"\']{8,})', re.IGNORECASE),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Known API key environment variable names
|
|
28
|
+
API_KEY_ENV_VARS = [
|
|
29
|
+
"OPENAI_API_KEY",
|
|
30
|
+
"ANTHROPIC_API_KEY",
|
|
31
|
+
"GOOGLE_API_KEY",
|
|
32
|
+
"GEMINI_API_KEY",
|
|
33
|
+
"TAVILY_API_KEY",
|
|
34
|
+
"EXA_API_KEY",
|
|
35
|
+
"COHERE_API_KEY",
|
|
36
|
+
"HUGGINGFACE_API_KEY",
|
|
37
|
+
"HF_TOKEN",
|
|
38
|
+
"LANGFUSE_PUBLIC_KEY",
|
|
39
|
+
"LANGFUSE_SECRET_KEY",
|
|
40
|
+
"LANGSMITH_API_KEY",
|
|
41
|
+
"AGENTOPS_API_KEY",
|
|
42
|
+
"WANDB_API_KEY",
|
|
43
|
+
"DATADOG_API_KEY",
|
|
44
|
+
"BRAINTRUST_API_KEY",
|
|
45
|
+
"YDC_API_KEY",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def redact_secrets(text: str, show_prefix_suffix: bool = False) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Redact secrets from text.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
text: Text to redact
|
|
55
|
+
show_prefix_suffix: If True, show first 4 and last 4 chars
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Text with secrets redacted
|
|
59
|
+
"""
|
|
60
|
+
if not text:
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
result = text
|
|
64
|
+
|
|
65
|
+
for pattern in SECRET_PATTERNS:
|
|
66
|
+
def replace_match(match):
|
|
67
|
+
secret = match.group(1)
|
|
68
|
+
if show_prefix_suffix and len(secret) > 12:
|
|
69
|
+
return f"{secret[:4]}...{secret[-4:]}"
|
|
70
|
+
return "***REDACTED***"
|
|
71
|
+
result = pattern.sub(replace_match, result)
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def redact_dict(data: Dict[str, Any], show_prefix_suffix: bool = False) -> Dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Recursively redact secrets from a dictionary.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
data: Dictionary to redact
|
|
82
|
+
show_prefix_suffix: If True, show first 4 and last 4 chars
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary with secrets redacted
|
|
86
|
+
"""
|
|
87
|
+
result = {}
|
|
88
|
+
for key, value in data.items():
|
|
89
|
+
if isinstance(value, str):
|
|
90
|
+
# Check if key suggests it's a secret
|
|
91
|
+
key_lower = key.lower()
|
|
92
|
+
if any(s in key_lower for s in ["key", "token", "secret", "password", "credential"]):
|
|
93
|
+
if show_prefix_suffix and len(value) > 12:
|
|
94
|
+
result[key] = f"{value[:4]}...{value[-4:]}"
|
|
95
|
+
else:
|
|
96
|
+
result[key] = "***REDACTED***"
|
|
97
|
+
else:
|
|
98
|
+
result[key] = redact_secrets(value, show_prefix_suffix)
|
|
99
|
+
elif isinstance(value, dict):
|
|
100
|
+
result[key] = redact_dict(value, show_prefix_suffix)
|
|
101
|
+
elif isinstance(value, list):
|
|
102
|
+
result[key] = [
|
|
103
|
+
redact_dict(v, show_prefix_suffix) if isinstance(v, dict)
|
|
104
|
+
else redact_secrets(v, show_prefix_suffix) if isinstance(v, str)
|
|
105
|
+
else v
|
|
106
|
+
for v in value
|
|
107
|
+
]
|
|
108
|
+
else:
|
|
109
|
+
result[key] = value
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BaseFormatter:
|
|
114
|
+
"""Base class for output formatters."""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
no_color: bool = False,
|
|
119
|
+
quiet: bool = False,
|
|
120
|
+
redact: bool = True,
|
|
121
|
+
show_prefix_suffix: bool = False,
|
|
122
|
+
):
|
|
123
|
+
self.no_color = no_color or not sys.stdout.isatty()
|
|
124
|
+
self.quiet = quiet
|
|
125
|
+
self.redact = redact
|
|
126
|
+
self.show_prefix_suffix = show_prefix_suffix
|
|
127
|
+
|
|
128
|
+
def format_report(self, report: DoctorReport) -> str:
|
|
129
|
+
"""Format a complete report."""
|
|
130
|
+
raise NotImplementedError
|
|
131
|
+
|
|
132
|
+
def format_result(self, result: CheckResult) -> str:
|
|
133
|
+
"""Format a single check result."""
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
def write(self, report: DoctorReport, output: Optional[TextIO] = None) -> None:
|
|
137
|
+
"""Write formatted report to output."""
|
|
138
|
+
output = output or sys.stdout
|
|
139
|
+
output.write(self.format_report(report))
|
|
140
|
+
if not self.format_report(report).endswith("\n"):
|
|
141
|
+
output.write("\n")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TextFormatter(BaseFormatter):
|
|
145
|
+
"""Text formatter with optional color support."""
|
|
146
|
+
|
|
147
|
+
# ANSI color codes
|
|
148
|
+
COLORS = {
|
|
149
|
+
"reset": "\033[0m",
|
|
150
|
+
"bold": "\033[1m",
|
|
151
|
+
"dim": "\033[2m",
|
|
152
|
+
"red": "\033[31m",
|
|
153
|
+
"green": "\033[32m",
|
|
154
|
+
"yellow": "\033[33m",
|
|
155
|
+
"blue": "\033[34m",
|
|
156
|
+
"cyan": "\033[36m",
|
|
157
|
+
"white": "\033[37m",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
STATUS_COLORS = {
|
|
161
|
+
CheckStatus.PASS: "green",
|
|
162
|
+
CheckStatus.WARN: "yellow",
|
|
163
|
+
CheckStatus.FAIL: "red",
|
|
164
|
+
CheckStatus.SKIP: "dim",
|
|
165
|
+
CheckStatus.ERROR: "red",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
STATUS_SYMBOLS = {
|
|
169
|
+
CheckStatus.PASS: "✓",
|
|
170
|
+
CheckStatus.WARN: "⚠",
|
|
171
|
+
CheckStatus.FAIL: "✗",
|
|
172
|
+
CheckStatus.SKIP: "○",
|
|
173
|
+
CheckStatus.ERROR: "✗",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def _color(self, text: str, color: str) -> str:
|
|
177
|
+
"""Apply color to text if colors are enabled."""
|
|
178
|
+
if self.no_color:
|
|
179
|
+
return text
|
|
180
|
+
return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"
|
|
181
|
+
|
|
182
|
+
def _status_symbol(self, status: CheckStatus) -> str:
|
|
183
|
+
"""Get colored status symbol."""
|
|
184
|
+
symbol = self.STATUS_SYMBOLS.get(status, "?")
|
|
185
|
+
color = self.STATUS_COLORS.get(status, "white")
|
|
186
|
+
return self._color(symbol, color)
|
|
187
|
+
|
|
188
|
+
def format_result(self, result: CheckResult) -> str:
|
|
189
|
+
"""Format a single check result."""
|
|
190
|
+
symbol = self._status_symbol(result.status)
|
|
191
|
+
message = result.message
|
|
192
|
+
if self.redact:
|
|
193
|
+
message = redact_secrets(message, self.show_prefix_suffix)
|
|
194
|
+
|
|
195
|
+
line = f"{symbol} {result.title}: {message}"
|
|
196
|
+
|
|
197
|
+
if result.details and not self.quiet:
|
|
198
|
+
details = result.details
|
|
199
|
+
if self.redact:
|
|
200
|
+
details = redact_secrets(details, self.show_prefix_suffix)
|
|
201
|
+
line += f"\n {self._color(details, 'dim')}"
|
|
202
|
+
|
|
203
|
+
if result.remediation and result.status in (CheckStatus.FAIL, CheckStatus.ERROR):
|
|
204
|
+
line += f"\n {self._color('Fix:', 'yellow')} {result.remediation}"
|
|
205
|
+
|
|
206
|
+
return line
|
|
207
|
+
|
|
208
|
+
def format_summary(self, summary: ReportSummary, strict: bool = False) -> str:
|
|
209
|
+
"""Format the summary section."""
|
|
210
|
+
parts = []
|
|
211
|
+
|
|
212
|
+
if summary.passed > 0:
|
|
213
|
+
parts.append(self._color(f"{summary.passed} passed", "green"))
|
|
214
|
+
if summary.warnings > 0:
|
|
215
|
+
color = "red" if strict else "yellow"
|
|
216
|
+
parts.append(self._color(f"{summary.warnings} warnings", color))
|
|
217
|
+
if summary.failed > 0:
|
|
218
|
+
parts.append(self._color(f"{summary.failed} failed", "red"))
|
|
219
|
+
if summary.skipped > 0:
|
|
220
|
+
parts.append(self._color(f"{summary.skipped} skipped", "dim"))
|
|
221
|
+
if summary.errors > 0:
|
|
222
|
+
parts.append(self._color(f"{summary.errors} errors", "red"))
|
|
223
|
+
|
|
224
|
+
return f"{summary.total} checks: " + ", ".join(parts)
|
|
225
|
+
|
|
226
|
+
def format_report(self, report: DoctorReport) -> str:
|
|
227
|
+
"""Format a complete report."""
|
|
228
|
+
lines = []
|
|
229
|
+
|
|
230
|
+
# Header
|
|
231
|
+
if not self.quiet:
|
|
232
|
+
header = f"PraisonAI Doctor v{report.version}"
|
|
233
|
+
lines.append(self._color(header, "bold"))
|
|
234
|
+
lines.append("━" * 70)
|
|
235
|
+
|
|
236
|
+
# Results
|
|
237
|
+
for result in report.results:
|
|
238
|
+
lines.append(self.format_result(result))
|
|
239
|
+
|
|
240
|
+
# Summary
|
|
241
|
+
if not self.quiet:
|
|
242
|
+
lines.append("━" * 70)
|
|
243
|
+
lines.append(self.format_summary(report.summary))
|
|
244
|
+
|
|
245
|
+
# Duration
|
|
246
|
+
if not self.quiet and report.duration_ms > 0:
|
|
247
|
+
lines.append(self._color(f"Completed in {report.duration_ms:.0f}ms", "dim"))
|
|
248
|
+
|
|
249
|
+
return "\n".join(lines)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class JsonFormatter(BaseFormatter):
|
|
253
|
+
"""JSON formatter with deterministic output."""
|
|
254
|
+
|
|
255
|
+
def format_result(self, result: CheckResult) -> str:
|
|
256
|
+
"""Format a single check result as JSON."""
|
|
257
|
+
data = result.to_dict()
|
|
258
|
+
if self.redact:
|
|
259
|
+
data = redact_dict(data, self.show_prefix_suffix)
|
|
260
|
+
return json.dumps(data, indent=2, sort_keys=True)
|
|
261
|
+
|
|
262
|
+
def format_report(self, report: DoctorReport) -> str:
|
|
263
|
+
"""Format a complete report as JSON."""
|
|
264
|
+
data = report.to_dict()
|
|
265
|
+
if self.redact:
|
|
266
|
+
data = redact_dict(data, self.show_prefix_suffix)
|
|
267
|
+
|
|
268
|
+
# Ensure deterministic ordering
|
|
269
|
+
return json.dumps(data, indent=2, sort_keys=True)
|
|
270
|
+
|
|
271
|
+
def write(self, report: DoctorReport, output: Optional[TextIO] = None) -> None:
|
|
272
|
+
"""Write formatted report to output."""
|
|
273
|
+
output = output or sys.stdout
|
|
274
|
+
output.write(self.format_report(report))
|
|
275
|
+
output.write("\n")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_formatter(
|
|
279
|
+
format_type: str = "text",
|
|
280
|
+
no_color: bool = False,
|
|
281
|
+
quiet: bool = False,
|
|
282
|
+
redact: bool = True,
|
|
283
|
+
show_prefix_suffix: bool = False,
|
|
284
|
+
) -> BaseFormatter:
|
|
285
|
+
"""
|
|
286
|
+
Get a formatter instance.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
format_type: "text" or "json"
|
|
290
|
+
no_color: Disable ANSI colors
|
|
291
|
+
quiet: Minimal output
|
|
292
|
+
redact: Redact secrets
|
|
293
|
+
show_prefix_suffix: Show partial secrets
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Formatter instance
|
|
297
|
+
"""
|
|
298
|
+
if format_type == "json":
|
|
299
|
+
return JsonFormatter(
|
|
300
|
+
no_color=True, # JSON never has colors
|
|
301
|
+
quiet=quiet,
|
|
302
|
+
redact=redact,
|
|
303
|
+
show_prefix_suffix=show_prefix_suffix,
|
|
304
|
+
)
|
|
305
|
+
return TextFormatter(
|
|
306
|
+
no_color=no_color,
|
|
307
|
+
quiet=quiet,
|
|
308
|
+
redact=redact,
|
|
309
|
+
show_prefix_suffix=show_prefix_suffix,
|
|
310
|
+
)
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Doctor CLI handler for PraisonAI.
|
|
3
|
+
|
|
4
|
+
Provides the main DoctorHandler class that integrates with the CLI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from ..base import CommandHandler
|
|
12
|
+
from .models import CheckCategory, DoctorConfig, DoctorReport
|
|
13
|
+
from .engine import DoctorEngine
|
|
14
|
+
from .registry import get_registry
|
|
15
|
+
from .formatters import get_formatter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DoctorHandler(CommandHandler):
|
|
19
|
+
"""
|
|
20
|
+
Handler for the 'praisonai doctor' command.
|
|
21
|
+
|
|
22
|
+
Provides comprehensive health checks and diagnostics.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def feature_name(self) -> str:
|
|
27
|
+
return "doctor"
|
|
28
|
+
|
|
29
|
+
def get_actions(self) -> List[str]:
|
|
30
|
+
return [
|
|
31
|
+
"env",
|
|
32
|
+
"config",
|
|
33
|
+
"tools",
|
|
34
|
+
"db",
|
|
35
|
+
"mcp",
|
|
36
|
+
"obs",
|
|
37
|
+
"skills",
|
|
38
|
+
"memory",
|
|
39
|
+
"permissions",
|
|
40
|
+
"network",
|
|
41
|
+
"performance",
|
|
42
|
+
"ci",
|
|
43
|
+
"selftest",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
def _register_checks(self) -> None:
|
|
47
|
+
"""Register all doctor checks."""
|
|
48
|
+
from .checks import register_all_checks
|
|
49
|
+
register_all_checks()
|
|
50
|
+
|
|
51
|
+
def _parse_args(self, args: List[str]) -> argparse.Namespace:
|
|
52
|
+
"""Parse doctor command arguments."""
|
|
53
|
+
parser = argparse.ArgumentParser(
|
|
54
|
+
prog="praisonai doctor",
|
|
55
|
+
description="PraisonAI health checks and diagnostics",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Subcommand
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"subcommand",
|
|
61
|
+
nargs="?",
|
|
62
|
+
choices=self.get_actions() + [None],
|
|
63
|
+
help="Subcommand to run (default: run all fast checks)",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Global flags
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--json",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Output in JSON format",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--format",
|
|
74
|
+
choices=["text", "json"],
|
|
75
|
+
default="text",
|
|
76
|
+
help="Output format (default: text)",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"--output", "-o",
|
|
80
|
+
type=str,
|
|
81
|
+
help="Write report to file",
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--deep",
|
|
85
|
+
action="store_true",
|
|
86
|
+
help="Enable deeper probes (DB connects, network checks)",
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--timeout",
|
|
90
|
+
type=float,
|
|
91
|
+
default=10.0,
|
|
92
|
+
help="Per-check timeout in seconds (default: 10)",
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--strict",
|
|
96
|
+
action="store_true",
|
|
97
|
+
help="Treat warnings as failures",
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--quiet", "-q",
|
|
101
|
+
action="store_true",
|
|
102
|
+
help="Minimal output",
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"--no-color",
|
|
106
|
+
action="store_true",
|
|
107
|
+
help="Disable ANSI colors",
|
|
108
|
+
)
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
"--only",
|
|
111
|
+
type=str,
|
|
112
|
+
help="Only run these check IDs (comma-separated)",
|
|
113
|
+
)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--skip",
|
|
116
|
+
type=str,
|
|
117
|
+
help="Skip these check IDs (comma-separated)",
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--list-checks",
|
|
121
|
+
action="store_true",
|
|
122
|
+
help="List available check IDs",
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--version",
|
|
126
|
+
action="store_true",
|
|
127
|
+
help="Show doctor module version",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Subcommand-specific flags
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--show-keys",
|
|
133
|
+
action="store_true",
|
|
134
|
+
help="Show masked API key values (env subcommand)",
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--require",
|
|
138
|
+
type=str,
|
|
139
|
+
help="Require these env vars (comma-separated)",
|
|
140
|
+
)
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
"--file", "-f",
|
|
143
|
+
type=str,
|
|
144
|
+
help="Config file to validate (config subcommand)",
|
|
145
|
+
)
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--schema",
|
|
148
|
+
action="store_true",
|
|
149
|
+
help="Print expected schema (config subcommand)",
|
|
150
|
+
)
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--dsn",
|
|
153
|
+
type=str,
|
|
154
|
+
help="Database DSN (db subcommand)",
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"--provider",
|
|
158
|
+
type=str,
|
|
159
|
+
help="Provider name (db/obs subcommand)",
|
|
160
|
+
)
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"--read-only",
|
|
163
|
+
action="store_true",
|
|
164
|
+
default=True,
|
|
165
|
+
help="Read-only mode for DB checks (default: true)",
|
|
166
|
+
)
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"--name",
|
|
169
|
+
type=str,
|
|
170
|
+
help="Name filter (mcp/tools subcommand)",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--category",
|
|
174
|
+
type=str,
|
|
175
|
+
help="Category filter (tools subcommand)",
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"--all",
|
|
179
|
+
action="store_true",
|
|
180
|
+
dest="all_checks",
|
|
181
|
+
help="Show all items (tools subcommand)",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"--missing-only",
|
|
185
|
+
action="store_true",
|
|
186
|
+
help="Show only missing items (tools subcommand)",
|
|
187
|
+
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--list-tools",
|
|
190
|
+
action="store_true",
|
|
191
|
+
help="List MCP tools (mcp subcommand)",
|
|
192
|
+
)
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
"--path",
|
|
195
|
+
type=str,
|
|
196
|
+
help="Path to check (skills subcommand)",
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"--all-installed",
|
|
200
|
+
action="store_true",
|
|
201
|
+
help="Check all installed skills",
|
|
202
|
+
)
|
|
203
|
+
parser.add_argument(
|
|
204
|
+
"--budget-ms",
|
|
205
|
+
type=int,
|
|
206
|
+
help="Import time budget in ms (performance subcommand)",
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument(
|
|
209
|
+
"--top",
|
|
210
|
+
type=int,
|
|
211
|
+
default=10,
|
|
212
|
+
dest="top_n",
|
|
213
|
+
help="Number of top items to show (default: 10)",
|
|
214
|
+
)
|
|
215
|
+
parser.add_argument(
|
|
216
|
+
"--fail-fast",
|
|
217
|
+
action="store_true",
|
|
218
|
+
help="Stop on first failure (ci subcommand)",
|
|
219
|
+
)
|
|
220
|
+
parser.add_argument(
|
|
221
|
+
"--mock",
|
|
222
|
+
action="store_true",
|
|
223
|
+
default=True,
|
|
224
|
+
help="Use mock mode (selftest, default: true)",
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--live",
|
|
228
|
+
action="store_true",
|
|
229
|
+
help="Use live API calls (selftest)",
|
|
230
|
+
)
|
|
231
|
+
parser.add_argument(
|
|
232
|
+
"--model",
|
|
233
|
+
type=str,
|
|
234
|
+
help="Model to use for selftest",
|
|
235
|
+
)
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"--save-report",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help="Save selftest report",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return parser.parse_args(args)
|
|
243
|
+
|
|
244
|
+
def _build_config(self, args: argparse.Namespace) -> DoctorConfig:
|
|
245
|
+
"""Build DoctorConfig from parsed arguments."""
|
|
246
|
+
return DoctorConfig(
|
|
247
|
+
deep=args.deep,
|
|
248
|
+
timeout=args.timeout if args.deep else min(args.timeout, 10.0),
|
|
249
|
+
strict=args.strict,
|
|
250
|
+
quiet=args.quiet,
|
|
251
|
+
no_color=args.no_color,
|
|
252
|
+
format="json" if args.json else args.format,
|
|
253
|
+
output_path=args.output,
|
|
254
|
+
only=args.only.split(",") if args.only else [],
|
|
255
|
+
skip=args.skip.split(",") if args.skip else [],
|
|
256
|
+
show_keys=args.show_keys,
|
|
257
|
+
require_keys=args.require.split(",") if args.require else [],
|
|
258
|
+
config_file=args.file,
|
|
259
|
+
dsn=args.dsn,
|
|
260
|
+
provider=args.provider,
|
|
261
|
+
read_only=args.read_only,
|
|
262
|
+
mock=args.mock and not args.live,
|
|
263
|
+
live=args.live,
|
|
264
|
+
model=args.model,
|
|
265
|
+
budget_ms=args.budget_ms,
|
|
266
|
+
top_n=args.top_n,
|
|
267
|
+
fail_fast=args.fail_fast,
|
|
268
|
+
list_tools=args.list_tools,
|
|
269
|
+
all_checks=args.all_checks,
|
|
270
|
+
missing_only=args.missing_only,
|
|
271
|
+
name=args.name,
|
|
272
|
+
category=args.category,
|
|
273
|
+
path=args.path,
|
|
274
|
+
save_report=args.save_report,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _get_categories_for_subcommand(self, subcommand: Optional[str]) -> Optional[List[CheckCategory]]:
|
|
278
|
+
"""Get check categories for a subcommand."""
|
|
279
|
+
category_map = {
|
|
280
|
+
"env": [CheckCategory.ENVIRONMENT],
|
|
281
|
+
"config": [CheckCategory.CONFIG],
|
|
282
|
+
"tools": [CheckCategory.TOOLS],
|
|
283
|
+
"db": [CheckCategory.DATABASE],
|
|
284
|
+
"mcp": [CheckCategory.MCP],
|
|
285
|
+
"obs": [CheckCategory.OBSERVABILITY],
|
|
286
|
+
"skills": [CheckCategory.SKILLS],
|
|
287
|
+
"memory": [CheckCategory.MEMORY],
|
|
288
|
+
"permissions": [CheckCategory.PERMISSIONS],
|
|
289
|
+
"network": [CheckCategory.NETWORK],
|
|
290
|
+
"performance": [CheckCategory.PERFORMANCE],
|
|
291
|
+
"selftest": [CheckCategory.SELFTEST],
|
|
292
|
+
"ci": None, # CI runs all checks
|
|
293
|
+
}
|
|
294
|
+
return category_map.get(subcommand)
|
|
295
|
+
|
|
296
|
+
def execute(self, action: str, action_args: List[str], **kwargs) -> int:
|
|
297
|
+
"""
|
|
298
|
+
Execute the doctor command.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
action: The subcommand or empty string
|
|
302
|
+
action_args: Additional arguments
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Exit code (0=pass, 1=fail, 2=error)
|
|
306
|
+
"""
|
|
307
|
+
# Combine action and action_args for parsing
|
|
308
|
+
all_args = [action] + action_args if action and action != "help" else action_args
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
args = self._parse_args(all_args)
|
|
312
|
+
except SystemExit:
|
|
313
|
+
return 0 # Help was shown
|
|
314
|
+
|
|
315
|
+
# Handle special flags
|
|
316
|
+
if args.version:
|
|
317
|
+
from . import __version__
|
|
318
|
+
print(f"PraisonAI Doctor v{__version__}")
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
# Register all checks
|
|
322
|
+
self._register_checks()
|
|
323
|
+
|
|
324
|
+
if args.list_checks:
|
|
325
|
+
registry = get_registry()
|
|
326
|
+
print(registry.list_checks_text())
|
|
327
|
+
return 0
|
|
328
|
+
|
|
329
|
+
# Build config
|
|
330
|
+
config = self._build_config(args)
|
|
331
|
+
|
|
332
|
+
# Handle CI mode
|
|
333
|
+
if args.subcommand == "ci":
|
|
334
|
+
config.format = "json"
|
|
335
|
+
config.no_color = True
|
|
336
|
+
config.quiet = True
|
|
337
|
+
|
|
338
|
+
# Get categories for subcommand
|
|
339
|
+
categories = self._get_categories_for_subcommand(args.subcommand)
|
|
340
|
+
|
|
341
|
+
# Run doctor
|
|
342
|
+
engine = DoctorEngine(config)
|
|
343
|
+
report = engine.run(categories=categories)
|
|
344
|
+
|
|
345
|
+
# Format output
|
|
346
|
+
formatter = get_formatter(
|
|
347
|
+
format_type=config.format,
|
|
348
|
+
no_color=config.no_color,
|
|
349
|
+
quiet=config.quiet,
|
|
350
|
+
show_prefix_suffix=config.show_keys,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Write output
|
|
354
|
+
if config.output_path:
|
|
355
|
+
with open(config.output_path, "w") as f:
|
|
356
|
+
formatter.write(report, f)
|
|
357
|
+
if not config.quiet:
|
|
358
|
+
print(f"Report written to: {config.output_path}")
|
|
359
|
+
else:
|
|
360
|
+
formatter.write(report, sys.stdout)
|
|
361
|
+
|
|
362
|
+
return report.exit_code
|
|
363
|
+
|
|
364
|
+
def run(self, args: List[str]) -> int:
|
|
365
|
+
"""
|
|
366
|
+
Run the doctor command from CLI.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
args: Command line arguments after 'doctor'
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Exit code
|
|
373
|
+
"""
|
|
374
|
+
if not args:
|
|
375
|
+
return self.execute("", [])
|
|
376
|
+
|
|
377
|
+
action = args[0] if args else ""
|
|
378
|
+
action_args = args[1:] if len(args) > 1 else []
|
|
379
|
+
|
|
380
|
+
return self.execute(action, action_args)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def run_doctor(args: Optional[List[str]] = None) -> int:
|
|
384
|
+
"""
|
|
385
|
+
Convenience function to run doctor from CLI.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
args: Command line arguments (default: sys.argv[1:])
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Exit code
|
|
392
|
+
"""
|
|
393
|
+
if args is None:
|
|
394
|
+
args = sys.argv[1:]
|
|
395
|
+
|
|
396
|
+
handler = DoctorHandler()
|
|
397
|
+
return handler.run(args)
|