praisonai-code 0.0.1__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_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Profile command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides detailed cProfile-based profiling for query execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Performance profiling and diagnostics")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("query")
|
|
16
|
+
def profile_query(
|
|
17
|
+
prompt: str = typer.Argument(..., help="Prompt to profile"),
|
|
18
|
+
model: Optional[str] = typer.Option(None, "--model", "-m", help="Model to use"),
|
|
19
|
+
stream: bool = typer.Option(False, "--stream/--no-stream", help="Use streaming mode"),
|
|
20
|
+
deep: bool = typer.Option(False, "--deep", help="Enable deep call tracing (higher overhead)"),
|
|
21
|
+
limit: int = typer.Option(30, "--limit", "-n", help="Top N functions to show"),
|
|
22
|
+
sort: str = typer.Option("cumulative", "--sort", "-s", help="Sort by: cumulative or tottime"),
|
|
23
|
+
show_callers: bool = typer.Option(False, "--show-callers", help="Show caller functions"),
|
|
24
|
+
show_callees: bool = typer.Option(False, "--show-callees", help="Show callee functions"),
|
|
25
|
+
save: Optional[str] = typer.Option(None, "--save", help="Save artifacts to path (creates .prof, .txt, .json)"),
|
|
26
|
+
output_format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Profile a query execution with detailed timing breakdown.
|
|
30
|
+
|
|
31
|
+
Uses the unified profiling architecture for consistent results across
|
|
32
|
+
CLI direct invocation (--profile) and this command.
|
|
33
|
+
|
|
34
|
+
Shows per-function timing, call graphs, and latency metrics.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
praisonai profile query "What is 2+2?"
|
|
38
|
+
praisonai profile query "Hello" --limit 20
|
|
39
|
+
praisonai profile query "Test" --deep --show-callers --show-callees
|
|
40
|
+
praisonai profile query "Test" --save ./profile_results
|
|
41
|
+
"""
|
|
42
|
+
# Use unified profiler for consistent results
|
|
43
|
+
try:
|
|
44
|
+
from praisonai.cli.execution import ExecutionRequest, Profiler, ProfilerConfig as UnifiedProfilerConfig
|
|
45
|
+
except ImportError as e:
|
|
46
|
+
typer.echo(f"Error: Unified profiler not available: {e}", err=True)
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
|
|
49
|
+
# Warn about deep tracing overhead
|
|
50
|
+
if deep:
|
|
51
|
+
typer.echo("⚠️ Deep call tracing enabled - this adds significant overhead", err=True)
|
|
52
|
+
|
|
53
|
+
# Create unified execution request
|
|
54
|
+
request = ExecutionRequest(
|
|
55
|
+
prompt=prompt,
|
|
56
|
+
agent_name="ProfiledAgent",
|
|
57
|
+
model=model,
|
|
58
|
+
stream=stream,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Create unified profiler config
|
|
62
|
+
config = UnifiedProfilerConfig(
|
|
63
|
+
layer=2 if deep else 1,
|
|
64
|
+
limit=limit,
|
|
65
|
+
sort_by=sort,
|
|
66
|
+
show_callers=show_callers,
|
|
67
|
+
show_callees=show_callees,
|
|
68
|
+
output_format=output_format,
|
|
69
|
+
save_path=save,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Run profiler
|
|
73
|
+
typer.echo("🔬 Starting profiled execution...", err=True)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
profiler = Profiler(config)
|
|
77
|
+
result, report = profiler.profile_sync(request, invocation_method="profile_command")
|
|
78
|
+
except ImportError as e:
|
|
79
|
+
typer.echo(f"Error: {e}", err=True)
|
|
80
|
+
typer.echo("Install praisonaiagents: pip install praisonaiagents", err=True)
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
typer.echo(f"Error during profiling: {e}", err=True)
|
|
84
|
+
raise typer.Exit(1)
|
|
85
|
+
|
|
86
|
+
# Output results
|
|
87
|
+
if output_format == "json":
|
|
88
|
+
typer.echo(report.to_json())
|
|
89
|
+
else:
|
|
90
|
+
typer.echo(report.to_text())
|
|
91
|
+
|
|
92
|
+
# Save artifacts if requested
|
|
93
|
+
if save:
|
|
94
|
+
try:
|
|
95
|
+
import os
|
|
96
|
+
os.makedirs(save, exist_ok=True)
|
|
97
|
+
|
|
98
|
+
# Save JSON report
|
|
99
|
+
json_path = os.path.join(save, "profile_report.json")
|
|
100
|
+
with open(json_path, 'w') as f:
|
|
101
|
+
f.write(report.to_json())
|
|
102
|
+
|
|
103
|
+
# Save text report
|
|
104
|
+
txt_path = os.path.join(save, "profile_report.txt")
|
|
105
|
+
with open(txt_path, 'w') as f:
|
|
106
|
+
f.write(report.to_text())
|
|
107
|
+
|
|
108
|
+
typer.echo("\n✅ Artifacts saved:", err=True)
|
|
109
|
+
typer.echo(f" {json_path}", err=True)
|
|
110
|
+
typer.echo(f" {txt_path}", err=True)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
typer.echo(f"Warning: Failed to save artifacts: {e}", err=True)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@app.command("imports")
|
|
116
|
+
def profile_imports():
|
|
117
|
+
"""
|
|
118
|
+
Profile module import times.
|
|
119
|
+
|
|
120
|
+
Shows which modules take longest to import, useful for optimizing startup time.
|
|
121
|
+
"""
|
|
122
|
+
import subprocess
|
|
123
|
+
import re
|
|
124
|
+
|
|
125
|
+
typer.echo("🔬 Profiling import times...", err=True)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
result = subprocess.run(
|
|
129
|
+
[sys.executable, "-X", "importtime", "-c", "import praisonaiagents"],
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
timeout=60,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Parse import times
|
|
136
|
+
import_times = []
|
|
137
|
+
for line in result.stderr.split('\n'):
|
|
138
|
+
if 'import time:' in line:
|
|
139
|
+
match = re.search(r'import time:\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(.+)', line)
|
|
140
|
+
if match:
|
|
141
|
+
self_time = int(match.group(1))
|
|
142
|
+
cumulative = int(match.group(2))
|
|
143
|
+
module = match.group(3).strip()
|
|
144
|
+
import_times.append((module, self_time, cumulative))
|
|
145
|
+
|
|
146
|
+
# Sort by cumulative time
|
|
147
|
+
import_times.sort(key=lambda x: x[2], reverse=True)
|
|
148
|
+
|
|
149
|
+
# Display
|
|
150
|
+
typer.echo("\n" + "=" * 70)
|
|
151
|
+
typer.echo("Import Time Analysis")
|
|
152
|
+
typer.echo("=" * 70)
|
|
153
|
+
typer.echo(f"{'Module':<45} {'Self (μs)':>10} {'Cumul (μs)':>12}")
|
|
154
|
+
typer.echo("-" * 70)
|
|
155
|
+
|
|
156
|
+
for module, self_time, cumulative in import_times[:30]:
|
|
157
|
+
module_name = module
|
|
158
|
+
if len(module_name) > 43:
|
|
159
|
+
module_name = "..." + module_name[-40:]
|
|
160
|
+
typer.echo(f"{module_name:<45} {self_time:>10} {cumulative:>12}")
|
|
161
|
+
|
|
162
|
+
typer.echo("-" * 70)
|
|
163
|
+
|
|
164
|
+
if import_times:
|
|
165
|
+
total_time = max(t[2] for t in import_times)
|
|
166
|
+
typer.echo(f"Total import time: {total_time / 1000:.2f} ms")
|
|
167
|
+
|
|
168
|
+
except subprocess.TimeoutExpired:
|
|
169
|
+
typer.echo("Error: Import profiling timed out", err=True)
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
typer.echo(f"Error: {e}", err=True)
|
|
173
|
+
raise typer.Exit(1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.command("startup")
|
|
177
|
+
def profile_startup():
|
|
178
|
+
"""
|
|
179
|
+
Profile CLI startup time.
|
|
180
|
+
|
|
181
|
+
Measures time from CLI invocation to ready state.
|
|
182
|
+
"""
|
|
183
|
+
import time
|
|
184
|
+
import subprocess
|
|
185
|
+
|
|
186
|
+
typer.echo("🔬 Profiling startup time...", err=True)
|
|
187
|
+
|
|
188
|
+
# Measure cold start (new process)
|
|
189
|
+
start = time.perf_counter()
|
|
190
|
+
subprocess.run(
|
|
191
|
+
[sys.executable, "-c", "import praisonai; import praisonai.cli"],
|
|
192
|
+
capture_output=True,
|
|
193
|
+
text=True,
|
|
194
|
+
)
|
|
195
|
+
cold_start = (time.perf_counter() - start) * 1000
|
|
196
|
+
|
|
197
|
+
# Measure warm start (imports cached)
|
|
198
|
+
start = time.perf_counter()
|
|
199
|
+
subprocess.run(
|
|
200
|
+
[sys.executable, "-c", "import praisonai; import praisonai.cli"],
|
|
201
|
+
capture_output=True,
|
|
202
|
+
text=True,
|
|
203
|
+
)
|
|
204
|
+
warm_start = (time.perf_counter() - start) * 1000
|
|
205
|
+
|
|
206
|
+
typer.echo("\n" + "=" * 50)
|
|
207
|
+
typer.echo("Startup Time Analysis")
|
|
208
|
+
typer.echo("=" * 50)
|
|
209
|
+
typer.echo(f"Cold Start: {cold_start:>10.2f} ms")
|
|
210
|
+
typer.echo(f"Warm Start: {warm_start:>10.2f} ms")
|
|
211
|
+
typer.echo("=" * 50)
|
|
212
|
+
|
|
213
|
+
if cold_start > 1000:
|
|
214
|
+
typer.echo("\n⚠️ Cold start > 1s - consider lazy imports", err=True)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@app.command("suite")
|
|
218
|
+
def profile_suite(
|
|
219
|
+
output_dir: Optional[str] = typer.Option("/tmp/praisonai_profile_suite", "--output", "-o", help="Output directory for results"),
|
|
220
|
+
iterations: int = typer.Option(3, "--iterations", "-n", help="Iterations per scenario"),
|
|
221
|
+
quick: bool = typer.Option(False, "--quick", help="Quick mode (fewer iterations)"),
|
|
222
|
+
):
|
|
223
|
+
"""
|
|
224
|
+
Run a comprehensive profiling suite.
|
|
225
|
+
|
|
226
|
+
Runs multiple scenarios (streaming/non-streaming, simple/complex prompts)
|
|
227
|
+
and produces aggregated statistics and reports.
|
|
228
|
+
|
|
229
|
+
Examples:
|
|
230
|
+
praisonai profile suite
|
|
231
|
+
praisonai profile suite --output ./my_profile_results
|
|
232
|
+
praisonai profile suite --quick
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
from praisonai.cli.features.profiler import run_profile_suite, ScenarioConfig
|
|
236
|
+
except ImportError as e:
|
|
237
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
238
|
+
raise typer.Exit(1)
|
|
239
|
+
|
|
240
|
+
# Define scenarios
|
|
241
|
+
iters = 1 if quick else iterations
|
|
242
|
+
scenarios = [
|
|
243
|
+
ScenarioConfig(name="simple_non_stream", prompt="hi", stream=False, iterations=iters, warmup=0 if quick else 1),
|
|
244
|
+
ScenarioConfig(name="simple_stream", prompt="hi", stream=True, iterations=iters, warmup=0 if quick else 1),
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
if not quick:
|
|
248
|
+
scenarios.extend([
|
|
249
|
+
ScenarioConfig(name="medium_non_stream", prompt="Explain Python in 2 sentences", stream=False, iterations=max(1, iters-1)),
|
|
250
|
+
ScenarioConfig(name="medium_stream", prompt="Explain Python in 2 sentences", stream=True, iterations=max(1, iters-1)),
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
typer.echo("🔬 Running Profile Suite...", err=True)
|
|
254
|
+
typer.echo(f" Output: {output_dir}", err=True)
|
|
255
|
+
typer.echo(f" Scenarios: {len(scenarios)}", err=True)
|
|
256
|
+
typer.echo(f" Iterations: {iterations}", err=True)
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
result = run_profile_suite(
|
|
260
|
+
output_dir=output_dir,
|
|
261
|
+
scenarios=scenarios,
|
|
262
|
+
verbose=True,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Print summary
|
|
266
|
+
typer.echo("\n" + "=" * 60)
|
|
267
|
+
typer.echo("Profile Suite Summary")
|
|
268
|
+
typer.echo("=" * 60)
|
|
269
|
+
typer.echo(f"Startup Cold: {result.startup_cold_ms:.2f}ms")
|
|
270
|
+
typer.echo(f"Startup Warm: {result.startup_warm_ms:.2f}ms")
|
|
271
|
+
|
|
272
|
+
if result.import_analysis:
|
|
273
|
+
typer.echo(f"\nTop Import: {result.import_analysis[0]['module']}")
|
|
274
|
+
typer.echo(f" Time: {result.import_analysis[0]['cumulative_ms']:.2f}ms")
|
|
275
|
+
|
|
276
|
+
typer.echo("\nScenario Results:")
|
|
277
|
+
for scenario in result.scenarios:
|
|
278
|
+
stats = scenario.get_stats(scenario.total_times)
|
|
279
|
+
typer.echo(f" {scenario.name}: {stats['mean']:.2f}ms (±{stats['stdev']:.2f}ms)")
|
|
280
|
+
|
|
281
|
+
typer.echo(f"\n✅ Full results saved to: {output_dir}")
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
typer.echo(f"Error running suite: {e}", err=True)
|
|
285
|
+
raise typer.Exit(1)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@app.command("snapshot")
|
|
289
|
+
def profile_snapshot(
|
|
290
|
+
name: str = typer.Argument("current", help="Name for the snapshot"),
|
|
291
|
+
baseline: bool = typer.Option(False, "--baseline", "-b", help="Save as baseline for comparison"),
|
|
292
|
+
compare: bool = typer.Option(False, "--compare", "-c", help="Compare against baseline"),
|
|
293
|
+
output_format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
|
|
294
|
+
):
|
|
295
|
+
"""
|
|
296
|
+
Create or compare performance snapshots.
|
|
297
|
+
|
|
298
|
+
Snapshots record timing data for baseline comparison.
|
|
299
|
+
Use --baseline to save a reference point, then --compare to check for regressions.
|
|
300
|
+
|
|
301
|
+
Examples:
|
|
302
|
+
praisonai profile snapshot --baseline
|
|
303
|
+
praisonai profile snapshot current --compare
|
|
304
|
+
praisonai profile snapshot v2.0 --format json
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
from praisonai.cli.features.profiler import (
|
|
308
|
+
run_profile_suite,
|
|
309
|
+
ScenarioConfig,
|
|
310
|
+
PerfSnapshotManager,
|
|
311
|
+
create_snapshot_from_suite,
|
|
312
|
+
format_comparison_report,
|
|
313
|
+
)
|
|
314
|
+
except ImportError as e:
|
|
315
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
316
|
+
raise typer.Exit(1)
|
|
317
|
+
|
|
318
|
+
manager = PerfSnapshotManager()
|
|
319
|
+
|
|
320
|
+
if compare:
|
|
321
|
+
# Load baseline and compare
|
|
322
|
+
baseline_snap = manager.load_baseline()
|
|
323
|
+
if not baseline_snap:
|
|
324
|
+
typer.echo("❌ No baseline found. Create one with: praisonai profile snapshot --baseline", err=True)
|
|
325
|
+
raise typer.Exit(1)
|
|
326
|
+
|
|
327
|
+
typer.echo("🔬 Running current profile for comparison...", err=True)
|
|
328
|
+
|
|
329
|
+
# Run quick suite
|
|
330
|
+
scenarios = [
|
|
331
|
+
ScenarioConfig(name="simple", prompt="hi", stream=False, iterations=2, warmup=1),
|
|
332
|
+
]
|
|
333
|
+
result = run_profile_suite(
|
|
334
|
+
output_dir="/tmp/praisonai_snapshot_compare",
|
|
335
|
+
scenarios=scenarios,
|
|
336
|
+
output="minimal",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
current_snap = create_snapshot_from_suite(result, name)
|
|
340
|
+
comparison = manager.compare(current_snap)
|
|
341
|
+
|
|
342
|
+
if output_format == "json":
|
|
343
|
+
import json
|
|
344
|
+
typer.echo(json.dumps(comparison.to_dict(), indent=2))
|
|
345
|
+
else:
|
|
346
|
+
typer.echo(format_comparison_report(comparison))
|
|
347
|
+
|
|
348
|
+
if comparison.is_regression():
|
|
349
|
+
raise typer.Exit(1) # Exit with error on regression
|
|
350
|
+
else:
|
|
351
|
+
# Create new snapshot
|
|
352
|
+
typer.echo(f"🔬 Creating performance snapshot '{name}'...", err=True)
|
|
353
|
+
|
|
354
|
+
scenarios = [
|
|
355
|
+
ScenarioConfig(name="simple", prompt="hi", stream=False, iterations=3, warmup=1),
|
|
356
|
+
ScenarioConfig(name="stream", prompt="hi", stream=True, iterations=2, warmup=1),
|
|
357
|
+
]
|
|
358
|
+
result = run_profile_suite(
|
|
359
|
+
output_dir="/tmp/praisonai_snapshot",
|
|
360
|
+
scenarios=scenarios,
|
|
361
|
+
verbose=True,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
snapshot = create_snapshot_from_suite(result, name)
|
|
365
|
+
|
|
366
|
+
if baseline:
|
|
367
|
+
path = manager.save_baseline(snapshot)
|
|
368
|
+
typer.echo(f"\n✅ Baseline saved to: {path}")
|
|
369
|
+
else:
|
|
370
|
+
path = manager.save(snapshot)
|
|
371
|
+
typer.echo(f"\n✅ Snapshot saved to: {path}")
|
|
372
|
+
|
|
373
|
+
if output_format == "json":
|
|
374
|
+
import json
|
|
375
|
+
typer.echo(json.dumps(snapshot.to_dict(), indent=2))
|
|
376
|
+
else:
|
|
377
|
+
typer.echo("\nSnapshot Summary:")
|
|
378
|
+
typer.echo(f" Startup Cold: {snapshot.startup_cold_ms:.2f}ms")
|
|
379
|
+
typer.echo(f" Import Time: {snapshot.import_time_ms:.2f}ms")
|
|
380
|
+
typer.echo(f" Query Time: {snapshot.query_time_ms:.2f}ms")
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@app.command("optimize")
|
|
384
|
+
def profile_optimize(
|
|
385
|
+
prewarm: bool = typer.Option(False, "--prewarm", help="Enable provider pre-warming"),
|
|
386
|
+
lite: bool = typer.Option(False, "--lite", help="Enable lite mode (skip type validation)"),
|
|
387
|
+
show_config: bool = typer.Option(False, "--show", help="Show current optimization config"),
|
|
388
|
+
):
|
|
389
|
+
"""
|
|
390
|
+
Configure performance optimizations.
|
|
391
|
+
|
|
392
|
+
All optimizations are opt-in and safe by default.
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
praisonai profile optimize --show
|
|
396
|
+
praisonai profile optimize --prewarm
|
|
397
|
+
praisonai profile optimize --lite
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
from praisonai.cli.features.profiler import (
|
|
401
|
+
PrewarmManager,
|
|
402
|
+
get_lite_mode_config,
|
|
403
|
+
get_provider_cache,
|
|
404
|
+
)
|
|
405
|
+
except ImportError as e:
|
|
406
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
407
|
+
raise typer.Exit(1)
|
|
408
|
+
|
|
409
|
+
if show_config:
|
|
410
|
+
typer.echo("=" * 50)
|
|
411
|
+
typer.echo("Performance Optimization Status")
|
|
412
|
+
typer.echo("=" * 50)
|
|
413
|
+
|
|
414
|
+
lite_config = get_lite_mode_config()
|
|
415
|
+
typer.echo(f"\nLite Mode: {'Enabled' if lite_config.enabled else 'Disabled'}")
|
|
416
|
+
if lite_config.enabled:
|
|
417
|
+
typer.echo(f" Skip Type Validation: {lite_config.skip_type_validation}")
|
|
418
|
+
typer.echo(f" Skip Model Validation: {lite_config.skip_model_validation}")
|
|
419
|
+
typer.echo(f" Minimal Imports: {lite_config.minimal_imports}")
|
|
420
|
+
|
|
421
|
+
typer.echo(f"\nPre-warming: {'Enabled' if PrewarmManager.is_enabled() else 'Disabled'}")
|
|
422
|
+
|
|
423
|
+
cache = get_provider_cache()
|
|
424
|
+
stats = cache.get_stats()
|
|
425
|
+
typer.echo("\nProvider Cache:")
|
|
426
|
+
typer.echo(f" Hits: {stats['hits']}")
|
|
427
|
+
typer.echo(f" Misses: {stats['misses']}")
|
|
428
|
+
typer.echo(f" Size: {stats['size']}")
|
|
429
|
+
|
|
430
|
+
typer.echo("\n" + "=" * 50)
|
|
431
|
+
typer.echo("\nTo enable optimizations, set environment variables:")
|
|
432
|
+
typer.echo(" PRAISONAI_LITE_MODE=1")
|
|
433
|
+
typer.echo(" PRAISONAI_SKIP_TYPE_VALIDATION=1")
|
|
434
|
+
typer.echo(" PRAISONAI_MINIMAL_IMPORTS=1")
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
if prewarm:
|
|
438
|
+
typer.echo("🔥 Enabling provider pre-warming...")
|
|
439
|
+
PrewarmManager.enable()
|
|
440
|
+
|
|
441
|
+
import os
|
|
442
|
+
if os.environ.get('OPENAI_API_KEY'):
|
|
443
|
+
typer.echo(" Pre-warming OpenAI...")
|
|
444
|
+
PrewarmManager.prewarm_provider('openai')
|
|
445
|
+
if os.environ.get('ANTHROPIC_API_KEY'):
|
|
446
|
+
typer.echo(" Pre-warming Anthropic...")
|
|
447
|
+
PrewarmManager.prewarm_provider('anthropic')
|
|
448
|
+
|
|
449
|
+
typer.echo("✅ Pre-warming initiated (runs in background)")
|
|
450
|
+
|
|
451
|
+
if lite:
|
|
452
|
+
typer.echo("⚡ Lite mode configuration:")
|
|
453
|
+
typer.echo(" Set these environment variables to enable:")
|
|
454
|
+
typer.echo(" export PRAISONAI_LITE_MODE=1")
|
|
455
|
+
typer.echo(" export PRAISONAI_SKIP_TYPE_VALIDATION=1")
|
|
456
|
+
typer.echo(" export PRAISONAI_MINIMAL_IMPORTS=1")
|
|
457
|
+
|
|
458
|
+
if not prewarm and not lite and not show_config:
|
|
459
|
+
typer.echo("Use --show to see current config, or --prewarm/--lite to enable optimizations")
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@app.callback(invoke_without_command=True)
|
|
463
|
+
def profile_callback(ctx: typer.Context):
|
|
464
|
+
"""Show profile help if no subcommand."""
|
|
465
|
+
if ctx.invoked_subcommand is None:
|
|
466
|
+
typer.echo(ctx.get_help())
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publish command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides package publishing with automatic version bumping.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from rich import print as rprint
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(help="Package publishing")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _find_pyproject() -> Path:
|
|
21
|
+
"""Find pyproject.toml in current directory."""
|
|
22
|
+
p = Path.cwd() / "pyproject.toml"
|
|
23
|
+
if not p.exists():
|
|
24
|
+
rprint("[red]Error: No pyproject.toml found in current directory[/red]")
|
|
25
|
+
raise typer.Exit(1)
|
|
26
|
+
return p
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _read_version(pyproject: Path) -> str:
|
|
30
|
+
"""Read version from pyproject.toml."""
|
|
31
|
+
content = pyproject.read_text()
|
|
32
|
+
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
|
|
33
|
+
if not match:
|
|
34
|
+
rprint("[red]Error: Could not find version in pyproject.toml[/red]")
|
|
35
|
+
raise typer.Exit(1)
|
|
36
|
+
return match.group(1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _bump_version(version: str, major: bool = False, minor: bool = False) -> str:
|
|
40
|
+
"""Bump version string.
|
|
41
|
+
|
|
42
|
+
Default bumps patch (last number): 1.5.21 -> 1.5.22
|
|
43
|
+
--minor: 1.5.21 -> 1.6.0
|
|
44
|
+
--major: 1.5.21 -> 2.0.0
|
|
45
|
+
"""
|
|
46
|
+
parts = version.split(".")
|
|
47
|
+
if len(parts) != 3:
|
|
48
|
+
rprint(f"[red]Error: Version '{version}' is not in X.Y.Z format[/red]")
|
|
49
|
+
raise typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
maj, min_, patch = int(parts[0]), int(parts[1]), int(parts[2])
|
|
52
|
+
|
|
53
|
+
if major:
|
|
54
|
+
return f"{maj + 1}.0.0"
|
|
55
|
+
elif minor:
|
|
56
|
+
return f"{maj}.{min_ + 1}.0"
|
|
57
|
+
else:
|
|
58
|
+
return f"{maj}.{min_}.{patch + 1}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _write_version(pyproject: Path, old_version: str, new_version: str):
|
|
62
|
+
"""Write new version to pyproject.toml."""
|
|
63
|
+
content = pyproject.read_text()
|
|
64
|
+
content = content.replace(f'version = "{old_version}"', f'version = "{new_version}"', 1)
|
|
65
|
+
pyproject.write_text(content)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _run_step(cmd: list[str], label: str, dry_run: bool = False) -> bool:
|
|
69
|
+
"""Run a build step, printing status."""
|
|
70
|
+
cmd_str = " ".join(cmd)
|
|
71
|
+
if dry_run:
|
|
72
|
+
rprint(f" [dim]→ {cmd_str}[/dim]")
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
rprint(f"[cyan]→ {cmd_str}[/cyan]")
|
|
76
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
77
|
+
if result.returncode != 0:
|
|
78
|
+
rprint(f"[red]✗ {label} failed[/red]")
|
|
79
|
+
if result.stderr:
|
|
80
|
+
rprint(f"[red]{result.stderr.strip()}[/red]")
|
|
81
|
+
if result.stdout:
|
|
82
|
+
rprint(f"[dim]{result.stdout.strip()}[/dim]")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
if result.stdout:
|
|
86
|
+
# Show concise output
|
|
87
|
+
lines = result.stdout.strip().split("\n")
|
|
88
|
+
for line in lines[-5:]: # last 5 lines
|
|
89
|
+
rprint(f" [dim]{line}[/dim]")
|
|
90
|
+
rprint(f"[green]✓ {label}[/green]")
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.callback(invoke_without_command=True)
|
|
95
|
+
def publish_main(ctx: typer.Context):
|
|
96
|
+
"""
|
|
97
|
+
Publish packages to registries.
|
|
98
|
+
|
|
99
|
+
Use a subcommand like 'pypi' to specify the target.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
praisonai publish pypi
|
|
103
|
+
praisonai publish pypi --minor
|
|
104
|
+
praisonai publish pypi --dry-run
|
|
105
|
+
"""
|
|
106
|
+
if ctx.invoked_subcommand is None:
|
|
107
|
+
rprint("[yellow]Usage: praisonai publish pypi [OPTIONS][/yellow]")
|
|
108
|
+
rprint("Run [bold]praisonai publish pypi --help[/bold] for options.")
|
|
109
|
+
raise typer.Exit(0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command("pypi")
|
|
113
|
+
def publish_pypi(
|
|
114
|
+
major: bool = typer.Option(False, "--major", help="Bump major version (X.0.0)"),
|
|
115
|
+
minor: bool = typer.Option(False, "--minor", help="Bump minor version (X.Y.0)"),
|
|
116
|
+
no_bump: bool = typer.Option(False, "--no-bump", help="Skip version increment"),
|
|
117
|
+
dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Show what would happen without executing"),
|
|
118
|
+
token_var: str = typer.Option("PYPI_TOKEN", "--token-var", help="Environment variable name for PyPI token"),
|
|
119
|
+
):
|
|
120
|
+
"""
|
|
121
|
+
Publish package to PyPI.
|
|
122
|
+
|
|
123
|
+
Auto-increments the patch version (last number) by default,
|
|
124
|
+
then runs: rm -rf dist && uv lock && uv build && uv publish
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
praisonai publish pypi # bump patch: 1.5.21 -> 1.5.22
|
|
128
|
+
praisonai publish pypi --minor # bump minor: 1.5.21 -> 1.6.0
|
|
129
|
+
praisonai publish pypi --major # bump major: 1.5.21 -> 2.0.0
|
|
130
|
+
praisonai publish pypi --no-bump # publish without version change
|
|
131
|
+
praisonai publish pypi --dry-run # preview without executing
|
|
132
|
+
praisonai publish pypi --token-var UV_PUBLISH_TOKEN
|
|
133
|
+
"""
|
|
134
|
+
# 1. Find pyproject.toml
|
|
135
|
+
pyproject = _find_pyproject()
|
|
136
|
+
old_version = _read_version(pyproject)
|
|
137
|
+
|
|
138
|
+
# 2. Bump version
|
|
139
|
+
if no_bump:
|
|
140
|
+
new_version = old_version
|
|
141
|
+
rprint(f"[yellow]Skipping version bump — publishing {old_version}[/yellow]")
|
|
142
|
+
else:
|
|
143
|
+
if major and minor:
|
|
144
|
+
rprint("[red]Error: Cannot use --major and --minor together[/red]")
|
|
145
|
+
raise typer.Exit(1)
|
|
146
|
+
new_version = _bump_version(old_version, major=major, minor=minor)
|
|
147
|
+
bump_type = "major" if major else ("minor" if minor else "patch")
|
|
148
|
+
rprint(f"[cyan]Version bump ({bump_type}): {old_version} → {new_version}[/cyan]")
|
|
149
|
+
|
|
150
|
+
# 3. Get PyPI token
|
|
151
|
+
token = os.environ.get(token_var)
|
|
152
|
+
if not token:
|
|
153
|
+
rprint(f"[red]Error: {token_var} environment variable not set[/red]")
|
|
154
|
+
rprint(f"[dim]Set it with: export {token_var}=pypi-...[/dim]")
|
|
155
|
+
raise typer.Exit(1)
|
|
156
|
+
rprint(f"[green]✓ PyPI token found ({token_var})[/green]")
|
|
157
|
+
|
|
158
|
+
# 4. Dry run summary
|
|
159
|
+
if dry_run:
|
|
160
|
+
rprint("\n[yellow]Dry run — no changes will be made:[/yellow]")
|
|
161
|
+
if not no_bump:
|
|
162
|
+
rprint(f" [dim]→ Update pyproject.toml: {old_version} → {new_version}[/dim]")
|
|
163
|
+
_run_step(["rm", "-rf", "dist"], "clean", dry_run=True)
|
|
164
|
+
_run_step(["uv", "lock"], "lock", dry_run=True)
|
|
165
|
+
_run_step(["uv", "build"], "build", dry_run=True)
|
|
166
|
+
_run_step(["uv", "publish", "--token", f"${token_var}"], "publish", dry_run=True)
|
|
167
|
+
rprint("[green]Dry run complete.[/green]")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# 5. Write new version
|
|
171
|
+
if not no_bump:
|
|
172
|
+
_write_version(pyproject, old_version, new_version)
|
|
173
|
+
rprint(f"[green]✓ Updated pyproject.toml[/green]")
|
|
174
|
+
|
|
175
|
+
# 6. Clean dist
|
|
176
|
+
dist_dir = Path.cwd() / "dist"
|
|
177
|
+
if dist_dir.exists():
|
|
178
|
+
shutil.rmtree(dist_dir)
|
|
179
|
+
rprint("[green]✓ Cleaned dist/[/green]")
|
|
180
|
+
|
|
181
|
+
# 7. uv lock
|
|
182
|
+
if not _run_step(["uv", "lock"], "uv lock"):
|
|
183
|
+
raise typer.Exit(1)
|
|
184
|
+
|
|
185
|
+
# 8. uv build
|
|
186
|
+
if not _run_step(["uv", "build"], "uv build"):
|
|
187
|
+
raise typer.Exit(1)
|
|
188
|
+
|
|
189
|
+
# 9. uv publish
|
|
190
|
+
if not _run_step(["uv", "publish", "--token", token], "uv publish"):
|
|
191
|
+
raise typer.Exit(1)
|
|
192
|
+
|
|
193
|
+
rprint(f"\n[bold green]✓ Published {new_version} to PyPI[/bold green]")
|