Anthropic OAuth LLM, entitlements (feature 1), renewals, + 7 enhancements
- LLM: Anthropic Sonnet 5 (effort=low) via OAuth Bearer (Claude Code keychain token, expiry-aware); NVIDIA/LM Studio endpoints kept commented for switch-back. - Entitlements: scripts/import_entitlements.py joins ACL export to accounts by normalized parent name (324/342 matched; ID column Excel-corrupted), maps products->capabilities via data/product_capability_map.json. Raw ACL csv and entitlements.json are gitignored (confidential); accounts.json carries derived currentProducts + ownedCapabilityIds. Owned capabilities tag briefs EXPANSION. - Renewals: local renewal-proximity score boost (window 365d, max +0.10) + 'renewals' report; briefs/digests show RENEWAL WINDOW context. - Batch runs: loop --limit/--rep with persistent cursor (strategic + stale-first). - Digests: per-rep HTML export; timestamped digest_<date>_<time>.md kept forever. - Account narrative memory fed into relevance/synthesis; synthesis maintains it. - Signal quality gate drops PR fluff pre-LLM; SEC EDGAR filings connector. - Citation verification: talking points must cite [S#] signals or are dropped. - Brief filenames use account-name slug; whitespace report + installed-base view. - USAGE.md: full command/flag/env reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,27 +60,37 @@ LLM_RATELIMIT_WAIT_SECONDS = int(os.environ.get("LLM_RATELIMIT_WAIT_SECONDS", "9
|
||||
LLM_RATELIMIT_MAX_RETRIES = int(os.environ.get("LLM_RATELIMIT_MAX_RETRIES", "24"))
|
||||
|
||||
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "").strip()
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "claude-sonnet-4-6")
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "claude-sonnet-5")
|
||||
# Anthropic OAuth (subscription auth instead of an API key). Token resolution:
|
||||
# ANTHROPIC_OAUTH_TOKEN env var, else the Claude Code credential in the macOS
|
||||
# keychain ("Claude Code-credentials"), read at runtime so refreshes are picked up.
|
||||
ANTHROPIC_OAUTH_TOKEN = os.environ.get("ANTHROPIC_OAUTH_TOKEN", "").strip()
|
||||
ANTHROPIC_USE_KEYCHAIN = os.environ.get("ANTHROPIC_USE_KEYCHAIN", "1").strip() == "1"
|
||||
# Effort level for reasoning models (Sonnet 5 etc.): low | medium | high | max.
|
||||
ANTHROPIC_EFFORT = os.environ.get("ANTHROPIC_EFFORT", "low").strip()
|
||||
|
||||
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "").strip()
|
||||
GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
|
||||
|
||||
_provider = os.environ.get("LLM_PROVIDER", "").strip().lower()
|
||||
if not _provider:
|
||||
if OPENAI_API_KEY:
|
||||
if ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY:
|
||||
_provider = "anthropic"
|
||||
elif OPENAI_API_KEY:
|
||||
_provider = "openai"
|
||||
elif GEMINI_API_KEY:
|
||||
_provider = "gemini"
|
||||
elif ANTHROPIC_API_KEY:
|
||||
_provider = "anthropic"
|
||||
else:
|
||||
_provider = "none"
|
||||
# Selecting a provider whose key is missing => no usable provider.
|
||||
# ("anthropic" is exempt here: the token may come from the keychain at runtime.)
|
||||
if _provider == "openai" and not OPENAI_API_KEY:
|
||||
_provider = "none"
|
||||
elif _provider == "gemini" and not GEMINI_API_KEY:
|
||||
_provider = "none"
|
||||
elif _provider == "anthropic" and not ANTHROPIC_API_KEY:
|
||||
elif _provider == "anthropic" and not (
|
||||
ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_USE_KEYCHAIN
|
||||
):
|
||||
_provider = "none"
|
||||
|
||||
LLM_PROVIDER = _provider
|
||||
@@ -95,7 +105,8 @@ def llm_label() -> str:
|
||||
return f"OpenAI-compatible ({OPENAI_MODEL} @ {host})"
|
||||
if LLM_PROVIDER == "gemini":
|
||||
return f"Gemini ({GEMINI_MODEL})"
|
||||
return f"Anthropic ({LLM_MODEL})"
|
||||
auth = "OAuth" if (ANTHROPIC_OAUTH_TOKEN or not ANTHROPIC_API_KEY) else "API key"
|
||||
return f"Anthropic ({LLM_MODEL}, effort={ANTHROPIC_EFFORT}, {auth})"
|
||||
|
||||
# --- Scoring thresholds (Phase 5 of the design) ---
|
||||
PRIORITY_THRESHOLD = float(os.environ.get("PRIORITY_THRESHOLD", "0.72"))
|
||||
@@ -105,6 +116,13 @@ STANDARD_THRESHOLD = float(os.environ.get("STANDARD_THRESHOLD", "0.45"))
|
||||
# the model itself doubts out of the rep's queue.
|
||||
CONFIDENCE_FLOOR = float(os.environ.get("CONFIDENCE_FLOOR", "0.35"))
|
||||
|
||||
# --- Renewal proximity (from local entitlement data; no LLM) ---
|
||||
# A renewal within this window is a live commercial event: boost the brief's
|
||||
# score, scaled by how close the renewal is. Same-capability renewals get the
|
||||
# full boost; any-product renewals on the account get half.
|
||||
RENEWAL_WINDOW_DAYS = int(os.environ.get("RENEWAL_WINDOW_DAYS", "365"))
|
||||
RENEWAL_BOOST_MAX = float(os.environ.get("RENEWAL_BOOST_MAX", "0.10"))
|
||||
|
||||
# --- Exa.ai news/web harvester ---
|
||||
EXA_API_KEY = os.environ.get("EXA_API_KEY", "").strip()
|
||||
# Enabled only when a key is present AND not explicitly turned off.
|
||||
@@ -128,6 +146,10 @@ EXA_EXCLUDE_DOMAINS = [
|
||||
d.strip() for d in os.environ.get("EXA_EXCLUDE_DOMAINS", "").split(",") if d.strip()
|
||||
]
|
||||
|
||||
# --- SEC EDGAR filings harvester (free, no key) ---
|
||||
SEC_ENABLED = os.environ.get("SEC_ENABLED", "1").strip() == "1"
|
||||
SEC_MAX_FILINGS_PER_ACCOUNT = int(os.environ.get("SEC_MAX_FILINGS_PER_ACCOUNT", "5"))
|
||||
|
||||
# --- Dedup / suppression windows ---
|
||||
SEMANTIC_DEDUP_DAYS = 90
|
||||
SAME_TYPE_SUPPRESS_DAYS = 14
|
||||
|
||||
Reference in New Issue
Block a user