gdrive-sync
CleanSync files between Google Drive and the local filesystem. Supports drive-to-local and local-to-drive with conflict resolution, folder setup, and persistent config.
SKILL.md
---
name: gdrive-sync
description: Sync files between Google Drive and the local filesystem. Use when the user asks to sync, pull, push, or mirror files between Google Drive and local folders. Triggers include phrases like "sync my Drive folder", "pull files from Drive", "push local files to Drive", "sync Drive to local", "upload local changes to Drive", or any request to keep a local folder and a Drive folder in sync.
tags: gdrive, google-drive, sync, files
---
# gdrive-sync
Syncs files between Google Drive and the local filesystem using `scripts/gdrive_sync.py`. Workspace-native files (Docs, Sheets, Slides) are skipped. Only adds files — never deletes.
---
## Skill Config
The skill config is stored at `$SKILL_DIR/.skill_config.json` where `SKILL_DIR` is the directory containing this file (`/home/polly/skills/user/gdrive-sync`).
Read it with:
```python
import json
cfg = json.load(open("/home/polly/skills/user/gdrive-sync/.skill_config.json"))
# cfg["drive_folder_id"] — saved Drive folder ID
# cfg["drive_folder_name"] — human-readable folder name
# cfg["local_path"] — saved local path
```
Write it with:
```python
import json, os
cfg_path = "/home/polly/skills/user/gdrive-sync/.skill_config.json"
cfg = {}
try:
cfg = json.load(open(cfg_path))
except Exception:
pass
cfg["drive_folder_id"] = "<ID>"
cfg["drive_folder_name"] = "<name>"
cfg["local_path"] = "<path>"
with open(cfg_path, "w") as f:
json.dump(cfg, f, indent=2)
```
---
## Workflow
### Step 0 — Resolve the Drive folder
**Always load the skill config first** to check for a saved folder.
#### If a Drive folder is already configured:
Tell the user: `"Syncing with Drive folder '<name>' (<id>). Use a different folder? Just say so."`
Proceed to Step 1.
#### If no folder is configured (first use or user asked to change it):
Ask the user for a folder name. Then search Drive:
```bash
python3 $HOME/skills/user/gdrive-sync/scripts/gdrive_sync.py find-folder \
--name "<folder name the user provided>"
```
**If one match:** Confirm with the user — `"Found '<name>' in Drive. Use this folder?"`
**If multiple matches:** Show the list (name + ID) and ask which to use.
**If no matches:** Ask the user: `"No folder named '<name>' found in your Drive. Create it?"`
- If yes, create it:
```bash
python3 $HOME/skills/user/gdrive-sync/scripts/gdrive_sync.py create-folder \
--name "<folder name>"
```
Report the new folder ID and confirm it's been saved.
Once confirmed, **save the folder ID and name to skill config**.
Also ask for the local path to sync with (if not already configured), and save it too.
---
### Step 1 — Resolve sync parameters
From the user's message or saved config, determine:
- **direction**: `drive-to-local` or `local-to-drive`
- **drive-id**: from skill config (or user provided)
- **local-path**: from skill config (or user provided)
Use a session-scoped scan cache path to avoid collisions:
```
SCAN_CACHE=/tmp/gdrive_sync_$(date +%s%N).json
```
---
### Step 2 — Scan
Run the scan (no writes occur):
```bash
SCAN_CACHE=/tmp/gdrive_sync_$(date +%s%N).json
python3 $HOME/skills/user/gdrive-sync/scripts/gdrive_sync.py scan \
--direction <drive-to-local|local-to-drive> \
--drive-id <FOLDER_ID> \
--local-path <LOCAL_PATH> \
--scan-cache $SCAN_CACHE
```
Parse the JSON output:
- `to_copy`: files only on the source side → will be copied automatically
- `conflicts`: files on both sides → require user decisions
- `scan_cache`: path to cached scan data (pass to execute step)
---
### Step 3 — Present results and collect decisions
**If no conflicts and files to copy:** Tell the user what will be copied and ask for confirmation.
**If conflicts exist (show max 50 at a time):**
Present a table:
| # | File | Drive (size / modified) | Local (size / modified) | Decision |
|---|------|------------------------|------------------------|----------|
Choices per file: `drive` (use Drive version) / `local` (use local version) / `skip`
Bulk options: **all=drive**, **all=local**, **all=skip**
Collect all decisions before proceeding.
---
### Step 4 — Execute
For **small syncs (<20 files)**, run directly:
```bash
python3 $HOME/skills/user/gdrive-sync/scripts/gdrive_sync.py execute \
--direction <drive-to-local|local-to-drive> \
--drive-id <FOLDER_ID> \
--local-path <LOCAL_PATH> \
--scan-cache $SCAN_CACHE \
--decisions '{"relative/path.md": "drive", "other/file.md": "skip"}'
```
For **large syncs (20+ files)**, run in the background:
```bash
SCAN_CACHE=<path from scan step>
python3 $HOME/skills/user/gdrive-sync/scripts/gdrive_sync.py execute \
--direction <drive-to-local|local-to-drive> \
--drive-id <FOLDER_ID> \
--local-path <LOCAL_PATH> \
--scan-cache $SCAN_CACHE \
--decisions '{}' > /tmp/gdrive_sync_output.json 2>/tmp/gdrive_sync_progress.log &
echo "PID: $!"
# Poll after sufficient wait (~1s/file)
sleep <N> && cat /tmp/gdrive_sync_output.json
```
Progress lines are written to stderr in the format:
```
[done/total] status: relative/path
```
---
### Step 5 — Report results
Parse the JSON output and report:
- ✅ N files copied
- ⏭ N files skipped
- ❌ N errors (list each with the error message)
---
## Notes
- Auth is handled automatically via the Google Drive credential file in `~/.credentials/`. Token refresh is transparent.
- The scan cache is session-scoped (timestamp in filename) to avoid collisions between concurrent runs.
- Workspace-native files (Google Docs, Sheets, Slides, etc.) are always skipped.
- Drive folder IDs can be found in Drive URLs: `https://drive.google.com/drive/folders/<ID>`
- Per-request timeouts: 120s for uploads/downloads, 30s for API calls.
- To change the configured folder, the user can say "use a different Drive folder" or "change my sync folder".
Version History
v1.1.0latest
First-class Drive folder setup: search, create, and persist folder config. Session-scoped scan cache. Removed hardcoded paths.
Apr 6, 2026
Clean.zip
SHA-256 (latest)
c77e4e66a169e5105a4512bda16a711a402592b0bca6b728271ffab9e378b001