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,684 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides recipe management commands.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Recipe management")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("create")
|
|
13
|
+
def recipe_create(
|
|
14
|
+
goal: str = typer.Argument(..., help="Natural language goal for the recipe"),
|
|
15
|
+
output: str = typer.Option(".", "--output", "-o", help="Output directory"),
|
|
16
|
+
no_optimize: bool = typer.Option(False, "--no-optimize", help="Skip optimization loop"),
|
|
17
|
+
iterations: int = typer.Option(3, "--iterations", help="Max optimization iterations"),
|
|
18
|
+
threshold: float = typer.Option(8.0, "--threshold", help="Score threshold to stop"),
|
|
19
|
+
agents: str = typer.Option(None, "--agents", help="Custom agents (format: name:role=X,goal=Y;name2:...)"),
|
|
20
|
+
tools: str = typer.Option(None, "--tools", help="Custom tools per agent (format: agent:tool1,tool2;...)"),
|
|
21
|
+
agent_types: str = typer.Option(None, "--agent-types", help="Agent types (format: agent:image;agent2:audio;...)"),
|
|
22
|
+
):
|
|
23
|
+
"""Create a new recipe from a natural language goal.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
praisonai recipe create "Build a web scraper for news"
|
|
27
|
+
praisonai recipe create "Research AI trends" --no-optimize
|
|
28
|
+
praisonai recipe create "Research AI" --agents "researcher:role=AI Researcher,goal=Find papers"
|
|
29
|
+
praisonai recipe create "Research AI" --tools "researcher:internet_search,arxiv"
|
|
30
|
+
"""
|
|
31
|
+
from praisonai.cli.main import PraisonAI
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
argv = ['recipe', 'create', goal]
|
|
35
|
+
if output != ".":
|
|
36
|
+
argv.extend(['--output', output])
|
|
37
|
+
if no_optimize:
|
|
38
|
+
argv.append('--no-optimize')
|
|
39
|
+
if iterations != 3:
|
|
40
|
+
argv.extend(['--iterations', str(iterations)])
|
|
41
|
+
if threshold != 8.0:
|
|
42
|
+
argv.extend(['--threshold', str(threshold)])
|
|
43
|
+
if agents:
|
|
44
|
+
argv.extend(['--agents', agents])
|
|
45
|
+
if tools:
|
|
46
|
+
argv.extend(['--tools', tools])
|
|
47
|
+
if agent_types:
|
|
48
|
+
argv.extend(['--agent-types', agent_types])
|
|
49
|
+
|
|
50
|
+
original_argv = sys.argv
|
|
51
|
+
sys.argv = ['praisonai'] + argv
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
praison = PraisonAI()
|
|
55
|
+
praison.main()
|
|
56
|
+
except SystemExit:
|
|
57
|
+
pass
|
|
58
|
+
finally:
|
|
59
|
+
sys.argv = original_argv
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command("list")
|
|
63
|
+
def recipe_list():
|
|
64
|
+
"""List available recipes."""
|
|
65
|
+
from praisonai.cli.main import PraisonAI
|
|
66
|
+
import sys
|
|
67
|
+
|
|
68
|
+
argv = ['recipe', 'list']
|
|
69
|
+
|
|
70
|
+
original_argv = sys.argv
|
|
71
|
+
sys.argv = ['praisonai'] + argv
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
praison = PraisonAI()
|
|
75
|
+
praison.main()
|
|
76
|
+
except SystemExit:
|
|
77
|
+
pass
|
|
78
|
+
finally:
|
|
79
|
+
sys.argv = original_argv
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command("run")
|
|
83
|
+
def recipe_run(
|
|
84
|
+
name: str = typer.Argument(..., help="Recipe name"),
|
|
85
|
+
model: str = typer.Option(None, "--model", "-m", help="LLM model to use"),
|
|
86
|
+
var: list[str] = typer.Option(None, "--var", help="Variable override (key=value), can be used multiple times"),
|
|
87
|
+
save: bool = typer.Option(False, "--save", "-s", help="Save replay trace for debugging"),
|
|
88
|
+
trace_name: str = typer.Option(None, "--name", "-n", help="Custom trace name (e.g., run-abc123). Overwrites if exists."),
|
|
89
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
90
|
+
output: str = typer.Option(None, "--output", "-o", help="Output mode: silent, status, trace, verbose, debug"),
|
|
91
|
+
debug: bool = typer.Option(False, "--debug", help="Enable debug mode"),
|
|
92
|
+
profile: bool = typer.Option(False, "--profile", help="Enable profiling"),
|
|
93
|
+
deep_profile: bool = typer.Option(False, "--deep-profile", help="Enable deep profiling"),
|
|
94
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Validate without executing"),
|
|
95
|
+
force: bool = typer.Option(False, "--force", help="Force execution even with missing deps"),
|
|
96
|
+
json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
|
97
|
+
):
|
|
98
|
+
"""Run a recipe with optional variable overrides.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
praisonai recipe run ai-url-blog-generator --var url="https://example.com/article"
|
|
102
|
+
praisonai recipe run ai-dynamic-blog-generator --var topic="LangGraph 0.3" --var style="coding"
|
|
103
|
+
praisonai recipe run ai-wordpress-post-generator --save --verbose
|
|
104
|
+
praisonai recipe run ai-topic-gatherer --save --name my-test-run --var topic="AI"
|
|
105
|
+
"""
|
|
106
|
+
from praisonai.cli.main import PraisonAI
|
|
107
|
+
import sys
|
|
108
|
+
|
|
109
|
+
argv = ['recipe', 'run', name]
|
|
110
|
+
if model:
|
|
111
|
+
argv.extend(['--model', model])
|
|
112
|
+
|
|
113
|
+
# Pass --var directly to the recipe handler (features/recipe.py)
|
|
114
|
+
# Don't convert to --workflow-var as that gets consumed by main.py's parser
|
|
115
|
+
if var:
|
|
116
|
+
for v in var:
|
|
117
|
+
argv.extend(['--var', v])
|
|
118
|
+
|
|
119
|
+
# Forward all flags to the recipe handler
|
|
120
|
+
if save:
|
|
121
|
+
argv.append('--save')
|
|
122
|
+
if trace_name:
|
|
123
|
+
argv.extend(['--name', trace_name])
|
|
124
|
+
if verbose:
|
|
125
|
+
argv.append('--verbose')
|
|
126
|
+
if output:
|
|
127
|
+
argv.extend(['--output', output])
|
|
128
|
+
if debug:
|
|
129
|
+
argv.append('--debug')
|
|
130
|
+
if profile:
|
|
131
|
+
argv.append('--profile')
|
|
132
|
+
if deep_profile:
|
|
133
|
+
argv.append('--deep-profile')
|
|
134
|
+
if dry_run:
|
|
135
|
+
argv.append('--dry-run')
|
|
136
|
+
if force:
|
|
137
|
+
argv.append('--force')
|
|
138
|
+
if json_output:
|
|
139
|
+
argv.append('--json')
|
|
140
|
+
|
|
141
|
+
original_argv = sys.argv
|
|
142
|
+
sys.argv = ['praisonai'] + argv
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
praison = PraisonAI()
|
|
146
|
+
praison.main()
|
|
147
|
+
except SystemExit:
|
|
148
|
+
pass
|
|
149
|
+
finally:
|
|
150
|
+
sys.argv = original_argv
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command("info")
|
|
156
|
+
def recipe_info(
|
|
157
|
+
name: str = typer.Argument(..., help="Recipe name"),
|
|
158
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show full YAML content"),
|
|
159
|
+
):
|
|
160
|
+
"""Show detailed information about a recipe.
|
|
161
|
+
|
|
162
|
+
Displays recipe metadata, agents, steps, and tools.
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
praisonai recipe info image-to-blog-generator
|
|
166
|
+
praisonai recipe info url-to-blog-generator --verbose
|
|
167
|
+
"""
|
|
168
|
+
from pathlib import Path
|
|
169
|
+
from rich.console import Console
|
|
170
|
+
from rich.table import Table
|
|
171
|
+
from rich.panel import Panel
|
|
172
|
+
from rich.syntax import Syntax
|
|
173
|
+
import yaml
|
|
174
|
+
|
|
175
|
+
console = Console()
|
|
176
|
+
|
|
177
|
+
# Find recipe path
|
|
178
|
+
recipe_path = None
|
|
179
|
+
|
|
180
|
+
# Check template search paths
|
|
181
|
+
try:
|
|
182
|
+
from praisonai.recipe.core import get_template_search_paths
|
|
183
|
+
for search_path in get_template_search_paths():
|
|
184
|
+
candidate = search_path / name
|
|
185
|
+
if candidate.exists() and (candidate / "agents.yaml").exists():
|
|
186
|
+
recipe_path = candidate
|
|
187
|
+
break
|
|
188
|
+
except ImportError:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
# Also check agent_recipes package
|
|
192
|
+
if not recipe_path:
|
|
193
|
+
try:
|
|
194
|
+
import agent_recipes
|
|
195
|
+
if hasattr(agent_recipes, 'get_template_path'):
|
|
196
|
+
template_base = Path(agent_recipes.get_template_path(""))
|
|
197
|
+
candidate = template_base / name
|
|
198
|
+
if candidate.exists() and (candidate / "agents.yaml").exists():
|
|
199
|
+
recipe_path = candidate
|
|
200
|
+
except ImportError:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
# Check current directory
|
|
204
|
+
if not recipe_path:
|
|
205
|
+
candidate = Path(name)
|
|
206
|
+
if candidate.exists() and (candidate / "agents.yaml").exists():
|
|
207
|
+
recipe_path = candidate
|
|
208
|
+
|
|
209
|
+
if not recipe_path:
|
|
210
|
+
console.print(f"[red]❌ Recipe not found: {name}[/red]")
|
|
211
|
+
console.print("[dim]Hint: Run 'praisonai recipe list' to see available recipes[/dim]")
|
|
212
|
+
raise typer.Exit(1)
|
|
213
|
+
|
|
214
|
+
# Load agents.yaml
|
|
215
|
+
yaml_path = recipe_path / "agents.yaml"
|
|
216
|
+
try:
|
|
217
|
+
with open(yaml_path) as f:
|
|
218
|
+
config = yaml.safe_load(f)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
console.print(f"[red]❌ Error loading recipe: {e}[/red]")
|
|
221
|
+
raise typer.Exit(1)
|
|
222
|
+
|
|
223
|
+
# Display recipe info
|
|
224
|
+
console.print()
|
|
225
|
+
console.print(Panel(f"[bold cyan]{name}[/bold cyan]", title="Recipe Info"))
|
|
226
|
+
|
|
227
|
+
# Metadata
|
|
228
|
+
metadata = config.get("metadata", {})
|
|
229
|
+
if metadata:
|
|
230
|
+
console.print("\n[bold]Metadata:[/bold]")
|
|
231
|
+
meta_table = Table(show_header=False, box=None)
|
|
232
|
+
meta_table.add_column("Key", style="dim")
|
|
233
|
+
meta_table.add_column("Value")
|
|
234
|
+
for key, value in metadata.items():
|
|
235
|
+
if isinstance(value, list):
|
|
236
|
+
value = ", ".join(str(v) for v in value)
|
|
237
|
+
elif isinstance(value, dict):
|
|
238
|
+
value = str(value)
|
|
239
|
+
meta_table.add_row(key, str(value))
|
|
240
|
+
console.print(meta_table)
|
|
241
|
+
|
|
242
|
+
# Topic
|
|
243
|
+
topic = config.get("topic", "Not specified")
|
|
244
|
+
console.print(f"\n[bold]Topic:[/bold] {topic}")
|
|
245
|
+
|
|
246
|
+
# Agents
|
|
247
|
+
agents = config.get("agents", {})
|
|
248
|
+
if agents:
|
|
249
|
+
console.print(f"\n[bold]Agents ({len(agents)}):[/bold]")
|
|
250
|
+
agent_table = Table(show_header=True, header_style="bold")
|
|
251
|
+
agent_table.add_column("Name", style="green")
|
|
252
|
+
agent_table.add_column("Role")
|
|
253
|
+
agent_table.add_column("Tools", style="cyan")
|
|
254
|
+
agent_table.add_column("LLM", style="dim")
|
|
255
|
+
|
|
256
|
+
for agent_name, agent_config in agents.items():
|
|
257
|
+
tools = agent_config.get("tools", [])
|
|
258
|
+
tools_str = ", ".join(tools) if tools else "-"
|
|
259
|
+
llm = agent_config.get("llm", "default")
|
|
260
|
+
agent_table.add_row(
|
|
261
|
+
agent_name,
|
|
262
|
+
agent_config.get("role", "-"),
|
|
263
|
+
tools_str,
|
|
264
|
+
str(llm)
|
|
265
|
+
)
|
|
266
|
+
console.print(agent_table)
|
|
267
|
+
|
|
268
|
+
# Steps
|
|
269
|
+
steps = config.get("steps", [])
|
|
270
|
+
if steps:
|
|
271
|
+
console.print(f"\n[bold]Steps ({len(steps)}):[/bold]")
|
|
272
|
+
step_table = Table(show_header=True, header_style="bold")
|
|
273
|
+
step_table.add_column("#", style="dim")
|
|
274
|
+
step_table.add_column("Name", style="green")
|
|
275
|
+
step_table.add_column("Agent", style="cyan")
|
|
276
|
+
step_table.add_column("Action (truncated)")
|
|
277
|
+
|
|
278
|
+
for i, step in enumerate(steps, 1):
|
|
279
|
+
step_name = step.get("name", f"step_{i}")
|
|
280
|
+
agent = step.get("agent", "-")
|
|
281
|
+
action = step.get("action", "")
|
|
282
|
+
# Truncate action for display
|
|
283
|
+
action_preview = action[:60].replace("\n", " ") + "..." if len(action) > 60 else action.replace("\n", " ")
|
|
284
|
+
step_table.add_row(str(i), step_name, agent, action_preview)
|
|
285
|
+
console.print(step_table)
|
|
286
|
+
|
|
287
|
+
# Tools.py
|
|
288
|
+
tools_path = recipe_path / "tools.py"
|
|
289
|
+
if tools_path.exists():
|
|
290
|
+
console.print("\n[bold]Custom Tools:[/bold] [green]✓ tools.py present[/green]")
|
|
291
|
+
else:
|
|
292
|
+
console.print("\n[bold]Custom Tools:[/bold] [dim]No tools.py[/dim]")
|
|
293
|
+
|
|
294
|
+
# Full YAML if verbose
|
|
295
|
+
if verbose:
|
|
296
|
+
console.print("\n[bold]Full YAML:[/bold]")
|
|
297
|
+
with open(yaml_path) as f:
|
|
298
|
+
yaml_content = f.read()
|
|
299
|
+
syntax = Syntax(yaml_content, "yaml", theme="monokai", line_numbers=True)
|
|
300
|
+
console.print(syntax)
|
|
301
|
+
|
|
302
|
+
# Path
|
|
303
|
+
console.print(f"\n[dim]Path: {recipe_path}[/dim]")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@app.command("install")
|
|
307
|
+
def recipe_install(
|
|
308
|
+
source: str = typer.Argument(..., help="Recipe source (path or URL)"),
|
|
309
|
+
):
|
|
310
|
+
"""Install a recipe."""
|
|
311
|
+
from praisonai.cli.main import PraisonAI
|
|
312
|
+
import sys
|
|
313
|
+
|
|
314
|
+
argv = ['recipe', 'install', source]
|
|
315
|
+
|
|
316
|
+
original_argv = sys.argv
|
|
317
|
+
sys.argv = ['praisonai'] + argv
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
praison = PraisonAI()
|
|
321
|
+
praison.main()
|
|
322
|
+
except SystemExit:
|
|
323
|
+
pass
|
|
324
|
+
finally:
|
|
325
|
+
sys.argv = original_argv
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@app.command("judge")
|
|
329
|
+
def recipe_judge(
|
|
330
|
+
trace_id: str = typer.Argument(..., help="Trace ID to judge (e.g., run-abc123)"),
|
|
331
|
+
yaml_file: str = typer.Option(None, "--yaml", "-y", help="YAML file path for fix recommendations"),
|
|
332
|
+
output: str = typer.Option(None, "--output", "-o", help="Output plan file (default: judge_plan.yaml)"),
|
|
333
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show plan without saving"),
|
|
334
|
+
context: bool = typer.Option(False, "--context", help="Evaluate context flow between agents (default mode)"),
|
|
335
|
+
memory: bool = typer.Option(False, "--memory", help="Evaluate memory utilization (store/search effectiveness)"),
|
|
336
|
+
knowledge: bool = typer.Option(False, "--knowledge", help="Evaluate knowledge retrieval effectiveness"),
|
|
337
|
+
goal: str = typer.Option(None, "--goal", "-g", help="Override recipe goal for evaluation (extracted from YAML if not provided)"),
|
|
338
|
+
chunked: bool = typer.Option(False, "--chunked", help="Use chunked evaluation for large outputs (preserves all content)"),
|
|
339
|
+
no_auto_chunk: bool = typer.Option(False, "--no-auto-chunk", help="Disable automatic chunking (auto-chunk is enabled by default)"),
|
|
340
|
+
chunk_size: int = typer.Option(8000, "--chunk-size", help="Max characters per chunk (default: 8000)"),
|
|
341
|
+
max_chunks: int = typer.Option(5, "--max-chunks", help="Max chunks per agent (default: 5)"),
|
|
342
|
+
aggregation: str = typer.Option("weighted_average", "--aggregation", help="Score aggregation: weighted_average, average, min, max"),
|
|
343
|
+
):
|
|
344
|
+
"""Judge a recipe execution trace and generate fix recommendations.
|
|
345
|
+
|
|
346
|
+
Similar to 'terraform plan' - analyzes the trace and generates a plan of fixes.
|
|
347
|
+
|
|
348
|
+
The judge now includes:
|
|
349
|
+
- Dynamic failure detection (LLM determines if task failed)
|
|
350
|
+
- Previous step quality context (each agent sees how prior agents performed)
|
|
351
|
+
- Recipe goal evaluation (evaluates against overall workflow objective)
|
|
352
|
+
- Input validation status (detects unresolved template variables)
|
|
353
|
+
|
|
354
|
+
Evaluation Modes:
|
|
355
|
+
- --context: Evaluate context flow between agents (default)
|
|
356
|
+
- --memory: Evaluate memory utilization (store/search effectiveness)
|
|
357
|
+
- --knowledge: Evaluate knowledge retrieval effectiveness
|
|
358
|
+
|
|
359
|
+
Examples:
|
|
360
|
+
praisonai recipe judge run-abc123
|
|
361
|
+
praisonai recipe judge run-abc123 --goal "Analyze image and create blog post"
|
|
362
|
+
praisonai recipe judge run-abc123 --memory
|
|
363
|
+
praisonai recipe judge run-abc123 --knowledge
|
|
364
|
+
praisonai recipe judge run-abc123 --yaml agents.yaml --output plan.yaml
|
|
365
|
+
praisonai recipe judge run-abc123 --dry-run
|
|
366
|
+
"""
|
|
367
|
+
from praisonai.replay import (
|
|
368
|
+
ContextTraceReader,
|
|
369
|
+
ContextEffectivenessJudge,
|
|
370
|
+
generate_plan_from_report,
|
|
371
|
+
format_judge_report,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Determine evaluation mode (default to context)
|
|
375
|
+
mode = "context"
|
|
376
|
+
if memory:
|
|
377
|
+
mode = "memory"
|
|
378
|
+
elif knowledge:
|
|
379
|
+
mode = "knowledge"
|
|
380
|
+
|
|
381
|
+
mode_emoji = {"context": "🔄", "memory": "🧠", "knowledge": "📚"}
|
|
382
|
+
# auto_chunk is True by default unless --no-auto-chunk is specified
|
|
383
|
+
auto_chunk = not no_auto_chunk
|
|
384
|
+
chunked_indicator = " [chunked]" if chunked else (" [auto-chunk]" if auto_chunk else "")
|
|
385
|
+
print(f"{mode_emoji.get(mode, '🔍')} Judging trace: {trace_id} (mode: {mode}){chunked_indicator}")
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
reader = ContextTraceReader(trace_id)
|
|
389
|
+
events = reader.get_all()
|
|
390
|
+
|
|
391
|
+
if not events:
|
|
392
|
+
print(f"❌ No events found for trace: {trace_id}")
|
|
393
|
+
raise typer.Exit(1)
|
|
394
|
+
|
|
395
|
+
print(f" 📊 Found {len(events)} events")
|
|
396
|
+
if chunked:
|
|
397
|
+
print(f" 📦 Chunked evaluation: {chunk_size} chars/chunk, max {max_chunks} chunks, {aggregation} aggregation")
|
|
398
|
+
elif auto_chunk:
|
|
399
|
+
print(" 🔄 Auto-chunk: will chunk if content exceeds model context window")
|
|
400
|
+
|
|
401
|
+
# Run judge with mode-specific evaluation
|
|
402
|
+
judge = ContextEffectivenessJudge(
|
|
403
|
+
mode=mode,
|
|
404
|
+
chunked=chunked,
|
|
405
|
+
auto_chunk=auto_chunk,
|
|
406
|
+
chunk_size=chunk_size,
|
|
407
|
+
max_chunks=max_chunks,
|
|
408
|
+
aggregation_strategy=aggregation,
|
|
409
|
+
)
|
|
410
|
+
report = judge.judge_trace(events, session_id=trace_id, yaml_file=yaml_file)
|
|
411
|
+
|
|
412
|
+
# Display report
|
|
413
|
+
print(format_judge_report(report))
|
|
414
|
+
|
|
415
|
+
# Generate plan if yaml_file provided
|
|
416
|
+
if yaml_file:
|
|
417
|
+
plan = generate_plan_from_report(report, yaml_file=yaml_file)
|
|
418
|
+
print(plan.format_summary())
|
|
419
|
+
|
|
420
|
+
if not dry_run:
|
|
421
|
+
output_file = output or "judge_plan.yaml"
|
|
422
|
+
plan.save(output_file)
|
|
423
|
+
print(f"\n✅ Plan saved to: {output_file}")
|
|
424
|
+
print(f" Run 'praisonai recipe apply {output_file}' to apply fixes")
|
|
425
|
+
else:
|
|
426
|
+
print("\n💡 Tip: Add --yaml <file> to generate actionable fix recommendations")
|
|
427
|
+
|
|
428
|
+
except Exception as e:
|
|
429
|
+
print(f"❌ Error: {e}")
|
|
430
|
+
raise typer.Exit(1)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@app.command("optimize")
|
|
434
|
+
def recipe_optimize(
|
|
435
|
+
recipe_path: str = typer.Argument(..., help="Path to recipe folder"),
|
|
436
|
+
target: str = typer.Argument(None, help="Optional optimization target (e.g., 'improve error handling')"),
|
|
437
|
+
iterations: int = typer.Option(3, "--iterations", "-i", help="Maximum optimization iterations"),
|
|
438
|
+
threshold: float = typer.Option(8.0, "--threshold", "-t", help="Score threshold to stop (1-10)"),
|
|
439
|
+
input_data: str = typer.Option("", "--input", help="Input data for recipe runs"),
|
|
440
|
+
criteria: str = typer.Option(None, "--criteria", "-c", help="Custom evaluation criteria (for domain-agnostic optimization)"),
|
|
441
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
442
|
+
):
|
|
443
|
+
"""Optimize a recipe using AI judge feedback.
|
|
444
|
+
|
|
445
|
+
Iteratively runs the recipe, judges output, and applies improvements.
|
|
446
|
+
|
|
447
|
+
Supports custom criteria for domain-agnostic optimization (e.g., water flow, data pipelines).
|
|
448
|
+
|
|
449
|
+
Examples:
|
|
450
|
+
praisonai recipe optimize my-recipe
|
|
451
|
+
praisonai recipe optimize my-recipe "improve error handling"
|
|
452
|
+
praisonai recipe optimize my-recipe --iterations 5 --threshold 9.0
|
|
453
|
+
praisonai recipe optimize my-recipe --input '{"query": "test"}'
|
|
454
|
+
praisonai recipe optimize my-recipe --criteria "Water flow is optimal: no leaks, pressure within range"
|
|
455
|
+
"""
|
|
456
|
+
from pathlib import Path
|
|
457
|
+
import logging
|
|
458
|
+
|
|
459
|
+
if verbose:
|
|
460
|
+
logging.basicConfig(level=logging.INFO)
|
|
461
|
+
|
|
462
|
+
recipe_path_obj = Path(recipe_path)
|
|
463
|
+
if not recipe_path_obj.exists():
|
|
464
|
+
print(f"❌ Recipe not found: {recipe_path}")
|
|
465
|
+
raise typer.Exit(1)
|
|
466
|
+
|
|
467
|
+
print(f"🔄 Optimizing recipe: {recipe_path}")
|
|
468
|
+
print(f" Max iterations: {iterations}")
|
|
469
|
+
print(f" Score threshold: {threshold}")
|
|
470
|
+
if target:
|
|
471
|
+
print(f" Target: {target}")
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
from praisonai.cli.features.recipe_optimizer import RecipeOptimizer
|
|
475
|
+
|
|
476
|
+
# Create custom judge if criteria provided
|
|
477
|
+
custom_judge = None
|
|
478
|
+
if criteria:
|
|
479
|
+
try:
|
|
480
|
+
from praisonaiagents.eval import Judge
|
|
481
|
+
custom_judge = Judge(criteria=criteria)
|
|
482
|
+
print(f" Custom criteria: {criteria[:50]}{'...' if len(criteria) > 50 else ''}")
|
|
483
|
+
except ImportError:
|
|
484
|
+
print(" ⚠️ Custom criteria requires praisonaiagents. Using default judge.")
|
|
485
|
+
|
|
486
|
+
optimizer = RecipeOptimizer(
|
|
487
|
+
max_iterations=iterations,
|
|
488
|
+
score_threshold=threshold,
|
|
489
|
+
judge=custom_judge,
|
|
490
|
+
criteria=criteria,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
final_report = optimizer.optimize(
|
|
494
|
+
recipe_path=recipe_path_obj,
|
|
495
|
+
input_data=input_data,
|
|
496
|
+
optimization_target=target,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if final_report:
|
|
500
|
+
score = getattr(final_report, 'overall_score', 0)
|
|
501
|
+
print(f"\n{'✅' if score >= threshold else '📊'} Final score: {score}/10")
|
|
502
|
+
|
|
503
|
+
if score >= threshold:
|
|
504
|
+
print(" Recipe optimization complete!")
|
|
505
|
+
else:
|
|
506
|
+
print(" Threshold not reached. Consider running more iterations.")
|
|
507
|
+
else:
|
|
508
|
+
print("❌ Optimization failed - no report generated")
|
|
509
|
+
raise typer.Exit(1)
|
|
510
|
+
|
|
511
|
+
except ImportError as e:
|
|
512
|
+
print(f"❌ Missing dependency: {e}")
|
|
513
|
+
print(" Install with: pip install litellm")
|
|
514
|
+
raise typer.Exit(1)
|
|
515
|
+
except Exception as e:
|
|
516
|
+
print(f"❌ Error: {e}")
|
|
517
|
+
raise typer.Exit(1)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@app.command("serve")
|
|
521
|
+
def recipe_serve(
|
|
522
|
+
recipe: str = typer.Argument(None, help="Recipe name or path to serve (optional, serves all if not specified)"),
|
|
523
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Server host"),
|
|
524
|
+
port: int = typer.Option(8765, "--port", "-p", help="Server port"),
|
|
525
|
+
reload: bool = typer.Option(False, "--reload", "-r", help="Enable hot reload"),
|
|
526
|
+
workers: int = typer.Option(1, "--workers", "-w", help="Number of worker processes"),
|
|
527
|
+
config: str = typer.Option(None, "--config", "-c", help="Path to serve.yaml config file"),
|
|
528
|
+
api_key: str = typer.Option(None, "--api-key", help="API key for authentication"),
|
|
529
|
+
cors: str = typer.Option("*", "--cors", help="CORS origins (comma-separated or *)"),
|
|
530
|
+
metrics: bool = typer.Option(False, "--metrics", help="Enable /metrics endpoint"),
|
|
531
|
+
admin: bool = typer.Option(False, "--admin", help="Enable /admin endpoints"),
|
|
532
|
+
):
|
|
533
|
+
"""Start HTTP server for recipe execution.
|
|
534
|
+
|
|
535
|
+
DEPRECATED: Use `praisonai serve recipe` instead.
|
|
536
|
+
|
|
537
|
+
Serves recipes as REST API endpoints.
|
|
538
|
+
|
|
539
|
+
Examples:
|
|
540
|
+
praisonai recipe serve
|
|
541
|
+
praisonai recipe serve my-recipe --port 8080
|
|
542
|
+
praisonai recipe serve --host 0.0.0.0 --port 8000
|
|
543
|
+
praisonai recipe serve --config serve.yaml
|
|
544
|
+
praisonai recipe serve --api-key my-secret-key
|
|
545
|
+
praisonai recipe serve --workers 4
|
|
546
|
+
"""
|
|
547
|
+
import sys
|
|
548
|
+
|
|
549
|
+
# Print deprecation warning
|
|
550
|
+
print("\n\033[93m⚠ DEPRECATION WARNING:\033[0m", file=sys.stderr)
|
|
551
|
+
print("\033[93m'praisonai recipe serve' is deprecated and will be removed in a future version.\033[0m", file=sys.stderr)
|
|
552
|
+
print("\033[93mPlease use 'praisonai serve recipe' instead.\033[0m\n", file=sys.stderr)
|
|
553
|
+
|
|
554
|
+
print(f"🚀 Starting recipe server...")
|
|
555
|
+
print(f" Host: {host}")
|
|
556
|
+
print(f" Port: {port}")
|
|
557
|
+
if recipe:
|
|
558
|
+
print(f" Recipe: {recipe}")
|
|
559
|
+
if config:
|
|
560
|
+
print(f" Config: {config}")
|
|
561
|
+
if api_key:
|
|
562
|
+
print(f" Auth: API Key enabled")
|
|
563
|
+
if workers > 1:
|
|
564
|
+
print(f" Workers: {workers}")
|
|
565
|
+
|
|
566
|
+
auth_mode = serve_config.get("auth", "none")
|
|
567
|
+
if host not in ("127.0.0.1", "localhost") and auth_mode == "none" and not api_key:
|
|
568
|
+
print(
|
|
569
|
+
"\n\033[91mError:\033[0m Auth required for non-localhost binding. "
|
|
570
|
+
"Use --api-key or --auth api-key",
|
|
571
|
+
file=sys.stderr,
|
|
572
|
+
)
|
|
573
|
+
raise typer.Exit(1)
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
from praisonai.recipe.serve import serve, load_config
|
|
577
|
+
|
|
578
|
+
# Load config from file if provided
|
|
579
|
+
serve_config = {}
|
|
580
|
+
if config:
|
|
581
|
+
serve_config = load_config(config)
|
|
582
|
+
|
|
583
|
+
# Override with CLI options
|
|
584
|
+
if api_key:
|
|
585
|
+
serve_config["api_key"] = api_key
|
|
586
|
+
serve_config["auth"] = "api-key"
|
|
587
|
+
if cors:
|
|
588
|
+
serve_config["cors_origins"] = cors
|
|
589
|
+
if metrics:
|
|
590
|
+
serve_config["enable_metrics"] = True
|
|
591
|
+
if admin:
|
|
592
|
+
serve_config["enable_admin"] = True
|
|
593
|
+
if recipe:
|
|
594
|
+
serve_config["recipes"] = [recipe]
|
|
595
|
+
|
|
596
|
+
print(f"\n📡 Server running at http://{host}:{port}")
|
|
597
|
+
print(f" Health: http://{host}:{port}/health")
|
|
598
|
+
print(f" Recipes: http://{host}:{port}/v1/recipes")
|
|
599
|
+
print(f" OpenAPI: http://{host}:{port}/openapi.json")
|
|
600
|
+
if metrics:
|
|
601
|
+
print(f" Metrics: http://{host}:{port}/metrics")
|
|
602
|
+
print(f"\n Press Ctrl+C to stop\n")
|
|
603
|
+
|
|
604
|
+
serve(
|
|
605
|
+
host=host,
|
|
606
|
+
port=port,
|
|
607
|
+
reload=reload,
|
|
608
|
+
config=serve_config,
|
|
609
|
+
workers=workers,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
except ImportError as e:
|
|
613
|
+
print(f"❌ Missing dependency: {e}")
|
|
614
|
+
print(" Install with: pip install praisonai[serve]")
|
|
615
|
+
raise typer.Exit(1)
|
|
616
|
+
except Exception as e:
|
|
617
|
+
print(f"❌ Error: {e}")
|
|
618
|
+
raise typer.Exit(1)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
@app.command("apply")
|
|
622
|
+
def recipe_apply(
|
|
623
|
+
plan_file: str = typer.Argument(..., help="Plan file to apply (from 'recipe judge')"),
|
|
624
|
+
confirm: bool = typer.Option(False, "--confirm", "-c", help="Apply without confirmation"),
|
|
625
|
+
fix_ids: str = typer.Option(None, "--fix-ids", help="Comma-separated fix IDs to apply"),
|
|
626
|
+
no_backup: bool = typer.Option(False, "--no-backup", help="Skip creating backup"),
|
|
627
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview changes without applying"),
|
|
628
|
+
):
|
|
629
|
+
"""Apply fixes from a judge plan to YAML files.
|
|
630
|
+
|
|
631
|
+
Similar to 'terraform apply' - applies the planned fixes.
|
|
632
|
+
|
|
633
|
+
Examples:
|
|
634
|
+
praisonai recipe apply judge_plan.yaml
|
|
635
|
+
praisonai recipe apply judge_plan.yaml --confirm
|
|
636
|
+
praisonai recipe apply judge_plan.yaml --fix-ids fix_001,fix_002
|
|
637
|
+
praisonai recipe apply judge_plan.yaml --dry-run
|
|
638
|
+
"""
|
|
639
|
+
from praisonai.replay import JudgePlan, PlanApplier
|
|
640
|
+
from pathlib import Path
|
|
641
|
+
|
|
642
|
+
print(f"📋 Loading plan: {plan_file}")
|
|
643
|
+
|
|
644
|
+
try:
|
|
645
|
+
if not Path(plan_file).exists():
|
|
646
|
+
print(f"❌ Plan file not found: {plan_file}")
|
|
647
|
+
raise typer.Exit(1)
|
|
648
|
+
|
|
649
|
+
plan = JudgePlan.load(plan_file)
|
|
650
|
+
applier = PlanApplier(plan)
|
|
651
|
+
|
|
652
|
+
# Show preview
|
|
653
|
+
print(applier.preview())
|
|
654
|
+
|
|
655
|
+
if dry_run:
|
|
656
|
+
print("\n🔍 Dry run - no changes applied")
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
# Confirm before applying
|
|
660
|
+
if not confirm:
|
|
661
|
+
response = typer.prompt("Apply these fixes? [y/N]", default="n")
|
|
662
|
+
if response.lower() != "y":
|
|
663
|
+
print("❌ Aborted")
|
|
664
|
+
raise typer.Exit(0)
|
|
665
|
+
|
|
666
|
+
# Parse fix IDs if provided
|
|
667
|
+
fix_id_list = None
|
|
668
|
+
if fix_ids:
|
|
669
|
+
fix_id_list = [f.strip() for f in fix_ids.split(",")]
|
|
670
|
+
|
|
671
|
+
# Apply fixes
|
|
672
|
+
result = applier.apply(backup=not no_backup, fix_ids=fix_id_list)
|
|
673
|
+
|
|
674
|
+
# Validate
|
|
675
|
+
if applier.validate():
|
|
676
|
+
print("✅ YAML validation passed")
|
|
677
|
+
else:
|
|
678
|
+
print("⚠️ YAML validation failed - consider rolling back")
|
|
679
|
+
if result.get("backup_path"):
|
|
680
|
+
print(f" Backup available at: {result['backup_path']}")
|
|
681
|
+
|
|
682
|
+
except Exception as e:
|
|
683
|
+
print(f"❌ Error: {e}")
|
|
684
|
+
raise typer.Exit(1)
|