Created
May 30, 2026 23:28
-
-
Save medhatdawoud/3cf1adabe4d5bc46cc869828526b94ba to your computer and use it in GitHub Desktop.
Export Zen browser workspaces to Chrome-importable HTML bookmarks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Export Zen browser workspaces to Chrome-importable HTML bookmarks. | |
| Each workspace becomes a bookmark folder; all tabs become bookmarks inside. | |
| Usage: | |
| python3 zen-to-html-bookmarks.py [output.html] | |
| Requires: lz4 (pip install lz4) | |
| """ | |
| import json | |
| import lz4.block | |
| import os | |
| import struct | |
| import sys | |
| from collections import defaultdict | |
| from pathlib import Path | |
| ZEN_PROFILE = None # auto-detected from profiles.ini | |
| def find_zen_profile(): | |
| base = Path.home() / "Library/Application Support/zen/Profiles" | |
| if not base.exists(): | |
| return None | |
| # Prefer "Default (release)" if it exists | |
| for name in base.iterdir(): | |
| if "release" in name.name.lower(): | |
| return name / "zen-sessions.jsonlz4" | |
| # Fall back to first profile found | |
| profiles = list(base.iterdir()) | |
| if profiles: | |
| return profiles[0] / "zen-sessions.jsonlz4" | |
| return None | |
| def read_mozlz4(path): | |
| with open(path, "rb") as f: | |
| magic = f.read(8) | |
| if magic != b"mozLz40\x00": | |
| raise ValueError(f"Not a mozlz4 file: {path}") | |
| size = struct.unpack("<I", f.read(4))[0] | |
| return lz4.block.decompress(f.read(), uncompressed_size=size) | |
| def tab_url_title(tab): | |
| entries = tab.get("entries", []) | |
| if not entries: | |
| return None, None | |
| idx = min(tab.get("index", len(entries) - 1), len(entries) - 1) | |
| entry = entries[idx] | |
| url = entry.get("url", "") | |
| title = entry.get("title", "") or url | |
| return url, title | |
| def extract_spaces(d): | |
| space_map = {s["uuid"]: s["name"] for s in d["spaces"]} | |
| tabs_by_space = defaultdict(list) | |
| for tab in d["tabs"]: | |
| if tab.get("zenIsEmpty"): | |
| continue | |
| url, title = tab_url_title(tab) | |
| if not url or url.startswith("about:") or url.startswith("chrome:"): | |
| continue | |
| ws = tab.get("zenWorkspace") | |
| tabs_by_space[ws].append({"url": url, "title": title}) | |
| spaces = [] | |
| for space in d["spaces"]: | |
| uuid = space["uuid"] | |
| name = space["name"] or "Unnamed" | |
| tabs = tabs_by_space.get(uuid, []) | |
| spaces.append({"name": name, "tabs": tabs}) | |
| return spaces | |
| def make_html(spaces): | |
| def esc(s): | |
| return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) | |
| lines = [ | |
| "<!DOCTYPE NETSCAPE-Bookmark-file-1>", | |
| '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">', | |
| "<TITLE>Bookmarks</TITLE>", | |
| "<H1>Bookmarks</H1>", | |
| "<DL><p>", | |
| " <DT><H3>Zen Workspaces</H3>", | |
| " <DL><p>", | |
| ] | |
| for space in spaces: | |
| lines.append(f" <DT><H3>{esc(space['name'])}</H3>") | |
| lines.append(" <DL><p>") | |
| for tab in space["tabs"]: | |
| lines.append(f' <DT><A HREF="{esc(tab["url"])}">{esc(tab["title"])}</A>') | |
| lines.append(" </DL><p>") | |
| lines += [" </DL><p>", "</DL>"] | |
| return "\n".join(lines) | |
| def main(): | |
| data_path = find_zen_profile() | |
| if not data_path or not data_path.exists(): | |
| print("Zen session file not found. Is Zen installed?") | |
| sys.exit(1) | |
| args = sys.argv[1:] | |
| out = Path(args[0]) if args else Path("/tmp/zen_workspaces.html") | |
| print(f"Reading {data_path} ...") | |
| raw = read_mozlz4(data_path) | |
| d = json.loads(raw) | |
| spaces = extract_spaces(d) | |
| print(f"Found {len(spaces)} workspaces:") | |
| for s in spaces: | |
| print(f" {s['name']} ({len(s['tabs'])} tabs)") | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| out.write_text(make_html(spaces)) | |
| print(f"\nHTML written to: {out}") | |
| print("Import via Chrome: chrome://bookmarks → ⋮ → Import bookmarks") | |
| os.system(f'open "{out}"') | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment