Add GET /oauth/youtube/connect and /oauth/youtube/callback, mounted inside
the login + registration guard so CurrentUserID is always set and every
connection binds to the authenticated tapir user.
- connect: generate a per-user CSRF state (single-use, short TTL, bound to
the user), redirect to Google consent with access_type=offline and
prompt=consent so a refresh token comes back.
- callback: verify the state belongs to this user, exchange the code via the
existing auth.Exchange, persist the refresh token under a PER-USER ref
(web.YouTubeTokenRef = "youtube/<userID>/refresh_token") so tenants never
collide, then UpsertConnection (provider=youtube, status=active). Any
failure renders a clean error page and leaves no half-written state.
Reuses auth.Exchange and adds auth.AuthCodeURL (offline + consent) rather
than the CLI's listener/terminal flow (ADR-006: web flow, not CLI). The
ConnectHandler depends on a narrow web.Connections port, not the concrete
store. Wired in cmdServe only when YT client credentials are present;
TAPIR_YT_CONNECT_REDIRECT_URL configures the callback URL. Per-user token-ref
scheme documented in docs/homelab-integration.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main.go dispatches `tapir auth` (interactive OAuth → persist refresh token via
SecretStore) and `tapir run` (wire YouTube source + local summarizer + store
sink, build engine, run the dedup-aware loop). Config-driven so live creds plug
in at demo time; SIGINT stops the loop cleanly. Block kept minimal so Worker E's
list/show cases union cleanly at merge.
Add .env.example documenting every TAPIR_* var and a README demo runbook. Pin
the summarizer alias-as-config decision and record the max_tokens fix in
docs/homelab-integration.md (clears two `confirm` items).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`tapir auth` mints a refresh token for the single Stage-0 user: bind a local
redirect listener, print the consent URL (offline access + forced consent so
Google returns a refresh token), validate the state param, exchange the code,
and persist the refresh token through the SecretStore port. Written fresh on
x/oauth2 (ADR-006). Token is never logged or returned. Tests cover exchange,
missing-refresh-token rejection, and the full listener flow with httptest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>