"""Generate the static terminal service-provider projection from core profiles.""" from __future__ import annotations import argparse import json import os import stat import sys import tempfile from pathlib import Path from flinttrade_core.llm_provider_profiles import LLM_PROVIDER_PROFILES from flinttrade_core.news_provider_profiles import NEWS_PROVIDER_PROFILES ROOT = Path(__file__).resolve().parents[1] OUTPUT = ROOT / "packages" / "apps" / "terminal" / "src" / "generated" / "serviceProviders.ts" HEADER = "// Generated by scripts/generate-terminal-service-profiles.py. Do not edit.\n\n" def _llm_projection() -> list[dict[str, object]]: return [ { "apiKeyEnv": profile.api_key_env, "authModes": list(profile.auth_modes), "defaultHost": profile.default_host, "defaultModel": profile.default_model, "displayName": profile.display_name, "endpointTemplate": profile.endpoint_template, "managedRuntime": profile.managed_runtime, "providerId": str(profile.provider_id), "requiresHost": profile.requires_host, "trustDestination": profile.trust_destination, } for profile in LLM_PROVIDER_PROFILES ] def _news_projection() -> list[dict[str, object]]: return [ { "channels": [{"name": name, "url": url} for name, url in profile.channels], "displayName": profile.display_name, "providerId": profile.provider_id, } for profile in NEWS_PROVIDER_PROFILES ] def render() -> str: """Render stable TypeScript literals from static core profile facts.""" llm_json = json.dumps(_llm_projection(), ensure_ascii=False, indent=2, sort_keys=True) news_json = json.dumps(_news_projection(), ensure_ascii=False, indent=2, sort_keys=True) return ( HEADER + f"export const LLM_PROVIDER_PROFILES = {llm_json} as const;\n\n" + 'export type LlmProviderId = (typeof LLM_PROVIDER_PROFILES)[number]["providerId"];\n\n' + f"export const NEWS_PUBLISHER_PROFILES = {news_json} as const;\n" ) def _atomic_write(content: str, output: Path) -> None: output.parent.mkdir(parents=True, exist_ok=True) mode = stat.S_IMODE(output.stat().st_mode) if output.exists() else 0o644 descriptor, temporary_name = tempfile.mkstemp(prefix=f".{output.name}.", dir=output.parent) temporary_path = Path(temporary_name) try: with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream: stream.write(content) temporary_path.chmod(mode) os.replace(temporary_path, output) except BaseException: temporary_path.unlink(missing_ok=True) raise def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="fail when the checked-in output is stale") parser.add_argument("--output", type=Path, default=OUTPUT, help="write or check a specific output path") arguments = parser.parse_args() rendered = render() output = arguments.output if arguments.check: if not output.is_file() or output.read_text(encoding="utf-8") != rendered: print(f"Generated terminal service profiles are stale: {output}", file=sys.stderr) return 1 return 0 _atomic_write(rendered, output) return 0 if __name__ == "__main__": raise SystemExit(main())