browser-migrate
README.md
# browser-migrate
Move your browser profile (bookmarks, history, tabs) between browsers. macOS, CLI.
You test a lot of browsers — Chrome, Arc, Dia, Comet, Helium, Firefox, Zen, Safari — and switching means leaving your browsing life behind in the old one. This moves it.
## Status: M4 (Chromium + Firefox/Zen + Safari; bookmarks, history, tabs)
Supported for **read/export**: Chrome, Dia, Brave, Edge, **Arc** (Chromium);
Firefox, Zen (Gecko); Safari (WebKit). Data types: bookmarks + history everywhere;
open tabs from Chromium (SNSS), Gecko (mozLz4 sessions) and Safari. Direct bookmark
**write** currently targets Chromium; other engines use the assisted HTML-import
path.
Safari reads require **Full Disk Access** (grant it to your terminal in System
Settings → Privacy & Security). Without it, `doctor` says so instead of failing.
The bundle format is documented in [FORMAT.md](FORMAT.md).
```
browser-migrate list # supported + installed browsers
browser-migrate doctor # bookmark / history counts per browser
browser-migrate export <browser> <outDir> # export a profile to a portable bundle
browser-migrate migrate --from <a> --to <b> # copy bookmarks a → b [--dry-run]
browser-migrate import --in <dir> --to <b> # import a bundle → b [--dry-run]
browser-migrate restore <backupDir> # undo a write
browser-migrate extensions <browser> # list installed extensions + store links
browser-migrate extensions <a> --open <b> # open a's extension store pages in b
```
**Extensions** can't be migrated as data (they're installed programs; state is keyed
to per-browser IDs, and browsers block silent install). `browser-migrate` reads the
installed list and gives store links — `extensions <a> --open <b>` opens each store
page in the destination browser so you just click Install. Same-engine reuses the
store ID; cross-engine is a name match (different store).
**Two migration paths:**
1. **Portable / universal** — `export` writes a bundle (`manifest.json`,
`bookmarks.json`, `history.json`, `bookmarks.html`). The Netscape `bookmarks.html`
imports into any browser via its UI. Zero risk.
2. **Direct** — `migrate`/`import` write bookmarks straight into a Chromium profile.
Every write: (a) refuses while the dest browser is running, (b) backs up the
profile first (`~/.browser-migrate/backups`, undo with `restore`), (c) writes a
`Bookmarks` file with a correct Chrome checksum and strips the stale MAC from
`Local State` so Chrome accepts it. The checksum algorithm is verified against a
real Chrome `Bookmarks` file.
Import enforces the bundle version policy: same major proceeds, newer-major is
refused (upgrade the tool), older-major proceeds.
**Cross-machine:** the export bundle is machine-independent — `export` on your old
Mac, copy the bundle dir over, `import` (or open `bookmarks.html`) on the new one.
Passwords never travel in a bundle (they'd be plaintext and Keychain is machine-bound).
## MCP server (drive it from Claude)
The same operations are exposed as MCP tools so Claude (or any MCP client) can run
them directly. Register with Claude Code:
```
claude mcp add browser-migrate -- bun run /ABSOLUTE/PATH/browser-migrate/src/mcp.ts
```
Or add to a client's `mcpServers` config:
```json
{ "browser-migrate": { "command": "bun", "args": ["run", "/ABSOLUTE/PATH/src/mcp.ts"] } }
```
Tools: `list_browsers`, `doctor`, `export_profile`, `migrate`, `import_bundle`,
`restore`. Writing tools (`migrate`, `import_bundle`) back up first and refuse while
the destination browser is running — the same safety as the CLI. Preview with
`dryRun: true`.
## Architecture
Hub-and-spoke. One adapter per browser reads into a neutral intermediate format;
export writes out of it. Adding a browser is one adapter file — N+M, not N×M.
```
source profile ─(adapter.read)─▶ Intermediate ─(export)─▶ bundle + bookmarks.html
```
- `src/core/intermediate.ts` — neutral format + `epochToUnixMs` (the one place
the three engine time formats get normalized).
- `src/core/adapter.ts` — `Adapter` interface + `capabilities` matrix.
- `src/adapters/chromium.ts` — Chrome/Dia/Brave/Edge (shared Chromium layout).
- `src/adapters/gecko.ts` — Firefox/Zen (`places.sqlite`, profiles.ini, mozLz4 tabs).
- `src/adapters/safari.ts` — Safari (plist via `plutil`, History.db, FDA-aware).
## Roadmap
- **M2 ✓** — direct `migrate`/`import` behind backup+guard, Chromium checksum +
`Local State` MAC strip, `--dry-run`, `restore`, bundle version policy.
- **M3 ✓** — Firefox/Zen adapters (`places.sqlite` bookmarks + history, epoch +
`place:` filtering), cross-machine via portable bundle.
- **M4 ✓** — Safari read (plist bookmarks + Core-Data history, FDA-aware); tabs in
the neutral format; Gecko tabs via a hand-rolled `mozLz4` decoder; `FORMAT.md`.
**Deferred, on purpose (with reasons):**
- **Non-Chromium bookmark write** — writing `places.sqlite` / Safari plists is
risky and version-fragile; the assisted `bookmarks.html` import path covers
Firefox/Safari losslessly instead.
- **Windows/Linux** — Chromium profile paths are resolved per-OS, but only macOS
is tested here. Firefox cross-OS layout still TODO (issue #5).
Write path is verified end-to-end on a live Chromium browser (Dia): a written
bookmark survives a quit/relaunch and Dia re-signs the file rather than resetting
it. Chrome's exact behavior can still vary by version, which is why every write is
backed up and undoable with `restore`.
Passwords ride native CSV export/import (never reimplement browser crypto) and
are excluded from bundles by default.
## Install
Once a release is tagged (`v*`), CI builds macOS arm64 + x64 binaries and attaches
them to the GitHub release. A Homebrew formula template lives in `Formula/` — publish
it to a `homebrew-tap` repo and then:
```
brew install navotvolkgroundup/tap/browser-migrate
```
Until then, run from source (below) or grab a release binary directly.
## Develop
```
bun install
bun test
bun run src/cli.ts list
bun run compile # single binary → ./browser-migrate
```
CI (`.github/workflows/ci.yml`) runs `bun test` on every push/PR. Tagging `vX.Y.Z`
triggers `release.yml` to build and publish binaries.
MIT.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessResponsive