A cross-browser extension (Firefox + Chrome + Safari) that captures web pages as self-contained Markdown archives in the TextBundle .textpack format. Built with WXT (Manifest V3/V2), TypeScript, and Vite.
git clone <repo-url> && cd textbundler
npm install
make chrome # build + launch Chrome with extension loadedThat's it. Chrome for Testing is auto-installed on first run. Navigate to any article and click the toolbar icon to archive it.
See docs/TESTING.md for the full testing guide.
- Node.js (v18+)
- npm
- GNU Make
npm installThis installs dependencies and runs wxt prepare to generate TypeScript types.
make chrome # build Chrome MV3 + launch with extension loaded
make firefox # build Firefox MV2 + launch via web-ext
make safari # build Safari MV2 + open Safari (manual load)
make safari-xcode # build + generate Xcode project for App Store
make build-all # build all three browsers
make test # Vitest
make typecheck # tsc --noEmit
make lint # ESLint
make e2e # Puppeteer end-to-end pipeline test
make clean # remove dist/, .wxt/, and xcode-safari/
make chrome-clean # wipe the Chrome test profilemake chrome uses a dedicated profile at .chrome-profile/ — isolated from your personal Chrome, and always loads the freshly built extension. Run make chrome-clean to reset it.
npm run dev # Chrome (MV3) with HMR
npm run dev:firefox # Firefox (MV2) with HMRFirefox:
- Navigate to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on..."
- Select any file inside
dist/firefox-mv2/(e.g.manifest.json) - The extension appears in the toolbar and context menu
To debug: click "Inspect" next to the extension on the about:debugging page to open the background script console.
Chrome:
- Navigate to
chrome://extensions - Enable "Developer mode" (toggle in top-right)
- Click "Load unpacked"
- Select the
dist/chrome-mv3/directory - The extension appears in the toolbar (pin it from the puzzle-piece menu if needed)
To debug: click "Inspect views: service worker" on the extension card to open the background script console.
Safari:
- Safari > Settings > Advanced > enable "Show features for web developers"
- Develop menu > check "Web Extension Developer Mode"
- Develop > Load Web Extension... > select
dist/safari-mv2/ - The extension appears in the toolbar
To debug: Develop > Web Extension Background Content > TextBundler to open the background script console.
make safari builds and opens Safari with instructions. make safari-xcode generates a full Xcode project for App Store distribution.
After rebuilding (npm run build / npm run build:firefox / npm run build:safari):
- Firefox: Click the reload icon (circular arrow) next to the extension on
about:debugging - Chrome: Click the reload icon on the extension card at
chrome://extensions - Safari: Develop > Load Web Extension... again (replaces the previous load)
Dev mode (npm run dev / npm run dev:firefox) watches for changes and auto-reloads. make chrome / make firefox / make safari always rebuild before launching.
npm run build # Chrome MV3 → dist/chrome-mv3/
npm run build:firefox # Firefox MV2 → dist/firefox-mv2/
npm run build:safari # Safari MV2 → dist/safari-mv2/npm run zip # → dist/textbundler-0.1.0-chrome.zip
npm run zip:firefox # → dist/textbundler-0.1.0-firefox.zip + sources.zip
npm run zip:safari # → dist/textbundler-0.1.0-safari.zip
make safari-xcode # generate Xcode project for App Store submissionmake test # or: npm run test
make typecheck # or: npm run typecheck
make e2e # Puppeteer Chrome pipeline testRun a single test file:
npm run test -- --run slug- Navigate to any article or blog post
- Click the TextBundler toolbar icon, or right-click → "Archive Page as TextBundle"
- The extension extracts the article, converts it to Markdown, downloads images, and packages everything into a
.textpackfile
Right-click the extension icon → Manage Extension → Options (or open about:addons in Firefox → TextBundler → Preferences).
| Setting | Options | Default | Effect |
|---|---|---|---|
| Figures | Markdown / HTML | Markdown | Markdown:  + *caption*. HTML: raw <figure> preserved. |
| Tables | Markdown / HTML | Markdown | Markdown: GFM pipe tables (complex tables with colspan/rowspan fall back to HTML automatically). HTML: all tables preserved as raw HTML. |
Settings are synced via browser.storage.sync and take effect on the next archive.
...(blue) — processingOK(blue) — success (clears after 3s)!(red) — error (clears after 5s)
A browser notification confirms the result.
The .textpack file is a ZIP archive containing:
info.json # TextBundle v2 metadata
text.md # YAML frontmatter + Markdown body
assets/
image-001.jpg # Downloaded images (3-digit zero-padded)
image-002.png
...
Inspect with:
unzip -l 2026-02-14-example-article.textpack- Background logs: Right-click extension icon → Inspect → Console
- Content script logs: Page DevTools (F12) → Console, filter by
[TextBundler:
Only warn and error logs are emitted; debug and info are stripped by Vite.
Two-context event-driven pipeline:
Content Script (entrypoints/content.ts) — injected on-demand:
- Resolves lazy-loaded images
- Runs Mozilla Readability for article extraction
- Scrapes metadata from
<head>, OG tags, JSON-LD - Converts HTML to Markdown (Turndown + GFM + custom rules) — runs here because Chrome MV3 service workers lack
DOMParser
Background Service Worker (entrypoints/background.ts):
- Downloads images in parallel (4 concurrent, 30s timeout)
- Patches failed image URLs back to absolute
- Builds YAML frontmatter
- Packages into
.textpackvia JSZip - Triggers browser download
Copyright (C) 2026 TextBundle Contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.