Skip to main content
Just type your prompt — praisonai "…" uses the same modern engine as praisonai run "…". The user types praisonai …; the dispatcher routes to Typer subcommands, version flags, bare prompts, or the legacy YAML/flag path. PraisonAI picks one of eight paths based on what you type — and adding a new subcommand means it Just Works.

Dispatch Paths

Quick Start

1

Check Version

2

Interactive Mode

3

Get Help

4

Use Subcommands

5

Free-text Prompts

Why route bare prompts to run? A bare prompt inherits the full modern engine: session continuity (--continue / --session / --fork), structured output (--output json, --output stream-json), the local-first credential gate, and the permission model + checkpoints. praisonai "…" and praisonai run "…" are the same engine underneath.

How It Works


Routing Rules

main() applies eight rules in order. The first match wins. Rule 5 fires when _mistyped_command_suggestions(argv, first_cmd) returns a non-empty list — a lone positional token, no whitespace or path separator, not ending in .yaml/.yml, no file of that name on disk, not itself a registered command, whose difflib.get_close_matches(..., cutoff=0.8) yields a registered command that the token does not extend. A token that a command starts with (an extension like tests for test, server for serve) is dropped so it stays a valid one-word prompt. The opposite direction — a token that is a truncation of a command (deplo for deploy, versio for version) — is now kept and surfaced as a suggestion instead of being forwarded to (and billed by) the model. Rule 6 fires when _looks_like_bare_prompt(argv, first_cmd) returns True — that is, when first_cmd is non-empty, doesn’t end with .yaml/.yml, and either no dash-prefixed token appears anywhere in argv or every flag present is one that Typer run itself accepts (--model, --continue, --session, --output, --stream, …, derived at dispatch time by introspecting run’s Click parameters). Rule 7 fires when _looks_like_yaml_run_target(argv, first_cmd) returns True — the first positional ends in .yaml/.yml and every flag is run-supported — forwarding praisonai agents.yaml … as run agents.yaml … so the workflow runs inside the modern session/output/credential/permission envelope. A single genuinely legacy-only flag (--auto, --serve, --n8n, …) still forces legacy. The short-form -s (legacy --save) and -f (legacy --file) live in _LEGACY_COLLIDING_SHORT_OPTS and also stay on legacy, so an existing script is never silently reinterpreted. If run-option discovery fails, both predicates fall back to the original conservative any-flag→legacy rule.

Never-silent legacy fallback

When an invocation reaches the legacy engine solely because it contains a flag that run doesn’t accept, praisonai prints a one-line stderr notice pointing at the modern equivalent:
The notice fires whenever a legacy-only flag forced the fallback — on either a prompt or a YAML workflow. A flagless .yaml/.yml reaches the modern engine and does not produce a notice.

Mistyped-verb guard

A lone token that is a close typo of a registered command exits 2 instead of being billed to an LLM.
The guard is deliberately narrow — it fires only when a single positional token has no whitespace, no / or path separator, does not end in .yaml/.yml, names no file on disk, is not itself a command, and difflib.get_close_matches(..., cutoff=0.8) returns a registered command that the token does not extend. Extensions (tests for test, server for serve) are dropped so legitimate one-word prompts still run; truncations (deplo, versio) are kept and suggested.

Global flags

Global flags (--output-format, --json, --quiet, …) are declared on the root Typer callback, not on run. When a bare prompt or YAML target follows one, the dispatcher hoists the global flag ahead of the synthesised run. Click accepts a group-level option only before the subcommand, so --output-format json must be emitted as praisonai --output-format json run <target>; appended after run, Click rejects it with “No such option”.
Global-only flags (hoisted ahead of run) — declared on the root callback and not on run: Shared flags that are NOT hoisted — declared by both the root callback and run, so they stay with run and keep their existing meaning:

Behaviour Matrix

An unquoted prompt (praisonai build a weather agent) arrives as four argv tokens; the dispatcher joins them with spaces into a single run argument so the whole prompt reaches the modern engine intact.

Auto-Discovery

Commands registered in praisonai/cli/app.py become routable automatically through Click introspection.
Adding a new subcommand? Register it in praisonai/cli/app.py (e.g. app.add_typer(my_app, name="mycmd")) and the dispatcher picks it up automatically — praisonai mycmd ... routes to Typer with no changes to __main__.py. The command set is discovered once via click.Context.list_commands() and cached behind a thread-safe lock.
The auto-discovery cache (_get_typer_commands()) works by:
  1. Importing the Typer app and calling register_commands()
  2. Using Click’s introspection to list all registered commands
  3. Caching the result in _typer_commands_cache with thread safety
  4. Returning an empty set on failure (cache not poisoned for retry)

Common Patterns

Bare Prompt

YAML File

Subcommand with Global Flags

Bare Prompt With run Flags


Two typo guards

There are now two guards, and they fire on different paths.

When it fires

Both guards print to stderr with exit code 2. Shell scripts must not swallow stderr if they need the diagnostic.

Best Practices

The --version flag takes a fast path that prints version information without importing any praisonai.cli.* modules. This keeps the command responsive even if optional dependencies are broken or missing. The version check happens before any heavy imports or command discovery.
To add a new subcommand, simply register it in praisonai/cli/app.py using app.add_typer(). The dispatcher automatically discovers it through Click introspection with no manual updates needed to routing logic. The command becomes available immediately after registration.
praisonai "build a weather agent" and praisonai run "build a weather agent" reach the same modern engine. Bare prompts — single word or multi word, quoted or unquoted — route to Typer run and inherit session continuity, --output modes, the credential gate, and permissions/checkpoints. A single word like hello or a reserved verb like show is sent to the model as a literal prompt, not blocked — neither is a close command match. The one exception is a close typo of a real command that isn’t an extension of it (deploi, memoyr, deplo, versio): the mistyped-verb guard blocks it with exit 2 and suggests the command.
Only close typos of a real command that aren’t extensions of it are blocked. If you meant a literal prompt, use praisonai run "<word>" — the error message already prints that escape hatch — or add more words so it’s no longer a lone token. hello and show are never blocked (no ≥ 0.8 command match), and tests / server stay valid prompts because they extend test / serve (contain the command as a prefix). Truncations of a command (deplo, versio) do the opposite and are caught with a suggestion.
Add a legacy-only flag (--auto, --serve, --n8n, …) — that works on both prompts and .yaml/.yml files. A flagless .yaml now reaches the modern engine. The short-form -s (legacy --save) and -f (legacy --file) also stay on legacy to protect existing scripts; use --session / --framework explicitly if you want the modern equivalents. Flags accepted by the modern run command (--model, --continue, --session, --output, --stream, --framework, …) stay on the modern engine instead of forcing legacy. When a legacy-only flag is what triggers the fallback, a one-line notice prints to stderr — the fallback is never silent, so you can spot un-migrated flags. The reserved-verb guard only runs once you are already on legacy; the mistyped-verb guard runs on the modern path regardless.
Registration errors from register_commands() propagate directly to the user — the dispatcher does not swallow them. If an optional dependency is missing or a command fails to register, you see the real error instead of silent fallback behavior. This fail-loud approach aids debugging.

Registration errors fail loud. If register_commands() raises (e.g. an ImportError from a missing optional dep), the exception propagates from praisonai ... — you see the real error, not Typer’s “no command” page. This is intentional and pinned by tests.

CLI Reference

Complete command reference

CLI Commands

Basic CLI usage guide

Gateway

Multi-bot WebSocket gateway

Version

Version management