feat(cli): run an analysis without questions from flags (#1127, #1133)

- --ticker, --date and --analysts answer their steps, checked as the prompts check them; --save/--no-save and --show/--no-show answer the questions after the run
- with no terminal, the run stops before it starts and names every flag or TRADINGAGENTS_* variable still needed; a missing API key and an announcement no longer wait for input
- README shows an unattended run
This commit is contained in:
Yijia-Xiao
2026-09-24 20:42:51 +00:00
parent a1b3b5bab8
commit 0c602846ba
11 changed files with 347 additions and 59 deletions
+42
View File
@@ -1,7 +1,10 @@
import datetime
import os
import sys
from pathlib import Path
import questionary
import typer
from dotenv import find_dotenv, set_key
from cli.display import console
@@ -60,6 +63,41 @@ def get_ticker() -> str:
return normalize_ticker_symbol(ticker) if ticker.strip() else "SPY"
def parse_ticker(value: str) -> str:
"""A ticker given on the command line, canonical; empty or malformed is refused."""
if not value.strip() or not is_valid_ticker_input(value):
raise ValueError(f"not a ticker symbol: {value!r} (e.g. {TICKER_INPUT_EXAMPLES})")
return normalize_ticker_symbol(value)
def parse_analysis_date(value: str) -> str:
"""An analysis date as YYYY-MM-DD, today or earlier."""
try:
day = datetime.datetime.strptime(value.strip(), "%Y-%m-%d").date()
except ValueError:
raise ValueError(f"not a date: {value!r}; use YYYY-MM-DD") from None
if day > datetime.date.today():
raise ValueError(f"{value} is in the future")
return day.isoformat()
def parse_analysts(value: str, asset_type: AssetType) -> list[AnalystType]:
"""Comma-separated analyst names, in the canonical order, checked against the asset."""
# "sentiment" is the name users see; the analyst's key is "social".
names = [{"sentiment": "social"}.get(n, n) for n in (n.strip().lower() for n in value.split(",")) if n]
if not names:
raise ValueError("name at least one analyst")
known = {a.value: a for a in AnalystType}
unknown = [n for n in names if n not in known]
if unknown:
raise ValueError(f"unknown analyst {', '.join(unknown)}; choose from {', '.join(known)}")
available = filter_analysts_for_asset_type(list(known.values()), asset_type)
unavailable = [n for n in names if known[n] not in available]
if unavailable:
raise ValueError(f"{', '.join(unavailable)} is not available for {asset_type.value}")
return [a for a in available if a.value in names]
def normalize_ticker_symbol(ticker: str) -> str:
"""Resolve user input to its canonical Yahoo symbol (single source of truth).
@@ -611,6 +649,10 @@ def ensure_api_key(provider: str) -> str | None:
if existing:
return existing
if not sys.stdin.isatty():
console.print(f"[red]{env_var} is not set; there is no terminal to ask for it.[/red]")
raise typer.Exit(code=1)
console.print(
f"\n[yellow]{env_var} is not set in your environment.[/yellow]"
)