GDriveSync is a Google Drive to local-file bridge for humans and agents.
Use it in VS Code when you want linked files that stay native to your workspace. Use the CLI when you want an agent-safe way to inspect and export Google Docs, DOCX, Google Slides, PowerPoint, Google Sheets, and XLSX into normal Markdown, Marp-flavored Markdown, or CSV outputs.
Core flows:
- Google Docs ->
.md - DOCX in Drive ->
.md - Google Slides -> Marp-flavored
.md - PowerPoint in Drive -> Marp-flavored
.md - Google Sheets ->
.csvorfolder-of-csvs - XLSX in Drive ->
.csvorfolder-of-csvs
The project is one-way by design: Google Drive is the source of truth.
Most agents still integrate through CLIs, not editor extensions. GDriveSync is useful there because it gives agents a clean bridge from Google Drive into normal local files they already know how to read and edit.
Agent-friendly qualities:
- OAuth-backed access instead of brittle browser scraping
- stable local outputs: Markdown and CSV
- direct export to stdout for single-file workflows
- first-class manifest-aware
link,status,sync, andunlinkcommands - machine-readable command envelopes with a stable
contractVersionandok/dataorok/errorshape under--json - explicit workspace targeting with
--cwd - a
doctorcommand for auth/manifest diagnostics and corruption recovery - predictable spreadsheet shape switching:
- one visible sheet ->
report.csv - multiple visible sheets ->
report/<sheet>.csv
- one visible sheet ->
- VS Code extension in TypeScript
- Google OAuth desktop flow with PKCE and VS Code
SecretStorage - Desktop OAuth uses a localhost loopback callback inside the extension
- Google Docs export to
.docxand local DOCX -> Markdown conversion for higher-resolution images - DOCX-in-Drive download and local DOCX -> Markdown conversion
- Google Slides export to
.pptxand local Marp Markdown generation - Automatic Google Slides API fallback for oversized native Slides decks when Drive export is too large
- Drive-hosted
.pptxdownload and local Marp Markdown generation - Google Sheets export to
.xlsxand local.csvgeneration - Drive-hosted
.xlsxdownload and local.csvgeneration - Optional local or cloud image enrichment for Markdown and Marp outputs
- Workspace sidecar manifest in
.gdrivesync.json - Commands for connecting accounts, switching the default account, linking, importing, syncing, auto-sync toggle, unlinking, and disconnecting accounts
- Progress notifications for manual import and sync flows in the VS Code extension
- Status bar, CodeLens, and editor/explorer command contributions for linked Markdown and CSV files
- Static site assets for Cloudflare Pages, including homepage, privacy policy, and Picker page
- Agent-friendly CLI entrypoint with auth, inspect, export, link, status, sync, and unlink flows
- Internal sync profiles so Docs/DOCX can share one Markdown flow, Slides/PPTX can share one Marp flow, and Sheets/XLSX can share one CSV flow
- Automatic spreadsheet shape switching:
- one visible sheet ->
report.csv - multiple visible sheets ->
report/<sheet>.csv
- one visible sheet ->
src/extension, sync core, and CLIsite/public/static site assets for Cloudflare Pagestest/unit/Vitest unit coverage for parsing, hashing, manifest validation, and sync policydocs/development.mdlocal development, Google Cloud setup, and hosted-site deployment notesdocs/release-checklist.mdgeneric release steps for publishing and launch prep
For local setup, your own Google Cloud project, and your own hosted picker site, see docs/development.md.
For generic public-release prep, see docs/release-checklist.md.
Quick local loop:
npm install
npm run compile
npm testThe development guide also covers:
GDRIVESYNC_*environment variables- Google Cloud OAuth setup
- hosted picker and Cloudflare Pages deployment
Note:
- desktop sign-in uses the extension's localhost callback and does not need a hosted picker site
- pasted-link workflows can often work without a hosted picker site
- picker-based selection and some link-share recovery flows require
GDRIVESYNC_HOSTED_BASE_URLorgdocSync.development.hostedBaseUrl
The CLI is a core interface for this project, not a sidecar dev script.
Current command set:
gdrivesync auth login
gdrivesync auth logout --account <account>
gdrivesync auth logout --all
gdrivesync auth list [--json]
gdrivesync auth use <account> [--json]
gdrivesync auth status [--json]
gdrivesync ai auth login openai
gdrivesync ai auth login anthropic
gdrivesync ai auth logout openai
gdrivesync ai auth logout anthropic
gdrivesync ai auth status [--json]
gdrivesync ai auth test openai|anthropic [--json]
gdrivesync configure image-enrichment [--json]
gdrivesync doctor [--cwd path] [--json] [--repair]
gdrivesync inspect <google-file-url-or-id> [--json]
gdrivesync metadata <google-file-url-or-id> [--json]
gdrivesync export <google-file-url-or-id> [output-path] [--json] [--include-backgrounds] [--image-enrichment off|local|cloud|hybrid] [--image-enrichment-provider auto|apple-vision|tesseract] [--image-enrichment-cloud-provider openai|anthropic] [--image-enrichment-cloud-model <model>] [--image-enrichment-max-images <n>] [--image-enrichment-store alt-plus-comment|alt-only]
gdrivesync link <google-file-url-or-id> <local-path> [--cwd path] [--json] [--force] [--image-enrichment off|local|cloud|hybrid] [--image-enrichment-provider auto|apple-vision|tesseract] [--image-enrichment-cloud-provider openai|anthropic] [--image-enrichment-cloud-model <model>] [--image-enrichment-max-images <n>] [--image-enrichment-store alt-plus-comment|alt-only]
gdrivesync status <local-path> [--cwd path] [--json]
gdrivesync status --all [--cwd path] [--json]
gdrivesync sync <local-path> [--cwd path] [--json] [--force] [--image-enrichment off|local|cloud|hybrid] [--image-enrichment-provider auto|apple-vision|tesseract] [--image-enrichment-cloud-provider openai|anthropic] [--image-enrichment-cloud-model <model>] [--image-enrichment-max-images <n>] [--image-enrichment-store alt-plus-comment|alt-only]
gdrivesync sync --all [--cwd path] [--json] [--force] [--image-enrichment off|local|cloud|hybrid] [--image-enrichment-provider auto|apple-vision|tesseract] [--image-enrichment-cloud-provider openai|anthropic] [--image-enrichment-cloud-model <model>] [--image-enrichment-max-images <n>] [--image-enrichment-store alt-plus-comment|alt-only]
gdrivesync unlink <local-path> [--cwd path] [--json] [--remove-generated]Key CLI behaviors:
auth loginconnects a Google account; connecting the same account again refreshes its saved sessionauth listandauth useexpose the multi-account model explicitly for agentsauth logoutrequires--accountor--allai auth login/logout/status/testmanages optional OpenAI and Anthropic API keys for cloud image enrichmentconfigure image-enrichmentis the rerunnable human CLI setup flow for local OCR or cloud AI defaults- CLI cloud provider keys resolve in this order:
OPENAI_API_KEY/ANTHROPIC_API_KEY, then OS keychain, then missing - CLI image-enrichment defaults are stored per user in a small local config file; explicit flags still override them
linkimmediately creates the manifest entry and runs an initial synclinkbinds the local file to the account that successfully read the source file- pasted-link imports probe the default connected account first, then other connected accounts
- every
--jsonresponse uses a top-level envelope withok,contractVersion,command, and eitherdataorerror status --all --jsonreturns manifest-aware file metadata for the whole workspacesync --all --jsonreturns per-file results withsynced,skipped,cancelled, orfailedsyncandlinkreturn a non-zero exit code when they are cancelled or fail- top-level command failures stay machine-readable in
--jsonmode with stable error codes exportwrites to stdout when no output path is given, and writes files when one is provided- oversized native Google Slides decks that fall back to the Slides API omit slide background images by default; use
--include-backgroundsif you want them - CLI image enrichment defaults can come from
gdrivesync configure image-enrichment; explicit--image-enrichment ...flags still override them doctor --repairbacks up corrupt CLI auth or manifest state before restoring a working baseline- when a Markdown or Marp file was previously enriched with local OCR, switching to cloud mode will upgrade those machine-generated image descriptions on the next sync even if the Google file did not change
Examples:
npm run cli -- auth login
npm run cli -- auth list --json
npm run cli -- auth use me@example.com --json
npm run cli -- auth status --json
npm run cli -- auth logout --account me@example.com --json
npm run cli -- ai auth status --json
npm run cli -- ai auth test openai --json
npm run cli -- configure image-enrichment
npm run cli -- doctor --cwd ./data --json
npm run cli -- doctor --cwd ./data --json --repair
npm run cli -- inspect https://docs.google.com/document/d/<file-id>/edit --json
npm run cli -- export https://docs.google.com/document/d/<file-id>/edit
npm run cli -- export https://docs.google.com/presentation/d/<file-id>/edit ./deck.md --json
npm run cli -- export https://docs.google.com/presentation/d/<file-id>/edit ./deck.md --image-enrichment hybrid --image-enrichment-cloud-provider openai --json
npm run cli -- export https://docs.google.com/presentation/d/<file-id>/edit ./deck.md --image-enrichment local --json
npm run cli -- export https://docs.google.com/spreadsheets/d/<file-id>/edit ./sheet.csv --json
npm run cli -- link https://docs.google.com/document/d/<file-id>/edit ./notes/spec.md --cwd ./data --json
npm run cli -- status --all --cwd ./data --json
npm run cli -- sync ./notes/spec.md --cwd ./data --json
npm run cli -- sync ./slides/deck.md --cwd ./data --image-enrichment local --json
npm run cli -- sync --all --cwd ./data --json
npm run cli -- unlink ./notes/spec.md --cwd ./data --jsonExample inspect --json output:
{
"ok": true,
"contractVersion": 1,
"command": "inspect",
"data": {
"fileId": "abc123",
"title": "Quarterly Planning",
"sourceMimeType": "application/vnd.google-apps.spreadsheet",
"sourceUrl": "https://docs.google.com/spreadsheets/d/abc123/edit",
"profileId": "google-sheet-csv",
"sourceTypeLabel": "Spreadsheet",
"targetFamily": "csv",
"targetFileExtension": "csv",
"retrievalMode": "drive-export-xlsx"
}
}Example sync --all --json output shape:
{
"ok": true,
"contractVersion": 1,
"command": "sync",
"data": {
"rootPath": "/workspace/data",
"manifestPath": "/workspace/data/.gdrivesync.json",
"results": [
{
"file": "/workspace/data/spec.md",
"outcome": {
"status": "synced",
"message": "Synced spec.md."
}
},
{
"file": "/workspace/data/report.csv",
"outcome": {
"status": "skipped",
"message": "Remote version unchanged."
}
}
],
"syncedCount": 1,
"skippedCount": 1,
"cancelledCount": 0,
"failedCount": 0
}
}Example doctor --json output shape:
{
"ok": true,
"contractVersion": 1,
"command": "doctor",
"data": {
"rootPath": "/workspace/data",
"manifest": {
"path": "/workspace/data/.gdrivesync.json",
"exists": true,
"valid": true,
"linkedFileCount": 8,
"droppedInvalidEntryCount": 0,
"missingPrimaryFileCount": 0,
"missingGeneratedFileCount": 0
},
"auth": {
"tokenPath": "/Users/me/.gdrivesync-dev-session.json",
"sessionFileExists": true,
"authenticated": true,
"sessionValid": true,
"accountCount": 2,
"refreshTokenPresent": true,
"scopeMatchesConfig": true,
"defaultAccountId": "perm-123",
"defaultAccountEmail": "me@example.com",
"accounts": [
{
"accountId": "perm-123",
"accountEmail": "me@example.com",
"isDefault": true
},
{
"accountId": "perm-456",
"accountEmail": "work@example.com",
"isDefault": false
}
]
},
"imageEnrichment": {
"mode": "off",
"cacheRootPath": "/Users/me/Library/Caches/GDriveSync",
"appleVision": {
"available": true,
"compilerAvailable": true,
"helperSourceExists": true,
"status": "compiled"
},
"tesseract": {
"available": true,
"path": "/opt/homebrew/bin/tesseract"
}
},
"issues": [],
"repair": {
"attempted": false,
"performed": false,
"actions": []
}
}
}If you are building agent integrations, prefer:
--jsonfor anything you plan to parse--cwdinstead of relying on implicit working-directory stateexportfor ephemeral readslink+syncfor durable workspace statestatus --all --jsonbeforesync --all --jsonwhen you want to reason about what will be touched
There is also a short agent-focused usage guide in docs/agent-cli.md.
GDriveSync can optionally improve generated Markdown and Marp image alt text using either local OCR or direct provider APIs from the user's machine. GDriveSync does not proxy these requests through its own servers.
Default behavior:
- VS Code extension:
gdocSync.imageEnrichment.mode = prompt - CLI: image enrichment stays off unless you save defaults with
gdrivesync configure image-enrichmentor pass explicit--image-enrichment ...flags
Supported outputs:
- Markdown from Google Docs and DOCX
- Marp-flavored Markdown from Google Slides and PowerPoint
- CSV outputs are not affected
Modes:
prompt: extension-only one-time prompt for local OCRoff: no image enrichmentlocal: Apple Vision on macOS when possible, then Tesseract if installedcloud: provider-only image understanding through OpenAI or Anthropichybrid: local OCR first, then cloud only for still-unresolved images
Local provider order:
- macOS prefers Apple Vision when the local helper can be compiled
- otherwise GDriveSync uses
tesseractif it is installed - otherwise sync/export falls back to the current no-enrichment behavior
Extension settings:
gdocSync.imageEnrichment.mode:prompt | off | local | cloud | hybridgdocSync.imageEnrichment.provider:auto | apple-vision | tesseractgdocSync.imageEnrichment.cloudProvider:openai | anthropicgdocSync.imageEnrichment.cloudModel: optional model overridegdocSync.imageEnrichment.maxImagesPerRun: cap cloud or hybrid image uploads per syncgdocSync.imageEnrichment.store:alt-plus-comment | alt-onlygdocSync.imageEnrichment.onlyWhenAltGeneric: only enrich generic generated alt text
Extension provider-key handling:
- cloud provider keys are stored only in VS Code
SecretStorage - use
Configure Image Enrichment...to set mode, connect providers, test them, or switch the default cloud provider without editing settings manually - on first cloud use per provider, the extension asks for one-time consent because eligible images leave the machine and provider billing may apply
CLI provider-key handling:
- automation can use
OPENAI_API_KEYorANTHROPIC_API_KEY - humans can use
gdrivesync configure image-enrichmentfor the full interactive setup flow, orgdrivesync ai auth ...for low-level provider-key management - stored CLI provider keys live in the OS keychain, not in
.gdrivesync.json
When enabled, GDriveSync can rewrite generic image alt text and optionally append compact OCR metadata comments like:

