Architecture in plain language

Link Garden keeps data as local files first, then builds a compact index for fast searching and filtering. The CLI reads from and writes to those files, and exports static output when you choose to share.

You can use Link Garden only on your machine, or add optional hosting later. Nothing in the core model requires a cloud account.

Files on disk (Markdown + frontmatter)

Each bookmark is saved as one Markdown file under data/bookmarks/. The metadata lives in YAML frontmatter; optional long text lives in the Markdown body.

Why this exists: human-readable files are easier to back up, move, and inspect.

---
id: a1b2c3d4e5
title: Example Domain
url: https://example.com
tags: [reference]
saved_at: "2026-03-02T07:45:00Z"
folder_path: bookmark_bar/Examples
visibility: private
---

This is optional body text.
Indexing (why an index exists)

A separate data/index.json file stores compact rows for quick list/search operations, so commands do not need to fully parse every Markdown file each time.

Why this exists: speed and predictable CLI output on larger bookmark sets.

[
  {
    "id": "a1b2c3d4e5",
    "title": "Example Domain",
    "url": "https://example.com",
    "path": "data/bookmarks/20260302T074500Z__example-domain__a1b2c3d4e5.md",
    "visibility": "private"
  }
]
Importing (Chrome bookmarks)

The importer reads Chrome's JSON format recursively and keeps folder lineage in folder_path (for example bookmark_bar/Research/GPCR).

Why this exists: to move existing bookmark history into a local, portable format.

link-garden import-chrome --bookmarks-file ./Bookmarks --dedupe both
link-garden import-chrome --bookmarks-file ./Bookmarks --dry-run
link-garden import-chrome --bookmarks-file ./Bookmarks --watch --interval 60
Exporting (HTML / Markdown / JSON)

Exports are generated from local files with visibility filtering. HTML produces index.html and bookmarks.html; Markdown produces bookmarks.md; JSON produces bookmarks.json.

Why this exists: one source of truth, multiple sharing formats.

link-garden export --format html --out ./exports --scope public
link-garden export --format markdown --out ./exports --scope unlisted
link-garden export --format json --out ./exports --scope all --dangerous-all
Serving locally

The serve command creates a temporary HTML export (unless --static-dir is given) and serves it with Python's built-in HTTP server.

Why this exists: quick local browsing without extra deployment setup.

link-garden serve --repo-dir . --port 8000
link-garden serve --repo-dir . --export-mode public
link-garden serve --static-dir ./exports --port 8080
Self-hosting with nginx (optional)

Remote access is optional. The recommended pattern is: keep Link Garden on localhost and put nginx in front for auth, TLS, and rate limits.

Why this exists: avoid exposing raw app ports directly to the internet.

server {
    listen 80;
    server_name links.example.com;
    location / {
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://127.0.0.1:8000;
    }
}
Security & visibility (private/unlisted/public)

Bookmarks are marked private, unlisted, or public. Export and serve scopes decide what gets included, with explicit confirmation required for full-scope export.

Why this exists: sharing should be explicit and reversible, not automatic.

link-garden set-visibility --id a1b2c3d4e5 --visibility public
link-garden export --format html --out ./exports --scope all --dangerous-all

# secure config defaults
default_visibility: private
export_default_scope: public
serve_default_scope: public
server_bind_host: 127.0.0.1
require_allow_remote: true