Setup Check
Before running any script, verify the skill is ready:
credentials.jsonexists in the working directory (or--credentials <path>is passed)- The target spreadsheet is shared with the service account
client_email
If credentials.json is missing, tell the user:
"This skill needs a Google service account key. Follow the setup guide in
SETUP.mdto create one from Google Cloud Console."
If a script returns 403 PERMISSION_DENIED, tell the user:
"The service account doesn't have access to this spreadsheet. Share it with the
client_emailin yourcredentials.json."
If a script returns SpreadsheetNotFound, tell the user:
"Spreadsheet not found or not shared. Make sure the URL is correct and the sheet is shared with the service account."
Credentials Safety
credentials.json contains a private key — treat it like a password.
When the user places credentials.json in a project directory, ALWAYS:
- Check if
.gitignoreexists in that directory - Check if
credentials.jsonis already listed — if not, add it immediately:# Google service account key credentials.json - Tell the user: "I've added
credentials.jsonto.gitignoreso it won't be committed."
If there is no .gitignore, create one with credentials.json in it.
If the user wants to store it outside the project, suggest:
~/.config/google/credentials.jsonand pass--credentials ~/.config/google/credentials.json- Or an environment variable path:
--credentials $GOOGLE_CREDENTIALS
Never echo or print the contents of credentials.json. Reference it by path only.
google-sheets
Google Sheets read/write automation via the Sheets API and gspread.
Run CLIs from this skill's directory: python3 scripts/<name>.py.
Setup
- Go to Google Cloud Console → APIs & Services → Enable Google Sheets API and Google Drive API
- Create a Service Account → Keys → Add Key → JSON → download as
credentials.json - Share the target spreadsheet with the service account email (found in
credentials.jsonasclient_email) - Place
credentials.jsonin the working directory, or pass--credentials <path>
Available Scripts
Read all rows
python3 scripts/read_rows.py --url <spreadsheet_url>
python3 scripts/read_rows.py --url <spreadsheet_url> --worksheet 1
Output: {"status": "ok", "worksheet": "Sheet1", "count": 42, "rows": [{"col": "val", ...}, ...]}
List worksheets
python3 scripts/list_worksheets.py --url <spreadsheet_url>
Output: {"status": "ok", "spreadsheet": "My Sheet", "worksheets": [{"index": 0, "title": "Sheet1", "rows": 1000, "cols": 26}]}
Update a single cell
python3 scripts/update_cell.py --url <url> --row 2 --col 3 --value "Hello"
Output: {"status": "updated", "worksheet": "Sheet1", "row": 2, "col": 3, "value": "Hello"}
Update a range
python3 scripts/update_range.py --url <url> --range A2:C3 --values "[[1,2,3],[4,5,6]]"
Output: {"status": "updated", "worksheet": "Sheet1", "range": "A2:C3", "rows": 2}
Batch update multiple cells
python3 scripts/batch_update.py --url <url> --cell 2,1,Alice --cell 2,2,30
Output: {"status": "updated", "worksheet": "Sheet1", "count": 2, "cells": [...]}
Append a row
python3 scripts/append_row.py --url <url> --values "Alice,30,Engineer"
Output: {"status": "appended", "worksheet": "Sheet1", "values": ["Alice", "30", "Engineer"]}
Delete rows
python3 scripts/delete_row.py --url <url> --row 5 --confirm
python3 scripts/delete_row.py --url <url> --start 2 --end 4 --confirm
Output: {"deleted": true, "worksheet": "Sheet1", "rows": [5]} (or [2, 3, 4] for a batch).
Destructive — requires --confirm. Rows are 1-indexed; batch delete spans --start..--end inclusive.
Create worksheet
python3 scripts/create_worksheet.py --url <url> --title "Sheet2" --rows 100 --cols 20
Output: {"created": true, "title": "Sheet2", "id": 1, "rows": 100, "cols": 20}
Delete worksheet
python3 scripts/delete_worksheet.py --url <url> --title "Sheet2" --confirm
python3 scripts/delete_worksheet.py --url <url> --index 1 --confirm
Output: {"deleted": true, "title": "Sheet2"}. Destructive — requires --confirm.
Target by --title or --index.
Find & replace
python3 scripts/find_replace.py --url <url> --range A1:C10 --find "old" --replace "new" --confirm
python3 scripts/find_replace.py --url <url> --range A1:C10 --find "TODO" --match-case --confirm
python3 scripts/find_replace.py --url <url> --range A1:A1 --find "x" --entire-cell --replace "y" --confirm
python3 scripts/find_replace.py --url <url> --range A1:C10 --find "\d+" --replace "#" --regex --confirm
Output: {"replaced": 3, "cells": ["A1", "B2", "C3"]}. Destructive — requires --confirm.
Options: --match-case (case-sensitive), --entire-cell (match only when the whole cell equals --find), --regex (treat --find as a regex). Range uses A1 notation; target a specific sheet with --worksheet <index>.
Script Conventions
- Output: JSON to stdout, diagnostics to stderr
- Exit codes:
0success ·1error (credentials missing, API error, bad input) ·2not found ·3missing--confirm - All scripts accept
--credentials <path>to override the defaultcredentials.jsonlocation --worksheetis always a zero-based index (default:0)--valuesforupdate_rangeexpects a JSON 2D array string--valuesforappend_rowexpects a comma-separated string
Important Notes
- The service account must have edit access to the spreadsheet (share via the
client_emailincredentials.json) credentials.jsonis sensitive — never commit it to version control- Scripts use PEP 723 inline dependencies — run with
uv runor installgspreadandgoogle-authmanually