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,848 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PraisonAI Batch CLI Command.
|
|
3
|
+
|
|
4
|
+
Run all Python files with PraisonAI imports in the current directory.
|
|
5
|
+
Designed for quick debugging - running multiple local scripts at once.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
praisonai batch # Run all PraisonAI scripts in current dir
|
|
9
|
+
praisonai batch --sub # Include subdirectories
|
|
10
|
+
praisonai batch --sub --depth 2 # Limit recursion depth
|
|
11
|
+
praisonai batch --parallel # Run in parallel with async reporting
|
|
12
|
+
praisonai batch list # List discovered files
|
|
13
|
+
praisonai batch stats # Show statistics
|
|
14
|
+
praisonai batch report # View latest report
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
help="Run all PraisonAI scripts in current folder for quick debugging",
|
|
25
|
+
no_args_is_help=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_default_report_dir() -> Path:
|
|
30
|
+
"""Get default report directory with timestamp."""
|
|
31
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
32
|
+
return Path.home() / "Downloads" / "reports" / "batch" / timestamp
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_latest_report_dir() -> Optional[Path]:
|
|
36
|
+
"""Get the latest report directory."""
|
|
37
|
+
base_dir = Path.home() / "Downloads" / "reports" / "batch"
|
|
38
|
+
if not base_dir.exists():
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# Find most recent timestamp directory
|
|
42
|
+
dirs = [d for d in base_dir.iterdir() if d.is_dir()]
|
|
43
|
+
if not dirs:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
return max(dirs, key=lambda d: d.name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.callback(invoke_without_command=True)
|
|
50
|
+
def batch_run(
|
|
51
|
+
ctx: typer.Context,
|
|
52
|
+
path: Optional[Path] = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--path", "-p",
|
|
55
|
+
help="Path to search (default: current directory)",
|
|
56
|
+
),
|
|
57
|
+
recursive: bool = typer.Option(
|
|
58
|
+
False,
|
|
59
|
+
"--sub", "--recursive", "-r",
|
|
60
|
+
help="Include subdirectories",
|
|
61
|
+
),
|
|
62
|
+
depth: Optional[int] = typer.Option(
|
|
63
|
+
None,
|
|
64
|
+
"--depth", "-d",
|
|
65
|
+
help="Maximum recursion depth (only with --sub)",
|
|
66
|
+
),
|
|
67
|
+
include_tests: bool = typer.Option(
|
|
68
|
+
False,
|
|
69
|
+
"--include-tests",
|
|
70
|
+
help="Include test files (test_*.py, *_test.py)",
|
|
71
|
+
),
|
|
72
|
+
timeout: int = typer.Option(
|
|
73
|
+
60,
|
|
74
|
+
"--timeout", "-t",
|
|
75
|
+
help="Per-script timeout in seconds",
|
|
76
|
+
),
|
|
77
|
+
max_items: Optional[int] = typer.Option(
|
|
78
|
+
None,
|
|
79
|
+
"--max-items",
|
|
80
|
+
help="Maximum scripts to run",
|
|
81
|
+
),
|
|
82
|
+
fail_fast: bool = typer.Option(
|
|
83
|
+
False,
|
|
84
|
+
"--fail-fast", "-x",
|
|
85
|
+
help="Stop on first failure",
|
|
86
|
+
),
|
|
87
|
+
parallel: bool = typer.Option(
|
|
88
|
+
True,
|
|
89
|
+
"--parallel/--sequential",
|
|
90
|
+
help="Run in parallel (default: parallel)",
|
|
91
|
+
),
|
|
92
|
+
max_workers: int = typer.Option(
|
|
93
|
+
4,
|
|
94
|
+
"--workers", "-w",
|
|
95
|
+
help="Max parallel workers (only with --parallel)",
|
|
96
|
+
),
|
|
97
|
+
no_stream: bool = typer.Option(
|
|
98
|
+
False,
|
|
99
|
+
"--no-stream",
|
|
100
|
+
help="Don't stream output to terminal",
|
|
101
|
+
),
|
|
102
|
+
report_dir: Optional[Path] = typer.Option(
|
|
103
|
+
None,
|
|
104
|
+
"--report-dir",
|
|
105
|
+
help="Directory for reports (default: ~/Downloads/reports/batch/<timestamp>)",
|
|
106
|
+
),
|
|
107
|
+
no_report: bool = typer.Option(
|
|
108
|
+
False,
|
|
109
|
+
"--no-report",
|
|
110
|
+
help="Skip report generation",
|
|
111
|
+
),
|
|
112
|
+
quiet: bool = typer.Option(
|
|
113
|
+
False,
|
|
114
|
+
"--quiet", "-q",
|
|
115
|
+
help="Minimal output",
|
|
116
|
+
),
|
|
117
|
+
server: bool = typer.Option(
|
|
118
|
+
False,
|
|
119
|
+
"--server",
|
|
120
|
+
help="Run only server scripts (uvicorn, Flask, streamlit, etc.) with 10s timeout",
|
|
121
|
+
),
|
|
122
|
+
filter_type: Optional[str] = typer.Option(
|
|
123
|
+
None,
|
|
124
|
+
"--filter", "-f",
|
|
125
|
+
help="Filter by type: 'agent', 'agents', or 'workflow'",
|
|
126
|
+
),
|
|
127
|
+
ci: bool = typer.Option(
|
|
128
|
+
False,
|
|
129
|
+
"--ci",
|
|
130
|
+
help="CI-friendly output (no colors, proper exit codes)",
|
|
131
|
+
),
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Run all PraisonAI scripts in the current folder.
|
|
135
|
+
|
|
136
|
+
Quick debugging tool for running multiple Python files that use
|
|
137
|
+
praisonaiagents or praisonai. Unlike 'praisonai run' (which runs
|
|
138
|
+
agent configs) or 'praisonai examples' (which runs repo examples),
|
|
139
|
+
this command finds and executes local scripts for rapid testing.
|
|
140
|
+
|
|
141
|
+
Only files containing 'from praisonaiagents' or 'from praisonai'
|
|
142
|
+
are executed. Test files (test_*.py) are excluded by default.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
praisonai batch # Run all in current dir
|
|
146
|
+
praisonai batch --sub # Include subdirectories
|
|
147
|
+
praisonai batch --sub --depth 2 # Limit depth
|
|
148
|
+
praisonai batch --parallel # Run in parallel
|
|
149
|
+
praisonai batch --timeout 120 # Custom timeout
|
|
150
|
+
"""
|
|
151
|
+
# If a subcommand was invoked, skip the default run
|
|
152
|
+
if ctx.invoked_subcommand is not None:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Lazy import to avoid loading at CLI startup
|
|
156
|
+
from praisonai.suite_runner import BatchSource, SuiteExecutor, RunResult
|
|
157
|
+
|
|
158
|
+
# Resolve paths
|
|
159
|
+
search_path = Path(path).resolve() if path else Path.cwd().resolve()
|
|
160
|
+
output_dir = report_dir or _get_default_report_dir()
|
|
161
|
+
|
|
162
|
+
if not search_path.exists():
|
|
163
|
+
if ci:
|
|
164
|
+
print(f"ERROR: Path not found: {search_path}")
|
|
165
|
+
else:
|
|
166
|
+
typer.echo(f"ā Path not found: {search_path}")
|
|
167
|
+
raise typer.Exit(2)
|
|
168
|
+
|
|
169
|
+
# Validate filter_type
|
|
170
|
+
if filter_type and filter_type not in ("agent", "agents", "workflow"):
|
|
171
|
+
if ci:
|
|
172
|
+
print(f"ERROR: Invalid filter type: {filter_type}. Use 'agent', 'agents', or 'workflow'")
|
|
173
|
+
else:
|
|
174
|
+
typer.echo(f"ā Invalid filter type: {filter_type}. Use 'agent', 'agents', or 'workflow'")
|
|
175
|
+
raise typer.Exit(2)
|
|
176
|
+
|
|
177
|
+
# Server mode uses 10s timeout by default
|
|
178
|
+
effective_timeout = 10 if server else timeout
|
|
179
|
+
|
|
180
|
+
# Create source
|
|
181
|
+
source = BatchSource(
|
|
182
|
+
root=search_path,
|
|
183
|
+
recursive=recursive,
|
|
184
|
+
depth=depth,
|
|
185
|
+
exclude_tests=not include_tests,
|
|
186
|
+
exclude_servers=not server, # Exclude servers by default, include when --server
|
|
187
|
+
server_only=server, # Only servers when --server flag
|
|
188
|
+
filter_type=filter_type,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Discover items
|
|
192
|
+
items = source.discover()
|
|
193
|
+
|
|
194
|
+
if not items:
|
|
195
|
+
if ci:
|
|
196
|
+
print(f"INFO: No PraisonAI scripts found in {search_path}")
|
|
197
|
+
else:
|
|
198
|
+
typer.echo(f"ā¹ļø No PraisonAI scripts found in {search_path}")
|
|
199
|
+
if not recursive:
|
|
200
|
+
typer.echo(" Tip: Use --sub to include subdirectories")
|
|
201
|
+
raise typer.Exit(0)
|
|
202
|
+
|
|
203
|
+
# Parallel execution
|
|
204
|
+
if parallel:
|
|
205
|
+
_run_parallel(
|
|
206
|
+
items=items,
|
|
207
|
+
source=source,
|
|
208
|
+
search_path=search_path,
|
|
209
|
+
output_dir=output_dir,
|
|
210
|
+
timeout=effective_timeout,
|
|
211
|
+
max_workers=max_workers,
|
|
212
|
+
quiet=quiet,
|
|
213
|
+
ci=ci,
|
|
214
|
+
)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Sequential execution (default)
|
|
218
|
+
executor = SuiteExecutor(
|
|
219
|
+
suite="batch",
|
|
220
|
+
source_path=search_path,
|
|
221
|
+
timeout=effective_timeout,
|
|
222
|
+
fail_fast=fail_fast,
|
|
223
|
+
stream_output=not no_stream,
|
|
224
|
+
max_items=max_items,
|
|
225
|
+
report_dir=output_dir if not no_report else None,
|
|
226
|
+
generate_json=not no_report,
|
|
227
|
+
generate_md=not no_report,
|
|
228
|
+
generate_csv=not no_report,
|
|
229
|
+
pythonpath_additions=source.get_pythonpath(),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Status icons (CI mode uses text)
|
|
233
|
+
if ci:
|
|
234
|
+
icons = {
|
|
235
|
+
"passed": "PASS",
|
|
236
|
+
"failed": "FAIL",
|
|
237
|
+
"skipped": "SKIP",
|
|
238
|
+
"timeout": "TIMEOUT",
|
|
239
|
+
"not_run": "NOT_RUN",
|
|
240
|
+
"xfail": "XFAIL",
|
|
241
|
+
}
|
|
242
|
+
else:
|
|
243
|
+
icons = {
|
|
244
|
+
"passed": "ā
",
|
|
245
|
+
"failed": "ā",
|
|
246
|
+
"skipped": "āļø",
|
|
247
|
+
"timeout": "ā±ļø",
|
|
248
|
+
"not_run": "š",
|
|
249
|
+
"xfail": "ā ļø",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
def on_item_start(item, idx: int, total: int):
|
|
253
|
+
if not quiet:
|
|
254
|
+
if ci:
|
|
255
|
+
print(f"[{idx}/{total}] Running: {item.display_name}")
|
|
256
|
+
else:
|
|
257
|
+
typer.echo(f"\n[{idx}/{total}] Running: {item.display_name}")
|
|
258
|
+
|
|
259
|
+
def on_item_end(result: RunResult, idx: int, total: int):
|
|
260
|
+
icon = icons.get(result.status, "?")
|
|
261
|
+
duration = f"{result.duration_seconds:.2f}s" if result.duration_seconds else ""
|
|
262
|
+
|
|
263
|
+
if quiet:
|
|
264
|
+
if ci:
|
|
265
|
+
print(f"{icon} {result.display_name}")
|
|
266
|
+
else:
|
|
267
|
+
typer.echo(f"{icon} {result.display_name}")
|
|
268
|
+
else:
|
|
269
|
+
msg = f" {icon} {result.status.upper()}"
|
|
270
|
+
if duration:
|
|
271
|
+
msg += f" ({duration})"
|
|
272
|
+
if result.skip_reason:
|
|
273
|
+
msg += f" - {result.skip_reason}"
|
|
274
|
+
if result.error_message and not no_stream:
|
|
275
|
+
msg += f"\n Error: {result.error_message[:100]}"
|
|
276
|
+
if ci:
|
|
277
|
+
print(msg)
|
|
278
|
+
else:
|
|
279
|
+
typer.echo(msg)
|
|
280
|
+
|
|
281
|
+
def on_output(line: str, stream: str):
|
|
282
|
+
if not quiet and not no_stream:
|
|
283
|
+
prefix = " | " if ci else " ā "
|
|
284
|
+
if stream != "stdout":
|
|
285
|
+
prefix += "[err] "
|
|
286
|
+
if ci:
|
|
287
|
+
print(f"{prefix}{line.rstrip()}")
|
|
288
|
+
else:
|
|
289
|
+
typer.echo(f"{prefix}{line.rstrip()}")
|
|
290
|
+
|
|
291
|
+
# Print header
|
|
292
|
+
if not quiet:
|
|
293
|
+
if ci:
|
|
294
|
+
print("=" * 60)
|
|
295
|
+
print("PraisonAI Batch Runner")
|
|
296
|
+
print("=" * 60)
|
|
297
|
+
print(f"Path: {search_path}")
|
|
298
|
+
print(f"Recursive: {recursive}")
|
|
299
|
+
if depth:
|
|
300
|
+
print(f"Depth: {depth}")
|
|
301
|
+
print(f"Timeout: {effective_timeout}s")
|
|
302
|
+
print(f"Scripts: {len(items)}")
|
|
303
|
+
if server:
|
|
304
|
+
print("Mode: Server scripts only")
|
|
305
|
+
if filter_type:
|
|
306
|
+
print(f"Filter: {filter_type}")
|
|
307
|
+
if not no_report:
|
|
308
|
+
print(f"Reports: {output_dir}")
|
|
309
|
+
else:
|
|
310
|
+
typer.echo("=" * 60)
|
|
311
|
+
typer.echo("PraisonAI Batch Runner")
|
|
312
|
+
typer.echo("=" * 60)
|
|
313
|
+
typer.echo(f"Path: {search_path}")
|
|
314
|
+
typer.echo(f"Recursive: {recursive}")
|
|
315
|
+
if depth:
|
|
316
|
+
typer.echo(f"Depth: {depth}")
|
|
317
|
+
typer.echo(f"Timeout: {effective_timeout}s")
|
|
318
|
+
typer.echo(f"Scripts: {len(items)}")
|
|
319
|
+
if server:
|
|
320
|
+
typer.echo("Mode: Server scripts only")
|
|
321
|
+
if filter_type:
|
|
322
|
+
typer.echo(f"Filter: {filter_type}")
|
|
323
|
+
if not no_report:
|
|
324
|
+
typer.echo(f"Reports: {output_dir}")
|
|
325
|
+
|
|
326
|
+
# Run
|
|
327
|
+
report = executor.run(
|
|
328
|
+
items=items,
|
|
329
|
+
on_item_start=on_item_start,
|
|
330
|
+
on_item_end=on_item_end,
|
|
331
|
+
on_output=on_output if not no_stream else None,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Print summary
|
|
335
|
+
totals = report.totals
|
|
336
|
+
total_count = sum(totals.values())
|
|
337
|
+
|
|
338
|
+
if not quiet:
|
|
339
|
+
if ci:
|
|
340
|
+
print("")
|
|
341
|
+
print("=" * 60)
|
|
342
|
+
print("SUMMARY")
|
|
343
|
+
print("=" * 60)
|
|
344
|
+
print(f" PASSED: {totals['passed']}")
|
|
345
|
+
print(f" FAILED: {totals['failed']}")
|
|
346
|
+
print(f" SKIPPED: {totals['skipped']}")
|
|
347
|
+
print(f" TIMEOUT: {totals['timeout']}")
|
|
348
|
+
print(" -----------------")
|
|
349
|
+
print(f" TOTAL: {total_count}")
|
|
350
|
+
print("=" * 60)
|
|
351
|
+
if not no_report and output_dir.exists():
|
|
352
|
+
print(f"Reports: {output_dir}")
|
|
353
|
+
else:
|
|
354
|
+
typer.echo("\n" + "=" * 60)
|
|
355
|
+
typer.echo("SUMMARY")
|
|
356
|
+
typer.echo("=" * 60)
|
|
357
|
+
typer.echo(f" ā
Passed: {totals['passed']}")
|
|
358
|
+
typer.echo(f" ā Failed: {totals['failed']}")
|
|
359
|
+
typer.echo(f" āļø Skipped: {totals['skipped']}")
|
|
360
|
+
typer.echo(f" ā±ļø Timeout: {totals['timeout']}")
|
|
361
|
+
typer.echo(" āāāāāāāāāāāāāāāāā")
|
|
362
|
+
typer.echo(f" Total: {total_count}")
|
|
363
|
+
typer.echo("=" * 60)
|
|
364
|
+
|
|
365
|
+
if not no_report and output_dir.exists():
|
|
366
|
+
typer.echo(f"\nš Reports saved to: {output_dir}")
|
|
367
|
+
|
|
368
|
+
# Exit code
|
|
369
|
+
if totals['failed'] > 0 or totals['timeout'] > 0:
|
|
370
|
+
raise typer.Exit(1)
|
|
371
|
+
|
|
372
|
+
raise typer.Exit(0)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _run_parallel(
|
|
376
|
+
items,
|
|
377
|
+
source,
|
|
378
|
+
search_path: Path,
|
|
379
|
+
output_dir: Path,
|
|
380
|
+
timeout: int,
|
|
381
|
+
max_workers: int,
|
|
382
|
+
quiet: bool,
|
|
383
|
+
ci: bool = False,
|
|
384
|
+
):
|
|
385
|
+
"""Run items in parallel with async reporting."""
|
|
386
|
+
import subprocess
|
|
387
|
+
import sys
|
|
388
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
389
|
+
from praisonai.suite_runner import RunResult, RunReport, SuiteReporter
|
|
390
|
+
from datetime import timezone
|
|
391
|
+
|
|
392
|
+
if ci:
|
|
393
|
+
print("=" * 60)
|
|
394
|
+
print("PraisonAI Batch Runner (Parallel Mode)")
|
|
395
|
+
print("=" * 60)
|
|
396
|
+
print(f"Path: {search_path}")
|
|
397
|
+
print(f"Workers: {max_workers}")
|
|
398
|
+
print(f"Timeout: {timeout}s")
|
|
399
|
+
print(f"Scripts: {len(items)}")
|
|
400
|
+
print(f"Reports: {output_dir}")
|
|
401
|
+
print("")
|
|
402
|
+
print("Running... Progress:")
|
|
403
|
+
print("-" * 60)
|
|
404
|
+
else:
|
|
405
|
+
typer.echo("=" * 60)
|
|
406
|
+
typer.echo("PraisonAI Batch Runner (Parallel Mode)")
|
|
407
|
+
typer.echo("=" * 60)
|
|
408
|
+
typer.echo(f"Path: {search_path}")
|
|
409
|
+
typer.echo(f"Workers: {max_workers}")
|
|
410
|
+
typer.echo(f"Timeout: {timeout}s")
|
|
411
|
+
typer.echo(f"Scripts: {len(items)}")
|
|
412
|
+
typer.echo(f"Reports: {output_dir}")
|
|
413
|
+
typer.echo("")
|
|
414
|
+
typer.echo("Running in background... Progress:")
|
|
415
|
+
typer.echo("-" * 60)
|
|
416
|
+
|
|
417
|
+
results = []
|
|
418
|
+
completed = 0
|
|
419
|
+
total = len(items)
|
|
420
|
+
|
|
421
|
+
# Build environment
|
|
422
|
+
import os
|
|
423
|
+
env = os.environ.copy()
|
|
424
|
+
pythonpath = source.get_pythonpath()
|
|
425
|
+
if pythonpath:
|
|
426
|
+
existing = env.get('PYTHONPATH', '')
|
|
427
|
+
env['PYTHONPATH'] = os.pathsep.join(pythonpath + ([existing] if existing else []))
|
|
428
|
+
|
|
429
|
+
def run_item(item):
|
|
430
|
+
"""Run a single item and return result."""
|
|
431
|
+
nonlocal completed
|
|
432
|
+
|
|
433
|
+
if not item.runnable:
|
|
434
|
+
completed += 1
|
|
435
|
+
return RunResult(
|
|
436
|
+
item_id=item.item_id,
|
|
437
|
+
suite="batch",
|
|
438
|
+
group=item.group,
|
|
439
|
+
source_path=item.source_path,
|
|
440
|
+
status="skipped" if item.skip else "not_run",
|
|
441
|
+
skip_reason=item.skip_reason,
|
|
442
|
+
code_hash=item.code_hash,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
start_time = datetime.now(timezone.utc)
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
result = subprocess.run(
|
|
449
|
+
[sys.executable, '-u', str(item.script_path)],
|
|
450
|
+
capture_output=True,
|
|
451
|
+
text=True,
|
|
452
|
+
timeout=timeout,
|
|
453
|
+
env=env,
|
|
454
|
+
cwd=item.script_path.parent,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
end_time = datetime.now(timezone.utc)
|
|
458
|
+
duration = (end_time - start_time).total_seconds()
|
|
459
|
+
|
|
460
|
+
status = "passed" if result.returncode == 0 else "failed"
|
|
461
|
+
if item.xfail and result.returncode != 0:
|
|
462
|
+
status = "xfail"
|
|
463
|
+
|
|
464
|
+
error_message = None
|
|
465
|
+
if result.returncode != 0 and result.stderr:
|
|
466
|
+
lines = result.stderr.strip().split('\n')
|
|
467
|
+
error_message = lines[-1][:200] if lines else None
|
|
468
|
+
|
|
469
|
+
completed += 1
|
|
470
|
+
|
|
471
|
+
# Progress indicator
|
|
472
|
+
if ci:
|
|
473
|
+
icon = "PASS" if status == "passed" else "FAIL" if status == "failed" else "XFAIL"
|
|
474
|
+
else:
|
|
475
|
+
icon = "ā
" if status == "passed" else "ā" if status == "failed" else "ā ļø"
|
|
476
|
+
if not quiet:
|
|
477
|
+
if ci:
|
|
478
|
+
print(f" [{completed}/{total}] {icon} {item.display_name} ({duration:.2f}s)")
|
|
479
|
+
else:
|
|
480
|
+
typer.echo(f" [{completed}/{total}] {icon} {item.display_name} ({duration:.2f}s)")
|
|
481
|
+
|
|
482
|
+
return RunResult(
|
|
483
|
+
item_id=item.item_id,
|
|
484
|
+
suite="batch",
|
|
485
|
+
group=item.group,
|
|
486
|
+
source_path=item.source_path,
|
|
487
|
+
status=status,
|
|
488
|
+
exit_code=result.returncode,
|
|
489
|
+
duration_seconds=duration,
|
|
490
|
+
start_time=start_time.isoformat(),
|
|
491
|
+
end_time=end_time.isoformat(),
|
|
492
|
+
error_message=error_message,
|
|
493
|
+
stdout=result.stdout,
|
|
494
|
+
stderr=result.stderr,
|
|
495
|
+
code_hash=item.code_hash,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
except subprocess.TimeoutExpired:
|
|
499
|
+
completed += 1
|
|
500
|
+
end_time = datetime.now(timezone.utc)
|
|
501
|
+
duration = (end_time - start_time).total_seconds()
|
|
502
|
+
|
|
503
|
+
if not quiet:
|
|
504
|
+
if ci:
|
|
505
|
+
print(f" [{completed}/{total}] TIMEOUT {item.display_name} (timeout)")
|
|
506
|
+
else:
|
|
507
|
+
typer.echo(f" [{completed}/{total}] ā±ļø {item.display_name} (timeout)")
|
|
508
|
+
|
|
509
|
+
return RunResult(
|
|
510
|
+
item_id=item.item_id,
|
|
511
|
+
suite="batch",
|
|
512
|
+
group=item.group,
|
|
513
|
+
source_path=item.source_path,
|
|
514
|
+
status="timeout",
|
|
515
|
+
duration_seconds=duration,
|
|
516
|
+
error_message=f"Exceeded {timeout}s timeout",
|
|
517
|
+
code_hash=item.code_hash,
|
|
518
|
+
)
|
|
519
|
+
except Exception as e:
|
|
520
|
+
completed += 1
|
|
521
|
+
if not quiet:
|
|
522
|
+
if ci:
|
|
523
|
+
print(f" [{completed}/{total}] FAIL {item.display_name} (error: {e})")
|
|
524
|
+
else:
|
|
525
|
+
typer.echo(f" [{completed}/{total}] ā {item.display_name} (error: {e})")
|
|
526
|
+
|
|
527
|
+
return RunResult(
|
|
528
|
+
item_id=item.item_id,
|
|
529
|
+
suite="batch",
|
|
530
|
+
group=item.group,
|
|
531
|
+
source_path=item.source_path,
|
|
532
|
+
status="failed",
|
|
533
|
+
error_message=str(e),
|
|
534
|
+
code_hash=item.code_hash,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# Run in parallel
|
|
538
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
539
|
+
futures = {executor.submit(run_item, item): item for item in items}
|
|
540
|
+
for future in as_completed(futures):
|
|
541
|
+
result = future.result()
|
|
542
|
+
results.append(result)
|
|
543
|
+
|
|
544
|
+
# Create report
|
|
545
|
+
report = RunReport(
|
|
546
|
+
results=results,
|
|
547
|
+
suite="batch",
|
|
548
|
+
source_path=search_path,
|
|
549
|
+
groups_run=[],
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Generate reports
|
|
553
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
554
|
+
reporter = SuiteReporter(output_dir)
|
|
555
|
+
reporter.save_logs(results)
|
|
556
|
+
reporter.generate_json(report)
|
|
557
|
+
reporter.generate_markdown(report)
|
|
558
|
+
reporter.generate_csv(report)
|
|
559
|
+
|
|
560
|
+
# Print summary
|
|
561
|
+
totals = report.totals
|
|
562
|
+
total_count = sum(totals.values())
|
|
563
|
+
|
|
564
|
+
if ci:
|
|
565
|
+
print("-" * 60)
|
|
566
|
+
print("")
|
|
567
|
+
print("SUMMARY")
|
|
568
|
+
print("=" * 60)
|
|
569
|
+
print(f" PASSED: {totals['passed']}")
|
|
570
|
+
print(f" FAILED: {totals['failed']}")
|
|
571
|
+
print(f" SKIPPED: {totals['skipped']}")
|
|
572
|
+
print(f" TIMEOUT: {totals['timeout']}")
|
|
573
|
+
print(" -----------------")
|
|
574
|
+
print(f" TOTAL: {total_count}")
|
|
575
|
+
print("=" * 60)
|
|
576
|
+
print(f"Reports: {output_dir}")
|
|
577
|
+
else:
|
|
578
|
+
typer.echo("-" * 60)
|
|
579
|
+
typer.echo("\nSUMMARY")
|
|
580
|
+
typer.echo("=" * 60)
|
|
581
|
+
typer.echo(f" ā
Passed: {totals['passed']}")
|
|
582
|
+
typer.echo(f" ā Failed: {totals['failed']}")
|
|
583
|
+
typer.echo(f" āļø Skipped: {totals['skipped']}")
|
|
584
|
+
typer.echo(f" ā±ļø Timeout: {totals['timeout']}")
|
|
585
|
+
typer.echo(" āāāāāāāāāāāāāāāāā")
|
|
586
|
+
typer.echo(f" Total: {total_count}")
|
|
587
|
+
typer.echo("=" * 60)
|
|
588
|
+
typer.echo(f"\nš Reports saved to: {output_dir}")
|
|
589
|
+
|
|
590
|
+
# Exit code
|
|
591
|
+
if totals['failed'] > 0 or totals['timeout'] > 0:
|
|
592
|
+
raise typer.Exit(1)
|
|
593
|
+
|
|
594
|
+
raise typer.Exit(0)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@app.command("list")
|
|
598
|
+
def batch_list(
|
|
599
|
+
path: Optional[Path] = typer.Option(
|
|
600
|
+
None,
|
|
601
|
+
"--path", "-p",
|
|
602
|
+
help="Path to search",
|
|
603
|
+
),
|
|
604
|
+
recursive: bool = typer.Option(
|
|
605
|
+
False,
|
|
606
|
+
"--sub", "--recursive", "-r",
|
|
607
|
+
help="Include subdirectories",
|
|
608
|
+
),
|
|
609
|
+
depth: Optional[int] = typer.Option(
|
|
610
|
+
None,
|
|
611
|
+
"--depth", "-d",
|
|
612
|
+
help="Maximum recursion depth",
|
|
613
|
+
),
|
|
614
|
+
include_tests: bool = typer.Option(
|
|
615
|
+
False,
|
|
616
|
+
"--include-tests",
|
|
617
|
+
help="Include test files",
|
|
618
|
+
),
|
|
619
|
+
show_groups: bool = typer.Option(
|
|
620
|
+
False,
|
|
621
|
+
"--groups",
|
|
622
|
+
help="Show available groups only",
|
|
623
|
+
),
|
|
624
|
+
):
|
|
625
|
+
"""
|
|
626
|
+
List discovered PraisonAI scripts without running them.
|
|
627
|
+
|
|
628
|
+
Examples:
|
|
629
|
+
praisonai batch list
|
|
630
|
+
praisonai batch list --sub
|
|
631
|
+
praisonai batch list --groups
|
|
632
|
+
"""
|
|
633
|
+
from praisonai.suite_runner import BatchSource
|
|
634
|
+
|
|
635
|
+
search_path = Path(path).resolve() if path else Path.cwd().resolve()
|
|
636
|
+
|
|
637
|
+
if not search_path.exists():
|
|
638
|
+
typer.echo(f"ā Path not found: {search_path}")
|
|
639
|
+
raise typer.Exit(2)
|
|
640
|
+
|
|
641
|
+
source = BatchSource(
|
|
642
|
+
root=search_path,
|
|
643
|
+
recursive=recursive,
|
|
644
|
+
depth=depth,
|
|
645
|
+
exclude_tests=not include_tests,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# Show groups only
|
|
649
|
+
if show_groups:
|
|
650
|
+
groups = source.get_groups()
|
|
651
|
+
if not groups:
|
|
652
|
+
typer.echo(f"No PraisonAI scripts found in {search_path}")
|
|
653
|
+
return
|
|
654
|
+
typer.echo(f"Available groups in {search_path}:\n")
|
|
655
|
+
for g in groups:
|
|
656
|
+
typer.echo(f" - {g}")
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
items = source.discover()
|
|
660
|
+
|
|
661
|
+
if not items:
|
|
662
|
+
typer.echo(f"No PraisonAI scripts found in {search_path}")
|
|
663
|
+
if not recursive:
|
|
664
|
+
typer.echo("Tip: Use --sub to include subdirectories")
|
|
665
|
+
return
|
|
666
|
+
|
|
667
|
+
typer.echo(f"Found {len(items)} PraisonAI scripts in {search_path}\n")
|
|
668
|
+
|
|
669
|
+
for idx, item in enumerate(items, 1):
|
|
670
|
+
rel_path = item.source_path.relative_to(search_path)
|
|
671
|
+
flags = []
|
|
672
|
+
if item.skip:
|
|
673
|
+
flags.append("skip")
|
|
674
|
+
if item.uses_agent:
|
|
675
|
+
flags.append("Agent")
|
|
676
|
+
if item.uses_agents:
|
|
677
|
+
flags.append("Agents")
|
|
678
|
+
if item.uses_workflow:
|
|
679
|
+
flags.append("Workflow")
|
|
680
|
+
|
|
681
|
+
flag_str = f" [{', '.join(flags)}]" if flags else ""
|
|
682
|
+
typer.echo(f" {idx:3}. [{item.group}] {rel_path}{flag_str}")
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@app.command("stats")
|
|
686
|
+
def batch_stats(
|
|
687
|
+
path: Optional[Path] = typer.Option(
|
|
688
|
+
None,
|
|
689
|
+
"--path", "-p",
|
|
690
|
+
help="Path to search",
|
|
691
|
+
),
|
|
692
|
+
recursive: bool = typer.Option(
|
|
693
|
+
False,
|
|
694
|
+
"--sub", "--recursive", "-r",
|
|
695
|
+
help="Include subdirectories",
|
|
696
|
+
),
|
|
697
|
+
depth: Optional[int] = typer.Option(
|
|
698
|
+
None,
|
|
699
|
+
"--depth", "-d",
|
|
700
|
+
help="Maximum recursion depth",
|
|
701
|
+
),
|
|
702
|
+
include_tests: bool = typer.Option(
|
|
703
|
+
False,
|
|
704
|
+
"--include-tests",
|
|
705
|
+
help="Include test files",
|
|
706
|
+
),
|
|
707
|
+
):
|
|
708
|
+
"""
|
|
709
|
+
Show statistics for PraisonAI scripts.
|
|
710
|
+
|
|
711
|
+
Displays counts by group, runnable status, and agent-centric usage.
|
|
712
|
+
|
|
713
|
+
Examples:
|
|
714
|
+
praisonai batch stats
|
|
715
|
+
praisonai batch stats --sub
|
|
716
|
+
"""
|
|
717
|
+
from collections import Counter
|
|
718
|
+
from praisonai.suite_runner import BatchSource
|
|
719
|
+
|
|
720
|
+
search_path = Path(path).resolve() if path else Path.cwd().resolve()
|
|
721
|
+
|
|
722
|
+
if not search_path.exists():
|
|
723
|
+
typer.echo(f"ā Path not found: {search_path}")
|
|
724
|
+
raise typer.Exit(2)
|
|
725
|
+
|
|
726
|
+
source = BatchSource(
|
|
727
|
+
root=search_path,
|
|
728
|
+
recursive=recursive,
|
|
729
|
+
depth=depth,
|
|
730
|
+
exclude_tests=not include_tests,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
items = source.discover()
|
|
734
|
+
|
|
735
|
+
if not items:
|
|
736
|
+
typer.echo(f"No PraisonAI scripts found in {search_path}")
|
|
737
|
+
return
|
|
738
|
+
|
|
739
|
+
# Calculate stats
|
|
740
|
+
group_counts = Counter(item.group for item in items)
|
|
741
|
+
runnable_by_group = Counter(item.group for item in items if item.runnable)
|
|
742
|
+
|
|
743
|
+
# Agent-centric stats
|
|
744
|
+
agent_count = sum(1 for item in items if item.uses_agent)
|
|
745
|
+
agents_count = sum(1 for item in items if item.uses_agents)
|
|
746
|
+
workflow_count = sum(1 for item in items if item.uses_workflow)
|
|
747
|
+
|
|
748
|
+
agent_by_group = Counter(item.group for item in items if item.uses_agent)
|
|
749
|
+
agents_by_group = Counter(item.group for item in items if item.uses_agents)
|
|
750
|
+
workflow_by_group = Counter(item.group for item in items if item.uses_workflow)
|
|
751
|
+
|
|
752
|
+
typer.echo("\nš Batch Execution Statistics")
|
|
753
|
+
typer.echo(f"Path: {search_path}")
|
|
754
|
+
typer.echo("=" * 80)
|
|
755
|
+
|
|
756
|
+
typer.echo(f"\n{'Group':<20} {'Total':>8} {'Runnable':>10} {'Agent':>8} {'Agents':>8} {'Workflow':>10}")
|
|
757
|
+
typer.echo("-" * 80)
|
|
758
|
+
|
|
759
|
+
for g in sorted(group_counts.keys()):
|
|
760
|
+
typer.echo(
|
|
761
|
+
f"{g:<20} {group_counts[g]:>8} {runnable_by_group[g]:>10} "
|
|
762
|
+
f"{agent_by_group[g]:>8} {agents_by_group[g]:>8} {workflow_by_group[g]:>10}"
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
typer.echo("-" * 80)
|
|
766
|
+
typer.echo(
|
|
767
|
+
f"{'TOTAL':<20} {len(items):>8} {sum(1 for i in items if i.runnable):>10} "
|
|
768
|
+
f"{agent_count:>8} {agents_count:>8} {workflow_count:>10}"
|
|
769
|
+
)
|
|
770
|
+
typer.echo()
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
@app.command("report")
|
|
774
|
+
def batch_report(
|
|
775
|
+
report_dir: Optional[Path] = typer.Option(
|
|
776
|
+
None,
|
|
777
|
+
"--dir", "-d",
|
|
778
|
+
help="Specific report directory (default: latest)",
|
|
779
|
+
),
|
|
780
|
+
format: str = typer.Option(
|
|
781
|
+
"summary",
|
|
782
|
+
"--format", "-f",
|
|
783
|
+
help="Output format: summary, failures, full",
|
|
784
|
+
),
|
|
785
|
+
):
|
|
786
|
+
"""
|
|
787
|
+
View the latest batch execution report.
|
|
788
|
+
|
|
789
|
+
Examples:
|
|
790
|
+
praisonai batch report
|
|
791
|
+
praisonai batch report --format failures
|
|
792
|
+
praisonai batch report --dir ~/Downloads/reports/batch/20240115_120000
|
|
793
|
+
"""
|
|
794
|
+
import json
|
|
795
|
+
|
|
796
|
+
# Find report directory
|
|
797
|
+
if report_dir:
|
|
798
|
+
target_dir = Path(report_dir)
|
|
799
|
+
else:
|
|
800
|
+
target_dir = _get_latest_report_dir()
|
|
801
|
+
|
|
802
|
+
if not target_dir or not target_dir.exists():
|
|
803
|
+
typer.echo("ā No batch reports found.")
|
|
804
|
+
typer.echo(" Run 'praisonai batch' first to generate a report.")
|
|
805
|
+
raise typer.Exit(1)
|
|
806
|
+
|
|
807
|
+
# Load JSON report
|
|
808
|
+
json_path = target_dir / "report.json"
|
|
809
|
+
if not json_path.exists():
|
|
810
|
+
typer.echo(f"ā Report not found: {json_path}")
|
|
811
|
+
raise typer.Exit(1)
|
|
812
|
+
|
|
813
|
+
report_data = json.loads(json_path.read_text())
|
|
814
|
+
|
|
815
|
+
typer.echo(f"\nš Batch Report: {target_dir.name}")
|
|
816
|
+
typer.echo("=" * 60)
|
|
817
|
+
|
|
818
|
+
# Summary
|
|
819
|
+
totals = report_data.get("totals", {})
|
|
820
|
+
typer.echo(f" ā
Passed: {totals.get('passed', 0)}")
|
|
821
|
+
typer.echo(f" ā Failed: {totals.get('failed', 0)}")
|
|
822
|
+
typer.echo(f" āļø Skipped: {totals.get('skipped', 0)}")
|
|
823
|
+
typer.echo(f" ā±ļø Timeout: {totals.get('timeout', 0)}")
|
|
824
|
+
typer.echo("=" * 60)
|
|
825
|
+
|
|
826
|
+
if format in ("failures", "full"):
|
|
827
|
+
results = report_data.get("results", [])
|
|
828
|
+
failures = [r for r in results if r.get("status") == "failed"]
|
|
829
|
+
|
|
830
|
+
if failures:
|
|
831
|
+
typer.echo("\nā FAILURES:")
|
|
832
|
+
typer.echo("-" * 60)
|
|
833
|
+
for r in failures:
|
|
834
|
+
typer.echo(f"\n {r.get('source_path', 'unknown')}")
|
|
835
|
+
if r.get("error_message"):
|
|
836
|
+
typer.echo(f" Error: {r['error_message'][:200]}")
|
|
837
|
+
|
|
838
|
+
if format == "full":
|
|
839
|
+
results = report_data.get("results", [])
|
|
840
|
+
typer.echo("\nš ALL RESULTS:")
|
|
841
|
+
typer.echo("-" * 60)
|
|
842
|
+
for r in results:
|
|
843
|
+
status = r.get("status", "unknown")
|
|
844
|
+
icon = {"passed": "ā
", "failed": "ā", "skipped": "āļø", "timeout": "ā±ļø"}.get(status, "?")
|
|
845
|
+
duration = r.get("duration_seconds", 0)
|
|
846
|
+
typer.echo(f" {icon} {r.get('source_path', 'unknown')} ({duration:.2f}s)")
|
|
847
|
+
|
|
848
|
+
typer.echo(f"\nš Full report: {target_dir}")
|