mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 17:33:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61e53754d4 | ||
|
|
bbcdf65b5a | ||
|
|
dd03c817b7 | ||
|
|
3ff99487d6 | ||
|
|
c47f572655 | ||
|
|
9c970df301 |
No files matched your search
@@ -25,6 +25,10 @@
|
||||
|
||||
PraisonAI 🦞 — **Hire a 24/7 AI Workforce.** Stop writing boilerplate and start shipping autonomous agents that research, plan, and execute tasks across your apps. From one agent to an entire organization, deployed in 5 lines of code.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://praison.ai/install.sh | bash
|
||||
```
|
||||
|
||||
<div align="center">
|
||||
<br>
|
||||
<a href="https://x.com/elonmusk/status/1893870468249141688" target="_blank">
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.6.14" \
|
||||
"praisonai>=4.6.16" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.6.14" \
|
||||
"praisonai>=4.6.16" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.6.14" \
|
||||
"praisonai>=4.6.16" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -40,8 +40,18 @@ def main() -> None:
|
||||
print("Starting agent...\n")
|
||||
|
||||
agent.start(
|
||||
"Create a 6-second title-card animation with the text 'Hello Motion' "
|
||||
"fading in and a cyan underline drawing across. Save as intro.mp4."
|
||||
"Create a short title-card animation.\n"
|
||||
"Steps you MUST follow, in order:\n"
|
||||
" 1. Call write_file with filepath='index.html' and content containing "
|
||||
" a complete HTML document with GSAP 3.12 from cdnjs, a #stage div "
|
||||
" with data-duration='3.0', visible text 'Hello Motion' that fades "
|
||||
" in, and a cyan underline that draws across. The file MUST set "
|
||||
" window.__timelines = [tl] at the end of its <script> block, and "
|
||||
" the timeline MUST be created with { paused: true }.\n"
|
||||
" 2. Call lint_composition() and verify ok=True.\n"
|
||||
" 3. Call render_composition(output_name='intro.mp4', fps=30, "
|
||||
" quality='standard') and report the resulting output_path.\n"
|
||||
"Return the concrete output_path of the MP4 or the exact stderr."
|
||||
)
|
||||
|
||||
print("\nArtifacts in workspace:")
|
||||
|
||||
@@ -1270,7 +1270,7 @@ Your Goal: {self.goal}"""
|
||||
task_description=task_description,
|
||||
task_id=task_id,
|
||||
execute_tool_fn=self.execute_tool,
|
||||
parallel_tool_calls=getattr(self.execution, "parallel_tool_calls", False),
|
||||
parallel_tool_calls=getattr(getattr(self, "execution", None), "parallel_tool_calls", False),
|
||||
reasoning_steps=reasoning_steps,
|
||||
stream=stream
|
||||
)
|
||||
@@ -1740,7 +1740,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
task_description=task_description,
|
||||
task_id=task_id,
|
||||
execute_tool_fn=self.execute_tool_async,
|
||||
parallel_tool_calls=getattr(self.execution, "parallel_tool_calls", False),
|
||||
parallel_tool_calls=getattr(getattr(self, "execution", None), "parallel_tool_calls", False),
|
||||
reasoning_steps=reasoning_steps,
|
||||
stream=stream
|
||||
)
|
||||
@@ -2271,7 +2271,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
task_description=kwargs.get('task_description'),
|
||||
task_id=kwargs.get('task_id'),
|
||||
execute_tool_fn=self.execute_tool,
|
||||
parallel_tool_calls=getattr(self.execution, "parallel_tool_calls", False)
|
||||
parallel_tool_calls=getattr(getattr(self, "execution", None), "parallel_tool_calls", False)
|
||||
):
|
||||
response_content += chunk
|
||||
yield chunk
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "1.6.14"
|
||||
version = "1.6.16"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
Generated
+1
-1
@@ -2951,7 +2951,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "1.6.14"
|
||||
version = "1.6.16"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.5.87",
|
||||
"lastUpdated": "2026-04-18",
|
||||
"lastUpdated": "2026-04-19",
|
||||
"generatedBy": "praisonai._dev.parity.generator",
|
||||
"sourceOfTruth": "Python SDK (praisonaiagents)",
|
||||
"status": "PARITY_ACHIEVED",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.5.87",
|
||||
"lastUpdated": "2026-04-18",
|
||||
"lastUpdated": "2026-04-19",
|
||||
"generatedBy": "praisonai._dev.parity.generator",
|
||||
"sourceOfTruth": "Python SDK (praisonaiagents)",
|
||||
"summary": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Feature Parity Tracker
|
||||
|
||||
> **Version:** 1.5.87 | **Last Updated:** 2026-04-18
|
||||
> **Version:** 1.5.87 | **Last Updated:** 2026-04-19
|
||||
> **Source of Truth:** Python SDK (praisonaiagents)
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
PraisonAI 🦞 — **Hire a 24/7 AI Workforce.** Stop writing boilerplate and start shipping autonomous agents that research, plan, and execute tasks across your apps. From one agent to an entire organization, deployed in 5 lines of code.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://praison.ai/install.sh | bash
|
||||
```
|
||||
|
||||
<div align="center">
|
||||
<br>
|
||||
<a href="https://x.com/elonmusk/status/1893870468249141688" target="_blank">
|
||||
|
||||
@@ -3,8 +3,8 @@ class Praisonai < Formula
|
||||
|
||||
desc "AI tools for various AI applications"
|
||||
homepage "https://github.com/MervinPraison/PraisonAI"
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.6.14.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.6.14.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.6.16.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.6.16.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Canonical filesystem paths for PraisonAI user state.
|
||||
|
||||
Mirrors the conventions used by ``hermes-agent`` (``~/.hermes``) and
|
||||
``openclaw`` (``~/.openclaw``): one user-home directory per tool, with
|
||||
configurable overrides via environment variables, so ``bot.yaml`` and
|
||||
``.env`` don't pollute whatever working directory the user happened to
|
||||
run ``praisonai onboard`` from.
|
||||
|
||||
Layout::
|
||||
|
||||
~/.praisonai/
|
||||
├── .env # secrets (chmod 600) — managed by onboard
|
||||
├── bot.yaml # default bot / gateway config
|
||||
└── logs/ # daemon stdout / stderr (created by launchd helper)
|
||||
|
||||
Overrides (first match wins for each key):
|
||||
|
||||
* ``PRAISONAI_HOME`` — full path for the directory (e.g. Nix store)
|
||||
* ``PRAISONAI_ENV_FILE`` — full path for the ``.env`` file
|
||||
* ``PRAISONAI_BOT_CONFIG`` — full path for the default ``bot.yaml``
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
__all__ = [
|
||||
"praison_home",
|
||||
"default_env_file",
|
||||
"default_bot_config_path",
|
||||
"resolve_bot_config_path",
|
||||
]
|
||||
|
||||
|
||||
def praison_home() -> Path:
|
||||
"""Return the canonical PraisonAI home directory.
|
||||
|
||||
Honours ``PRAISONAI_HOME`` so packaging systems (Nix, Docker) can
|
||||
relocate state. Defaults to ``~/.praisonai``.
|
||||
"""
|
||||
override = os.environ.get("PRAISONAI_HOME")
|
||||
return Path(override).expanduser() if override else Path.home() / ".praisonai"
|
||||
|
||||
|
||||
def default_env_file() -> Path:
|
||||
"""Return the path to the canonical ``.env`` file."""
|
||||
override = os.environ.get("PRAISONAI_ENV_FILE")
|
||||
return Path(override).expanduser() if override else praison_home() / ".env"
|
||||
|
||||
|
||||
def default_bot_config_path() -> Path:
|
||||
"""Return the path where ``praisonai onboard`` writes ``bot.yaml``.
|
||||
|
||||
Users can force a custom path with ``PRAISONAI_BOT_CONFIG``.
|
||||
"""
|
||||
override = os.environ.get("PRAISONAI_BOT_CONFIG")
|
||||
return Path(override).expanduser() if override else praison_home() / "bot.yaml"
|
||||
|
||||
|
||||
def resolve_bot_config_path(cli_value: str = "bot.yaml") -> str:
|
||||
"""Resolve the bot config path for CLI commands with sensible fallback.
|
||||
|
||||
Precedence:
|
||||
|
||||
1. ``cli_value`` if it is not the sentinel ``"bot.yaml"`` (i.e. user
|
||||
passed an explicit ``--config X``).
|
||||
2. ``./bot.yaml`` if it exists in the current working directory
|
||||
(back-compat for projects that already keep bot.yaml checked in).
|
||||
3. ``~/.praisonai/bot.yaml`` (or ``PRAISONAI_BOT_CONFIG``) — the
|
||||
canonical location written by ``praisonai onboard``.
|
||||
4. Fall back to ``cli_value`` unchanged so existing error messages
|
||||
("config not found at bot.yaml") keep working.
|
||||
"""
|
||||
# (1) explicit override from the caller
|
||||
if cli_value and cli_value != "bot.yaml":
|
||||
return cli_value
|
||||
# (2) honour an existing cwd file (back-compat)
|
||||
cwd_path = Path.cwd() / "bot.yaml"
|
||||
if cwd_path.is_file():
|
||||
return str(cwd_path)
|
||||
# (3) canonical home location
|
||||
home_path = default_bot_config_path()
|
||||
if home_path.is_file():
|
||||
return str(home_path)
|
||||
# (4) give up — return the caller's value so their own error handling
|
||||
# surfaces a useful "not found" message.
|
||||
return cli_value
|
||||
@@ -22,20 +22,28 @@ def _common_options():
|
||||
|
||||
@app.command("start")
|
||||
def bot_start(
|
||||
config: str = typer.Option(..., "--config", "-c", help="Path to bot YAML config file"),
|
||||
config: str = typer.Option(
|
||||
"bot.yaml", "--config", "-c",
|
||||
help="Path to bot YAML config file (defaults to ./bot.yaml if present, else ~/.praisonai/bot.yaml)",
|
||||
),
|
||||
):
|
||||
"""Start a bot from a single YAML config file (zero-code).
|
||||
|
||||
|
||||
The config file contains platform, token, and agent settings all in one place.
|
||||
|
||||
Resolution order when ``--config`` is not given:
|
||||
``./bot.yaml`` (back-compat) → ``~/.praisonai/bot.yaml`` (canonical).
|
||||
|
||||
Examples:
|
||||
praisonai bot start --config bot.yaml
|
||||
praisonai bot start # auto-detect
|
||||
praisonai bot start --config ~/.praisonai/bot.yaml
|
||||
praisonai bot start -c my-telegram-bot.yaml
|
||||
"""
|
||||
from .._paths import resolve_bot_config_path
|
||||
from ..features.bots_cli import BotHandler
|
||||
|
||||
|
||||
resolved = resolve_bot_config_path(config)
|
||||
handler = BotHandler()
|
||||
handler.start_from_config(config)
|
||||
handler.start_from_config(resolved)
|
||||
|
||||
|
||||
@app.command("telegram")
|
||||
@@ -440,7 +448,10 @@ def bot_agentmail(
|
||||
|
||||
@app.command("install-daemon")
|
||||
def bot_install_daemon(
|
||||
config: str = typer.Option("bot.yaml", "--config", help="Path to bot.yaml"),
|
||||
config: str = typer.Option(
|
||||
"bot.yaml", "--config",
|
||||
help="Path to bot.yaml (defaults to ./bot.yaml → ~/.praisonai/bot.yaml)",
|
||||
),
|
||||
start: bool = typer.Option(True, "--start/--no-start", help="Start after install"),
|
||||
):
|
||||
"""Install bot as OS daemon service (alias for 'praisonai gateway install').
|
||||
@@ -450,9 +461,11 @@ def bot_install_daemon(
|
||||
praisonai bot install-daemon --config my-bot.yaml --no-start
|
||||
"""
|
||||
from praisonai.daemon import install_daemon
|
||||
from .._paths import resolve_bot_config_path
|
||||
from ..output.console import get_output_controller
|
||||
|
||||
output = get_output_controller()
|
||||
config = resolve_bot_config_path(config)
|
||||
|
||||
try:
|
||||
result = install_daemon(config_path=config)
|
||||
|
||||
@@ -149,7 +149,10 @@ def gateway_channels(
|
||||
|
||||
@app.command("install")
|
||||
def gateway_install(
|
||||
config: str = typer.Option("bot.yaml", "--config", help="Path to bot.yaml"),
|
||||
config: str = typer.Option(
|
||||
"bot.yaml", "--config",
|
||||
help="Path to bot.yaml (defaults to ./bot.yaml → ~/.praisonai/bot.yaml)",
|
||||
),
|
||||
start: bool = typer.Option(True, "--start/--no-start", help="Start after install"),
|
||||
):
|
||||
"""Install the gateway as an OS daemon (LaunchAgent / systemd).
|
||||
@@ -159,9 +162,11 @@ def gateway_install(
|
||||
praisonai gateway install --config my-bot.yaml --no-start
|
||||
"""
|
||||
from praisonai.daemon import install_daemon
|
||||
from .._paths import resolve_bot_config_path
|
||||
from ..output.console import get_output_controller
|
||||
|
||||
output = get_output_controller()
|
||||
config = resolve_bot_config_path(config)
|
||||
|
||||
try:
|
||||
result = install_daemon(config_path=config)
|
||||
|
||||
@@ -23,7 +23,8 @@ from ..registry import register_check
|
||||
)
|
||||
def check_bot_tokens(config: DoctorConfig) -> CheckResult:
|
||||
"""Check bot token environment variables."""
|
||||
config_path = getattr(config, 'config_file', None) or "bot.yaml"
|
||||
from praisonai.cli._paths import resolve_bot_config_path
|
||||
config_path = getattr(config, 'config_file', None) or resolve_bot_config_path("bot.yaml")
|
||||
token_vars = {
|
||||
"TELEGRAM_BOT_TOKEN": "Telegram",
|
||||
"DISCORD_BOT_TOKEN": "Discord",
|
||||
@@ -63,7 +64,8 @@ def check_bot_tokens(config: DoctorConfig) -> CheckResult:
|
||||
def check_bot_config(config: DoctorConfig) -> CheckResult:
|
||||
"""Check bot.yaml exists and is valid."""
|
||||
start = time.time()
|
||||
config_path = getattr(config, 'config_file', None) or "bot.yaml"
|
||||
from praisonai.cli._paths import resolve_bot_config_path
|
||||
config_path = getattr(config, 'config_file', None) or resolve_bot_config_path("bot.yaml")
|
||||
if not os.path.exists(config_path):
|
||||
return CheckResult(
|
||||
id="bot_config",
|
||||
@@ -107,7 +109,8 @@ def check_bot_config(config: DoctorConfig) -> CheckResult:
|
||||
def check_bot_security(config: DoctorConfig) -> CheckResult:
|
||||
"""Check bot security configuration for safe defaults."""
|
||||
start = time.time()
|
||||
config_path = getattr(config, 'config_file', None) or "bot.yaml"
|
||||
from praisonai.cli._paths import resolve_bot_config_path
|
||||
config_path = getattr(config, 'config_file', None) or resolve_bot_config_path("bot.yaml")
|
||||
channel_warnings = []
|
||||
global_warnings = []
|
||||
|
||||
|
||||
@@ -127,6 +127,24 @@ def _save_env_vars(env_vars: Dict[str, Optional[str]]) -> Optional[Path]:
|
||||
return env_file
|
||||
|
||||
|
||||
def _read_env_value(key: str) -> Optional[str]:
|
||||
"""Read a single value from ``~/.praisonai/.env`` (if present)."""
|
||||
env_file = _praison_home() / ".env"
|
||||
if not env_file.exists():
|
||||
return None
|
||||
try:
|
||||
for line in env_file.read_text().splitlines():
|
||||
s = line.strip()
|
||||
if not s or s.startswith("#") or "=" not in s:
|
||||
continue
|
||||
k, v = s.split("=", 1)
|
||||
if k.strip() == key:
|
||||
return v.strip().strip('"').strip("'") or None
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _generate_bot_yaml(platforms: List[str], agent_name: str = "assistant", agent_instructions: str = "") -> str:
|
||||
"""Generate bot.yaml content compatible with BOTH ``praisonai bot start``
|
||||
and ``praisonai gateway start``.
|
||||
@@ -437,6 +455,20 @@ class OnboardWizard:
|
||||
default="You are a helpful AI assistant.",
|
||||
)
|
||||
|
||||
# Step 4b: Ensure a stable GATEWAY_AUTH_TOKEN is persisted. Without
|
||||
# this the gateway server auto-generates a random token on every
|
||||
# start (see server.py _check_auth), which means the dashboard link
|
||||
# we show in the Done panel would rotate each restart. Persisting
|
||||
# one value to ~/.praisonai/.env keeps clicks working across
|
||||
# daemon restarts.
|
||||
existing_gateway_token = _read_env_value("GATEWAY_AUTH_TOKEN")
|
||||
if not existing_gateway_token:
|
||||
import secrets as _secrets
|
||||
self._gateway_token = _secrets.token_hex(16)
|
||||
_save_env_vars({"GATEWAY_AUTH_TOKEN": self._gateway_token})
|
||||
else:
|
||||
self._gateway_token = existing_gateway_token
|
||||
|
||||
# Step 5: Generate config
|
||||
console.print("\n[bold]Step 5: Generate configuration[/bold]\n")
|
||||
yaml_content = _generate_bot_yaml(
|
||||
@@ -445,7 +477,11 @@ class OnboardWizard:
|
||||
agent_instructions=self.agent_instructions,
|
||||
)
|
||||
|
||||
self.config_path = Prompt.ask("Config file path", default="bot.yaml")
|
||||
from praisonai.cli._paths import default_bot_config_path
|
||||
default_cfg = str(default_bot_config_path())
|
||||
self.config_path = Prompt.ask("Config file path", default=default_cfg)
|
||||
# Ensure parent dir exists (e.g. ~/.praisonai/) before writing.
|
||||
os.makedirs(os.path.dirname(os.path.abspath(self.config_path)) or ".", exist_ok=True)
|
||||
|
||||
if os.path.exists(self.config_path):
|
||||
if not Confirm.ask(f" {self.config_path} exists. Overwrite?", default=False):
|
||||
@@ -486,13 +522,23 @@ class OnboardWizard:
|
||||
console.print(f" [red]✗[/red] {str(e)[:200]}")
|
||||
|
||||
# Done — commands referenced here must exist in `praisonai --help`.
|
||||
_host = "127.0.0.1"
|
||||
_port = 8765
|
||||
_tok = getattr(self, "_gateway_token", "")
|
||||
_masked = (_tok[:4] + "…" + _tok[-4:]) if len(_tok) >= 10 else "(set)"
|
||||
_dashboard_url = f"http://{_host}:{_port}/info?token={_tok}" if _tok else f"http://{_host}:{_port}/info"
|
||||
_health_url = f"http://{_host}:{_port}/health"
|
||||
console.print(Panel(
|
||||
f"[bold green]Setup complete![/bold green]\n\n"
|
||||
f"Start your bot:\n"
|
||||
f"[bold]Dashboard link (with token):[/bold]\n"
|
||||
f" [cyan]{_dashboard_url}[/cyan]\n"
|
||||
f" [dim]Token {_masked} stored in ~/.praisonai/.env as GATEWAY_AUTH_TOKEN[/dim]\n\n"
|
||||
f"[bold]Public health (no token):[/bold]\n"
|
||||
f" [cyan]{_health_url}[/cyan]\n\n"
|
||||
f"[bold]Start your bot (foreground):[/bold]\n"
|
||||
f" [cyan]praisonai bot start --config {self.config_path}[/cyan]\n\n"
|
||||
f"Check health:\n"
|
||||
f" [cyan]praisonai doctor[/cyan]\n\n"
|
||||
f"View gateway status:\n"
|
||||
f"[bold]Check everything:[/bold]\n"
|
||||
f" [cyan]praisonai doctor[/cyan]\n"
|
||||
f" [cyan]praisonai gateway status[/cyan]",
|
||||
title="✅ Done",
|
||||
border_style="green",
|
||||
@@ -549,12 +595,15 @@ class OnboardWizard:
|
||||
|
||||
env_file = _save_env_vars(env_to_save)
|
||||
yaml_content = _generate_bot_yaml(self.selected_platforms)
|
||||
with open("bot.yaml", "w") as f:
|
||||
from praisonai.cli._paths import default_bot_config_path
|
||||
cfg_path = default_bot_config_path()
|
||||
os.makedirs(cfg_path.parent, exist_ok=True)
|
||||
with open(cfg_path, "w") as f:
|
||||
f.write(yaml_content)
|
||||
print("\n✓ Written to bot.yaml")
|
||||
print(f"\n✓ Written to {cfg_path}")
|
||||
if env_file:
|
||||
print(f"✓ Secrets saved to {env_file} (chmod 600)")
|
||||
print("Start with: praisonai bot start --config bot.yaml")
|
||||
print(f"Start with: praisonai bot start --config {cfg_path}")
|
||||
|
||||
|
||||
def run_onboard() -> None:
|
||||
|
||||
@@ -57,7 +57,7 @@ class CloudDeployer:
|
||||
file.write("FROM python:3.11-slim\n")
|
||||
file.write("WORKDIR /app\n")
|
||||
file.write("COPY . .\n")
|
||||
file.write("RUN pip install flask praisonai==4.6.14 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==4.6.16 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -203,12 +203,21 @@ class WebSocketGateway:
|
||||
"""
|
||||
self.config = config or GatewayConfig(host=host, port=port)
|
||||
if hasattr(self.config, 'auth_token') and not self.config.auth_token:
|
||||
import secrets
|
||||
self.config.auth_token = secrets.token_hex(16)
|
||||
logger.warning(
|
||||
f"No auth_token provided for Gateway server. Generated temporary token: {self.config.auth_token}. "
|
||||
"For production, set GATEWAY_AUTH_TOKEN."
|
||||
)
|
||||
# Prefer a user-configured token (persisted by `praisonai onboard`
|
||||
# to ~/.praisonai/.env as GATEWAY_AUTH_TOKEN) so the dashboard
|
||||
# URL stays stable across daemon restarts. Only fall back to
|
||||
# generating a random ephemeral token if nothing is set.
|
||||
env_tok = os.environ.get("GATEWAY_AUTH_TOKEN", "").strip()
|
||||
if env_tok:
|
||||
self.config.auth_token = env_tok
|
||||
logger.info("Gateway using GATEWAY_AUTH_TOKEN from environment")
|
||||
else:
|
||||
import secrets
|
||||
self.config.auth_token = secrets.token_hex(16)
|
||||
logger.warning(
|
||||
f"No auth_token provided for Gateway server. Generated temporary token: {self.config.auth_token}. "
|
||||
"For production, set GATEWAY_AUTH_TOKEN."
|
||||
)
|
||||
|
||||
self._host = self.config.host
|
||||
self._port = self.config.port
|
||||
@@ -267,17 +276,27 @@ class WebSocketGateway:
|
||||
return JSONResponse(self.health())
|
||||
|
||||
def _check_auth(request) -> Optional[JSONResponse]:
|
||||
"""Validate auth token if configured. Returns error response or None."""
|
||||
"""Validate auth token if configured. Returns error response or None.
|
||||
|
||||
Accepts either:
|
||||
- ``Authorization: Bearer <token>`` header (preferred for APIs)
|
||||
- ``?token=<token>`` query parameter (so the dashboard URL from
|
||||
``praisonai onboard`` is clickable in a browser)
|
||||
"""
|
||||
if not self.config.auth_token:
|
||||
return None
|
||||
auth_header = request.headers.get("authorization", "")
|
||||
if not auth_header.startswith("Bearer "):
|
||||
token: str = ""
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:]
|
||||
else:
|
||||
token = request.query_params.get("token", "")
|
||||
if not token:
|
||||
return JSONResponse(
|
||||
{"error": "Authentication required"},
|
||||
status_code=401,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
token = auth_header[7:]
|
||||
if not secrets.compare_digest(token, self.config.auth_token):
|
||||
return JSONResponse(
|
||||
{"error": "Invalid authentication token"},
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "4.6.14"
|
||||
__version__ = "4.6.16"
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"rich>=13.7",
|
||||
"markdown>=3.5",
|
||||
"pyparsing>=3.0.0",
|
||||
"praisonaiagents>=1.6.14",
|
||||
"praisonaiagents>=1.6.16",
|
||||
"python-dotenv>=0.19.0",
|
||||
"litellm>=1.81.0,<=1.82.6",
|
||||
"PyYAML>=6.0",
|
||||
|
||||
Generated
+1
-1
@@ -4808,7 +4808,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "1.6.14"
|
||||
version = "1.6.16"
|
||||
source = { directory = "../praisonai-agents" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
Reference in new issue
Block a user