Crawl and summarize newsletter posts from Nonzero and the NZN Network.
When Invoked
1. Ask what to summarize
If the user provides args (e.g. /nznet nonzero 30d), skip asking.
Otherwise, read ~/Documents/Projects/Nonzero/NZNet/nzn-network.md and print a numbered list of all newsletters as plain text:
Which newsletter(s)? Reply with numbers (e.g. "1", "1,3,5", or "all")
1. Nonzero (Robert Wright)
2. Small Potatoes (Paul Bloom)
3. Foreign Exchanges
...etc (full list from nzn-network.md)
Time period? (e.g. 7d, 14d, 30d, or a date range)
Do NOT use AskUserQuestion — just print the list and wait for the user to type their selection. Parse their reply (numbers, comma-separated, or "all") and the period.
2. Crawl the archive
Use RSS feeds for discovery, not agent-browser. Every Substack has an RSS feed at <url>/feed. Fetching it with curl is faster and more reliable than opening archive pages in a browser. Save agent-browser for reading the actual posts (Step 3), where you need auth for paywalled content.
For each selected newsletter:
- Fetch the RSS feed:
curl -s "<newsletter-url>/feed" - Extract post URLs, titles, and dates from the XML (
<item>elements with<link>,<title>,<pubDate>). - Filter to posts within the requested time period.
Fallback: If the RSS feed is missing or broken, fall back to agent-browser on the archive page:
agent-browser close
agent-browser --profile ~/.agent-browser/substack open "<url>/archive"
3. Read posts
ALWAYS use agent-browser. NEVER use WebFetch. Agent-browser has Nikita's paid Substack login and returns the actual article text, not an AI summary. WebFetch has no auth and produces lossy summaries.
For each post, sequentially:
1. agent-browser close (if needed)
2. agent-browser --profile ~/.agent-browser/substack open "<post-url>"
3. agent-browser get text "article" to extract the text
Access tracking: Mark each post with one of:
- [full] — got the complete article text
- [full+transcript] — got the article text and extracted the podcast transcript
- [free preview] — hit a paywall, only got the preview text above it
- [title only] — only title and date available (no content extracted)
Always include these markers so the user knows exactly what was read vs. inferred.
3a. Extract podcast transcripts
When a post is a podcast episode (has an audio player), extract its auto-generated Substack transcript. This is essential for podcast-heavy newsletters — the article text is usually just show notes, while the substance is in the conversation.
How to extract:
- With the episode page already open in agent-browser, click the Transcript button:
agent-browser eval 'const btn = Array.from(document.querySelectorAll(".label")).find(el => el.textContent.trim() === "Transcript"); if (btn) { btn.closest("button").click(); "clicked"; } else { "no transcript button"; }' - Wait for the transcript to load (2–3 seconds), then grab it:
sleep 3 agent-browser eval 'document.querySelector("[class*=transcript]")?.innerHTML' > /tmp/transcript-raw.html - Parse the HTML into clean timestamped text. The structure is: each chunk is a
divwith a timestampdiv(class containstranscription-timestamp) showingM:SSand a textdiv(class containstranscriptionWords) with the spoken words. Use this Python script:python import re with open("/tmp/transcript-raw.html") as f: html = f.read() if html.startswith('"') and html.endswith('"'): html = html[1:-1].replace('\\"', '"').replace('\\n', '\n') chunks = re.findall( r'transcription-timestamp[^"]*"[^>]*>(\d+:\d+)</div>.*?transcriptionWords[^"]*"[^>]*>(.*?)</div>', html, re.DOTALL ) lines = [] for ts, text in chunks: clean = re.sub(r'<[^>]+>', '', text).strip() clean = re.sub(r'\s+', ' ', clean) if clean: lines.append(f"{ts}\t{clean}") with open("/tmp/transcript-clean.txt", "w") as f: f.write("\n".join(lines)) - Save transcript to
~/Documents/Projects/Nonzero/transcripts/{date}-{slug}.txt
Notes: - Not every podcast has a transcript. If step 1 returns "no transcript button", skip — don't retry. - The transcript has no speaker labels (Substack doesn't generate them). Speakers must be inferred from context. - Typical transcript is ~50k chars / 194 segments for a 1-hour episode.
3b. Pull a quote (for digest-ready podcast episodes)
After reading a podcast transcript, find one compelling quote (2–8 sentences) that could represent the episode in the NZNet digest. Use ~/Documents/Projects/Nonzero/wright-worldview.md as the lens — the best quote for the digest is one that connects to Bob's intellectual framework (cognitive empathy, security dilemmas, non-zero-sum dynamics, the explain/excuse conflation, anti-Manichaeism, etc.). Good quotes:
- Connect to a Wright concept — either illustrating it, extending it, or productively challenging it
- Stand alone without context — a reader who hasn't listened should still get something from it
- Sound like a real person talking (don't pick the most polished sentence — pick the most alive one)
Present the quote with its timestamp so the editor can verify it against the audio. Format:
> "Quote text here." —Speaker Name (MM:SS)
Include this in the post summary (Step A) when the post is a podcast episode.
4. Present results (three-step flow)
Step A — One-line index. List every post from every newsletter, grouped by newsletter. Each entry is one substantive full sentence describing the post's argument or content. Consistent depth — no post gets a paragraph, no post gets a fragment. Include the access marker.
Step B — Bob's highlights. Read ~/Documents/Projects/Nonzero/wright-worldview.md (Bob's comprehensive worldview profile) and use it to flag which posts connect to Nonzero's intellectual territory. Don't just match by topic — match by framework. A foreign policy story is only a Bob story if it illustrates something he cares about structurally: a security dilemma spiral, an explain/excuse conflation in media coverage, a case where cognitive empathy would change the analysis, a non-zero-sum dynamic being misread as zero-sum, tribalism distorting policy. Briefly say which Wright concept the post connects to and why. This helps Nikita decide what to surface to Bob.
Step C — Ask. Ask if the user wants fuller summaries of any specific items, or wants to save as-is.
5. Save
When the user is ready, save to ~/Documents/Projects/Nonzero/summaries/{date}-{newsletter-slug}-{period}.md
agent-browser Setup
This skill requires agent-browser — a browser automation CLI that runs a real Chromium instance with persistent login sessions. Unlike WebFetch, it carries the user's actual Substack credentials, which means it can read paywalled content.
Install
npm install -g agent-browser
Create a Substack profile
agent-browser uses --profile <path> to maintain separate browser sessions with their own cookies, logins, and state. This skill expects a profile at ~/.agent-browser/substack logged into a Substack account with paid subscriptions to the NZNet newsletters.
To set up the profile, open a headed (visible) browser window and log in manually:
agent-browser --headed --profile ~/.agent-browser/substack open "https://substack.com/sign-in"
Log in with the Substack account that has the relevant paid subscriptions. Once logged in, close the browser. The session cookies persist in the profile directory — future headless runs will use them automatically.
Key commands used by this skill
agent-browser close # close any open page
agent-browser --profile ~/.agent-browser/substack open "<url>" # open a URL (headless)
agent-browser --headed --profile ~/.agent-browser/substack open "<url>" # open visible (for login/debug)
agent-browser get text "<selector>" # extract text from a CSS selector
agent-browser get html "<selector>" # extract raw HTML
agent-browser eval '<javascript>' # run JS in the page context
Session expiry
Substack login cookies expire periodically. If agent-browser starts hitting paywalls on content that should be accessible, the session has expired. Re-login:
agent-browser --headed --profile ~/.agent-browser/substack open "https://substack.com/sign-in"
Access Notes
~/Documents/Projects/Nonzero/NZNet/nzn-network.mdtracks which newsletters have full paid access vs. free preview only- Auto-update access status: If a post hits a paywall on a newsletter marked
fullin nzn-network.md, update it tofree preview only. If access works on one markedfree preview only, update it tofull. This keeps the file current across runs.
Autonomous Digest Mode
When invoked with digest (e.g. /nznet digest 7d), skip the interactive flow (Steps 1–5 above) and produce a ready-to-edit digest draft autonomously. The output is a single .md file that could, with light editing, go out as the weekly NZNet Network News email.
How it works
- Crawl all newsletters in
nzn-network.md— both Members and Prospective — for the given period. - Read every post. For podcast episodes, extract transcripts (3a) and pull quotes (3b).
- Select the strongest item from each newsletter — the one with the most structural insight, not just the most topical. If a newsletter published nothing strong this week, skip it — but leave a note for the editor at the bottom (e.g., "Small Potatoes: no posts this week" or "Glenn Show: two posts, neither had a clear digest angle").
- Write the digest (see format below). Members and Prospective go in separate sections.
- Update
~/Documents/Projects/Nonzero/NZNet/digest-tracking.md(see Tracking below). - Save to
~/Documents/Projects/Nonzero/NZNet/digests/{date}-nznet-digest.md.
Format
Study the reference examples in ~/Documents/Projects/Nonzero/NZNet/2026-02-25-pilot/ before writing. The TOM EDIT version is the target — not the staff submissions.
Structure:
NETWORK NEWS!
{intro paragraph — what NZNet is, one sentence, plus discount link placeholder}
— NETWORK MEMBERS —
[NEWSLETTER NAME](url)
{One tight paragraph: lede sentence + context + one block quote.}
[NEXT NEWSLETTER](url)
...
{coupon box — reading widely as cognitive empathy practice, subscribe for half off}
— FROM THE WIDER ORBIT —
{Prospective newsletters — same format as members, but in this separate section}
SIGNALS FROM BEYOND:
{Non-network recommendations, shorter entries}
Editorial rules (learned from Tom's editing):
- One paragraph per newsletter. Lede sentence that frames the argument, then one block quote that delivers the thesis in the author's own words. No multi-paragraph entries.
- Quote selection = argument distillation. Find the sentences where the author's thesis is most compressed. Cut the tour — keep the destination.
- Flag tone mismatches. If an entry feels too partisan or tribal for Nonzero — if it's dunking rather than diagnosing — flag it with a
{NOTE: ...}comment rather than including it uncritically. Nonzero is anti-tribalism, not anti-any-particular-tribe. - Nonzero Podcast entries get teasers, not summaries. Don't give away the home team's content. Set up the question, then: "Listen here to find out."
- Think in pairs. When two entries share a theme (e.g., two different angles on AI utopia), place them near each other. The juxtaposition adds value.
- Non-network recs are shorter than network entries — one paragraph max, tighter quotes.
- Timestamp every podcast quote. When quoting from a transcript, include the timestamp as an editor's note:
{~MM:SS}. The editor needs to be able to verify quotes against the audio. - Use
wright-worldview.mdas the lens for selecting and framing everything. The digest should read like a curated week in the Nonzero intellectual universe.
Reference examples
~/Documents/Projects/Nonzero/NZNet/2026-02-25-pilot/— first pilot digest. Two versions:2026-02-25-NZNet Review.md— raw staff submissions (what each team member contributed)2026-02-25-NZNet Review TOM EDIT.md— Tom's edit (the target output). Study the differences: what he cut, reordered, compressed, and flagged.
More examples will be added here as the digest matures week over week.
Tracking
Maintain ~/Documents/Projects/Nonzero/NZNet/digest-tracking.md — a running log of which newsletters have been featured in digests. Do NOT update when generating a draft — only update once the user confirms what actually published in the final newsletter.
Format:
# NZNet Digest Tracking
## Featured Count
| Newsletter | Status | Times Featured | Last Featured |
|---|---|---|---|
| Small Potatoes | member | 1 | 2026-02-25 |
| Con-Current | non-network | 1 | 2026-02-25 |
| ... | ... | ... | ... |
## Promotion Candidates
{Automatically listed here when a non-network newsletter reaches 3 features}
Promotion rule: When any non-network newsletter gets featured 3 times, flag it to the user: "X has been featured 3 times — should it be added to the Prospective list in nzn-network.md?" Don't auto-promote; ask first.
Tone
Informational. Neutral summaries that capture the authors' arguments faithfully without editorializing. The digest voice is warm but not cheerful, curious but not breathless — a smart friend telling you what's worth reading this week.
$ARGUMENTS