diff --git a/.env.example b/.env.example index 3c2edae0..1ffe6a72 100644 --- a/.env.example +++ b/.env.example @@ -21,7 +21,7 @@ GOOGLE_API_KEY= # ── Model selection ─────────────────────────── # Override the default model for all agents (set automatically by onboarding). -# OpenAI example: DEFAULT_MODEL=gpt-5.2 +# OpenAI example: DEFAULT_MODEL=gpt-5.4 # Anthropic example: DEFAULT_MODEL=litellm/claude-sonnet-4-6 # Google example: DEFAULT_MODEL=litellm/gemini/gemini-3-flash DEFAULT_MODEL= diff --git a/config.py b/config.py index 0ad216f1..7054ba0d 100644 --- a/config.py +++ b/config.py @@ -2,7 +2,7 @@ import os -def get_default_model(fallback: str = "gpt-5.2"): +def get_default_model(fallback: str = "gpt-5.4"): """Return the configured default model for standard agents.""" model = os.getenv("DEFAULT_MODEL", fallback) return _resolve(model) @@ -11,7 +11,7 @@ def get_default_model(fallback: str = "gpt-5.2"): def is_openai_provider() -> bool: """Return True when the configured provider is OpenAI (not LiteLLM). - OpenAI model IDs never contain a slash (e.g. 'gpt-5.2', 'o3'). + OpenAI model IDs never contain a slash (e.g. 'gpt-5.4', 'o3'). Any 'provider/model' string (e.g. 'anthropic/claude-sonnet-4-6', 'litellm/gemini/gemini-3-flash') is treated as a LiteLLM-routed model. """ diff --git a/onboard.py b/onboard.py deleted file mode 100644 index 07391602..00000000 --- a/onboard.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env python3 -"""OpenSwarm interactive setup wizard. - -Run directly: python onboard.py -Auto-launched: python run.py (when no provider key is found) -""" - -import getpass -import sys -from pathlib import Path - -from dotenv import dotenv_values, set_key -from rich import box -from rich.console import Console -from rich.panel import Panel -from rich.rule import Rule -from rich.table import Table - -from run_utils import _openswarm_state_root - -try: - import questionary - from questionary import Choice, Style as QStyle - import questionary.prompts.common as _qc_common - - # Swap filled circle → checkmark for selected state. - _qc_common.INDICATOR_SELECTED = "✓" - - _HAS_QUESTIONARY = True -except ImportError: - class Choice: - def __init__(self, title: object, value: object | None = None) -> None: - self.title = title - self.value = title if value is None else value - - _HAS_QUESTIONARY = False - -console = Console() - -ENV_PATH = _openswarm_state_root() / ".env" - -# ── questionary theme ───────────────────────────────────────────────────────── -_QSTYLE = None -if _HAS_QUESTIONARY: - _QSTYLE = QStyle([ - ("qmark", "fg:#4fc3f7 bold"), - ("question", "bold"), - ("answer", "fg:#4fc3f7 bold"), - ("pointer", "fg:#4fc3f7 bold noreverse"), - ("highlighted", "noreverse"), - ("selected", "fg:#4fc3f7 bold noreverse"), - ("separator", "fg:#555555 noreverse"), - ("instruction", "fg:#555555 italic noreverse"), - ("text", "noreverse"), - ]) - -# ── provider definitions ────────────────────────────────────────────────────── -PROVIDERS = [ - { - "name": "OpenAI", - "env_key": "OPENAI_API_KEY", - "default_model": "gpt-5.2", - "url": "https://platform.openai.com/api-keys", - }, - { - "name": "Anthropic", - "env_key": "ANTHROPIC_API_KEY", - "default_model": "litellm/claude-sonnet-4-6", - "url": "https://console.anthropic.com/settings/keys", - }, - { - "name": "Google Gemini", - "env_key": "GOOGLE_API_KEY", - "default_model": "litellm/gemini/gemini-3-flash", - "url": "https://aistudio.google.com/app/apikey", - }, -] - -# ── add-on definitions ──────────────────────────────────────────────────────── -# exclude_for: list of provider names that already cover this key -ADD_ONS = [ - { - "id": "search", - "name": "Web Search", - "description": "Web, Scholar & product search for all agents", - "keys": [ - {"env": "SEARCH_API_KEY", "label": "SearchAPI key", - "url": "https://www.searchapi.io"}, - ], - "exclude_for": [], - }, - { - "id": "anthropic", - "name": "Anthropic Claude — better slides quality", - "description": "Claude produces significantly better slide HTML output", - "keys": [ - {"env": "ANTHROPIC_API_KEY", "label": "Anthropic API key", - "url": "https://console.anthropic.com/settings/keys"}, - ], - "exclude_for": ["Anthropic"], - }, - { - "id": "composio", - "name": "Composio — 10,000+ integrations", - "description": "Gmail, Slack, GitHub, HubSpot, Google Calendar and more", - "keys": [ - {"env": "COMPOSIO_API_KEY", "label": "Composio API key", - "url": "https://composio.dev"}, - {"env": "COMPOSIO_USER_ID", "label": "Composio user ID", - "url": "https://composio.dev"}, - ], - "exclude_for": [], - }, - { - "id": "google", - "name": "Google Gemini — image gen & Veo video", - "description": "Gemini image generation/editing and Veo video generation", - "keys": [ - {"env": "GOOGLE_API_KEY", "label": "Google AI API key", - "url": "https://aistudio.google.com/app/apikey"}, - ], - "exclude_for": ["Google Gemini"], - }, - { - "id": "fal", - "name": "Fal.ai — Seedance video & background removal", - "description": "Seedance 1.5 Pro video gen, video editing, background removal", - "keys": [ - {"env": "FAL_KEY", "label": "Fal.ai API key", - "url": "https://fal.ai/dashboard/keys"}, - ], - "exclude_for": [], - }, - { - "id": "stock", - "name": "Stock photos — Pexels / Pixabay / Unsplash", - "description": "Image search for the Slides Agent", - "keys": [ - {"env": "PEXELS_API_KEY", "label": "Pexels API key", - "url": "https://www.pexels.com/api"}, - {"env": "PIXABAY_API_KEY", "label": "Pixabay API key", - "url": "https://pixabay.com/api/docs"}, - {"env": "UNSPLASH_ACCESS_KEY", "label": "Unsplash access key", - "url": "https://unsplash.com/developers"}, - ], - "exclude_for": [], - }, -] - -# ── ui helpers ──────────────────────────────────────────────────────────────── - -def _step(n: int, label: str) -> None: - console.print() - console.print(Rule(f"[bold]Step {n} · {label}[/bold]", style="cyan")) - console.print() - - -def _ask_select(message: str, choices: list) -> object: - if _HAS_QUESTIONARY: - return questionary.select(message, choices=choices, style=_QSTYLE).ask() - # plain fallback - titles = [c.title if isinstance(c, Choice) else c for c in choices] - values = [c.value if isinstance(c, Choice) else c for c in choices] - console.print(f"\n[bold]{message}[/bold]") - for i, title in enumerate(titles, 1): - console.print(f" [cyan]{i}.[/cyan] {title}") - while True: - raw = input("Enter number: ").strip() - if raw.isdigit() and 1 <= int(raw) <= len(titles): - return values[int(raw) - 1] - console.print("[red]Invalid choice, try again.[/red]") - - -def _ask_checkbox(message: str, choices: list) -> list: - if _HAS_QUESTIONARY: - return questionary.checkbox(message, choices=choices, style=_QSTYLE, pointer="❯").ask() or [] - # plain fallback — comma-separated numbers - titles = [c.title if isinstance(c, Choice) else c for c in choices] - values = [c.value if isinstance(c, Choice) else c for c in choices] - console.print(f"\n[bold]{message}[/bold]") - console.print("[dim] Enter comma-separated numbers, or press Enter to skip[/dim]") - for i, title in enumerate(titles, 1): - console.print(f" [cyan]{i}.[/cyan] {title}") - raw = input("Selection: ").strip() - if not raw: - return [] - result = [] - for part in raw.split(","): - part = part.strip() - if part.isdigit() and 1 <= int(part) <= len(titles): - result.append(values[int(part) - 1]) - return result - - -def _ask_secret(label: str, url: str) -> str: - console.print(f" [dim]Get yours at[/dim] [link={url}]{url}[/link]") - if _HAS_QUESTIONARY: - val = questionary.password(f" {label}: ", style=_QSTYLE).ask() - return (val or "").strip() - return getpass.getpass(f" {label}: ").strip() - - -def _ask_confirm(message: str, default: bool = True) -> bool: - if _HAS_QUESTIONARY: - return questionary.confirm(message, default=default, style=_QSTYLE).ask() - prompt = f"{message} [{'Y/n' if default else 'y/N'}]: " - raw = input(prompt).strip().lower() - return default if not raw else raw in ("y", "yes") - - -def _write_env(updates: dict) -> None: - ENV_PATH.parent.mkdir(parents=True, exist_ok=True) - if not ENV_PATH.exists(): - ENV_PATH.write_text("", encoding="utf-8") - for key, value in updates.items(): - if value: - set_key(str(ENV_PATH), key, value) - - -# ── main wizard ─────────────────────────────────────────────────────────────── - -def run_onboarding() -> None: - console.print() - console.print(Panel.fit( - "[bold cyan]OpenSwarm[/bold cyan] [dim]— open-source multi-agent AI team[/dim]\n" - "[dim]Let's get you set up in a few steps.[/dim]", - border_style="cyan", - padding=(1, 4), - )) - - existing = dotenv_values(str(ENV_PATH)) if ENV_PATH.exists() else {} - updates: dict[str, str] = {} - - # ── Step 1: provider ────────────────────────────────────────────────────── - _step(1, "AI Provider") - - provider_choices = [ - Choice(title=p["name"], value=p) - for p in PROVIDERS - ] - provider = _ask_select("Choose your primary AI provider:", provider_choices) - - # ── Step 2: API key ─────────────────────────────────────────────────────── - _step(2, "API Key") - - existing_key = existing.get(provider["env_key"], "") - if existing_key: - console.print(f" [dim]{provider['env_key']} is already configured.[/dim]") - if _ask_confirm(" Update it?", default=False): - key = _ask_secret(f"{provider['name']} API key", provider["url"]) - updates[provider["env_key"]] = key or existing_key - else: - updates[provider["env_key"]] = existing_key - else: - key = _ask_secret(f"{provider['name']} API key", provider["url"]) - if key: - updates[provider["env_key"]] = key - - updates["DEFAULT_MODEL"] = provider["default_model"] - - # ── Step 3: add-ons ─────────────────────────────────────────────────────── - _step(3, "Add-ons [dim](optional)[/dim]") - - available = [a for a in ADD_ONS if provider["name"] not in a["exclude_for"]] - addon_choices = [ - Choice( - title=( - [ - ("class:text", a["name"]), - ("fg:#555555", " · "), - ("fg:#666666", a["description"]), - ] - if _HAS_QUESTIONARY - else f"{a['name']} — {a['description']}" - ), - value=a["id"], - ) - for a in available - ] - selected_ids = _ask_checkbox("Select add-ons to enable:", addon_choices) - selected_addons = [a for a in available if a["id"] in selected_ids] - - # ── Step 4: add-on keys ─────────────────────────────────────────────────── - if selected_addons: - _step(4, "Add-on Keys") - for addon in selected_addons: - console.print(f"\n [bold]{addon['name'].split(' ')[0]}[/bold]") - for key_spec in addon["keys"]: - existing_val = existing.get(key_spec["env"], "") - if existing_val: - console.print(f" [dim]{key_spec['env']} is already configured.[/dim]") - if not _ask_confirm(" Update it?", default=False): - updates[key_spec["env"]] = existing_val - continue - val = _ask_secret(key_spec["label"], key_spec["url"]) - if val: - updates[key_spec["env"]] = val - - # ── write .env ──────────────────────────────────────────────────────────── - _write_env(updates) - - # ── summary ─────────────────────────────────────────────────────────────── - console.print() - console.print(Rule("[bold green]Setup complete[/bold green]", style="green")) - console.print() - - table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2)) - table.add_column(style="dim", no_wrap=True) - table.add_column() - table.add_row("Provider", f"[cyan]{provider['name']}[/cyan]") - table.add_row("Model", f"[cyan]{provider['default_model']}[/cyan]") - table.add_row(".env", f"[cyan]{ENV_PATH}[/cyan]") - saved = [k for k, v in updates.items() if v and not k.startswith("DEFAULT_")] - if saved: - table.add_row("Keys saved", f"[cyan]{', '.join(saved)}[/cyan]") - console.print(table) - - console.print() - console.print(Panel( - "[bold]python swarm.py[/bold] [dim]launch interactive terminal[/dim]\n" - "[bold]python server.py[/bold] [dim]start the API server[/dim]", - border_style="green", - padding=(0, 3), - )) - console.print() - - -if __name__ == "__main__": - try: - run_onboarding() - except KeyboardInterrupt: - console.print("\n\n[dim]Setup cancelled.[/dim]\n") - sys.exit(0) diff --git a/package.json b/package.json index 660f1195..3caf3aa5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "openswarm.config.mjs", "openswarm.product-env.json", "run_utils.py", - "onboard.py", "swarm.py", "config.py", "helpers.py", diff --git a/pyproject.toml b/pyproject.toml index 88351519..024d5de9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ dependencies = [ openswarm = "run_utils:main" [tool.setuptools] -py-modules = ["agency", "swarm", "helpers", "config", "onboard", "server", "run_utils"] +py-modules = ["agency", "swarm", "helpers", "config", "server", "run_utils"] [tool.setuptools.packages.find] where = ["."] diff --git a/run_utils.py b/run_utils.py index 40a4715c..ea506346 100644 --- a/run_utils.py +++ b/run_utils.py @@ -4,7 +4,6 @@ import site import subprocess import shutil -import tempfile import platform as platform_module from pathlib import Path @@ -560,53 +559,36 @@ def main() -> None: from swarm import create_agency - onboard_flag = Path(tempfile.gettempdir()) / "_openswarm_onboard.flag" - os.environ["OPENSWARM_ONBOARD_FLAG"] = str(onboard_flag) - onboard_flag.unlink(missing_ok=True) - - while True: - import logging - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - logging.disable(logging.NOTSET) - print("\nStarting OpenSwarm… this may take a few seconds.") - _configure_demo_console() - - # Suppress OS-level stderr (fd 2) to prevent GLib/GIO UWP-app - # warnings from appearing in the terminal during startup and TUI. - _saved_stderr_fd = None - try: - _saved_stderr_fd = os.dup(2) - _dn = os.open(os.devnull, os.O_WRONLY) - os.dup2(_dn, 2) - os.close(_dn) - except OSError: - pass + import logging + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + logging.disable(logging.NOTSET) + print("\nStarting OpenSwarm… this may take a few seconds.") + _configure_demo_console() - print(build_integration_summary()) - print() + # Suppress OS-level stderr (fd 2) to prevent GLib/GIO UWP-app + # warnings from appearing in the terminal during startup and TUI. + _saved_stderr_fd = None + try: + _saved_stderr_fd = os.dup(2) + _dn = os.open(os.devnull, os.O_WRONLY) + os.dup2(_dn, 2) + os.close(_dn) + except OSError: + pass - agency = create_agency() - agency.tui(show_reasoning=True, reload=False) + print(build_integration_summary()) + print() - if _saved_stderr_fd is not None: - try: - os.dup2(_saved_stderr_fd, 2) - os.close(_saved_stderr_fd) - except OSError: - pass - - if onboard_flag.exists(): - onboard_flag.unlink(missing_ok=True) - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - logging.disable(logging.NOTSET) - print("\nLaunching setup wizard…") - from onboard import run_onboarding - run_onboarding() - _load_openswarm_dotenv(override=True) - else: - break + agency = create_agency() + agency.tui(show_reasoning=True, reload=False) + + if _saved_stderr_fd is not None: + try: + os.dup2(_saved_stderr_fd, 2) + os.close(_saved_stderr_fd) + except OSError: + pass if __name__ == "__main__": diff --git a/scripts/smoke-bootstrap-onboard.py b/scripts/smoke-bootstrap.py similarity index 91% rename from scripts/smoke-bootstrap-onboard.py rename to scripts/smoke-bootstrap.py index f19931e8..18042ee7 100644 --- a/scripts/smoke-bootstrap-onboard.py +++ b/scripts/smoke-bootstrap.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Focused smoke checks for OpenSwarm import bootstrap and onboarding writes.""" +"""Focused smoke checks for OpenSwarm import bootstrap.""" from __future__ import annotations @@ -89,55 +89,6 @@ def smoke_swarm_import_skips_bootstrap() -> None: raise RuntimeError(f"swarm.py did not configure runtime during import: {order}") -def smoke_onboard_env_writes() -> None: - sys.path.insert(0, str(ROOT)) - try: - import onboard - from rich.console import Console - finally: - sys.path.pop(0) - - provider = next(item for item in onboard.PROVIDERS if item["name"] == "OpenAI") - secrets = iter( - [ - "sk-test-openai", - "search-test-key", - "composio-test-key", - "composio-test-user", - ] - ) - - with tempfile.TemporaryDirectory(prefix="openswarm-onboard-smoke-") as tmp: - env = Path(tmp) / ".env" - sink = io.StringIO() - with ( - patch.object(onboard, "ENV_PATH", env), - patch.object(onboard, "console", Console(file=sink, force_terminal=False)), - patch.object(onboard, "_ask_select", lambda _message, _choices: provider), - patch.object( - onboard, - "_ask_checkbox", - lambda _message, _choices: ["search", "composio"], - ), - patch.object(onboard, "_ask_secret", lambda _label, _url: next(secrets)), - patch.object(onboard, "_ask_confirm", lambda _message, default=True: default), - ): - onboard.run_onboarding() - - values = onboard.dotenv_values(str(env)) - - expected = { - "OPENAI_API_KEY": "sk-test-openai", - "DEFAULT_MODEL": provider["default_model"], - "SEARCH_API_KEY": "search-test-key", - "COMPOSIO_API_KEY": "composio-test-key", - "COMPOSIO_USER_ID": "composio-test-user", - } - missing = {key: value for key, value in expected.items() if values.get(key) != value} - if missing: - raise RuntimeError(f"onboarding did not write expected .env values: {missing}") - - def smoke_product_state_root_env() -> None: sys.path.insert(0, str(ROOT)) try: @@ -537,11 +488,10 @@ def setup_node(repo: Path, npm: str) -> None: def main() -> int: smoke_swarm_import_skips_bootstrap() - smoke_onboard_env_writes() smoke_product_state_root_env() smoke_python_openswarm_tui_binary_resolution() smoke_bootstrap_node_setup_installs_slides_dependencies() - print("OpenSwarm import bootstrap and onboarding smoke passed") + print("OpenSwarm import bootstrap smoke passed") return 0