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.
Files changed (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. 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)