Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/generate-builder-feeds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Generate builder feeds

on:
schedule:
# Daily at 01:15 UTC
- cron: '15 1 * * *'
workflow_dispatch:

permissions:
contents: write

concurrency:
group: generate-builder-feeds
cancel-in-progress: false

jobs:
generate:
name: generate · publish feeds
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Generate public feeds
run: npm run generate:builder-feeds

- name: Publish to feeds branch
run: |
set -euo pipefail
PUBLISH_DIR="${RUNNER_TEMP}/zero-tab-feeds"
rm -rf "$PUBLISH_DIR"
mkdir -p "$PUBLISH_DIR"

if git ls-remote --exit-code --heads origin feeds >/dev/null 2>&1; then
git fetch origin feeds
git worktree add "$PUBLISH_DIR" origin/feeds
git -C "$PUBLISH_DIR" checkout -B feeds
else
git worktree add --detach "$PUBLISH_DIR"
git -C "$PUBLISH_DIR" checkout --orphan feeds
git -C "$PUBLISH_DIR" rm -rf . >/dev/null 2>&1 || true
fi

cp builder-feeds/generated/feed-x.json "$PUBLISH_DIR/feed-x.json"
cp builder-feeds/generated/feed-blogs.json "$PUBLISH_DIR/feed-blogs.json"
cp builder-feeds/generated/feed-podcasts.json "$PUBLISH_DIR/feed-podcasts.json"
cp builder-feeds/generated/generation-report.json "$PUBLISH_DIR/generation-report.json"

git -C "$PUBLISH_DIR" config user.name "github-actions[bot]"
git -C "$PUBLISH_DIR" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -C "$PUBLISH_DIR" add feed-x.json feed-blogs.json feed-podcasts.json generation-report.json

if git -C "$PUBLISH_DIR" diff --cached --quiet; then
echo "No feed changes to publish."
else
git -C "$PUBLISH_DIR" commit -m "Update builder feeds $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git -C "$PUBLISH_DIR" push origin HEAD:feeds
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ dist/

# Personal config (landing page patterns, etc.) — never push to GitHub
extension/config.local.js

# Local feed generation output; published only on the feeds branch
builder-feeds/generated/
tests/fixtures/out/
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,5 @@ Once the extension is loaded:
- Run `npm run build` and load `dist/extension/` in Chrome.
- Saved tabs are stored in `chrome.storage.local` (persists across sessions).
- Tab management is fully local. Open-tab and saved-tab data is never uploaded.
- The optional AI Builder digest requests access only to `raw.githubusercontent.com`, fetches public feeds at most once per local day, and stores a compact cache locally.
- The optional AI Builder digest requests access only to `raw.githubusercontent.com`, fetches public feeds from this repository's `feeds` branch at most once per local day, and stores a compact cache locally.
- To update: `cd zero-tab && git pull && npm install && npm run build`, then reload the extension in `chrome://extensions`.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ No server and no account are required. Open-tab URLs, titles, and saved links st
- Local, deterministic daily horoscope by zodiac sign
- Localhost port labels
- macOS-inspired light and dark themes
- Full-height AI Builder Daily Report drawer sourced from public Follow Builders feeds
- Full-height AI Builder Daily Report drawer sourced from public Zero Tab feeds
- Optional on-device translation through Chrome's built-in Translator API

## Install locally
Expand Down Expand Up @@ -46,7 +46,7 @@ No server and no account are required. Open-tab URLs, titles, and saved links st

Core tab management makes no external requests. Tab URLs, titles, Saved for later items, read state, and preferences are stored locally using Chrome extension storage.

AI Builder Daily Report is disabled until the user enables it. When enabled, Zero Tab requests optional access to `raw.githubusercontent.com` and downloads three public Follow Builders JSON feeds at most once per local calendar day. No tab, browsing, saved-link, identifier, or API-key data is included in those requests.
AI Builder Daily Report is disabled until the user enables it. When enabled, Zero Tab requests optional access to `raw.githubusercontent.com` and downloads three public JSON feeds published by this repository's `feeds` branch at most once per local calendar day. No tab, browsing, saved-link, identifier, or API-key data is included in those requests.

Translation uses Chrome's built-in on-device Translator API when available. The language model or language pack may be downloaded by Chrome, but report text is not sent to a third-party translation service by Zero Tab.

Expand Down Expand Up @@ -87,6 +87,16 @@ The previous Vanilla implementation remains in `extension/` as a migration
reference while the remaining publishing assets are moved to the new source
tree.

## Builder feed generation

Zero Tab publishes AI Builder JSON feeds from this repository's `feeds` branch.

```bash
npm run generate:builder-feeds
```

Sources live in [`builder-feeds/sources.json`](builder-feeds/sources.json). GitHub Actions runs [`.github/workflows/generate-builder-feeds.yml`](.github/workflows/generate-builder-feeds.yml) daily and on `workflow_dispatch`, then commits `feed-x.json`, `feed-blogs.json`, and `feed-podcasts.json` to the `feeds` branch root. The extension reads those files from `raw.githubusercontent.com`.

## Attribution and license

Zero Tab began as a fork of Zara Zhang's MIT-licensed original project.
Expand Down
9 changes: 9 additions & 0 deletions builder-feeds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Builder feeds

Curated source list and local generation output for the AI Builder Daily Report.

- [`sources.json`](sources.json) — X handles, blogs (RSS and/or HTML), podcasts (RSS)
- `generated/` — local output of `npm run generate:builder-feeds` (gitignored)
- Published artifacts live on the repository `feeds` branch root

