feat(x): bird first, grok CLI opt-in only (#1005)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Matt Van Horn
2026-08-14 10:16:04 -07:00
committed by GitHub
parent 0b35e0c2c8
commit 40b42b7bed
11 changed files with 845 additions and 137 deletions
+4 -2
View File
@@ -167,7 +167,9 @@ python3 skills/last30days/scripts/last30days.py "MCP servers" \
**YouTube transcript tuning.** `LAST30DAYS_YT_SUB_LANGS` controls the comma-separated caption-language priority passed to yt-dlp and defaults to `en,es,pt`. When `SCRAPECREATORS_API_KEY` is available, yt-dlp uses one fast attempt before the paid fallback; set `LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT` to the number of seconds allowed for that attempt when a throttled host needs longer than the 12-second default. A VTT completed before the timeout is reused rather than discarded. `LAST30DAYS_YT_SEARCH_TIMEOUT` sets the per-search yt-dlp deadline (default 120s). Comparison-mode fan-out also caps concurrent yt-dlp processes process-wide and caches identical searches within a run so redundant `ytsearch` calls do not self-throttle the same IP.
**X with no X credential (Grok CLI).** Install the Grok CLI (`curl -fsSL https://x.ai/cli/install.sh | bash`) and run `grok login`, and X works with no X account, no browser cookies, and no `XAI_API_KEY`. It sits ahead of the cookie path in the backend chain; pin `LAST30DAYS_X_BACKEND=bird` to prefer cookies. It is not "free" in the way the cookie path is: calls draw on your Grok plan, and depth costs several calls per run because the underlying tool caps each search at 10 posts. Results are validated before use — every returned post's ID is decoded to confirm it falls inside the requested date range, because the retrieval is performed by a language model and can otherwise return confident, well-formed posts that were never searched for.
**X backend priority (bird first).** The default X backend chain is bird (browser cookies) → xai (API key) → xurl (OAuth2 CLI) → xquik (API key). Cookies beat `XAI_API_KEY` when both are present. A leftover grok login never steals the X lane; see below.
**Grok CLI (opt-in backup).** Install the Grok CLI (`curl -fsSL https://x.ai/cli/install.sh | bash`) and run `grok login`, and X can work with no X account, no browser cookies, and no `XAI_API_KEY`. However, grok is **opt-in only**: a leftover `~/.grok/auth.json` must never steal the X lane. Pin `LAST30DAYS_X_BACKEND=grok` to enable it. It is not "free" in the way the cookie path is: calls draw on your Grok plan, and depth costs several calls per run because the underlying tool caps each search at 10 posts. Results are validated before use — every returned post's ID is decoded to confirm it falls inside the requested date range, because the retrieval is performed by a language model and can otherwise return confident, well-formed posts that were never searched for.
**X on cookie-less hosts.** Bird (the free X source) scrapes X using your logged-in browser cookies (`AUTH_TOKEN`/`CT0`), which agent hosts like OpenClaw, CI, or headless runs often can't supply — and scraping carries some account risk. On those, set `XQUIK_API_KEY` (or `XAI_API_KEY`) for full, ranked X coverage from a single API key: the same engagement-based ranking, first-party authorship, and handle (from/mentions) lanes the native X source gets. `--diagnose` reports whether the key is working (and flags an unpaid key).
@@ -421,7 +423,7 @@ Every live run writes its JSON result to `~/.config/last30days/doctor-cache.json
| --- | --- |
| `LAST30DAYS_DOCTOR_TTL` | Freshness window for `doctor --cached`, in **seconds**. Defaults to `900` (15 minutes). `0` makes every `--cached` call run live. |
| `LAST30DAYS_DOCTOR_PROBE_TIMEOUT` | Per-source deadline (**seconds**) for `doctor --probe` live checks. Defaults to `10`. Caps each concurrent probe so a slow source cannot hang the command. |
| `LAST30DAYS_X_BACKEND` | Pins the X backend (`xai` / `grok` / `bird` / `xurl` / `xquik`); doctor renders the pin and predicts "will use" accordingly. Pin `bird` to keep the cookie path when a signed-in `grok` CLI is present, since `grok` otherwise wins by default. |
| `LAST30DAYS_X_BACKEND` | Pins the X backend (`bird` / `xai` / `xurl` / `xquik` / `grok`); doctor renders the pin and predicts "will use" accordingly. The unpinned auto chain is bird → xai → xurl → xquik (grok is opt-in only). Pin `grok` to enable it; a leftover `~/.grok/auth.json` is never auto-selected. |
| `LAST30DAYS_REDDIT_BACKEND` | `scrapecreators` makes ScrapeCreators the primary Reddit backend; doctor renders Reddit's conditional routing with the pin applied. |
| `LAST30DAYS_REDDIT_SC_MIN_ITEMS` | Integer thinness floor for ScrapeCreators Reddit **search** backfill. Default `0` = empty-only (free path keeps any non-empty result; no credit spend). Set above `0` to backfill when free yield is below that count; merged results dedupe by post id. Requires `SCRAPECREATORS_API_KEY`. Ignored when `LAST30DAYS_REDDIT_BACKEND=scrapecreators` (SC is already primary). |
@@ -0,0 +1 @@
X backend priority changed: bird (browser cookies) is now first in the auto chain, ahead of xai/xurl/xquik. Cookies beat XAI_API_KEY when both are present. Grok CLI is demoted to opt-in only: a leftover `~/.grok/auth.json` no longer steals the X lane. Pin `LAST30DAYS_X_BACKEND=grok` to enable it explicitly.
@@ -0,0 +1,40 @@
# feat(x): demote Grok CLI to opt-in backup
Stop using the Grok CLI as the default X backend. A leftover `~/.grok/auth.json` must never steal the X lane. Grok stays as a pin-only backup: off unless LAST30DAYS_X_BACKEND=grok or --x-backend grok.
### Requirements
- R1. Unpinned auto chain is bird → xai → xurl → xquik. Bird is first. Grok is not a member. Presence of ~/.grok/auth.json (ok, expired, or error) must not change which backend an unpinned run uses.
- R2. Grok remains a valid explicit selection: LAST30DAYS_X_BACKEND=grok and --x-backend grok. A pin forces grok with no failover. If grok is unusable, X is unconfigured and doctor/footer say so with the existing login hint.
- R3. Doctor "will use: grok" only when grok is pinned and the probe is OK or DEGRADED. Unpinned, grok may appear as unused opt-in ("available, unused — pin LAST30DAYS_X_BACKEND=grok"), never as the predicted winner.
- R4. get_x_source_status and get_x_source_with_method must prefer bird over xai/xurl/xquik when cookies are present. Grok wins only when the pin is grok.
- R5. Host docs stop presenting grok as the default keyless X path. Document it as opt-in backup. Default story is bird first, then xai / xurl / xquik.
- R6. Setup / first-run / prescriptions do not nag grok login as the fix for missing X. Cookie consent and paid keys remain the default prescriptions. Grok login is mentioned only as an optional pin.
- R7. Do not delete scripts/lib/grok_x.py, retrieve-judge-retry, or expires_at honesty. Pinned grok still uses them.
- R8. A machine with only a grok login (no cookies, no XAI/XQUIK, no xurl) has X unconfigured until the user pins grok. Footer: X skipped-unconfigured, not auth-failed-from-grok.
- R9. Tests cover the cases above; docs/changelog updated.
### Implementation units
U1 env.py: _X_BACKEND_ORDER = ("bird", "xai", "xurl", "xquik"); X_BACKEND_OPT_IN = ("grok",); X_BACKEND_KNOWN = ORDER + OPT_IN; pin uses KNOWN; unpinned walks ORDER only; get_x_source_status bird first, grok only if pinned; get_x_source_with_method bird before xai.
U2 backends.py / doctor.py / prescriptions.py: descriptor is auto ORDER then grok opt-in; unpinned collect-then-pick ignores opt-in; do not change _probe_grok honesty.
U3 SKILL.md, CONFIGURATION.md, README.md, README.pt-BR.md if needed, changelog.d: remove "sits ahead of the cookie path"; document bird → xai → xurl → xquik; pin grok to enable it.
U4 tests: unpinned grok-only empty; unpinned grok+bird → bird; unpinned bird+xai → bird; pin grok+store → ["grok"]; pin grok no store empty; doctor unpinned never predicts grok; descriptor parity treats grok as trailing opt-in.
### Tests T1T7
T1 unpinned grok AUTH_OK, no other creds → X unconfigured
T2 unpinned grok AUTH_EXPIRED, no other creds → X unconfigured (not will-use grok)
T3 unpinned grok AUTH_OK + cookies → bird
T4 unpinned XAI_API_KEY + grok store, no cookies → xai
T4b unpinned XAI_API_KEY + cookies → bird
T5 pin grok AUTH_OK → grok no failover
T6 pin grok no store → error / grok login prescription
T7 docs match R5
### Keep-the-door-open (KTD)
1. grok_x.py stays untouched
2. x_judge.py stays untouched
3. expires_at honesty stays untouched
4. auth.x.ai is never called
5. bird cookie extraction is unchanged
+3 -13
View File
@@ -577,8 +577,6 @@ Options:
**If the user picks Auto setup:**
**Check for a Grok path before asking for cookies.** Run `command -v grok`. If it resolves, X can be unlocked with no X credential at all, so lead with that instead of a cookie read: tell the user `grok login` is enough and only offer the cookie options if they decline. Do not call it free — it needs a Grok plan.
Get cookie consent first. Check if `BROWSER_CONSENT=true` already exists in `~/.config/last30days/.env`; if so, skip the consent prompt and run `setup --allow-browser-cookies` directly. Otherwise **call AskUserQuestion:**
Question: "Auto setup installs the free CLIs either way - yt-dlp (YouTube), Digg, arXiv, and Techmeme. The only thing that needs your OK is reading your browser's x.com cookies to authenticate X/Twitter search: I check Chrome first (a one-time macOS Keychain prompt may appear; click Always Allow), then Firefox and Safari. Cookies are read live, never saved to disk. Include X?"
Options (give each option the description shown):
@@ -586,7 +584,7 @@ Options (give each option the description shown):
- "Skip X - just the CLIs" - description: "No cookie reads. Still installs yt-dlp (YouTube), Digg, arXiv, and Techmeme." Run `FROM_BROWSER=off "${LAST30DAYS_PYTHON:-python3}" skills/last30days/scripts/last30days.py setup`.
- "xAI API key for X instead" - description: "Use an api.x.ai key for X search (no cookie read), plus install yt-dlp (YouTube), Digg, arXiv, and Techmeme." Ask them to paste it, write `XAI_API_KEY` to `.env`, then run `FROM_BROWSER=off "${LAST30DAYS_PYTHON:-python3}" skills/last30days/scripts/last30days.py setup`.
When `command -v grok` resolved, replace the "xAI API key for X instead" option with: "Sign in to Grok instead" - description: "X search with no X account, cookies, or API key. Run `grok login` once." Nothing is written to `.env`; still install the free CLIs with `FROM_BROWSER=off "${LAST30DAYS_PYTHON:-python3}" skills/last30days/scripts/last30days.py setup`.
**Grok CLI is an opt-in backup, not a setup-time recommendation.** Do NOT check for grok first or offer it as a primary option during setup. A leftover `~/.grok/auth.json` must never steal the X lane. If the user mentions having a Grok account, tell them: "You can use the Grok CLI by pinning `LAST30DAYS_X_BACKEND=grok` in your `.env` after running `grok login`. This is opt-in because a leftover grok login should not take over X automatically." Do not call it free — it needs a Grok plan.
The consented `setup --allow-browser-cookies` run extracts cookies (Chrome/Chromium family first via the Keychain with no Full Disk Access, then Firefox and Safari as fallbacks; the winning browser is pinned for future runs only when it is Firefox or Safari, so Chrome never re-triggers the Keychain prompt on later runs) and best-effort installs yt-dlp (YouTube), the free keyless Digg CLI (`digg-pp-cli` via `@mvanhorn/printing-press-library install digg --cli-only`; Digg activates only when the binary is on the **agent subprocess PATH**, typically `$HOME/.local/bin`; setup reports honestly if installed off-PATH; recommend-only if `npx` is unavailable), plus the free keyless arXiv and Techmeme CLIs. Show the user what was found and installed - including whether Digg landed on PATH (active) or off-PATH (installed but not yet active).
@@ -2010,23 +2008,15 @@ If the research output contains a `**🔍 Research Coverage:**` block, render it
**Just-in-time X unlock:** If X returned 0 results because no X auth is configured (no AUTH_TOKEN/CT0, no XAI_API_KEY, no FROM_BROWSER), offer to set it up right there.
**First check whether a Grok path is already present.** Run `command -v grok`. If it resolves, the user needs no X credential at all — say so and offer `grok login` as the first option, because it is one command and unlocks X with no X account, no cookies, and no API key.
**Call AskUserQuestion.** Question: "X/Twitter wasn't searched. Want to unlock it?"
Options when `grok` IS on PATH (or the user has said they have a Grok account):
- "Sign in to Grok (no X account needed)" - Have them run `grok login`; nothing is written to .env and no X credential is involved
- "Scan my browser cookies (free)" - Get consent, run cookie scan, write BROWSER_CONSENT=true + FROM_BROWSER=auto to .env
- "I have AUTH_TOKEN and CT0 from my browser" - Ask them to paste each value, then write AUTH_TOKEN=<value>\nCT0=<value> to .env
- "Skip for now"
Options when `grok` is NOT on PATH — offer today's options unchanged, and mention the Grok path only as a closing one-liner rather than a menu entry. A user with no Grok account should not have their actionable choices pushed down the list by one they cannot take:
Default options (always presented first — cookie consent and paid keys are the primary X fix):
- "Scan my browser cookies (free)" - Get consent, run cookie scan, write BROWSER_CONSENT=true + FROM_BROWSER=auto to .env
- "I have AUTH_TOKEN and CT0 from my browser" - Ask them to paste each value, then write AUTH_TOKEN=<value>\nCT0=<value> to .env
- "I have an xAI API key" - Ask them to paste it, write XAI_API_KEY to .env
- "Skip for now"
After the modal, if `grok` was absent, add one line: "If you have a Grok account, installing the Grok CLI (`curl -fsSL https://x.ai/cli/install.sh | bash`) unlocks X with no X credential at all." Do not describe the Grok path as free — it needs a Grok plan.
**Grok CLI is an opt-in backup, not a default prescription.** After showing the modal, add one line: "If you have a Grok account and prefer to use it: install the Grok CLI (`curl -fsSL https://x.ai/cli/install.sh | bash`), run `grok login`, then set `LAST30DAYS_X_BACKEND=grok` to enable it." Do not describe the Grok path as free — it needs a Grok plan. Do not put grok first or as a primary recommendation; a leftover `~/.grok/auth.json` must never steal the X lane.
**THEN - Engine footer pass-through (right before invitation):**
+28 -11
View File
@@ -107,12 +107,15 @@ class BackendSpec:
``probe`` must be side-effect-free. When ``paid`` is True the probe is
key-presence only: no subprocess, no network, no credential spend.
``opt_in`` marks backends that are never auto-selected and require an
explicit pin (grok).
"""
name: str
requires: str
probe: Callable[[Dict[str, Any]], "BackendFinding"]
paid: bool = False
opt_in: bool = False
@dataclass(frozen=True)
@@ -427,6 +430,8 @@ _X_PROBES: Dict[str, Callable[[Dict[str, Any]], BackendFinding]] = {
"xquik": _key_probe("xquik", "XQUIK_API_KEY", "XQUIK_API_KEY (xquik.com)"),
}
_X_PAID = {"xai", "xquik"}
# Opt-in backends: never auto-selected; require explicit pin.
_X_OPT_IN = set(env.X_BACKEND_OPT_IN)
_WEB_PROBES: Dict[str, Callable[[Dict[str, Any]], BackendFinding]] = {
"brave": _key_probe("brave", "BRAVE_API_KEY", "BRAVE_API_KEY"),
@@ -447,25 +452,32 @@ _SC_SPEC = BackendSpec(
paid=True,
)
# X backend requirements, keyed by name.
_X_REQUIRES: Dict[str, str] = {
"xai": "XAI_API_KEY (xAI/Grok live search)",
"grok": "grok CLI installed + signed in (opt-in only; pin to enable)",
"bird": "X browser cookies (AUTH_TOKEN/CT0) + node",
"xurl": "xurl CLI installed + OAuth2 login",
"xquik": "XQUIK_API_KEY (xquik.com)",
}
DESCRIPTORS: Dict[str, ChainDescriptor] = {
# X: chain order and pin var imported from env.py (single source of truth).
# Backends include the auto chain (X_BACKEND_ORDER) plus opt-in entries
# (X_BACKEND_OPT_IN) for doctor visibility. Opt-in backends like grok
# appear in findings but are never auto-selected; pin to enable.
"x": ChainDescriptor(
source="x",
mode=MODE_ALTERNATIVE,
backends=tuple(
BackendSpec(
name=name,
requires={
"xai": "XAI_API_KEY (xAI/Grok live search)",
"grok": "grok CLI installed + signed in (no X credential)",
"bird": "X browser cookies (AUTH_TOKEN/CT0) + node",
"xurl": "xurl CLI installed + OAuth2 login",
"xquik": "XQUIK_API_KEY (xquik.com)",
}[name],
requires=_X_REQUIRES[name],
probe=_X_PROBES[name],
paid=name in _X_PAID,
opt_in=name in _X_OPT_IN,
)
for name in env.X_BACKEND_ORDER
for name in env.X_BACKEND_ORDER + env.X_BACKEND_OPT_IN
),
pin_var=env.X_BACKEND_PIN_VAR,
),
@@ -575,6 +587,8 @@ def _resolve_alternative(
) -> BackendResolution:
names = [spec.name for spec in descriptor.backends]
by_name = {f.name: f for f in findings}
# Track which backends are opt-in (never auto-selected).
opt_in_names = {spec.name for spec in descriptor.backends if spec.opt_in}
res = BackendResolution(
source=descriptor.source,
mode=MODE_ALTERNATIVE,
@@ -611,18 +625,21 @@ def _resolve_alternative(
# Collect-then-pick: first fully-usable wins; else best degraded; else
# error carrying the highest-priority backend's prescription.
for finding in findings:
# Opt-in backends are NEVER auto-selected; skip them entirely.
auto_findings = [f for f in findings if f.name not in opt_in_names]
for finding in auto_findings:
if finding.status == health.OK:
res.active_backend = finding.name
res.tier = TIER_OK
return res
for finding in findings:
for finding in auto_findings:
if finding.status == health.DEGRADED:
res.active_backend = finding.name
res.tier = TIER_WARN
return res
res.tier = TIER_ERROR
res.prescription = findings[0].prescription if findings else ""
# Prescription comes from the first auto-chain backend, not opt-in.
res.prescription = auto_findings[0].prescription if auto_findings else ""
return res
+84 -23
View File
@@ -417,20 +417,6 @@ def _reddit_record(config):
def _x_record(config):
record = _chained_record("x", config)
# A usable grok CLI covers X with no X credential at all, so it must not be
# reported as unconfigured. Checked before the browser-auth override below
# because it outranks bird in the chain and, unlike a cookie session, its
# availability is verifiable locally rather than "not verified until a run".
from . import grok_x
if record["status"] == "unconfigured" and grok_x.has_stored_auth():
record["status"] = health.OK
record["tier"] = TIER_BY_STATUS[health.OK]
record["note"] = (
"will use: grok (grok CLI, signed in; no X account, cookies, or "
"API key needed)"
)
record["fix"] = ""
return record
# Diagnose/doctor load config in plan_only mode, so browser cookies are not
# extracted and every X backend reads as statically missing -> unconfigured.
# But if bird is installed and FROM_BROWSER will authenticate X at run time,
@@ -439,16 +425,91 @@ def _x_record(config):
# diagnose cannot drift. It reads no cookie *values*, so it confirms a run
# will *attempt* browser auth, not that the session is currently valid -
# keep the note honest and point at the verified key-backed path.
if record["status"] == "unconfigured" and env.x_pending_browser_auth(
config, local_only=True
):
record["status"] = health.OK
record["tier"] = TIER_BY_STATUS[health.OK]
record["note"] = (
"will use: bird (browser cookies; session not verified until a run "
"- add XAI_API_KEY for a verified, cookie-free path)"
#
# This check MUST come before grok normalization: a pending bird path takes
# precedence over marking X as unconfigured due to an unused grok store.
# Handle both "unconfigured" (all backends missing) and "error" (grok present
# but opt-in, no auto-chain backend usable) when pending bird applies.
#
# HOWEVER: pending bird must NOT replace a record that has a configured
# auto-chain backend in ERROR/DEGRADED/BROKEN/TIMEOUT. Same rule as the
# grok normalizer: only upgrade when no auto backend is configured-but-broken.
pending_bird = env.x_pending_browser_auth(config, local_only=True)
if pending_bird and record["status"] in ("unconfigured", health.ERROR):
backends_list = record.get("backends", [])
auto_chain_names = {"bird", "xai", "xurl", "xquik"}
auto_backends = [b for b in backends_list if b.get("name") in auto_chain_names]
# Only apply pending-bird upgrade if ALL auto-chain backends are MISSING.
# If any auto backend is configured but broken, keep that error.
all_auto_missing = all(
b.get("status") == health.MISSING for b in auto_backends
)
record["fix"] = ""
if all_auto_missing:
record["status"] = health.OK
record["tier"] = TIER_BY_STATUS[health.OK]
record["note"] = (
"will use: bird (browser cookies; session not verified until a run "
"- add XAI_API_KEY for a verified, cookie-free path)"
)
record["fix"] = ""
return record
#
# Grok is opt-in only: a leftover ~/.grok/auth.json must never steal the X
# lane. The grok backend appears in the chain findings (for visibility) but
# is never auto-selected. Doctor reports it as "available, unused - pin
# LAST30DAYS_X_BACKEND=grok to enable" rather than "will use: grok".
#
# R3/R8: When no auto-chain backend is CONFIGURED (all MISSING) but grok has
# any non-MISSING status, X is unconfigured/skipped - NOT broken/auth-failed.
# The tier must be "off" (unconfigured), not "error" (NOT WORKING).
#
# HOWEVER: if an auto-chain backend IS configured but broken (ERROR/DEGRADED),
# do NOT normalize to unconfigured. Keep that backend's error and repair
# guidance. Unused grok must not swallow a genuine auto-chain failure.
#
# Do NOT apply this normalization when pending browser auth would make bird
# usable — check pending_bird first (handled above via early return).
if (
record["tier"] == TIER_ERROR
and not record.get("pinned")
and record.get("active_backend") is None
and not pending_bird
):
backends_list = record.get("backends", [])
auto_chain_names = {"bird", "xai", "xurl", "xquik"}
auto_backends = [b for b in backends_list if b.get("name") in auto_chain_names]
# Only normalize if ALL auto-chain backends are MISSING (not configured).
# If any auto backend is ERROR/DEGRADED/BROKEN/TIMEOUT, keep that error.
all_auto_missing = all(
b.get("status") == health.MISSING for b in auto_backends
)
if not all_auto_missing:
# An auto-chain backend is configured but broken — do NOT normalize.
# Keep the original error and its repair guidance.
return record
grok_finding = next(
(b for b in backends_list if b.get("name") == "grok"),
None,
)
if grok_finding and grok_finding.get("status") in (
health.OK,
health.DEGRADED,
health.ERROR,
):
record["status"] = "unconfigured"
record["tier"] = TIER_OFF
if grok_finding.get("status") == health.ERROR:
record["note"] = (
"X unconfigured; grok CLI store is broken but unused (opt-in only) — "
"pin LAST30DAYS_X_BACKEND=grok to enable, then fix the store"
)
else:
record["note"] = (
"X unconfigured; grok CLI available but opt-in only — "
"pin LAST30DAYS_X_BACKEND=grok to enable"
)
record["fix"] = ""
return record
return record
+66 -26
View File
@@ -751,12 +751,18 @@ def extract_browser_credentials(config: dict[str, Any]) -> dict[str, str]:
def get_x_source_with_method(config: dict[str, Any]) -> tuple[str | None, str]:
"""Return (source, method) for X search, where method describes the auth origin."""
if config.get("XAI_API_KEY"):
return "xai", "xai"
"""Return (source, method) for X search, where method describes the auth origin.
Order mirrors _X_BACKEND_ORDER: bird first (cookies beat XAI_API_KEY when
both are present), then xai, then xurl. Grok is opt-in only and is never
auto-selected here.
"""
# Bird first: cookies beat XAI_API_KEY when both are present.
if config.get("AUTH_TOKEN") and config.get("CT0"):
method = config.get("_AUTH_TOKEN_SOURCE", "env")
return "bird", method
if config.get("XAI_API_KEY"):
return "xai", "xai"
# Fall back to xurl CLI (official X API v2, OAuth2, free developer app)
from . import xurl_x
if xurl_x.is_available():
@@ -789,17 +795,27 @@ def get_reddit_source(config: dict[str, Any]) -> str | None:
# source; the rest are ordered failover backups, tried only if the one before
# returns nothing or errors. There is one X source ("x"); these are its
# interchangeable backends, never run in parallel.
# xai — xAI/Grok live search (XAI_API_KEY)
# bird — X GraphQL scrape via the user's browser cookies (AUTH_TOKEN/CT0)
# xai — xAI/Grok live search (XAI_API_KEY)
# xurl — official X API v2 (xurl CLI, OAuth2)
# xquik — key-based REST X search (XQUIK_API_KEY); keyless of browser cookies
_X_BACKEND_ORDER = ("xai", "grok", "bird", "xurl", "xquik")
# xquik — key-based REST X search (XQUIK_API_KEY)
_X_BACKEND_ORDER = ("bird", "xai", "xurl", "xquik")
# Opt-in backends: never in the unpinned auto chain; require explicit pin.
# grok is here because a leftover ~/.grok/auth.json must never steal the X
# lane. Pin LAST30DAYS_X_BACKEND=grok to enable it.
_X_BACKEND_OPT_IN = ("grok",)
# All known backends (auto chain + opt-in): valid values for the pin var.
_X_BACKEND_KNOWN = _X_BACKEND_ORDER + _X_BACKEND_OPT_IN
# Public routing definitions for the doctor/backend-descriptor layer
# (lib/backends.py). These are aliases for knowledge this module already
# owns — the declared X chain order and the pin/floor env var names — so
# descriptors import one source of truth instead of restating it.
X_BACKEND_ORDER = _X_BACKEND_ORDER
X_BACKEND_OPT_IN = _X_BACKEND_OPT_IN
X_BACKEND_KNOWN = _X_BACKEND_KNOWN
X_BACKEND_PIN_VAR = 'LAST30DAYS_X_BACKEND'
REDDIT_BACKEND_PIN_VAR = 'LAST30DAYS_REDDIT_BACKEND'
REDDIT_SC_MIN_ITEMS_VAR = 'LAST30DAYS_REDDIT_SC_MIN_ITEMS'
@@ -842,9 +858,14 @@ def x_backend_chain(config: dict[str, Any], local_only: bool = False) -> list[st
exactly one X source — these are its backends, never fetched in parallel.
A ``LAST30DAYS_X_BACKEND`` pin forces a single backend (no failover): the
user explicitly chose it. Browser-cookie probing is intentionally avoided
(automatic Keychain access causes popups); bird counts as available only
when AUTH_TOKEN and CT0 are present explicitly.
user explicitly chose it. Valid pin values are in ``_X_BACKEND_KNOWN``
(the auto chain plus opt-in backends like grok). Browser-cookie probing
is intentionally avoided (automatic Keychain access causes popups); bird
counts as available only when AUTH_TOKEN and CT0 are present explicitly.
Unpinned runs walk only ``_X_BACKEND_ORDER``: opt-in backends like grok
are never auto-selected. A leftover ~/.grok/auth.json must not steal the
X lane; pin ``LAST30DAYS_X_BACKEND=grok`` to enable it explicitly.
``local_only=True`` is the doctor/safe-diagnose flavor: availability is
answered from local evidence only (no subprocess spawns that reach the
@@ -857,11 +878,14 @@ def x_backend_chain(config: dict[str, Any], local_only: bool = False) -> list[st
bird_x.set_credentials(config.get('AUTH_TOKEN'), config.get('CT0'))
preferred = (config.get(X_BACKEND_PIN_VAR) or '').lower()
if preferred in _X_BACKEND_ORDER:
# Pin accepted from _X_BACKEND_KNOWN (auto chain + opt-in like grok).
if preferred in _X_BACKEND_KNOWN:
if _x_backend_available(preferred, config, has_bird_creds, local_only):
return [preferred]
return []
# Unpinned: walk only _X_BACKEND_ORDER (bird -> xai -> xurl -> xquik).
# Opt-in backends like grok are never auto-selected.
return [
b for b in _X_BACKEND_ORDER
if _x_backend_available(b, config, has_bird_creds, local_only)
@@ -1252,27 +1276,43 @@ def get_x_source_status(config: dict[str, Any], probe: bool = False) -> dict[str
# Grok availability is filesystem-only on both paths (PATH lookup plus the
# credential store), so it is safe to compute here regardless of `probe`.
# Grok is opt-in only: it appears in grok_available but never wins the
# unpinned source selection.
from . import grok_x as _grok_x
grok_available = _grok_x.has_stored_auth()
# Determine active source. bird (browser cookies) and xAI win when present;
# when neither is available, xquik is the active X source. A probe that
# clearly failed (False) means xquik is not actually usable.
if xai_available:
source = 'xai'
elif grok_available:
# Ahead of bird per the chain order: grok needs no X credential at all,
# while a cookie session is one expiry away from silently degrading.
source = 'grok'
elif bird_status["authenticated"]:
source = 'bird'
else:
if xurl_available:
source = 'xurl'
elif xquik_available and xquik_working is not False:
source = 'xquik'
# Determine active source. A pin forces a single backend (R4): ANY known
# pin is exclusive, mirroring x_backend_chain's [] semantics. Pinned
# backend available → that source. Pinned backend unavailable → None.
# Otherwise, order mirrors _X_BACKEND_ORDER: bird first (cookies beat
# XAI_API_KEY when both are present), then xai, then xurl, then xquik.
# Grok is opt-in only and never auto-selected; a leftover ~/.grok/auth.json
# must not steal the X lane.
pin = (config.get(X_BACKEND_PIN_VAR) or '').lower()
if pin and pin in _X_BACKEND_KNOWN:
# Pin is exclusive: pinned backend if available, else None (no fallback).
if pin == 'bird':
source = 'bird' if bird_status["authenticated"] else None
elif pin == 'xai':
source = 'xai' if xai_available else None
elif pin == 'xurl':
source = 'xurl' if xurl_available else None
elif pin == 'xquik':
source = 'xquik' if (xquik_available and xquik_working is not False) else None
elif pin == 'grok':
source = 'grok' if grok_available else None
else:
source = None
elif bird_status["authenticated"]:
source = 'bird'
elif xai_available:
source = 'xai'
elif xurl_available:
source = 'xurl'
elif xquik_available and xquik_working is not False:
source = 'xquik'
else:
source = None
return {
"source": source,
+6 -3
View File
@@ -315,9 +315,12 @@ def resolve_runtime(config: dict[str, Any], depth: str) -> tuple[schema.Provider
def _resolve_x_backend(config: dict[str, Any]) -> str | None:
preferred = (config.get(env.X_BACKEND_PIN_VAR) or "").lower()
if preferred in env.X_BACKEND_ORDER:
return preferred
"""Resolve the X backend for runtime fetch.
Delegates to env.get_x_source which handles:
- Any known pin (X_BACKEND_KNOWN) exclusively: returns pin if available, None otherwise
- Unpinned: walks auto-chain (X_BACKEND_ORDER) only, never auto-selects opt-in backends
"""
return env.get_x_source(config)
+318 -15
View File
@@ -153,8 +153,21 @@ class TestDescriptorRegistry:
def test_x_chain_comes_from_env_definitions(self):
d = backends.get_descriptor("x")
assert d.mode == backends.MODE_ALTERNATIVE
assert tuple(s.name for s in d.backends) == env.X_BACKEND_ORDER
assert env.X_BACKEND_ORDER == ("xai", "grok", "bird", "xurl", "xquik")
# Auto chain order: bird first, grok excluded (opt-in only).
assert env.X_BACKEND_ORDER == ("bird", "xai", "xurl", "xquik")
# Grok is opt-in only, not in the auto chain.
assert env.X_BACKEND_OPT_IN == ("grok",)
# All known backends (auto + opt-in) for pin validation.
assert env.X_BACKEND_KNOWN == ("bird", "xai", "xurl", "xquik", "grok")
# Descriptor includes all backends (auto + opt-in) for doctor visibility.
assert tuple(s.name for s in d.backends) == env.X_BACKEND_ORDER + env.X_BACKEND_OPT_IN
# Grok is marked opt-in in the descriptor.
grok_spec = next(s for s in d.backends if s.name == "grok")
assert grok_spec.opt_in is True
# Auto chain backends are NOT marked opt-in.
for name in env.X_BACKEND_ORDER:
spec = next(s for s in d.backends if s.name == name)
assert spec.opt_in is False
assert d.pin_var == env.X_BACKEND_PIN_VAR == "LAST30DAYS_X_BACKEND"
def test_env_exposes_reddit_pin_constants(self):
@@ -197,11 +210,49 @@ class TestXPrediction:
assert res.active_backend == "bird"
assert res.tier == backends.TIER_OK
assert res.pinned is False
# Chain rendered in declared order regardless of availability.
assert res.chain == list(env.X_BACKEND_ORDER)
assert [f.name for f in res.findings] == list(env.X_BACKEND_ORDER)
# Chain includes all backends (auto + opt-in) for doctor visibility.
expected_chain = list(env.X_BACKEND_ORDER + env.X_BACKEND_OPT_IN)
assert res.chain == expected_chain
assert [f.name for f in res.findings] == expected_chain
assert "will use: bird" in res.summary
def test_bird_predicted_even_when_xai_key_present(self):
"""Cookies beat XAI_API_KEY when both are present (bird-first chain)."""
config = {"AUTH_TOKEN": "dummy-token", "CT0": "dummy-ct0", "XAI_API_KEY": "dummy-key"}
res = _resolve_x(config, bird_installed=True)
assert res.active_backend == "bird"
assert res.tier == backends.TIER_OK
assert "will use: bird" in res.summary
def test_grok_is_never_auto_selected_unpinned(self):
"""Grok is opt-in only: even if grok is the only configured backend, X is unconfigured unpinned."""
config = {}
res = _resolve_x(config, grok_installed=True, grok_authed=True)
# Grok is available but opt-in - should NOT be auto-selected.
grok = next(f for f in res.findings if f.name == "grok")
assert grok.status == health.OK
# But it should not be the active backend.
assert res.active_backend is None
assert res.tier == backends.TIER_ERROR
def test_grok_selected_when_pinned(self):
"""Pin grok to enable it explicitly."""
config = {"LAST30DAYS_X_BACKEND": "grok"}
res = _resolve_x(config, grok_installed=True, grok_authed=True)
assert res.active_backend == "grok"
assert res.pinned is True
assert res.pin == "grok"
assert res.tier == backends.TIER_OK
def test_grok_pin_with_no_store_is_error(self):
"""Pin grok without a valid store -> error with grok login prescription."""
config = {"LAST30DAYS_X_BACKEND": "grok"}
res = _resolve_x(config, grok_installed=True, grok_authed=False)
assert res.active_backend is None
assert res.pinned is True
assert res.tier == backends.TIER_ERROR
assert "grok login" in res.prescription.lower()
# Scenario 2: pin var set to a later backend -> honored + marked pinned.
def test_pin_to_later_backend_honored_and_marked(self):
config = {
@@ -233,7 +284,9 @@ class TestXPrediction:
res = _resolve_x({})
assert res.active_backend is None
assert res.tier == backends.TIER_ERROR
assert "XAI_API_KEY" in res.prescription
# bird (cookies) is first in the chain, so the prescription is about
# browser cookies, not XAI_API_KEY.
assert "browser-cookie" in res.prescription or "cookies" in res.prescription.lower()
def test_pinned_but_unusable_backend_is_error_with_its_prescription(self):
# Pin bird without cookies: env.x_backend_chain returns [] (pipeline
@@ -286,21 +339,32 @@ class TestXPrediction:
def test_grok_expired_is_degraded_not_ok(self):
"""Expired grok session -> DEGRADED tier (warn), not OK."""
res = _resolve_x({}, grok_installed=True, grok_expired=True)
# Grok is opt-in: needs explicit pin to be selected.
config = {"LAST30DAYS_X_BACKEND": "grok"}
res = _resolve_x(config, grok_installed=True, grok_expired=True)
grok = next(f for f in res.findings if f.name == "grok")
assert grok.status == health.DEGRADED
assert grok.usable # DEGRADED is still usable (refresh may work)
assert "expired" in grok.detail.lower()
assert "grok login" in grok.prescription.lower()
# The chain resolution should still pick grok (degraded is usable).
# With pin, grok is selected (degraded is usable).
assert res.active_backend == "grok"
assert res.tier == backends.TIER_WARN
def test_grok_expired_unpinned_not_selected(self):
"""Expired grok without pin: X unconfigured, grok not auto-selected."""
res = _resolve_x({}, grok_installed=True, grok_expired=True)
grok = next(f for f in res.findings if f.name == "grok")
assert grok.status == health.DEGRADED
# Grok is opt-in, so even though it's usable (degraded), it's not selected.
assert res.active_backend is None
assert res.tier == backends.TIER_ERROR
def test_grok_expired_with_fallback_picks_fallback(self):
"""When grok is expired AND a better backend is OK, pick the OK one."""
"""When grok is expired AND a better auto-chain backend is OK, pick the OK one."""
config = {"AUTH_TOKEN": "dummy-token", "CT0": "dummy-ct0"}
res = _resolve_x(config, grok_installed=True, grok_expired=True, bird_installed=True)
# Bird is OK, grok is DEGRADED. OK wins over DEGRADED.
# Bird is OK and in the auto chain. Grok is not considered (opt-in).
assert res.active_backend == "bird"
assert res.tier == backends.TIER_OK
@@ -313,14 +377,17 @@ class TestXPrediction:
# ---------------------------------------------------------------------------
# Grok session expiry: three states
# Grok session expiry: three states (grok is opt-in only)
# ---------------------------------------------------------------------------
class TestGrokExpiryStates:
"""Test the three grok auth states from the plan:
1. No grok CLI -> silent fallback
2. CLI installed, never logged in -> silent fallback
3. CLI installed, WAS logged in, session dead -> DEGRADED with expiry info
1. No grok CLI -> silent fallback (opt-in only)
2. CLI installed, never logged in -> silent fallback (opt-in only)
3. CLI installed, WAS logged in, session dead -> DEGRADED with expiry info (opt-in only)
Note: Grok is opt-in only. These tests verify the finding status, but grok
is never auto-selected unpinned.
"""
def test_no_grok_cli_is_missing(self):
@@ -329,6 +396,9 @@ class TestGrokExpiryStates:
grok = next(f for f in res.findings if f.name == "grok")
assert grok.status == health.MISSING
assert "not found on PATH" in grok.detail
# Grok is opt-in, so even MISSING doesn't affect the resolution.
# X is unconfigured (no auto-chain backends available).
assert res.active_backend is None
def test_grok_installed_never_logged_in_is_missing(self):
"""CLI installed but never logged in -> MISSING with login hint."""
@@ -337,6 +407,8 @@ class TestGrokExpiryStates:
assert grok.status == health.MISSING
assert "not signed in" in grok.detail
assert "grok login" in grok.prescription
# Grok is opt-in, so X is unconfigured.
assert res.active_backend is None
def test_grok_session_expired_is_degraded_with_expiry(self):
"""Session expired -> DEGRADED with timestamp and refresh hint."""
@@ -346,12 +418,16 @@ class TestGrokExpiryStates:
assert "expired" in grok.detail.lower()
# The detail should include the expiry timestamp and hint
assert "refresh" in grok.detail.lower() or "login" in grok.prescription.lower()
# Grok is opt-in, so X is unconfigured even with degraded grok.
assert res.active_backend is None
def test_grok_healthy_session_is_ok(self):
"""Non-expired credentials -> OK."""
"""Non-expired credentials -> OK, but still opt-in only."""
res = _resolve_x({}, grok_installed=True, grok_authed=True)
grok = next(f for f in res.findings if f.name == "grok")
assert grok.status == health.OK
# Grok is opt-in, so X is unconfigured unpinned.
assert res.active_backend is None
# ---------------------------------------------------------------------------
@@ -605,6 +681,233 @@ class TestXParityWithPipeline:
def test_parity_nothing_configured(self):
self._assert_parity({})
def test_parity_grok_only_unpinned_is_unconfigured(self):
"""Grok-only with no pin: X unconfigured (parity with env.x_backend_chain)."""
self._assert_parity({}, grok_installed=True, grok_authed=True)
def test_parity_grok_pinned(self):
"""Grok pinned: grok is selected (parity with env.x_backend_chain)."""
self._assert_parity(
{"LAST30DAYS_X_BACKEND": "grok"},
grok_installed=True,
grok_authed=True,
)
def test_parity_cookies_beat_xai_key(self):
"""Cookies beat XAI_API_KEY when both present (bird-first chain)."""
self._assert_parity(
{"AUTH_TOKEN": "dummy-token", "CT0": "dummy-ct0", "XAI_API_KEY": "dummy-key"},
bird_installed=True,
)
# ---------------------------------------------------------------------------
# get_x_source_status pin semantics (R4): grok pin forces grok source
# ---------------------------------------------------------------------------
class TestGetXSourceStatusGrokPin:
"""get_x_source_status must respect LAST30DAYS_X_BACKEND=grok pin."""
def test_pin_grok_with_store_returns_grok_source(self):
"""Pin grok + valid store -> get_x_source_status source is 'grok'."""
config = {"LAST30DAYS_X_BACKEND": "grok"}
bird_status = {
"installed": False,
"authenticated": False,
"username": "",
"can_install": False,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=True),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
assert status["source"] == "grok"
assert status["grok_available"] is True
def test_unpinned_with_store_does_not_return_grok_source(self):
"""Unpinned + valid grok store -> source is NOT 'grok' (opt-in only)."""
config = {} # No pin
bird_status = {
"installed": False,
"authenticated": False,
"username": "",
"can_install": False,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=True),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
# Grok is available but NOT the source (opt-in only)
assert status["source"] != "grok"
assert status["source"] is None # No other backend configured
assert status["grok_available"] is True
def test_pin_grok_with_cookies_still_returns_grok(self):
"""Pin grok with cookies present -> grok (pin forces single backend)."""
config = {
"LAST30DAYS_X_BACKEND": "grok",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
}
bird_status = {
"installed": True,
"authenticated": True,
"username": "test",
"can_install": True,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=True),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
# Pin forces grok even when bird is available
assert status["source"] == "grok"
def test_pin_grok_no_store_with_other_creds_returns_none(self):
"""Pin grok + no store + other creds present -> source is None (exclusive pin)."""
config = {
"LAST30DAYS_X_BACKEND": "grok",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
"XAI_API_KEY": "dummy-key",
}
bird_status = {
"installed": True,
"authenticated": True,
"username": "test",
"can_install": True,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=False),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
# Pin is exclusive: grok unavailable -> None, NOT fallback to bird/xai
assert status["source"] is None
assert status["grok_available"] is False
# Other backends ARE available, but pin blocks fallback
assert status["bird_authenticated"] is True
assert status["xai_available"] is True
def test_pin_xai_with_cookies_returns_xai(self):
"""Pin xai + cookies + XAI_API_KEY -> source is xai, not bird."""
config = {
"LAST30DAYS_X_BACKEND": "xai",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
"XAI_API_KEY": "dummy-key",
}
bird_status = {
"installed": True,
"authenticated": True,
"username": "test",
"can_install": True,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=False),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
# Pin is exclusive: xai pinned + available -> xai (not bird)
assert status["source"] == "xai"
# Bird is also available, but pin forces xai
assert status["bird_authenticated"] is True
def test_pin_xai_no_key_with_cookies_returns_none(self):
"""Pin xai + no XAI_API_KEY + cookies -> source is None (exclusive pin)."""
config = {
"LAST30DAYS_X_BACKEND": "xai",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
# No XAI_API_KEY
}
bird_status = {
"installed": True,
"authenticated": True,
"username": "test",
"can_install": True,
}
with (
mock.patch("lib.grok_x.has_stored_auth", return_value=False),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
):
status = env.get_x_source_status(config, probe=False)
# Pin is exclusive: xai pinned but unavailable -> None (no fallback)
assert status["source"] is None
assert status["xai_available"] is False
# Bird is available but pin blocks fallback
assert status["bird_authenticated"] is True
# ---------------------------------------------------------------------------
# Runtime X backend pin (x_backend_chain / _resolve_x_backend)
# ---------------------------------------------------------------------------
class TestRuntimeXBackendPin:
"""Runtime fetch path must honor any known pin exclusively, including grok."""
def test_pin_grok_with_store_and_cookies_returns_grok(self):
"""Pin grok + grok store + cookies -> runtime returns grok, not bird."""
from lib import grok_x, providers
config = {
"LAST30DAYS_X_BACKEND": "grok",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
"XAI_API_KEY": "dummy-key",
}
with (
mock.patch.object(grok_x, "has_stored_auth", return_value=True),
mock.patch("lib.bird_x.is_bird_installed", return_value=True),
):
# x_backend_chain is the authoritative runtime path
chain = env.x_backend_chain(config)
# _resolve_x_backend delegates to get_x_source (wraps x_backend_chain)
resolved = providers._resolve_x_backend(config)
# Pin grok + available -> grok (not bird/xai)
assert chain == ["grok"]
assert resolved == "grok"
def test_pin_grok_no_store_with_cookies_returns_none(self):
"""Pin grok + no store + cookies -> runtime returns None (exclusive pin)."""
from lib import grok_x, providers
config = {
"LAST30DAYS_X_BACKEND": "grok",
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
}
with (
mock.patch.object(grok_x, "has_stored_auth", return_value=False),
mock.patch("lib.bird_x.is_bird_installed", return_value=True),
):
chain = env.x_backend_chain(config)
resolved = providers._resolve_x_backend(config)
# Pin grok + unavailable -> [] / None (no fallthrough to bird)
assert chain == []
assert resolved is None
def test_unpinned_with_grok_store_and_cookies_returns_bird(self):
"""Unpinned + grok store + cookies -> runtime returns bird, never grok."""
from lib import grok_x, providers
config = {
"AUTH_TOKEN": "dummy-token",
"CT0": "dummy-ct0",
}
with (
mock.patch.object(grok_x, "has_stored_auth", return_value=True),
mock.patch("lib.bird_x.is_bird_installed", return_value=True),
):
chain = env.x_backend_chain(config)
resolved = providers._resolve_x_backend(config)
# Unpinned -> auto-chain (bird first), grok never auto-selected
assert chain[0] == "bird"
assert "grok" not in chain
assert resolved == "bird"
# ---------------------------------------------------------------------------
# YouTube chain: yt-dlp -> ScrapeCreators
+291 -42
View File
@@ -1,28 +1,37 @@
"""grok must be visible to a user, not just functional.
"""grok must be visible as an opt-in backup, not as a default.
A backend nobody is told about is a backend nobody uses, and the whole point
of this path is removing a credential wall the user currently hits.
Grok is demoted to opt-in only: a leftover ~/.grok/auth.json must never steal
the X lane. The default auto chain is bird → xai → xurl → xquik. Pin
LAST30DAYS_X_BACKEND=grok to enable grok explicitly.
"""
import inspect
from pathlib import Path
from unittest import mock
from lib import doctor, quality_nudge
from lib import backends, doctor, health, quality_nudge
REPO = Path(__file__).resolve().parent.parent
def test_doctor_reports_x_covered_by_a_signed_in_grok():
def test_doctor_does_not_auto_select_grok_unpinned():
"""Doctor must NOT report 'will use: grok' for unpinned runs.
Grok is opt-in only; a leftover auth.json must never steal the X lane."""
src = inspect.getsource(doctor._x_record)
assert "grok_x.has_stored_auth()" in src
assert "will use: grok" in src
# The old code would check grok_x.has_stored_auth() and set status="ok"
# with record["will_use"]="grok" when grok was available unpinned. That
# promotion block is removed: no "has_stored_auth()" call that sets
# will_use to grok for unpinned runs.
#
# Comments may mention "will use: grok" to explain what we DON'T do, so
# check for the old logic pattern: has_stored_auth -> grok promotion.
assert "has_stored_auth" not in src
def test_doctor_prefers_grok_over_the_unverified_cookie_note():
"""grok outranks bird in the chain, and unlike a cookie session its
availability is locally verifiable rather than 'not verified until a run'."""
def test_doctor_mentions_grok_as_opt_in():
"""Doctor comments explain that grok is opt-in only."""
src = inspect.getsource(doctor._x_record)
assert src.index("has_stored_auth") < src.index("x_pending_browser_auth")
assert "opt-in" in src.lower()
def test_quality_nudge_offers_grok_but_does_not_call_it_free():
@@ -34,33 +43,41 @@ def test_quality_nudge_offers_grok_but_does_not_call_it_free():
assert "if you have a Grok" in src
def test_configuration_documents_the_grok_path():
def test_configuration_documents_the_grok_path_as_opt_in():
text = (REPO / "CONFIGURATION.md").read_text()
assert "X with no X credential (Grok CLI)" in text
assert "Grok CLI (opt-in backup)" in text
assert "grok login" in text
assert "LAST30DAYS_X_BACKEND=bird" in text, (
"users need the documented escape hatch back to the cookie path"
)
# Document that grok requires a pin.
assert "LAST30DAYS_X_BACKEND=grok" in text
def test_configuration_pin_row_lists_grok():
def test_configuration_pin_row_lists_grok_last():
"""Pin row shows all backends with grok last (opt-in)."""
text = (REPO / "CONFIGURATION.md").read_text()
assert "`xai` / `grok` / `bird` / `xurl` / `xquik`" in text
# New order: bird first, grok last (opt-in).
assert "`bird` / `xai` / `xurl` / `xquik` / `grok`" in text
def test_configuration_does_not_claim_grok_is_free():
text = (REPO / "CONFIGURATION.md").read_text()
section = text[text.index("X with no X credential (Grok CLI)"):][:1200]
section = text[text.index("Grok CLI (opt-in backup)"):][:1200]
assert "draws on your Grok plan" in section or "draw on your Grok plan" in section
def test_configuration_documents_bird_first_chain():
"""Auto chain is bird first: cookies beat XAI_API_KEY."""
text = (REPO / "CONFIGURATION.md").read_text()
assert "bird first" in text.lower() or "bird (browser cookies) → xai" in text.lower()
def test_changelog_fragments_exist_and_changelog_is_untouched():
frags = list((REPO / "changelog.d").glob("*grok*")) + \
list((REPO / "changelog.d").glob("*first-party*"))
list((REPO / "changelog.d").glob("*bird*"))
if not frags:
# Release PRs consume fragments into CHANGELOG.md via towncrier.
changelog = (REPO / "CHANGELOG.md").read_text()
assert "X search now works with no X credential" in changelog
# Old text or new text about X backend changes.
assert "X search now works with no X credential" in changelog or "bird first" in changelog.lower()
return
assert frags, "feature PRs add a changelog.d fragment"
@@ -71,34 +88,266 @@ def _skill_md():
return (REPO / "skills" / "last30days" / "SKILL.md").read_text()
def test_all_unlock_surfaces_mention_the_grok_path():
def test_skill_md_does_not_check_grok_first():
"""Grok is opt-in only: SKILL.md should NOT check for grok before cookies."""
text = _skill_md()
for marker in (
"Just-in-time X unlock",
"Check for a Grok path before asking for cookies",
"Grok CLI (no X credential)",
):
assert marker in text, f"missing grok surfacing at: {marker}"
# The old "Check for a Grok path before asking for cookies" should be removed.
assert "Check for a Grok path before asking for cookies" not in text
def test_grok_ordering_is_conditional_on_a_detected_path():
"""A user with no Grok account must not have their actionable options
pushed down the list by one they cannot take."""
def test_skill_md_presents_grok_as_opt_in_backup():
"""SKILL.md presents grok as an opt-in backup, not a primary option."""
text = _skill_md()
assert "Grok CLI is an opt-in backup" in text
# Should mention the pin requirement.
assert "LAST30DAYS_X_BACKEND=grok" in text
def test_skill_md_just_in_time_unlock_defaults():
"""Just-in-time X unlock presents cookies and keys first."""
text = _skill_md()
section = text[text.index("Just-in-time X unlock"):][:3000]
assert "command -v grok" in section
assert "Options when `grok` IS on PATH" in section
assert "Options when `grok` is NOT on PATH" in section
def test_no_grok_branch_keeps_todays_options_unchanged():
text = _skill_md()
section = text[text.index("Options when `grok` is NOT on PATH"):][:900]
for option in ("Scan my browser cookies", "AUTH_TOKEN and CT0", "xAI API key", "Skip for now"):
assert option in section
# Default options should be cookies and keys, not grok.
assert "Scan my browser cookies" in section
assert "xAI API key" in section
def test_skill_md_does_not_call_the_grok_path_free():
text = _skill_md()
section = text[text.index("Just-in-time X unlock"):][:3000]
assert "Do not describe the Grok path as free" in section
assert "Do not describe the Grok path as free" in section or "Do not call it free" in section
# --- Doctor grok-only unpinned behavior (R3/R8) -----------------------------
def test_doctor_grok_only_unpinned_is_not_tier_error():
"""Unpinned grok-only is unconfigured (tier off), NOT broken (tier error).
R3: unpinned grok is 'available, unused — pin LAST30DAYS_X_BACKEND=grok'
R8: grok-only unpinned = X unconfigured / skipped, not auth-failed or broken
"""
from lib import grok_x
config = {} # No pin, no auto-chain credentials
bird_status = {
"installed": False,
"authenticated": False,
"username": "",
"can_install": False,
}
# Grok is the only backend with OK status; all auto-chain backends are MISSING.
with (
mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
mock.patch.object(grok_x, "has_stored_auth", return_value=True),
mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
mock.patch("lib.bird_x.is_bird_installed", return_value=False),
mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
):
record = doctor._x_record(config)
# Must NOT be tier error / NOT WORKING.
assert record["tier"] != "error", "grok-only unpinned must not be tier error"
# Should be unconfigured (tier off).
assert record["status"] == "unconfigured"
assert record["tier"] == "off"
# Note should mention grok is available but requires a pin.
assert "grok" in record["note"].lower()
assert "pin" in record["note"].lower() or "LAST30DAYS_X_BACKEND" in record["note"]
def test_doctor_grok_error_unpinned_is_not_tier_error():
"""Unpinned grok ERROR (broken store) is unconfigured, NOT tier error.
When grok's auth store is unreadable/corrupt, doctor should NOT report X
as NOT WORKING with a `grok login` prescription. An unused opt-in backend
with a broken store is still unused — tier off, not tier error.
"""
from lib import grok_x
config = {} # No pin, no auto-chain credentials
bird_status = {
"installed": False,
"authenticated": False,
"username": "",
"can_install": False,
}
# Grok has ERROR status (unreadable store); all auto-chain backends MISSING.
with (
mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
mock.patch.object(grok_x, "has_stored_auth", return_value=False),
mock.patch.object(
grok_x, "stored_auth_status",
return_value=(grok_x.AUTH_ERROR, "store unreadable", None),
),
mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
mock.patch("lib.bird_x.is_bird_installed", return_value=False),
mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
):
record = doctor._x_record(config)
# Must NOT be tier error / NOT WORKING.
assert record["tier"] != "error", "grok ERROR unpinned must not be tier error"
# Should be unconfigured (tier off).
assert record["status"] == "unconfigured"
assert record["tier"] == "off"
# Note should mention grok is unused/opt-in; should NOT prescribe grok login.
assert "grok" in record["note"].lower()
assert "opt-in" in record["note"].lower() or "unused" in record["note"].lower()
# Fix should be empty (no grok login prescription for unused opt-in).
assert record["fix"] == ""
def test_doctor_grok_store_with_pending_bird_predicts_bird():
"""Unpinned + grok store + pending browser auth -> doctor predicts bird.
When bird is installed and browser-cookie extraction is configured,
doctor should predict bird (pending-cookie usable), NOT report X as
unconfigured due to an unused grok store. Pending bird takes precedence.
"""
from lib import env, grok_x
config = {} # No pin, no static AUTH_TOKEN/CT0
bird_status = {
"installed": True, # Bird IS installed
"authenticated": False, # No static cookies in config
"username": "",
"can_install": True,
}
# Grok has OK status; bird is pending (installed, FROM_BROWSER configured).
with (
mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
mock.patch.object(grok_x, "has_stored_auth", return_value=True),
mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
mock.patch("lib.bird_x.is_bird_installed", return_value=True),
mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
# Simulate x_pending_browser_auth returning True (bird pending)
mock.patch.object(env, "x_pending_browser_auth", return_value=True),
):
record = doctor._x_record(config)
# Doctor should predict bird (pending), NOT report X as unconfigured.
assert record["tier"] != "off", "pending bird should not be tier off"
assert record["status"] != "unconfigured", "pending bird should not be unconfigured"
# Should be OK tier with bird prediction.
assert record["tier"] == "ok"
assert record["status"] == health.OK
assert "bird" in record["note"].lower()
assert "browser cookies" in record["note"].lower() or "cookie" in record["note"].lower()
def test_doctor_grok_does_not_hide_xurl_error():
"""Unpinned + grok store + xurl ERROR -> doctor keeps xurl error, not unconfigured.
When an auto-chain backend (xurl) is configured but broken, doctor must
report that error with its repair guidance. Unused grok must NOT swallow
the genuine auto-chain failure.
"""
from lib import backends as _backends
from lib import grok_x
config = {} # No pin
bird_status = {
"installed": False,
"authenticated": False,
"username": "",
"can_install": False,
}
# Mock xurl probe to return ERROR status
def mock_probe_xurl(config):
return _backends.BackendFinding(
name="xurl",
status=health.ERROR,
detail="store unreadable",
prescription="xurl auth oauth2 login",
requires="xurl CLI installed + OAuth2 login",
)
# Intercept _run_probe to inject xurl ERROR
original_run_probe = _backends._run_probe
def patched_run_probe(spec, config):
if spec.name == "xurl":
return mock_probe_xurl(config)
return original_run_probe(spec, config)
# Grok has OK status; xurl has ERROR (unreadable store).
# All other auto-chain backends are MISSING.
with (
mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
mock.patch.object(grok_x, "has_stored_auth", return_value=True),
mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
mock.patch("lib.backends.which", side_effect=lambda cmd: "/usr/bin/grok" if cmd == "grok" else None),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
mock.patch("lib.bird_x.is_bird_installed", return_value=False),
mock.patch.object(_backends, "_run_probe", patched_run_probe),
):
record = doctor._x_record(config)
# Doctor should NOT report X as unconfigured.
assert record["tier"] != "off", "xurl error must not be hidden by unused grok"
assert record["status"] != "unconfigured", "xurl error must not become unconfigured"
# Should keep the error tier and xurl repair guidance.
assert record["tier"] == "error"
# The fix should contain xurl repair guidance, not be empty.
assert record["fix"], "xurl repair guidance must not be cleared"
assert "xurl" in record["fix"].lower() or "oauth" in record["fix"].lower()
def test_doctor_pending_bird_does_not_hide_xurl_error():
"""Unpinned + FROM_BROWSER + bird installed + xurl ERROR -> keeps xurl error.
Pending bird must NOT replace a record with a configured auto-chain backend
in ERROR. Doctor should report the xurl error with its repair guidance,
NOT report X as OK via pending bird.
"""
from lib import backends as _backends
from lib import env, grok_x
config = {} # No pin, no static AUTH_TOKEN/CT0
bird_status = {
"installed": True, # Bird IS installed (for pending bird)
"authenticated": False, # No static cookies
"username": "",
"can_install": True,
}
# Mock xurl probe to return ERROR status
def mock_probe_xurl(config):
return _backends.BackendFinding(
name="xurl",
status=health.ERROR,
detail="store unreadable",
prescription="xurl auth oauth2 login",
requires="xurl CLI installed + OAuth2 login",
)
original_run_probe = _backends._run_probe
def patched_run_probe(spec, config):
if spec.name == "xurl":
return mock_probe_xurl(config)
return original_run_probe(spec, config)
# x_pending_browser_auth returns True (bird pending), but xurl has ERROR.
with (
mock.patch.object(grok_x, "binary_path", return_value=None),
mock.patch.object(grok_x, "has_stored_auth", return_value=False),
mock.patch("lib.backends.which", return_value=None),
mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
mock.patch("lib.bird_x.is_bird_installed", return_value=True),
mock.patch.object(env, "x_pending_browser_auth", return_value=True),
mock.patch.object(_backends, "_run_probe", patched_run_probe),
):
record = doctor._x_record(config)
# Doctor should NOT report X as OK via pending bird.
assert record["tier"] != "ok", "xurl error must not be hidden by pending bird"
assert record["status"] != health.OK, "xurl error must not become OK"
# Should keep the error tier and xurl repair guidance.
assert record["tier"] == "error"
assert record["fix"], "xurl repair guidance must not be cleared"
assert "xurl" in record["fix"].lower() or "oauth" in record["fix"].lower()
+4 -2
View File
@@ -1,7 +1,9 @@
"""grok must never be a dead end: every failure falls through to the next backend.
grok sits ahead of bird in the chain, so any way it can come back empty has to
hand off rather than leave the run with no X coverage.
Grok is opt-in only (not in the auto chain), but when pinned via
LAST30DAYS_X_BACKEND=grok, any way grok can come back empty or error must
surface properly so the run reports the failure honestly rather than silently
leaving X uncovered.
"""
from unittest import mock