<!-- gdrivesync:image-meta {"v":1,"hash":"sha256:...","source":"apple-vision","ocr":"HAIR BURST ... MULTIVITAMIN"} -->
<!-- gdrivesync:image-meta {"v":1,"hash":"sha256:...","source":"openai","model":"gpt-5.4-nano","detail":"Comparison ad showing Hairburst against drugstore multivitamins."} -->The image-enrichment cache is per-user and keyed by image hash plus provider/model, so unchanged assets are not reprocessed on every sync.
GDriveSync supports multiple connected Google accounts in both the extension and the CLI.
Extension account commands:
Connect Google Account...Disconnect Google Account...Switch Default Google AccountGoogle Accounts
Default account behavior:
- pasted Google file URLs try the default connected account first, then other connected accounts
- Google Picker is explicit: if multiple accounts are connected, you choose which one to use
- linked files stay pinned to their bound Google account unless that account becomes unusable
- if a bound account is broken and another connected account can read the file, GDriveSync can recover and rebind it because sync is one-way
If the CLI manifest or saved CLI OAuth session gets corrupted, GDriveSync now fails with explicit machine-readable error codes instead of a raw JSON parse stack. The supported recovery path is:
gdrivesync doctor --cwd ./workspace --json
gdrivesync doctor --cwd ./workspace --repairdoctor --repair backs up corrupt local CLI state before restoring a working baseline:
- corrupt
.gdrivesync.jsonmanifests are backed up and replaced with a clean manifest, preserving valid entries when possible - corrupt CLI OAuth state files are backed up and cleared so you can reconnect cleanly
The VS Code extension uses SecretStorage instead of the CLI auth file. If that state ever becomes corrupted, disconnecting and reconnecting the affected Google account is the intended repair path.
Planned distribution targets:
- VS Code Marketplace
- Open VSX
- Homebrew
The CLI is intended to be a real integration surface for agent builders, not just a local developer helper.
- One-way sync only: Google files -> local Markdown, Marp Markdown, or CSV
- The hosted Picker fallback is still used as a backup if direct pasted-link access fails
- Formatting fidelity depends on local DOCX conversion for native Docs and Word files, local presentation parsing for Slides/PPTX, and local workbook parsing for Sheets/XLSX
- Presentation sync targets Marp-flavored Markdown and focuses on slide text plus extracted images rather than full visual layout fidelity
- Very large native Google Slides decks may bypass Drive export and use the Google Slides API fallback automatically
- Spreadsheet sync only supports native Google Sheets and
.xlsx - Cloud image enrichment is explicit and opt-in; it requires user-supplied OpenAI or Anthropic credentials
- Apple Vision enrichment requires macOS plus local Swift compiler availability; otherwise GDriveSync falls back to Tesseract when installed
- Linked files must live inside an open VS Code workspace folder