A local/hosted web app that automates the scan, rank, draft, and export workflow for the A-Street Weekly Wrap-Up newsletter.
- Node.js v18+ — nodejs.org
- Anthropic API key — console.anthropic.com
- Supabase project (free tier is fine) — supabase.com
- Google Cloud project with Drive API enabled (for Google Doc export)
- Railway account (for deployment) — railway.app
git clone <your-repo-url>
cd wwu-tool
npm installcp .env.example .envFill in the values (see sections below for how to get each one).
- Create a new project at supabase.com
- Go to SQL Editor and paste the contents of
schema.sql, then click Run - Go to Project Settings → API and copy:
- Project URL →
SUPABASE_URL - anon / public key →
SUPABASE_ANON_KEY
- Project URL →
- Go to console.cloud.google.com
- Create a new project (or use an existing one)
- Enable the Google Drive API: APIs & Services → Enable APIs → search "Google Drive API" → Enable
- Create OAuth credentials: APIs & Services → Credentials → Create Credentials → OAuth client ID
- Application type: Web application
- Authorized redirect URIs: add
https://YOUR-APP.railway.app/oauth/callback - (Also add
http://localhost:3000/oauth/callbackfor local testing)
- Copy the Client ID →
GOOGLE_CLIENT_IDand Client Secret →GOOGLE_CLIENT_SECRET - Update
GOOGLE_REDIRECT_URIin.envto match your Railway URL
First-time authorization: When you first click "Create Google Doc," the app will prompt you to connect your Google account. Click the link, authorize in the browser, and you'll be redirected back. The token is stored server-side and refreshes automatically.
node server.js
# or for auto-reload during development:
node --watch server.jsNavigate to http://localhost:3000. Username: astreet, password: whatever you set in APP_PASSWORD.
- Push this repo to GitHub
- Go to railway.app → New Project → Deploy from GitHub repo
- Select your repo
- Go to Variables and add all the values from your
.envfile - Railway will detect
railway.jsonand deploy automatically - Copy your Railway app URL (e.g.,
https://wwu-tool-production.up.railway.app) - Update
GOOGLE_REDIRECT_URIin Railway Variables tohttps://your-app.railway.app/oauth/callback - Also update the authorized redirect URI in Google Cloud Console to match
- Click Refresh scan to fetch articles from all active sources
- The scan runs in the background (1–3 minutes depending on sources)
- Articles are ranked automatically by Claude using
preferences.json
- Check articles you want to include (they turn green)
- Uncheck to decline (use "Show declined" to restore)
- Use Add URL to paste any article not in the scan
- Sort by rank, source, or date using the dropdown
- When satisfied, click Draft & organize →
- All selected articles land in In this week
- Click Generate summaries — Claude drafts all entries at once
- Drag entries between columns using the ⠿ handle:
- In this week — final published entries (aim for 5–10)
- Considered — reviewed but not included (goes in Google Doc for Marc/Heather)
- Save for future — strong pieces to hold for a future week
- Click any summary to edit it inline
- Click Regenerate on any entry for a fresh AI draft
- For paywalled entries, click Paywall options to paste text, find a free alternative, or write manually
- Click Export → then Create Google Doc
- Doc is created in
A-Street Workspace / Internal Operations / Weekly Wrap Up / {YEAR}/ - Shared automatically with Marc and Heather
- Incorporate feedback in the tool (edit summaries inline)
- Click Copy HTML to clipboard
- Paste into the Mailchimp template body
- Done
Edit preferences.json to change ranking behavior:
topic_priorities— reorder or add topics to shift what ranks higherpreferred_sources— sources listed here get a ranking boosteditorial_guidance— plain-language instructions to the rankerportfolio_companies/portfolio_advisors— names to flag in summariesrecency_window_days— how far back to look (default: 10 days)
Changes take effect on the next scan.
Sources are seeded automatically on first run. To manage them, go to Supabase → Table Editor → sources and toggle the active column. You can also add custom RSS feeds by inserting a new row with type: rss and filling in rss_url with the direct feed URL.
Add more example entries to examples.md. The more real examples Claude has, the better it matches your voice. Edit voice_guide.md to update framing guidance.
| File | Purpose |
|---|---|
server.js |
Express backend + all API routes |
preferences.json |
Ranking tuning — edit this to adjust what surfaces |
voice_guide.md |
A-Street voice guide injected into Claude prompts |
examples.md |
Past WWU entries used as few-shot examples |
sources.json |
(not used — sources live in Supabase) |
services/scanner.js |
RSS fetching + web scraping |
services/ranker.js |
Claude-powered article ranking |
services/summarizer.js |
Claude-powered summary generation |
services/gdrive.js |
Google Drive / Docs integration |
.google_token.json |
OAuth token (auto-created, never commit) |
schema.sql |
Supabase database schema |
Scan shows 0 articles — Check that Supabase credentials are correct and that the sources table has rows (it auto-seeds on first run).
Ranking fails — Verify ANTHROPIC_API_KEY is set in Railway Variables.
"Folder not found" on Google Doc export — The Drive folder path must be exactly: A-Street Workspace / Internal Operations (Finance, HR, Legal, Internal Meetings, etc.) / Weekly Wrap Up / {YEAR}. Check that your Google account has access to the A-Street shared drive.
OAuth token expired — Delete .google_token.json (or the equivalent in Railway's ephemeral storage) and re-authorize. On Railway, environment variables persist but files don't — store the token in Supabase if this becomes an issue.
Source fetch errors — Normal for some sources. The scan logs errors per-source and continues. Check the source URL and RSS feed URL in Supabase if a source consistently fails.