{"id":46597680,"url":"https://github.com/mikewaters/safari-raindrop-tabgroups","last_synced_at":"2026-03-07T15:03:36.452Z","repository":{"id":341266237,"uuid":"1169498842","full_name":"mikewaters/safari-raindrop-tabgroups","owner":"mikewaters","description":"A tool to retrieve and analyze Safari and Raindrop.io Tab Groups/Collections.  Uses the local MacOS Safari sqlite database and the Raindrop.io API.","archived":false,"fork":false,"pushed_at":"2026-02-28T19:33:15.000Z","size":23528,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-28T22:50:41.491Z","etag":null,"topics":["raindrop","safari","tab-groups"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikewaters.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-28T19:26:01.000Z","updated_at":"2026-02-28T19:33:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mikewaters/safari-raindrop-tabgroups","commit_stats":null,"previous_names":["mikewaters/safari-raindrop-tabgroups"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mikewaters/safari-raindrop-tabgroups","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikewaters%2Fsafari-raindrop-tabgroups","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikewaters%2Fsafari-raindrop-tabgroups/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikewaters%2Fsafari-raindrop-tabgroups/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikewaters%2Fsafari-raindrop-tabgroups/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikewaters","download_url":"https://codeload.github.com/mikewaters/safari-raindrop-tabgroups/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikewaters%2Fsafari-raindrop-tabgroups/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30219297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T14:02:48.375Z","status":"ssl_error","status_checked_at":"2026-03-07T14:02:43.192Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["raindrop","safari","tab-groups"],"created_at":"2026-03-07T15:03:35.060Z","updated_at":"2026-03-07T15:03:36.438Z","avatar_url":"https://github.com/mikewaters.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# safari-tabgroups\n\nA CLI toolkit for extracting, listing, and describing browser tab groups from Safari and Raindrop.io. Built with [Bun](https://bun.sh).\n\n## Prerequisites\n\n- macOS (reads Safari's local SQLite database)\n- [Bun](https://bun.sh) v1.3+\n- An [OpenRouter](https://openrouter.ai) API key (for `fetch --prompt` and `describe` commands)\n- A [Raindrop.io](https://raindrop.io) API token (for syncing Raindrop collections)\n\n## Install\n\n```bash\nbun install\nmake install          # builds + copies to /usr/local/bin\n# or install elsewhere:\nPREFIX=~/.local/bin make install\n```\n\n## Commands\n\n1. `sync-tabgroups` - Sync our cache of sources\n2. `safari-tabgroups` - Read safari data from cached source\n3. `raindrop-tabgroups` - Read raindrop data from cached source\n4. `list-tabgroups` - List combined cached source data\n5. `describe-tabgroup` - Perform classification of bookmarks data\n6. `bookmark-index` - Manages bookmarks data in a database and orchestrate\n\n### sync-tabgroups\n\nPopulates the local cache for Safari and/or Raindrop.io. All other commands read from this cache — run sync first.\n\n```bash\n# Sync both sources\nbun run sync\n\n# Sync only Safari\nbun run sync -- --safari\n\n# Sync only Raindrop.io\nbun run sync -- --raindrop\n\n# Sync from Safari Technology Preview\nbun run sync -- --safari --stp\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--safari` | Only sync Safari tab groups |\n| `--raindrop` | Only sync Raindrop.io collections |\n| `--full-raindrop` | Force full Raindrop sync (skip delta mode) |\n| `--stp` | Sync from Safari Technology Preview instead of Safari |\n| `--verbose` | Print debug info to stderr |\n| `--debug` | Implies `--verbose` |\n\nWithout `--safari` or `--raindrop`, syncs both sources.\n\n**How it works:**\n\n- **Safari:** Copies `SafariTabs.db` (plus WAL/SHM files) to `~/.cache/safari-tabgroups/`, skipping the copy if the cache is already fresh. Runs a WAL checkpoint to consolidate writes.\n- **Raindrop:** Fetches collections on every run. If a prior cache exists, performs a delta sync using `lastUpdate:\u003efetchedAt` and merges changes by raindrop ID; use `--full-raindrop` to force a complete refresh (recommended periodically to reconcile deletions). Cache is written to `~/.cache/safari-tabgroups/raindrop-collections.json`.\n\nWhen syncing both, sources are fetched in parallel.\n\n---\n\n### safari-tabgroups\n\nReads Safari tab groups from the local cache.\n\n```bash\n# Human-readable output\nbun run safari\n\n# JSON output\nbun run safari -- --json\n\n# Pipe JSON to jq\nbun run safari -- --json | jq '.profiles[].tabGroups[].name'\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--json` | Output as JSON (`{ profiles: [...] }`) |\n| `--verbose` | Print debug info to stderr |\n| `--debug` | Implies `--verbose` |\n\n**Text output (default):** one line per tab as `Profile / Tab Group / Title (URL)`\n\n```\nPersonal / Research / Some Page (https://example.com)\nPersonal / Work / Jira Board (https://jira.example.com)\n```\n\n**JSON output (`--json`):**\n\n```json\n{\n  \"profiles\": [\n    {\n      \"name\": \"Personal\",\n      \"tabGroups\": [\n        {\n          \"name\": \"Research\",\n          \"tabs\": [\n            { \"title\": \"Some Page\", \"url\": \"https://example.com\" }\n          ]\n        }\n      ]\n    }\n  ]\n}\n```\n\n---\n\n### raindrop-tabgroups\n\nReads Raindrop.io collections from the local cache, output in the same schema as `safari-tabgroups`.\n\n```bash\n# Human-readable output\nbun run raindrop\n\n# JSON output\nbun run raindrop -- --json\n\n# Pipe to jq\nbun run raindrop -- --json | jq '.profiles[0].tabGroups[] | .name'\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--json` | Output as JSON (`{ profiles: [...] }`) |\n| `--verbose` | Print debug info to stderr |\n| `--debug` | Implies `--verbose` |\n\n**Mapping:** Collections map to tab groups; raindrops (bookmarks) map to tabs. Nested collections are flattened with `\"Parent / Child\"` naming. All collections are placed under a single profile named `\"Raindrop.io\"`.\n\n---\n\n### list-tabgroups\n\nLists all tab group names across both sources.\n\n```bash\n# Plain text listing\nbun run list\n\n# JSON output\nbun run list -- --json\n\n# Only Safari groups\nbun run list -- --safari\n\n# Only Raindrop groups\nbun run list -- --raindrop\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--json` | Output merged JSON (`{ profiles: [...] }`) |\n| `--safari` | Only include Safari tab groups |\n| `--raindrop` | Only include Raindrop.io collections |\n| `--verbose` | Print debug info to stderr |\n| `--debug` | Implies `--verbose` |\n\n**Text output (default):**\n\n```\nPersonal\n  Research (12 tabs)\n  Work (3 tabs)\n\nRaindrop.io\n  AI Tools (8 tabs)\n  Reading List (4 tabs)\n```\n\n---\n\n### describe-tabgroup\n\nDerives structured metadata about tab groups using an LLM. Works with both Safari and Raindrop sources.\n\n- **Tier 1 (default):** Sends only tab titles and URLs to the LLM. Fast and cheap.\n- **Tier 2 (`--fetch`):** Also fetches and includes page content for the top N tabs, providing richer context.\n\n```bash\n# Describe a single tab group\nbun run describe \"My Research\"\n\n# Describe with page content (Tier 2)\nbun run describe \"My Research\" -- --fetch\n\n# Describe all tab groups\nbun run describe -- --all\n\n# Only describe Safari groups\nbun run describe -- --all --safari\n\n# Only describe Raindrop groups\nbun run describe -- --all --raindrop\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--all` | Describe all tab groups (outputs a JSON object keyed by group name) |\n| `--fetch` | Tier 2: fetch markdown from the top N tabs and include in the prompt |\n| `--safari` | Only include Safari tab groups |\n| `--raindrop` | Only include Raindrop.io collections |\n| `--verbose` | Print debug info to stderr, including the full assembled prompt |\n| `--debug` | Implies `--verbose` |\n\n**Output (single group):**\n\n```json\n{\n  \"description\": \"Research into AI agent frameworks and their memory architectures.\",\n  \"category\": \"research\",\n  \"topics\": [\"ai-agents\", \"agent-memory\", \"open-source\"],\n  \"intent\": \"Evaluating agent frameworks to find or build a successor setup.\",\n  \"confidence\": 0.95,\n  \"page_snapshot\": \"## Agent Memory Docs\\nMarkdown content from fetched pages...\"\n}\n```\n\nWhen `--fetch` is used, the fetched markdown content is included as `page_snapshot` in the output. This is stored alongside the classification in the database. Without `--fetch`, `page_snapshot` is omitted.\n\n**Output (`--all`):** a JSON object where each key is a tab group name.\n\n---\n\n### bookmark-index\n\nMaintains a unified SQLite index of Safari tab groups and Raindrop collections. Supports LLM-powered classification and URL matching against stored groups.\n\n```bash\n# Sync index from cached data\nbun run index update\n\n# List all indexed groups\nbun run index list\n\n# Show full detail for a group\nbun run index show \"My Research\"\n\n# Classify groups using LLM\nbun run index classify --all\n\n# Match a URL against classified groups\nbun run index match \"https://example.com\"\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--db \u003cpath\u003e` | Override database location (default: `$XDG_DATA_HOME/safari-tabgroups/bookmarks.db`) |\n| `--json` | Output as JSON |\n| `--safari` | Only include Safari tab groups |\n| `--raindrop` | Only include Raindrop.io collections |\n| `--all` | Classify all groups |\n| `--fetch` | Include page content when classifying and store page snapshot |\n| `--unclassified` | Only classify groups without existing classification |\n| `--top N` | Limit match results (default: 5) |\n| `--strategy NAME` | Match strategy to use (default: `llm-fetch`) |\n| `--verbose` | Print debug info to stderr |\n\nRe-classifying an already-classified collection creates a new version — previous versions are preserved and can be managed with `bookmark-index version`.\n\nThe database defaults to `~/.local/share/safari-tabgroups/bookmarks.db` (following the XDG Base Directory spec). This can be configured in `fetch.config.toml` or overridden per-invocation with `--db`.\n\n---\n\n### fetch-tabgroup\n\nFetches a URL and converts it to markdown. Optionally sends the markdown to an LLM via OpenRouter.\n\n```bash\n# Fetch a URL and output markdown\nbun run fetch \"https://example.com\"\n\n# Fetch and ask an LLM about the content\nbun run fetch \"https://example.com\" --prompt \"Summarize this page in 3 bullet points\"\n```\n\n**Flags:**\n\n| Flag | Description |\n|------|-------------|\n| `--prompt \u003ctext\u003e` | Send the fetched markdown to OpenRouter with this user prompt |\n| `--verbose` | Print debug info to stderr |\n| `--debug` | Implies `--verbose`. Saves raw markdown to `./debug-\u003ctimestamp\u003e.md` |\n\n---\n\n## Configuration\n\nAll settings are stored in `fetch.config.toml` at the project root.\n\n```toml\n[database]\npath = \"$XDG_DATA_HOME/safari-tabgroups/bookmarks.db\"  # Supports $ENV_VAR syntax; falls back to ~/.local/share\n\n[openrouter]\napi_key = \"$OPENROUTER_API_KEY\"     # Supports $ENV_VAR syntax\nmodel = \"google/gemini-2.5-flash\"\nsystem_prompt = \"You are a helpful assistant that analyzes web page content.\"\nmax_content_bytes = 40_000          # Max markdown bytes sent to LLM (fetch command)\nmax_tokens = 1_000                  # Max response tokens from the LLM\n\n[raindrop]\napi_key = \"$RAINDROP_TOKEN\"         # Raindrop.io API token\n\n[describe]\nmax_tabs_to_fetch = 5               # How many tabs to fetch in Tier 2\nskip_domains = [\"discord.com\", \"localhost\"]\nper_tab_max_bytes = 500             # Max bytes of markdown per tab in Tier 2\nsystem_prompt = \"\"\"\nYou are a research librarian cataloging a user's browser tab groups...\n\"\"\"\n```\n\n### Environment variables\n\n| Variable | Used by | Description |\n|----------|---------|-------------|\n| `OPENROUTER_API_KEY` | `fetch`, `describe`, `index` | OpenRouter API key |\n| `RAINDROP_TOKEN` | `sync` | Raindrop.io API token |\n| `XDG_DATA_HOME` | `index` | Base directory for persistent data (default: `~/.local/share`) |\n\n---\n\n## Building\n\nCompiled binaries are produced under `dist/`. `make install` copies them from there to `PREFIX`.\n\n```bash\nmake build            # compile all standalone binaries into dist/\nmake install          # build + install to /usr/local/bin\nmake install PREFIX=~/.local/bin  # custom install path\nmake uninstall        # remove installed binaries\nmake clean            # remove dist/ and build artifacts\n```\n\nCompiled binaries:\n\n| Binary | Source | Description |\n|--------|--------|-------------|\n| `sync-tabgroups` | `src/sync.ts` | Cache population for Safari and Raindrop |\n| `safari-tabgroups` | `src/safari.ts` | Safari tab group reader (read-only) |\n| `raindrop-tabgroups` | `src/raindrop.ts` | Raindrop.io collection reader (read-only) |\n| `list-tabgroups` | `src/list.ts` | List all tab group names |\n| `describe-tabgroup` | `src/describe.ts` | Tab group metadata derivation via LLM |\n| `fetch-tabgroup` | `src/fetch.ts` | URL-to-markdown + optional LLM analysis |\n| `bookmark-index` | `src/index.ts` | Unified index with classification and URL matching |\n\n## Database\n\nThe index database (`bookmarks.db`) stores collections, their items, and LLM-generated classifications.\n\n### Schema overview\n\n| Table | Description |\n|-------|-------------|\n| `groups` | Collections from Safari and Raindrop with inline classification fields |\n| `items` | Individual bookmarks/tabs within each group |\n| `group_classifications` | Versioned Collection Cards with `page_snapshot` |\n| `highlights` | Raindrop highlights linked to items |\n| `match_log` / `match_cache` | URL match history and caching |\n| `match_feedback` | User feedback on match quality |\n| `meta` | Key-value metadata (last sync times) |\n\n### Classification snapshots\n\nWhen classifying with `--fetch`, the markdown fetched from tab pages is stored as `page_snapshot` in `group_classifications`. This preserves the source material that informed each classification.\n\n- The field is nullable — classifications without `--fetch` or where all fetches failed have `NULL`\n- When using `--import`, include an optional `page_snapshot` field in the JSON\n- An ERROR is logged if `--fetch` is used but no snapshot is captured\n\n### Stored metadata\n\nThe `metadata` JSON column on `items` and `groups` stores rich source data:\n\n**Safari tabs** — All columns from Safari's bookmarks table except `server_id` and binary blobs (`icon`, `sync_data`, `extra_attributes`, `local_attributes`). Includes `order_index`, `subtype`, `last_modified`, `external_uuid`, `date_closed`, `read`, and more. Core Data timestamps are converted to ISO 8601.\n\n**Raindrop items** — All fields from the Raindrop API except `cover`. Includes `type`, `excerpt`, `note`, `tags`, `domain`, `important`, `broken`, `media`, `cache`, `user`, `creatorRef`, `sort`, `removed`, and more.\n\n**Raindrop collections** — All collection fields except `cover`, `_id`, `title`, and `parent` (stored as first-class columns). Includes `description`, `color`, `slug`, `access`, `author`, `count`, `creatorRef`, and more.\n\n---\n\n## Architecture\n\n```\nsrc/\n  sync.ts        Cache population — copies Safari DB, fetches Raindrop API\n  safari.ts      Safari SQLite reader — reads tab groups from cached SafariTabs.db\n  raindrop.ts    Raindrop.io reader — reads collections from cached JSON\n  list.ts        Lists tab group names from both sources\n  describe.ts    Tab group metadata derivation via LLM (spawns safari.ts and raindrop.ts)\n  fetch.ts       URL-to-markdown converter with optional LLM analysis\n  index.ts       Unified bookmark index — stores groups, classifications, and matches\n  match/\n    types.ts     MatchStrategy interface and strategy registry\n    llm-fetch.ts LLM-based match strategy (pre-score + OpenRouter)\n  plist.ts       Apple plist parser for Safari timestamp extraction\n\nfetch.config.toml   Shared configuration (API keys, LLM settings, database path)\nMakefile            Build and install targets\n```\n\n`describe.ts` and `list.ts` spawn `safari.ts` and `raindrop.ts` as subprocesses to get tab group data. All reader commands are read-only — only `sync.ts` writes to the cache. `index.ts` maintains its own SQLite database at `$XDG_DATA_HOME/safari-tabgroups/bookmarks.db` (configurable).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikewaters%2Fsafari-raindrop-tabgroups","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikewaters%2Fsafari-raindrop-tabgroups","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikewaters%2Fsafari-raindrop-tabgroups/lists"}