{"id":49340946,"url":"https://github.com/domdewom/granola-backup","last_synced_at":"2026-04-27T04:01:39.328Z","repository":{"id":354076538,"uuid":"1221996501","full_name":"domdewom/granola-backup","owner":"domdewom","description":"Back up your Granola.ai meetings to GitHub automatically.","archived":false,"fork":false,"pushed_at":"2026-04-27T02:32:21.000Z","size":16,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T03:36:34.968Z","etag":null,"topics":["backup","github-actions","granola","granola-ai","md-files","meeting-notes","transcripts"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/domdewom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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-04-27T00:04:27.000Z","updated_at":"2026-04-27T02:34:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/domdewom/granola-backup","commit_stats":null,"previous_names":["domdewom/granola-backup"],"tags_count":1,"template":true,"template_full_name":null,"purl":"pkg:github/domdewom/granola-backup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domdewom%2Fgranola-backup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domdewom%2Fgranola-backup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domdewom%2Fgranola-backup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domdewom%2Fgranola-backup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/domdewom","download_url":"https://codeload.github.com/domdewom/granola-backup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domdewom%2Fgranola-backup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32321940,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["backup","github-actions","granola","granola-ai","md-files","meeting-notes","transcripts"],"created_at":"2026-04-27T04:01:37.687Z","updated_at":"2026-04-27T04:01:39.321Z","avatar_url":"https://github.com/domdewom.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Granola Backup Exporter\n\nThis is a [Granola.ai](https://www.granola.ai/) meetings export script best used\nto daily incrementally back up meetings to your GitHub. The exporter writes\ngenerated artifacts into the `granola-backups` branch. Locally, artifacts are\nstored under `backups/`, but ignored on `main` by default.\n\nThis is an unofficial exporter that relies on Granola API behavior observed\nfrom the desktop app. It is not affiliated with or endorsed by Granola.\n\nFor each meeting, it exports:\n\n- `notes.md` and `notes.json`\n- `enhanced.md` and `enhanced.json`\n- `transcript.md` and `transcript.json`\n- `meeting.json` (metadata snapshot)\n\nMeeting folder naming:\n\n- `YYYYMMDD_Title` (example: `20260303_Allie`)\n- if two meetings collide on same date/title, exporter appends `--\u003cid8\u003e`\n\n## Use This Template\n\nThis repository is designed to be copied into your own private GitHub repo.\nWhen used as a GitHub template, the workflow and exporter code are copied, but\nyour GitHub Secrets and backup data are not.\n\nRecommended setup:\n\n1. Create a new private repo from this template.\n2. Add the `GRANOLA_SUPABASE_JSON` repository secret.\n3. Run the `Granola Backup` workflow manually once from the Actions tab.\n4. Keep `main` for code and config.\n5. Let the workflow publish generated backups to the `granola-backups` branch.\n\nThe workflow is intentionally included in the template. If it runs before\n`GRANOLA_SUPABASE_JSON` is configured, it exits successfully with a setup notice\nand does not create backup files or branches.\n\n## Privacy and Security\n\nExported backups contain private meeting content, transcripts, notes, metadata,\nand AI summaries. Treat the backup branch and any local `backups/` folder as\nsensitive data.\n\n- Keep repos containing real backups private.\n- Do not commit `.env`, `supabase.json`, access tokens, or refresh tokens.\n- Do not make a repo public if its Git history ever contained real backups.\n- For public sharing, create a fresh repo with clean history and no generated\n  backup files.\n\n## Layout\n\n- `scripts/export_granola.py`: main exporter\n- `.github/workflows/granola-backup.yml`: daily workflow\n- `backup.config.yaml`: runtime config\n- `backups/`: generated output artifacts and manifests, ignored by Git\n\nExport paths:\n\n- `backups/granola-md/\u003cmeeting-folder\u003e/...`\n- `backups/granola-json/\u003cmeeting-folder\u003e/...`\n- `backups/manifests/...`\n\n## Generated Artifacts and Git\n\n- `backups/` is ignored in `.gitignore`.\n- Existing backup files have been removed from Git tracking with `git rm --cached`; the files can still exist locally.\n- Moving `backups/` out of this repo will not remove tracked files from Git, because it is not currently tracked.\n- The exporter always recreates `backups/` inside the repo on the next run. If `backups/manifests/sync_state.json` is moved away, the next run has no incremental marker and behaves like a first export.\n\n## GitHub Actions Backup Branch\n\nThe scheduled workflow runs from `main`, using the latest exporter code from\n`main`, but publishes generated backup files to a separate `granola-backups`\nbranch.\n\n- `main` contains code, config, docs, and workflow files.\n- `granola-backups` contains generated `backups/` output only.\n- Before each export, the workflow restores `backups/` from `granola-backups` if the branch exists, so incremental state is preserved in `backups/manifests/sync_state.json`.\n- After each export, the workflow replaces the backup branch contents with the newly generated `backups/` tree.\n- Code changes only need to be made on `main`; the next scheduled run automatically uses them.\n\n## Required GitHub Secret\n\n- `GRANOLA_SUPABASE_JSON`: full contents of your Granola `supabase.json`\n\nOn macOS, this file is typically at:\n\n- `~/Library/Application Support/Granola/supabase.json`\n\n## Optional Secrets\n\n- `GRANOLA_CLIENT_ID`: override client id (default: `client_GranolaMac`)\n- `GRANOLA_API_BASE`: override API base (default: `https://api.granola.ai`)\n- `BACKUP_WORKSPACE_ID`: restrict backup to a single workspace\n- `ALLOW_LARGE_DROP`: set to `true` to bypass health guard if meeting count drops unexpectedly\n- `ALLOW_EMPTY_CONTENT_OVERWRITE`: set to `true` to allow empty/null API responses to overwrite existing non-empty export files\n\n## Local Run\n\n```bash\npython -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\n# one-time setup\ncp .env.example .env\n# then paste your real supabase.json content into GRANOLA_SUPABASE_JSON in .env\n\n# run (script auto-loads .env)\npython scripts/export_granola.py\n```\n\n## GitHub Actions Setup\n\nThe workflow can run on a schedule or manually from the Actions tab. It expects\nthe `GRANOLA_SUPABASE_JSON` repository secret to be present. Without that\nsecret, the workflow skips cleanly and prints a setup notice.\n\nTo add the secret:\n\n1. Open your GitHub repo.\n2. Go to **Settings** -\u003e **Secrets and variables** -\u003e **Actions**.\n3. Add a new repository secret named `GRANOLA_SUPABASE_JSON`.\n4. Paste the full contents of your local Granola `supabase.json` file.\n\nThe workflow is scheduled with:\n\n```yaml\ncron: \"0 2 * * *\"\n```\n\nYou can change that cron expression in `.github/workflows/granola-backup.yml`.\nGenerated backups are published to `granola-backups`, not `main`, so the default\nbranch stays focused on code, config, docs, and workflow files.\n\n## Progress Output\n\n- Local terminal (`PROGRESS_MODE=auto`, default): shows spinner/progress and phase updates.\n- GitHub Actions (`GITHUB_ACTIONS=true`): progress UI is suppressed by default.\n\nOptional controls:\n\n- `PROGRESS_MODE=auto|rich|plain|off`\n- `PROGRESS_LOG_EVERY=25` (for plain mode update cadence)\n\n## Incremental State\n\nState is stored in `backups/manifests/sync_state.json`.\n\n- First run exports all meetings.\n- Later runs export only meetings with `updated_at` newer than the last successful sync marker.\n- The run manifest records per-file statuses, content counts, missing-content warnings, and skipped empty overwrites.\n\nUse `FULL_EXPORT=true` to force a full re-export.\n\n## Notes vs Enhanced\n\n- `notes.md` contains manual notes typed into Granola. It can be empty even when a transcript exists.\n- `enhanced.md` contains Granola's AI-generated summary from the document panel.\n- The exporter skips overwriting existing non-empty export files with empty/null content unless `ALLOW_EMPTY_CONTENT_OVERWRITE=true` is set.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomdewom%2Fgranola-backup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdomdewom%2Fgranola-backup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomdewom%2Fgranola-backup/lists"}