Invoke recipes from any language by shelling out to the PraisonAI CLI and parsing JSON stdout.
from praisonaiagents import Agent
agent = Agent (
name = " CLI Orchestrator " ,
instructions = " Describe when subprocess CLI recipe runs fit CI/CD. " ,
)
agent . start ( " How do I run a recipe from a GitHub Actions job? " )
The user spawns praisonai recipe run; stdout JSON carries the result back to the caller.
When to Use : Shell scripts, CI/CD pipelines, batch processing, or when you need language-agnostic recipe invocation without running a server.
How It Works
The CLI model spawns a subprocess to run recipes. Output is captured via stdout in JSON format for easy parsing.
Pros & Cons
Language-agnostic - Works from any language that can spawn processes
Simple JSON output - Easy to parse in any language
No SDK dependency - Calling app doesn’t need praisonai installed
Process isolation - Recipe runs in separate process
Easy debugging - Run commands manually to test
Process spawn overhead - ~100-500ms per invocation
Stdout/stderr parsing - Need to handle output parsing
No streaming - Unless using --stream flag
Environment setup - CLI must be in PATH
Step-by-Step Tutorial
List Available Recipes
# Human-readable format
praisonai recipe list
# JSON format for parsing
praisonai recipe list --json
Get Recipe Info
praisonai recipe info my-recipe --json
Run Recipe with JSON Output
praisonai recipe run my-recipe \
--input ' {"query": "Hello"} ' \
--json
Parse Output in Your Application
import subprocess
import json
result = subprocess . run (
[ " praisonai " , " recipe " , " run " , " my-recipe " ,
" --input " , ' {"query": "Hello"} ' , " --json " ],
capture_output = True ,
text = True ,
timeout = 60
)
if result . returncode == 0 :
data = json . loads ( result . stdout )
print ( f "Run ID: { data [ ' run_id ' ] } " )
print ( f "Output: { data [ ' output ' ] } " )
else :
print ( f "Error: { result . stderr } " )
const { execSync } = require ( ' child_process ' );
try {
const result = execSync (
' praisonai recipe run my-recipe --input \' {"query": "Hello"} \' --json ' ,
{ encoding : ' utf-8 ' , timeout : 60000 }
);
const data = JSON . parse ( result );
console . log ( ` Run ID: ${ data . run_id } ` );
console . log ( ` Output: ${ data . output } ` );
} catch ( error ) {
console . error ( ` Error: ${ error . message } ` );
}
result =$( praisonai recipe run my-recipe \
--input ' {"query": "Hello"} ' \
--json )
echo " $result " | jq ' .output '
package main
import (
" encoding/json "
" os/exec "
)
func runRecipe ( name string , input map [ string ] interface {}) ( map [ string ] interface {}, error ) {
inputJSON , _ := json . Marshal ( input )
cmd := exec . Command ( " praisonai " , " recipe " , " run " , name ,
" --input " , string ( inputJSON ), " --json " )
output , err := cmd . Output ()
if err != nil {
return nil , err
}
var result map [ string ] interface {}
json . Unmarshal ( output , & result )
return result , nil
}
Production-Ready Example
import subprocess
import json
import logging
from typing import Any , Dict , Optional
logging . basicConfig ( level = logging . INFO )
logger = logging . getLogger ( __name__ )
class RecipeCLIClient :
""" Production-ready CLI client for recipe invocation. """
def __init__ ( self , timeout : int = 60 , retries : int = 3 ):
self . timeout = timeout
self . retries = retries
def run (
self ,
recipe_name : str ,
input_data : Dict [ str , Any ],
dry_run : bool = False
) -> Dict [ str , Any ]:
""" Run a recipe via CLI with retries. """
cmd = [
" praisonai " , " recipe " , " run " , recipe_name ,
" --input " , json . dumps ( input_data ),
" --json "
]
if dry_run :
cmd . append ( " --dry-run " )
last_error = None
for attempt in range ( self . retries ):
try :
result = subprocess . run (
cmd ,
capture_output = True ,
text = True ,
timeout = self . timeout
)
if result . returncode == 0 :
data = json . loads ( result . stdout )
logger . info ( f "Recipe { recipe_name } completed: { data . get ( ' run_id ' ) } " )
return data
else :
logger . warning ( f "Recipe failed (attempt { attempt + 1 } ): { result . stderr } " )
last_error = result . stderr
except subprocess . TimeoutExpired :
logger . error ( f "Recipe timeout (attempt { attempt + 1 } )" )
last_error = " Timeout "
except json . JSONDecodeError as e :
logger . error ( f "JSON parse error: { e } " )
last_error = str ( e )
raise RuntimeError ( f "Recipe { recipe_name } failed: { last_error } " )
def list_recipes ( self ) -> list :
""" List available recipes. """
result = subprocess . run (
[ " praisonai " , " recipe " , " list " , " --json " ],
capture_output = True ,
text = True ,
timeout = 30
)
if result . returncode == 0 :
return json . loads ( result . stdout )
return []
# Usage
if __name__ == " __main__ " :
client = RecipeCLIClient ( timeout = 60 , retries = 3 )
# List recipes
recipes = client . list_recipes ()
print ( f "Found { len ( recipes ) } recipes" )
# Run a recipe
result = client . run (
" support-reply-drafter " ,
{ " ticket_id " : " T-123 " , " message " : " I need help " }
)
print ( result [ " output " ])
CI/CD Integration
GitHub Actions
name : Run Recipe
on : [ push ]
jobs :
run-recipe :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Set up Python
uses : actions/setup-python@v5
with :
python-version : ' 3.11 '
- name : Install PraisonAI
run : pip install praisonai
- name : Run Recipe
env :
OPENAI_API_KEY : ${{ secrets.OPENAI_API_KEY }}
run : |
praisonai recipe run code-review \
--input '{"diff": "..."}' \
--json > result.json
- name : Upload Result
uses : actions/upload-artifact@v4
with :
name : recipe-result
path : result.json
GitLab CI
run-recipe :
image : python:3.11
script :
- pip install praisonai
- praisonai recipe run my-recipe --input '{"query" : " test " } ' --json
variables:
OPENAI_API_KEY: $OPENAI_API_KEY
Troubleshooting
Command not found: praisonai
Add praisonai to your PATH or use the full path: # Find installation path
python -c " import praisonai; print(praisonai.__file__) "
# Or use python -m
python -m praisonai recipe run my-recipe --json
Ensure you’re using the --json flag and only parsing stdout: result = subprocess . run ( cmd , capture_output = True , text = True )
# Only parse stdout, not stderr
data = json . loads ( result . stdout )
Check stderr for error details: if result . returncode != 0 :
print ( f "Exit code: { result . returncode } " )
print ( f "Error: { result . stderr } " )
Common exit codes:
1: General error
2: Validation error
7: Recipe not found
Environment variables not passed
Explicitly pass environment variables: import os
result = subprocess . run (
cmd ,
capture_output = True ,
text = True ,
env ={ ** os . environ , " OPENAI_API_KEY " : " sk-... " }
)
Security & Ops Notes
Input sanitization : Never pass unsanitized user input directly to CLI
Shell injection : Use list form of subprocess.run, not shell=True
API keys : Pass via environment variables, not command line arguments
Timeouts : Always set timeouts to prevent hanging processes
# GOOD: List form, env vars
subprocess . run (
[ " praisonai " , " recipe " , " run " , name , " --input " , json . dumps ( data )],
env ={ " OPENAI_API_KEY " : key }
)
# BAD: Shell form, key in command
subprocess . run (
f "OPENAI_API_KEY= { key } praisonai recipe run { name } " ,
shell = True # Dangerous!
)
CLI Reference
praisonai recipe run < nam e > [options]
Options:
--input < jso n > Input data as JSON string
--output < di r > Output directory
--json Output as JSON
--dry-run Show what would be done
--write Execute (for dry-run-default recipes )
--force Force execution despite missing deps
--consent Acknowledge consent requirements
--skip-checks Skip dependency checks
Next Steps