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,631 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Package CLI Feature Handler
|
|
3
|
+
|
|
4
|
+
Provides pip-like CLI commands for package management:
|
|
5
|
+
- install: Install Python packages
|
|
6
|
+
- uninstall: Uninstall Python packages
|
|
7
|
+
- list: List installed packages
|
|
8
|
+
- search: Search packages
|
|
9
|
+
- index: Manage package indexes
|
|
10
|
+
|
|
11
|
+
All commands use the canonical `praisonai` or `praisonai package` prefix.
|
|
12
|
+
|
|
13
|
+
Security:
|
|
14
|
+
- Default to single authoritative index (PyPI)
|
|
15
|
+
- Warn loudly about dependency confusion when using extra indexes
|
|
16
|
+
- Require explicit opt-in for extra index fallback
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, List, Tuple
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Default configuration
|
|
28
|
+
DEFAULT_INDEX_URL = "https://pypi.org/simple"
|
|
29
|
+
CONFIG_DIR = Path.home() / ".praison"
|
|
30
|
+
CONFIG_FILE = CONFIG_DIR / "config.toml"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PackageConfig:
|
|
34
|
+
"""Package configuration manager."""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""Initialize config."""
|
|
38
|
+
self._config = None
|
|
39
|
+
|
|
40
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
41
|
+
"""Load configuration from file."""
|
|
42
|
+
if self._config is not None:
|
|
43
|
+
return self._config
|
|
44
|
+
|
|
45
|
+
self._config = {
|
|
46
|
+
"package": {
|
|
47
|
+
"index_url": DEFAULT_INDEX_URL,
|
|
48
|
+
"extra_index_urls": [],
|
|
49
|
+
"allow_extra_index": False,
|
|
50
|
+
"trusted_hosts": [],
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if CONFIG_FILE.exists():
|
|
55
|
+
try:
|
|
56
|
+
import tomllib
|
|
57
|
+
except ImportError:
|
|
58
|
+
try:
|
|
59
|
+
import tomli as tomllib
|
|
60
|
+
except ImportError:
|
|
61
|
+
return self._config
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
with open(CONFIG_FILE, "rb") as f:
|
|
65
|
+
file_config = tomllib.load(f)
|
|
66
|
+
if "package" in file_config:
|
|
67
|
+
self._config["package"].update(file_config["package"])
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# Environment variable overrides
|
|
72
|
+
if os.environ.get("PRAISONAI_PACKAGE_INDEX_URL"):
|
|
73
|
+
self._config["package"]["index_url"] = os.environ["PRAISONAI_PACKAGE_INDEX_URL"]
|
|
74
|
+
|
|
75
|
+
if os.environ.get("PRAISONAI_PACKAGE_EXTRA_INDEX_URLS"):
|
|
76
|
+
urls = os.environ["PRAISONAI_PACKAGE_EXTRA_INDEX_URLS"].split(",")
|
|
77
|
+
self._config["package"]["extra_index_urls"] = [u.strip() for u in urls if u.strip()]
|
|
78
|
+
|
|
79
|
+
return self._config
|
|
80
|
+
|
|
81
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
82
|
+
"""Get config value."""
|
|
83
|
+
config = self._load_config()
|
|
84
|
+
parts = key.split(".")
|
|
85
|
+
value = config
|
|
86
|
+
for part in parts:
|
|
87
|
+
if isinstance(value, dict) and part in value:
|
|
88
|
+
value = value[part]
|
|
89
|
+
else:
|
|
90
|
+
return default
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
def save_index(self, index_url: str) -> None:
|
|
94
|
+
"""Save index URL to config."""
|
|
95
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
config = {}
|
|
98
|
+
if CONFIG_FILE.exists():
|
|
99
|
+
try:
|
|
100
|
+
import tomllib
|
|
101
|
+
except ImportError:
|
|
102
|
+
try:
|
|
103
|
+
import tomli as tomllib
|
|
104
|
+
except ImportError:
|
|
105
|
+
# Fall back to simple write
|
|
106
|
+
with open(CONFIG_FILE, "w") as f:
|
|
107
|
+
f.write(f'[package]\nindex_url = "{index_url}"\n')
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with open(CONFIG_FILE, "rb") as f:
|
|
112
|
+
config = tomllib.load(f)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
if "package" not in config:
|
|
117
|
+
config["package"] = {}
|
|
118
|
+
config["package"]["index_url"] = index_url
|
|
119
|
+
|
|
120
|
+
# Write config (simple TOML format)
|
|
121
|
+
with open(CONFIG_FILE, "w") as f:
|
|
122
|
+
for section, values in config.items():
|
|
123
|
+
f.write(f"[{section}]\n")
|
|
124
|
+
for key, value in values.items():
|
|
125
|
+
if isinstance(value, str):
|
|
126
|
+
f.write(f'{key} = "{value}"\n')
|
|
127
|
+
elif isinstance(value, bool):
|
|
128
|
+
f.write(f'{key} = {"true" if value else "false"}\n')
|
|
129
|
+
elif isinstance(value, list):
|
|
130
|
+
f.write(f'{key} = {json.dumps(value)}\n')
|
|
131
|
+
else:
|
|
132
|
+
f.write(f'{key} = {value}\n')
|
|
133
|
+
f.write("\n")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class PackageHandler:
|
|
137
|
+
"""
|
|
138
|
+
CLI handler for package operations.
|
|
139
|
+
|
|
140
|
+
Commands:
|
|
141
|
+
- install: Install Python packages
|
|
142
|
+
- uninstall: Uninstall Python packages
|
|
143
|
+
- list: List installed packages
|
|
144
|
+
- search: Search packages (best-effort)
|
|
145
|
+
- index: Manage package indexes
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
# Stable exit codes
|
|
149
|
+
EXIT_SUCCESS = 0
|
|
150
|
+
EXIT_GENERAL_ERROR = 1
|
|
151
|
+
EXIT_VALIDATION_ERROR = 2
|
|
152
|
+
EXIT_NETWORK_ERROR = 10
|
|
153
|
+
EXIT_DEPENDENCY_ERROR = 11
|
|
154
|
+
|
|
155
|
+
def __init__(self):
|
|
156
|
+
"""Initialize the handler."""
|
|
157
|
+
self.config = PackageConfig()
|
|
158
|
+
|
|
159
|
+
def handle(self, args: List[str]) -> int:
|
|
160
|
+
"""
|
|
161
|
+
Handle package subcommand.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
args: Command arguments
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Exit code
|
|
168
|
+
"""
|
|
169
|
+
if not args:
|
|
170
|
+
self._print_help()
|
|
171
|
+
return self.EXIT_SUCCESS
|
|
172
|
+
|
|
173
|
+
command = args[0]
|
|
174
|
+
remaining = args[1:]
|
|
175
|
+
|
|
176
|
+
commands = {
|
|
177
|
+
"list": self.cmd_list,
|
|
178
|
+
"search": self.cmd_search,
|
|
179
|
+
"index": self.cmd_index,
|
|
180
|
+
"help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
181
|
+
"--help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
182
|
+
"-h": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
handler = commands.get(command)
|
|
186
|
+
if handler:
|
|
187
|
+
return handler(remaining)
|
|
188
|
+
|
|
189
|
+
self._print_error(f"Unknown command: {command}")
|
|
190
|
+
self._print_help()
|
|
191
|
+
return self.EXIT_VALIDATION_ERROR
|
|
192
|
+
|
|
193
|
+
def _print_help(self):
|
|
194
|
+
"""Print help message."""
|
|
195
|
+
print("""
|
|
196
|
+
PraisonAI Package Commands
|
|
197
|
+
|
|
198
|
+
Usage: praisonai package <command> [options]
|
|
199
|
+
praisonai install <spec...>
|
|
200
|
+
praisonai uninstall <pkg...>
|
|
201
|
+
|
|
202
|
+
Commands:
|
|
203
|
+
install Install Python packages (shortcut: praisonai install)
|
|
204
|
+
uninstall Uninstall Python packages (shortcut: praisonai uninstall)
|
|
205
|
+
list List installed packages
|
|
206
|
+
search Search for packages
|
|
207
|
+
index Manage package indexes
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
praisonai install requests
|
|
211
|
+
praisonai install "requests>=2.28" httpx
|
|
212
|
+
praisonai uninstall requests
|
|
213
|
+
praisonai package list
|
|
214
|
+
praisonai package search langchain
|
|
215
|
+
praisonai package index show
|
|
216
|
+
praisonai package index set https://my-index.example.com/simple
|
|
217
|
+
|
|
218
|
+
Options for 'install':
|
|
219
|
+
--index-url URL Use custom index URL
|
|
220
|
+
--extra-index-url URL Add extra index (requires --allow-extra-index)
|
|
221
|
+
--allow-extra-index Allow extra index URLs (security risk!)
|
|
222
|
+
--python PATH Python interpreter to use
|
|
223
|
+
--upgrade, -U Upgrade packages
|
|
224
|
+
--no-deps Don't install dependencies
|
|
225
|
+
--json Output in JSON format
|
|
226
|
+
|
|
227
|
+
Options for 'uninstall':
|
|
228
|
+
--python PATH Python interpreter to use
|
|
229
|
+
--yes, -y Don't ask for confirmation
|
|
230
|
+
--json Output in JSON format
|
|
231
|
+
|
|
232
|
+
Security Notes:
|
|
233
|
+
- By default, only the primary index is used (PyPI)
|
|
234
|
+
- Using --extra-index-url can lead to dependency confusion attacks
|
|
235
|
+
- Always prefer --index-url over --extra-index-url when possible
|
|
236
|
+
""")
|
|
237
|
+
|
|
238
|
+
def _print_error(self, message: str):
|
|
239
|
+
"""Print error message."""
|
|
240
|
+
print(f"Error: {message}", file=sys.stderr)
|
|
241
|
+
|
|
242
|
+
def _print_warning(self, message: str):
|
|
243
|
+
"""Print warning message."""
|
|
244
|
+
print(f"Warning: {message}", file=sys.stderr)
|
|
245
|
+
|
|
246
|
+
def _print_success(self, message: str):
|
|
247
|
+
"""Print success message."""
|
|
248
|
+
print(f"✓ {message}")
|
|
249
|
+
|
|
250
|
+
def _print_json(self, data: Any):
|
|
251
|
+
"""Print JSON output."""
|
|
252
|
+
print(json.dumps(data, indent=2))
|
|
253
|
+
|
|
254
|
+
def _parse_args(self, args: List[str], spec: Dict[str, Any]) -> Tuple[Dict[str, Any], List[str]]:
|
|
255
|
+
"""Parse command arguments, returning parsed args and positional args."""
|
|
256
|
+
result = {k: v.get("default") for k, v in spec.items()}
|
|
257
|
+
positional = []
|
|
258
|
+
|
|
259
|
+
i = 0
|
|
260
|
+
while i < len(args):
|
|
261
|
+
arg = args[i]
|
|
262
|
+
|
|
263
|
+
if arg.startswith("--"):
|
|
264
|
+
key = arg[2:].replace("-", "_")
|
|
265
|
+
if key in spec:
|
|
266
|
+
if spec[key].get("flag"):
|
|
267
|
+
result[key] = True
|
|
268
|
+
elif i + 1 < len(args):
|
|
269
|
+
result[key] = args[i + 1]
|
|
270
|
+
i += 1
|
|
271
|
+
else:
|
|
272
|
+
positional.append(arg)
|
|
273
|
+
elif arg.startswith("-") and len(arg) == 2:
|
|
274
|
+
# Short flag
|
|
275
|
+
found = False
|
|
276
|
+
for key, val in spec.items():
|
|
277
|
+
if val.get("short") == arg:
|
|
278
|
+
if val.get("flag"):
|
|
279
|
+
result[key] = True
|
|
280
|
+
elif i + 1 < len(args):
|
|
281
|
+
result[key] = args[i + 1]
|
|
282
|
+
i += 1
|
|
283
|
+
found = True
|
|
284
|
+
break
|
|
285
|
+
if not found:
|
|
286
|
+
positional.append(arg)
|
|
287
|
+
else:
|
|
288
|
+
positional.append(arg)
|
|
289
|
+
i += 1
|
|
290
|
+
|
|
291
|
+
return result, positional
|
|
292
|
+
|
|
293
|
+
def _get_python(self, python_path: str = None) -> str:
|
|
294
|
+
"""Get Python interpreter path."""
|
|
295
|
+
if python_path:
|
|
296
|
+
return python_path
|
|
297
|
+
return sys.executable
|
|
298
|
+
|
|
299
|
+
def _run_pip(
|
|
300
|
+
self,
|
|
301
|
+
args: List[str],
|
|
302
|
+
python: str = None,
|
|
303
|
+
capture: bool = False,
|
|
304
|
+
) -> Tuple[int, str, str]:
|
|
305
|
+
"""
|
|
306
|
+
Run pip command.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
args: pip arguments
|
|
310
|
+
python: Python interpreter path
|
|
311
|
+
capture: Capture output instead of streaming
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Tuple of (return_code, stdout, stderr)
|
|
315
|
+
"""
|
|
316
|
+
python = self._get_python(python)
|
|
317
|
+
cmd = [python, "-m", "pip"] + args
|
|
318
|
+
|
|
319
|
+
if capture:
|
|
320
|
+
result = subprocess.run(
|
|
321
|
+
cmd,
|
|
322
|
+
capture_output=True,
|
|
323
|
+
text=True,
|
|
324
|
+
env={**os.environ, "PAGER": "cat"},
|
|
325
|
+
)
|
|
326
|
+
return result.returncode, result.stdout, result.stderr
|
|
327
|
+
else:
|
|
328
|
+
result = subprocess.run(
|
|
329
|
+
cmd,
|
|
330
|
+
env={**os.environ, "PAGER": "cat"},
|
|
331
|
+
)
|
|
332
|
+
return result.returncode, "", ""
|
|
333
|
+
|
|
334
|
+
def cmd_install(self, args: List[str]) -> int:
|
|
335
|
+
"""Install Python packages."""
|
|
336
|
+
spec = {
|
|
337
|
+
"index_url": {"default": None},
|
|
338
|
+
"extra_index_url": {"default": None},
|
|
339
|
+
"allow_extra_index": {"flag": True, "default": False},
|
|
340
|
+
"python": {"default": None},
|
|
341
|
+
"upgrade": {"flag": True, "short": "-U", "default": False},
|
|
342
|
+
"no_deps": {"flag": True, "default": False},
|
|
343
|
+
"json": {"flag": True, "default": False},
|
|
344
|
+
}
|
|
345
|
+
parsed, packages = self._parse_args(args, spec)
|
|
346
|
+
|
|
347
|
+
if not packages:
|
|
348
|
+
self._print_error("No packages specified")
|
|
349
|
+
return self.EXIT_VALIDATION_ERROR
|
|
350
|
+
|
|
351
|
+
# Build pip command
|
|
352
|
+
pip_args = ["install"]
|
|
353
|
+
|
|
354
|
+
# Index URL
|
|
355
|
+
index_url = parsed["index_url"] or self.config.get("package.index_url", DEFAULT_INDEX_URL)
|
|
356
|
+
pip_args.extend(["--index-url", index_url])
|
|
357
|
+
|
|
358
|
+
# Extra index URL (with security warning)
|
|
359
|
+
extra_index = parsed["extra_index_url"]
|
|
360
|
+
if extra_index:
|
|
361
|
+
allow_extra = parsed["allow_extra_index"] or self.config.get("package.allow_extra_index", False)
|
|
362
|
+
if not allow_extra:
|
|
363
|
+
self._print_error(
|
|
364
|
+
"Extra index URLs are disabled by default for security.\n"
|
|
365
|
+
"Using multiple indexes can lead to dependency confusion attacks.\n"
|
|
366
|
+
"To enable, use --allow-extra-index flag or set package.allow_extra_index=true in config."
|
|
367
|
+
)
|
|
368
|
+
return self.EXIT_VALIDATION_ERROR
|
|
369
|
+
|
|
370
|
+
self._print_warning(
|
|
371
|
+
"⚠️ SECURITY WARNING: Using extra index URLs can lead to dependency confusion attacks!\n"
|
|
372
|
+
" An attacker could publish a malicious package with the same name on PyPI.\n"
|
|
373
|
+
" Only use this if you trust both indexes and understand the risks."
|
|
374
|
+
)
|
|
375
|
+
pip_args.extend(["--extra-index-url", extra_index])
|
|
376
|
+
|
|
377
|
+
# Other options
|
|
378
|
+
if parsed["upgrade"]:
|
|
379
|
+
pip_args.append("--upgrade")
|
|
380
|
+
if parsed["no_deps"]:
|
|
381
|
+
pip_args.append("--no-deps")
|
|
382
|
+
|
|
383
|
+
# Add packages
|
|
384
|
+
pip_args.extend(packages)
|
|
385
|
+
|
|
386
|
+
if parsed["json"]:
|
|
387
|
+
returncode, stdout, stderr = self._run_pip(pip_args, parsed["python"], capture=True)
|
|
388
|
+
self._print_json({
|
|
389
|
+
"ok": returncode == 0,
|
|
390
|
+
"packages": packages,
|
|
391
|
+
"returncode": returncode,
|
|
392
|
+
"stdout": stdout,
|
|
393
|
+
"stderr": stderr,
|
|
394
|
+
})
|
|
395
|
+
return returncode
|
|
396
|
+
else:
|
|
397
|
+
print(f"Installing: {', '.join(packages)}")
|
|
398
|
+
returncode, _, _ = self._run_pip(pip_args, parsed["python"])
|
|
399
|
+
if returncode == 0:
|
|
400
|
+
self._print_success("Installation complete")
|
|
401
|
+
return returncode
|
|
402
|
+
|
|
403
|
+
def cmd_uninstall(self, args: List[str]) -> int:
|
|
404
|
+
"""Uninstall Python packages."""
|
|
405
|
+
spec = {
|
|
406
|
+
"python": {"default": None},
|
|
407
|
+
"yes": {"flag": True, "short": "-y", "default": False},
|
|
408
|
+
"json": {"flag": True, "default": False},
|
|
409
|
+
}
|
|
410
|
+
parsed, packages = self._parse_args(args, spec)
|
|
411
|
+
|
|
412
|
+
if not packages:
|
|
413
|
+
self._print_error("No packages specified")
|
|
414
|
+
return self.EXIT_VALIDATION_ERROR
|
|
415
|
+
|
|
416
|
+
pip_args = ["uninstall"]
|
|
417
|
+
if parsed["yes"]:
|
|
418
|
+
pip_args.append("-y")
|
|
419
|
+
pip_args.extend(packages)
|
|
420
|
+
|
|
421
|
+
if parsed["json"]:
|
|
422
|
+
returncode, stdout, stderr = self._run_pip(pip_args, parsed["python"], capture=True)
|
|
423
|
+
self._print_json({
|
|
424
|
+
"ok": returncode == 0,
|
|
425
|
+
"packages": packages,
|
|
426
|
+
"returncode": returncode,
|
|
427
|
+
"stdout": stdout,
|
|
428
|
+
"stderr": stderr,
|
|
429
|
+
})
|
|
430
|
+
return returncode
|
|
431
|
+
else:
|
|
432
|
+
print(f"Uninstalling: {', '.join(packages)}")
|
|
433
|
+
returncode, _, _ = self._run_pip(pip_args, parsed["python"])
|
|
434
|
+
if returncode == 0:
|
|
435
|
+
self._print_success("Uninstall complete")
|
|
436
|
+
return returncode
|
|
437
|
+
|
|
438
|
+
def cmd_list(self, args: List[str]) -> int:
|
|
439
|
+
"""List installed packages."""
|
|
440
|
+
spec = {
|
|
441
|
+
"python": {"default": None},
|
|
442
|
+
"json": {"flag": True, "default": False},
|
|
443
|
+
"outdated": {"flag": True, "default": False},
|
|
444
|
+
}
|
|
445
|
+
parsed, _ = self._parse_args(args, spec)
|
|
446
|
+
|
|
447
|
+
pip_args = ["list", "--format=json"]
|
|
448
|
+
if parsed["outdated"]:
|
|
449
|
+
pip_args.append("--outdated")
|
|
450
|
+
|
|
451
|
+
returncode, stdout, stderr = self._run_pip(pip_args, parsed["python"], capture=True)
|
|
452
|
+
|
|
453
|
+
if returncode != 0:
|
|
454
|
+
self._print_error(stderr or "Failed to list packages")
|
|
455
|
+
return returncode
|
|
456
|
+
|
|
457
|
+
try:
|
|
458
|
+
packages = json.loads(stdout)
|
|
459
|
+
except json.JSONDecodeError:
|
|
460
|
+
packages = []
|
|
461
|
+
|
|
462
|
+
if parsed["json"]:
|
|
463
|
+
self._print_json({
|
|
464
|
+
"ok": True,
|
|
465
|
+
"packages": packages,
|
|
466
|
+
"count": len(packages),
|
|
467
|
+
})
|
|
468
|
+
else:
|
|
469
|
+
if not packages:
|
|
470
|
+
print("No packages installed.")
|
|
471
|
+
else:
|
|
472
|
+
print(f"{'Package':<30} {'Version':<15}")
|
|
473
|
+
print("-" * 45)
|
|
474
|
+
for pkg in packages:
|
|
475
|
+
name = pkg.get("name", "")
|
|
476
|
+
version = pkg.get("version", "")
|
|
477
|
+
print(f"{name:<30} {version:<15}")
|
|
478
|
+
print(f"\nTotal: {len(packages)} packages")
|
|
479
|
+
|
|
480
|
+
return self.EXIT_SUCCESS
|
|
481
|
+
|
|
482
|
+
def cmd_search(self, args: List[str]) -> int:
|
|
483
|
+
"""Search for packages (best-effort, uses pip search or PyPI API)."""
|
|
484
|
+
spec = {
|
|
485
|
+
"json": {"flag": True, "default": False},
|
|
486
|
+
}
|
|
487
|
+
parsed, query_parts = self._parse_args(args, spec)
|
|
488
|
+
|
|
489
|
+
if not query_parts:
|
|
490
|
+
self._print_error("Search query required")
|
|
491
|
+
return self.EXIT_VALIDATION_ERROR
|
|
492
|
+
|
|
493
|
+
query = " ".join(query_parts)
|
|
494
|
+
|
|
495
|
+
# pip search is deprecated, use PyPI JSON API
|
|
496
|
+
try:
|
|
497
|
+
import urllib.request
|
|
498
|
+
import urllib.error
|
|
499
|
+
|
|
500
|
+
url = f"https://pypi.org/pypi/{query}/json"
|
|
501
|
+
try:
|
|
502
|
+
with urllib.request.urlopen(url, timeout=10) as response:
|
|
503
|
+
data = json.loads(response.read().decode())
|
|
504
|
+
|
|
505
|
+
info = data.get("info", {})
|
|
506
|
+
result = {
|
|
507
|
+
"name": info.get("name"),
|
|
508
|
+
"version": info.get("version"),
|
|
509
|
+
"summary": info.get("summary"),
|
|
510
|
+
"author": info.get("author"),
|
|
511
|
+
"home_page": info.get("home_page"),
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if parsed["json"]:
|
|
515
|
+
self._print_json({"ok": True, "results": [result]})
|
|
516
|
+
else:
|
|
517
|
+
print(f"Found: {result['name']} ({result['version']})")
|
|
518
|
+
print(f" {result['summary']}")
|
|
519
|
+
if result['home_page']:
|
|
520
|
+
print(f" Homepage: {result['home_page']}")
|
|
521
|
+
|
|
522
|
+
return self.EXIT_SUCCESS
|
|
523
|
+
|
|
524
|
+
except urllib.error.HTTPError as e:
|
|
525
|
+
if e.code == 404:
|
|
526
|
+
if parsed["json"]:
|
|
527
|
+
self._print_json({"ok": True, "results": [], "message": "No exact match found"})
|
|
528
|
+
else:
|
|
529
|
+
print(f"No exact match for '{query}'")
|
|
530
|
+
print("Try: pip search <query> (if available) or browse https://pypi.org")
|
|
531
|
+
return self.EXIT_SUCCESS
|
|
532
|
+
raise
|
|
533
|
+
|
|
534
|
+
except Exception as e:
|
|
535
|
+
if parsed["json"]:
|
|
536
|
+
self._print_json({"ok": False, "error": str(e)})
|
|
537
|
+
else:
|
|
538
|
+
self._print_error(f"Search failed: {e}")
|
|
539
|
+
return self.EXIT_NETWORK_ERROR
|
|
540
|
+
|
|
541
|
+
def cmd_index(self, args: List[str]) -> int:
|
|
542
|
+
"""Manage package indexes."""
|
|
543
|
+
if not args:
|
|
544
|
+
return self._index_show([])
|
|
545
|
+
|
|
546
|
+
subcommand = args[0]
|
|
547
|
+
remaining = args[1:]
|
|
548
|
+
|
|
549
|
+
subcommands = {
|
|
550
|
+
"show": self._index_show,
|
|
551
|
+
"set": self._index_set,
|
|
552
|
+
"add": self._index_add,
|
|
553
|
+
"remove": self._index_remove,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
handler = subcommands.get(subcommand)
|
|
557
|
+
if handler:
|
|
558
|
+
return handler(remaining)
|
|
559
|
+
|
|
560
|
+
self._print_error(f"Unknown index subcommand: {subcommand}")
|
|
561
|
+
return self.EXIT_VALIDATION_ERROR
|
|
562
|
+
|
|
563
|
+
def _index_show(self, args: List[str]) -> int:
|
|
564
|
+
"""Show current index configuration."""
|
|
565
|
+
spec = {
|
|
566
|
+
"json": {"flag": True, "default": False},
|
|
567
|
+
}
|
|
568
|
+
parsed, _ = self._parse_args(args, spec)
|
|
569
|
+
|
|
570
|
+
index_url = self.config.get("package.index_url", DEFAULT_INDEX_URL)
|
|
571
|
+
extra_urls = self.config.get("package.extra_index_urls", [])
|
|
572
|
+
allow_extra = self.config.get("package.allow_extra_index", False)
|
|
573
|
+
|
|
574
|
+
if parsed["json"]:
|
|
575
|
+
self._print_json({
|
|
576
|
+
"ok": True,
|
|
577
|
+
"index_url": index_url,
|
|
578
|
+
"extra_index_urls": extra_urls,
|
|
579
|
+
"allow_extra_index": allow_extra,
|
|
580
|
+
})
|
|
581
|
+
else:
|
|
582
|
+
print(f"Primary Index: {index_url}")
|
|
583
|
+
if extra_urls:
|
|
584
|
+
print(f"Extra Indexes: {', '.join(extra_urls)}")
|
|
585
|
+
print(f"Allow Extra Index: {'yes' if allow_extra else 'no'}")
|
|
586
|
+
|
|
587
|
+
return self.EXIT_SUCCESS
|
|
588
|
+
|
|
589
|
+
def _index_set(self, args: List[str]) -> int:
|
|
590
|
+
"""Set primary index URL."""
|
|
591
|
+
if not args:
|
|
592
|
+
self._print_error("Index URL required")
|
|
593
|
+
return self.EXIT_VALIDATION_ERROR
|
|
594
|
+
|
|
595
|
+
url = args[0]
|
|
596
|
+
|
|
597
|
+
# Validate URL format
|
|
598
|
+
if not url.startswith(("http://", "https://")):
|
|
599
|
+
self._print_error("Index URL must start with http:// or https://")
|
|
600
|
+
return self.EXIT_VALIDATION_ERROR
|
|
601
|
+
|
|
602
|
+
self.config.save_index(url)
|
|
603
|
+
self._print_success(f"Primary index set to: {url}")
|
|
604
|
+
|
|
605
|
+
return self.EXIT_SUCCESS
|
|
606
|
+
|
|
607
|
+
def _index_add(self, args: List[str]) -> int:
|
|
608
|
+
"""Add extra index URL."""
|
|
609
|
+
self._print_warning(
|
|
610
|
+
"Adding extra index URLs is a security risk!\n"
|
|
611
|
+
"This can lead to dependency confusion attacks.\n"
|
|
612
|
+
"Consider using --index-url to switch indexes instead."
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if not args:
|
|
616
|
+
self._print_error("Index URL required")
|
|
617
|
+
return self.EXIT_VALIDATION_ERROR
|
|
618
|
+
|
|
619
|
+
# For now, just print instructions
|
|
620
|
+
print("\nTo use extra index, run:")
|
|
621
|
+
print(f" praisonai install <pkg> --extra-index-url {args[0]} --allow-extra-index")
|
|
622
|
+
|
|
623
|
+
return self.EXIT_SUCCESS
|
|
624
|
+
|
|
625
|
+
def _index_remove(self, args: List[str]) -> int:
|
|
626
|
+
"""Remove extra index URL."""
|
|
627
|
+
print("Extra index URLs are not persisted by default.")
|
|
628
|
+
print("To reset to PyPI, run:")
|
|
629
|
+
print(f" praisonai package index set {DEFAULT_INDEX_URL}")
|
|
630
|
+
|
|
631
|
+
return self.EXIT_SUCCESS
|