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:
2026-07-11 19:12:36 -04:00
parent 9131e83c08
commit c2854ba163
23 changed files with 22050 additions and 144 deletions

View File

@@ -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