From session-backup
גיבוי יומי של תיקיות הסקילס, המשימות המתוזמנות והקונפיגורציות לגוגל דרייב
npx claudepluginhub msapps-mobile/claude-plugins --plugin session-backupThis skill uses the workspace's default tool permissions.
Back up all Cowork-related folders to Google Drive.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Back up all Cowork-related folders to Google Drive.
Session path note: Cowork VM session names change every run. Do NOT hardcode a session name like
loving-gracious-allen. Instead, discover the active session dynamically (Step 1).
import json, os, glob, sys
# Discover the active session that has the credentials file
candidates = glob.glob('/sessions/*/mnt/Claude/credentials/cowork-gdrive-config.json')
if not candidates:
print('STATUS:NOT_FOUND')
sys.exit(1)
cfg_path = candidates[0]
session_mnt = cfg_path.replace('/Claude/credentials/cowork-gdrive-config.json', '') # e.g. /sessions/abc-xyz/mnt
with open(cfg_path) as f:
cfg = json.load(f)
print('STATUS:OK')
print(f'SESSION_MNT:{session_mnt}')
print(f'URL:{cfg["url"]}')
print(f'APIKEY:{cfg["apiKey"]}')
If STATUS is NOT_FOUND, stop and report:
Config file missing. On your Mac, run once:
cp ~/.cowork-gdrive-config.json ~/Documents/Claude/credentials/cowork-gdrive-config.json
Delete temporary debug screenshots from the Claude docs folder (top level only).
Why Desktop Commander? The VM filesystem mount is read-only for deletes — use Desktop Commander to run the delete natively on the Mac.
Use mcp__Desktop_Commander__start_process with:
command: find ~/Documents/Claude -maxdepth 1 -type f \( -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' \) -delete && echo 'Screenshots flushed'
timeout_ms: 15000
(Failure is non-fatal — continue anyway.)
All zips must exclude: */node_modules/*, */.git/*, */.next/*, *.pyc, *.png, *.jpg, *.jpeg, *.gif, */package-lock.json
Use SESSION_MNT from Step 1 for all paths.
mkdir -p /tmp/cowork-backup
cd '{SESSION_MNT}/.claude' && zip -r /tmp/cowork-backup/skills-backup.zip skills/ \
-x '*/node_modules/*' -x '*/.git/*' -x '*/.next/*' -x '*.pyc' \
-x '*.png' -x '*.jpg' -x '*.jpeg' -x '*.gif' -x '*/package-lock.json' \
2>&1 | tail -2 && ls -lh /tmp/cowork-backup/skills-backup.zip
First try as ONE zip. If under 30MB, upload as one file. If over 30MB, split into 3 parts.
cd '{SESSION_MNT}' && zip -r /tmp/cowork-backup/claude-docs-backup.zip Claude/ \
-x '*/node_modules/*' -x '*/.git/*' -x '*/.next/*' -x '*.pyc' \
-x '*.png' -x '*.jpg' -x '*.jpeg' -x '*.gif' -x '*/package-lock.json' \
2>&1 | tail -1 && ls -lh /tmp/cowork-backup/claude-docs-backup.zip
If the zip is over 30MB, delete it and split instead:
Claude/gcal-mcp/ → claude-docs-gcalmcp.zipEach part uses the same exclusion flags.
The .remote-plugins folder is mounted read-only at {SESSION_MNT}/.remote-plugins — zip it directly from the VM:
cd '{SESSION_MNT}' && zip -r /tmp/cowork-backup/plugins-backup.zip .remote-plugins/ \
-x '*.png' -x '*.jpg' -x '*.jpeg' -x '*.gif' \
2>&1 | tail -1 && ls -lh /tmp/cowork-backup/plugins-backup.zip
mkdir -p /tmp/cowork-backup/configs
cp '{SESSION_MNT}/Claude/credentials/cowork-gdrive-config.json' /tmp/cowork-backup/configs/
cd /tmp/cowork-backup && zip -r /tmp/cowork-backup/configs-backup.zip configs/ && echo done
Why Desktop Commander? The Cowork VM proxy blocks outbound connections to
script.google.com. Uploads must run on the Mac side. We do this by:
- Copying zip files to the mounted Claude folder (which is the Mac's
~/Documents/Claude/)- Writing a Python upload script there
- Using Desktop Commander's
start_processto run it natively on the Mac
Why the two-step POST→GET? Google Apps Script returns a 302 redirect to
script.googleusercontent.com. curl's-Lflag converts POST→GET for 302s, but the redirect endpoint still returns HTML. The fix: POST without following redirect, captureLocationheader, then GET that URL directly.
import shutil, os
session_mnt = 'SESSION_MNT_FROM_STEP1'
staging = f'{session_mnt}/Claude/.backup-tmp'
os.makedirs(staging, exist_ok=True)
backup_dir = '/tmp/cowork-backup'
for fname in os.listdir(backup_dir):
if fname.endswith('.zip'):
shutil.copy(os.path.join(backup_dir, fname), os.path.join(staging, fname))
print(f'Staged to: {staging}')
Write this Python script to {staging}/do_upload.py (substitute real url and api_key values):
import base64, json, subprocess, os
url = 'URL_FROM_STEP1'
api_key = cfg["apiKey"] # loaded from ~/.cowork-gdrive-config.json
staging = os.path.expanduser('~/Documents/Claude/.backup-tmp')
for fname in sorted(os.listdir(staging)):
if not fname.endswith('.zip'):
continue
fpath = os.path.join(staging, fname)
size_kb = os.path.getsize(fpath) // 1024
print(f'Uploading {fname} ({size_kb}KB)...')
with open(fpath, 'rb') as f:
b64 = base64.b64encode(f.read()).decode()
payload = {
'fileName': fname,
'content': b64,
'mimeType': 'application/zip',
'apiKey': api_key,
'folderPath': 'Cowork-Backups',
'replaceExisting': True
}
payload_path = '/tmp/upload_payload.json'
with open(payload_path, 'w') as f:
json.dump(payload, f)
# Step 1: POST without following redirect — capture Location header
r1 = subprocess.run(
['curl', '-s', '-D', '-', '--max-redirs', '0',
'-X', 'POST', url,
'-H', 'Content-Type: application/json',
'--data', f'@{payload_path}',
'--max-time', '120'],
capture_output=True, text=True
)
if os.path.exists(payload_path):
os.remove(payload_path)
# Extract Location header from response headers
location = None
for line in r1.stdout.splitlines():
if line.lower().startswith('location:'):
location = line.split(':', 1)[1].strip()
break
if not location:
print(f'ERROR: No redirect location for {fname}')
print(r1.stdout[:300])
continue
# Step 2: GET the redirect URL to retrieve the JSON response
r2 = subprocess.run(
['curl', '-s', location, '--max-time', '30'],
capture_output=True, text=True
)
print(f'{fname}: {r2.stdout[:300]}')
print('Upload complete.')
Start the process (output will be slow — capture to file for reliability):
command: python3 ~/Documents/Claude/.backup-tmp/do_upload.py > ~/Documents/Claude/.backup-tmp/upload_result.txt 2>&1; echo "EXIT:$?" >> ~/Documents/Claude/.backup-tmp/upload_result.txt
timeout_ms: 15000
Then poll read_process_output until ✅ Process completed appears (may take 60–180s for large files).
Finally, read the result:
command: cat ~/Documents/Claude/.backup-tmp/upload_result.txt
timeout_ms: 10000
If any upload fails with 413: split that zip into smaller parts and retry.
rm -rf /tmp/cowork-backup /tmp/upload_payload.json
Also remove the staging area from the mounted folder via Desktop Commander:
command: rm -rf ~/Documents/Claude/.backup-tmp && echo 'Mac staging cleaned'
timeout_ms: 10000
List all uploaded files with their Drive links (from the JSON responses). Note which components were backed up and any that were skipped.
replaceExisting: true so each run overwrites the previous backupfolderPath must be Cowork-Backups*/node_modules/*, */.git/*, */.next/*, *.pyc, *.png, *.jpg, *.jpeg, *.gif, */package-lock.json{SESSION_MNT}/.remote-plugins (read-only) — include in backup as plugins-backup.zip-L alone lands on wrong pagescript.google.com