Stand up OAuth credentials and a thin YouTube Data API client, exposed as zdots publish jobs (following the queue pattern). This is the foundation for captions, metadata sync, video upload, and playlist/shorts publishing. No live writes beyond an authenticated read/quota check in this task.
This task is the shared substrate for TASK-248/249/252/258. No live writes here beyond an authenticated read.
https://www.googleapis.com/auth/youtube.force-ssl — read + write incl. captions (captions.* REQUIRE force-ssl); covers videos.list/update, playlists., playlistItems..https://www.googleapis.com/auth/youtube.upload — resumable videos.insert (TASK-249/258 Shorts)..env is already gitignored. Store YT_CLIENT_ID / YT_CLIENT_SECRET in .env, and the obtained refresh token in a gitignored file (e.g. .credentials/youtube_oauth.json) or YT_REFRESH_TOKEN env. Add .credentials/ to .gitignore.faraday + faraday-retry + faraday-multipart (good for resumable multipart) but no signet/google-auth. Choose one and record it: add signet (+ optionally google-apis-youtube_v3) for OAuth refresh, OR hand-roll the refresh grant over faraday. Recommendation: add signet for the token flow, keep API calls on the existing faraday stack to stay thin.bin/lib/youtube_client.rb, mirroring bin/archive/modules/*.rb): OAuth refresh + a thin wrapper over the endpoints the track needs:
videos.list, videos.update, resumable videos.insert, thumbnails.setcaptions.list, captions.insert, captions.update, captions.deleteplaylists.list/insert, playlistItems.list/inserttranscript_id → _data/video_assets.yml item → platforms[youtube].asset_id).bin/batch_ztranscribe.rb / doc-041: youtube_metadata (248), youtube_upload (249), youtube_captions (252), youtube_playlist + youtube_short (258). Enqueue via zdots-ctx enqueue <type> '<json>' / ctx_enqueue; worker drains; fingerprint-idempotent + retried.Per-call: videos.list 1 · videos.update 50 · videos.insert 1600 · captions.list 50 · captions.insert 400 · captions.update 450 · captions.delete 50 · playlists.insert 50 · playlistItems.insert 50 · search.list 100 (avoid — use stored asset_ids). Aggregate budgeting lives in the consuming tasks.
channels.list(mine=true) + a videos.list on a known asset_id → confirms OAuth refresh works and prints remaining quota headroom. Zero write calls in this task.bin/lib/youtube_client.rb (new), Gemfile/Gemfile.lock (signet), .gitignore (.credentials/), .env (local only, ungit). Reads: _data/video_assets.yml, _data/transcripts/*.yml.