Generation is owned by Zero Tab. It does not call Follow Builders services or APIs.
56 changes: 56 additions & 0 deletions builder-feeds/sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"version": 1,
"x": [
{ "name": "Boris Cherny", "handle": "boris_cherny" },
{ "name": "Anthropic", "handle": "AnthropicAI" },
{ "name": "Guillermo Rauch", "handle": "rauchg" },
{ "name": "Simon Willison", "handle": "simonw" },
{ "name": "Addy Osmani", "handle": "addyosmani" },
{ "name": "Swyx", "handle": "swyx" },
{ "name": "Andrej Karpathy", "handle": "karpathy" }
],
"blogs": [
{
"name": "Anthropic Engineering",
"url": "https://www.anthropic.com/engineering"
},
{
"name": "Anthropic News",
"url": "https://www.anthropic.com/news"
},
{
"name": "OpenAI Blog",
"url": "https://openai.com/blog",
"rssUrl": "https://openai.com/blog/rss.xml"
},
{
"name": "Simon Willison",
"url": "https://simonwillison.net/",
"rssUrl": "https://simonwillison.net/atom/everything/"
},
{
"name": "Julia Evans",
"url": "https://jvns.ca/",
"rssUrl": "https://jvns.ca/atom.xml"
},
{
"name": "Latent Space",
"url": "https://www.latent.space/",
"rssUrl": "https://www.latent.space/feed"
},
{
"name": "Cursor Blog",
"url": "https://cursor.com/blog"
}
],
"podcasts": [
{
"name": "Latent Space",
"rssUrl": "https://api.substack.com/feed/podcast/1084089.rss"
},
{
"name": "Practical AI",
"rssUrl": "https://changelog.com/practicalai/feed"
}
]
}
2 changes: 1 addition & 1 deletion docs/privacy.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h2>Local storage</h2>
<p>Saved links, preferences, AI Builder Daily Report cache, read state, and translations are stored in <code>chrome.storage.local</code>. Zero Tab does not operate a server and does not synchronize this data to the publisher.</p>

<h2>AI Builder Daily Report</h2>
<p>This optional feature is disabled until the user enables it. When enabled, Zero Tab downloads public JSON feeds from <code>https://raw.githubusercontent.com/</code>. These requests do not contain tab data, saved links, browsing history, identifiers, credentials, or API keys.</p>
<p>This optional feature is disabled until the user enables it. When enabled, Zero Tab downloads public JSON feeds from <code>https://raw.githubusercontent.com/beforeload/zero-tab/feeds/</code>. These requests do not contain tab data, saved links, browsing history, identifiers, credentials, or API keys.</p>

<h2>On-device translation</h2>
<p>When supported and explicitly requested, Zero Tab uses Chrome's built-in Translator API. Chrome may download a language pack or model. Zero Tab does not send report text to a third-party translation API.</p>
Expand Down
6 changes: 3 additions & 3 deletions extension/builder-digest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
const CACHE_KEY = 'builderDigestState';
const OPTIONAL_ORIGIN = 'https://raw.githubusercontent.com/';
const FEED_URLS = {
x: 'https://raw.githubusercontent.com/zarazhangrui/follow-builders/main/feed-x.json',
podcasts: 'https://raw.githubusercontent.com/zarazhangrui/follow-builders/main/feed-podcasts.json',
blogs: 'https://raw.githubusercontent.com/zarazhangrui/follow-builders/main/feed-blogs.json',
x: 'https://raw.githubusercontent.com/beforeload/zero-tab/feeds/feed-x.json',
podcasts: 'https://raw.githubusercontent.com/beforeload/zero-tab/feeds/feed-podcasts.json',
blogs: 'https://raw.githubusercontent.com/beforeload/zero-tab/feeds/feed-blogs.json',
};
const MAX_RESPONSE_CHARS = 1_500_000;
const CACHE_RETENTION_MS = 48 * 60 * 60 * 1000;
Expand Down
2 changes: 1 addition & 1 deletion extension/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ <h1 id="greeting"></h1>
</div>
<div>
<h2 id="builderDigestTitle">AI Builder Daily Report</h2>
<div class="builder-digest-meta" id="builderDigestMeta">Public updates from Follow Builders</div>
<div class="builder-digest-meta" id="builderDigestMeta">Public updates from Zero Tab feeds</div>
</div>
</div>
<div class="builder-digest-actions">
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"test": "TZ=UTC node --test tests/*.test.js && TZ=UTC vitest run",
"typecheck": "tsc -b"
"test": "TZ=UTC node --test tests/*.test.js tests/*.test.mjs && TZ=UTC vitest run",
"typecheck": "tsc -b",
"generate:builder-feeds": "node scripts/generate-builder-feeds.mjs"
},
"repository": {
"type": "git",
Expand Down
17 changes: 17 additions & 0 deletions scripts/generate-builder-feeds.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node
import { generateBuilderFeeds } from './lib/feed-generator.mjs';

const result = await generateBuilderFeeds();
if (!result.hasData) {
console.error('Feed generation produced no items.');
for (const error of result.errors) console.error(`- ${error}`);
process.exit(1);
}

console.log(
`Generated feeds: x=${result.feedX.x.length} blogs=${result.feedBlogs.blogs.length} podcasts=${result.feedPodcasts.podcasts.length}`,
);
if (result.errors.length) {
console.warn(`Completed with ${result.errors.length} source warning(s).`);
for (const error of result.errors) console.warn(`- ${error}`);
}
Loading
Loading