Spaces:
Sleeping
Sleeping
Download app/cli_setup.py from ColdAshSage/reed: direct link, hf CLI and curl.
- Browser
- Download file 6.23 kB
-
https://huggingface.co/spaces/ColdAshSage/reed/resolve/main/app/cli_setup.py
- Command line
-
hf download hf://spaces/ColdAshSage/reed/app/cli_setup.py
-
curl -L -o cli_setup.py https://huggingface.co/spaces/ColdAshSage/reed/resolve/main/app/cli_setup.py
6.23 kB
| #!/usr/bin/env python3 | |
| """REED operator setup wizard. | |
| Detects provider keys in .env, lets the operator pick a provider and | |
| model, then writes backend/settings.yaml deterministically. Safe to | |
| re-run any time. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import logging | |
| import sys | |
| from pathlib import Path | |
| import yaml | |
| from app.config import ( | |
| EnvSettings, | |
| MarketDataConfig, | |
| ProviderName, | |
| SchedulerConfig, | |
| SessionsConfig, | |
| SettingsYaml, | |
| ToolsConfig, | |
| TriggerConfig, | |
| ) | |
| logger = logging.getLogger(__name__) | |
| PROVIDER_MODELS: dict[ProviderName, list[str]] = { | |
| ProviderName.OPENAI: ["gpt-4o-mini", "gpt-4o", "o4-mini"], | |
| ProviderName.ANTHROPIC: [ | |
| "claude-3-5-sonnet-latest", | |
| "claude-3-5-haiku-latest", | |
| "claude-opus-4-0", | |
| ], | |
| ProviderName.OPENROUTER: [ | |
| "google/gemini-2.5-flash-lite", | |
| "anthropic/claude-3.5-sonnet", | |
| "openai/gpt-4o-mini", | |
| ], | |
| ProviderName.OLLAMA: ["llama3.2", "qwen2.5", "mistral"], | |
| ProviderName.OPENAI_COMPATIBLE: [], | |
| } | |
| PROVIDER_KEY_NAMES: dict[ProviderName, str] = { | |
| ProviderName.OPENAI: "openai_api_key", | |
| ProviderName.ANTHROPIC: "anthropic_api_key", | |
| ProviderName.OPENROUTER: "openrouter_api_key", | |
| ProviderName.OLLAMA: "ollama_api_key", | |
| ProviderName.OPENAI_COMPATIBLE: "openai_api_key", | |
| } | |
| PROVIDER_LABELS: dict[ProviderName, str] = { | |
| ProviderName.OPENAI: "OpenAI", | |
| ProviderName.ANTHROPIC: "Anthropic", | |
| ProviderName.OPENROUTER: "OpenRouter", | |
| ProviderName.OLLAMA: "Ollama", | |
| ProviderName.OPENAI_COMPATIBLE: "OpenAI-compatible (custom base URL)", | |
| } | |
| def detect_available_providers(env: EnvSettings) -> list[ProviderName]: | |
| """Return the providers whose API keys are present in the environment.""" | |
| available = [] | |
| if env.openai_api_key: | |
| available.append(ProviderName.OPENAI) | |
| if env.anthropic_api_key: | |
| available.append(ProviderName.ANTHROPIC) | |
| if env.openrouter_api_key: | |
| available.append(ProviderName.OPENROUTER) | |
| if env.ollama_host or env.ollama_api_key: | |
| available.append(ProviderName.OLLAMA) | |
| return available | |
| def prompt_provider(available: list[ProviderName]) -> ProviderName: | |
| print("REED setup wizard") | |
| print("Detected provider keys in .env:") | |
| if not available: | |
| print(" (none detected)") | |
| print() | |
| print("OpenAI-compatible is always available; configure the base_url in step 2.") | |
| return ProviderName.OPENAI_COMPATIBLE | |
| for i, name in enumerate(available, start=1): | |
| print(f" {i}. {PROVIDER_LABELS[name]}") | |
| print(f" {len(available) + 1}. OpenAI-compatible (custom base URL)") | |
| while True: | |
| choice = input(f"Pick a provider [1-{len(available) + 1}]: ").strip() | |
| if choice.isdigit(): | |
| idx = int(choice) | |
| if 1 <= idx <= len(available) + 1: | |
| if idx == len(available) + 1: | |
| return ProviderName.OPENAI_COMPATIBLE | |
| return available[idx - 1] | |
| print("Invalid choice. Try again.") | |
| def prompt_model(provider: ProviderName) -> str: | |
| models = PROVIDER_MODELS.get(provider, []) | |
| if not models: | |
| return input("Enter the model name (e.g. my-model-3.5): ").strip() | |
| print(f"Available models for {PROVIDER_LABELS[provider]}:") | |
| for i, model in enumerate(models, start=1): | |
| print(f" {i}. {model}") | |
| print(f" {len(models) + 1}. Custom model name") | |
| while True: | |
| choice = input(f"Pick a model [1-{len(models) + 1}]: ").strip() | |
| if choice.isdigit(): | |
| idx = int(choice) | |
| if 1 <= idx <= len(models) + 1: | |
| if idx == len(models) + 1: | |
| return input("Enter the model name: ").strip() | |
| return models[idx - 1] | |
| print("Invalid choice. Try again.") | |
| def prompt_base_url(provider: ProviderName) -> str | None: | |
| if provider != ProviderName.OPENAI_COMPATIBLE: | |
| return None | |
| return input("Enter the OpenAI-compatible base URL: ").strip() or None | |
| def write_settings( | |
| path: Path, | |
| *, | |
| provider: ProviderName, | |
| model: str, | |
| base_url: str | None, | |
| ) -> None: | |
| settings = SettingsYaml( | |
| provider=provider, | |
| model=model, | |
| base_url=base_url, | |
| sessions=SessionsConfig(), | |
| tools=ToolsConfig(), | |
| market_data=MarketDataConfig(), | |
| data_dir=Path("./data/digests"), | |
| scheduler=SchedulerConfig(), | |
| trigger=TriggerConfig(), | |
| ) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with path.open("w", encoding="utf-8") as f: | |
| yaml.safe_dump( | |
| settings.model_dump(mode="json", exclude_none=True), | |
| f, | |
| sort_keys=False, | |
| default_flow_style=False, | |
| ) | |
| print(f"Wrote {path}") | |
| def main(argv: list[str] | None = None) -> int: | |
| parser = argparse.ArgumentParser(description="REED setup wizard") | |
| parser.add_argument( | |
| "--env", | |
| default=".env", | |
| help="Path to .env file (default: .env)", | |
| ) | |
| parser.add_argument( | |
| "--out", | |
| default="settings.yaml", | |
| help="Path to write settings.yaml (default: settings.yaml, relative to backend/)", | |
| ) | |
| parser.add_argument( | |
| "--non-interactive", | |
| action="store_true", | |
| help="Skip prompts and fail if no provider keys are detected", | |
| ) | |
| args = parser.parse_args(argv) | |
| logging.basicConfig(level=logging.INFO, format="%(message)s") | |
| env = EnvSettings(_env_file=args.env) | |
| available = detect_available_providers(env) | |
| if not available and args.non_interactive: | |
| print("No provider keys detected and --non-interactive set; exiting.", file=sys.stderr) | |
| return 1 | |
| if args.non_interactive: | |
| provider = available[0] | |
| model = PROVIDER_MODELS[provider][0] | |
| base_url = None | |
| print(f"Non-interactive mode: provider={provider.value}, model={model}") | |
| else: | |
| provider = prompt_provider(available) | |
| model = prompt_model(provider) | |
| base_url = prompt_base_url(provider) | |
| write_settings(Path(args.out), provider=provider, model=model, base_url=base_url) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |