Skip to main content
Glama
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.

Maintenance

ActivityMaintained
ResponsivenessResponsive