Composable Google Sheets primitives for humans and agents
Installation • Quick Start • Commands • For Agents
Fast, deterministic CLI for Google Sheets. Read tables, append rows, update cells by key or index, batch operations—all with JSON output for programmatic consumption.
# Read a sheet as structured data
sheets-cli read table --sheet "Projects" --limit 10
# Update by key column (no fragile row indices)
sheets-cli update key --sheet "Projects" --key-col "Name" --key "Acme" --set '{"Status":"Done"}'🆕 Agent Skills — Install as a skill for Claude Code, OpenAI Codex, or VS Code (Insiders preview; enable
chat.useAgentSkills). The agent automatically discovers sheets-cli when you mention spreadsheets. See For Agents.
Prerequisites: Bun runtime
git clone https://github.com/gmickel/sheets-cli.git
cd sheets-cli
bun install
bun run build
# Binary at ./dist/sheets-cliAdd to PATH
# Symlink
ln -s "$(pwd)/dist/sheets-cli" /usr/local/bin/sheets-cli
# Or add to shell config
echo 'export PATH="$PATH:/path/to/sheets-cli/dist"' >> ~/.zshrc- Go to Google Cloud Console → APIs
- Enable Google Sheets API
- Enable Google Drive API (required for
sheets findcommand)
- Go to Google Cloud Console → Credentials
- Create OAuth 2.0 Client ID → Desktop app
- Download the JSON file
Desktop apps auto-allow localhost redirects. CLI captures OAuth code via
http://localhost:3847.
sheets-cli auth login --credentials ./client_secret.jsonBrowser opens → authorize → done.
# Set env var to avoid passing --spreadsheet every time
export SHEETS_CLI_DEFAULT_SPREADSHEET_ID="your-spreadsheet-id"Get the ID from your sheet URL: docs.google.com/spreadsheets/d/<ID>/edit
sheets-cli sheets list --spreadsheet <id>
sheets-cli read table --spreadsheet <id> --sheet "Sheet1" --limit 5
sheets-cli append --spreadsheet <id> --sheet "Sheet1" --values '{"Name":"New Item","Status":"Active"}'sheets-cli auth login --credentials <file> [--token-store <path>]
sheets-cli auth status
sheets-cli auth logoutsheets-cli sheets list [--spreadsheet <id>]
sheets-cli sheets find --name "<query>" [--limit 10] # Search by name
sheets-cli sheet info --sheet "<name>" [--spreadsheet <id>]
sheets-cli sheet info --gid <gid> [--spreadsheet <id>]
sheets-cli header --sheet "<name>" [--header-row 1]sheets-cli read table --sheet "<name>" [--limit 500] [--range "A1:Z500"] [--raw]
sheets-cli read range --range "<sheet>!A1:Z50"sheets-cli append --sheet "<name>" --values '<json>' [--value-input USER_ENTERED|RAW] [--dry-run]
sheets-cli update row --sheet "<name>" --row 12 --set '<json>' [--dry-run]
sheets-cli update key --sheet "<name>" --key-col "Col" --key "Val" --set '<json>' [--dry-run] [--allow-multi]
sheets-cli set range --range "<sheet>!M2:M2" --values '<json_2d_array>' [--dry-run]
sheets-cli batch --ops '<json>' [--dry-run]All flags
| Flag | Description | Default |
|---|---|---|
--spreadsheet <id> |
Spreadsheet ID or full URL | env var or required |
--dry-run |
Preview without applying | false |
--value-input <mode> |
USER_ENTERED or RAW |
USER_ENTERED |
--header-row <n> |
Header row number | Auto-detect |
--limit <n> |
Max rows to return | unlimited |
--raw |
Return unformatted values | false |
--allow-multi |
Update multiple matching rows | false |
{"Name": "Acme Corp", "Status": "Active", "Start Date": "2025-01-15"}Headerless sheets (column letters):
{"A": "Acme Corp", "C": "Active"}[["Value1", "Value2"], ["Value3", "Value4"]][
{"op": "append", "sheet": "Tasks", "values": {"Name": "New Task"}},
{"op": "updateRow", "sheet": "Tasks", "row": 5, "set": {"Status": "Done"}},
{"op": "updateKey", "sheet": "Tasks", "keyCol": "ID", "key": "TASK-123", "set": {"Status": "Active"}},
{"op": "setRange", "range": "Tasks!A1:B1", "values": [["Col1", "Col2"]]}
]All commands return JSON to stdout:
{
"ok": true,
"cmd": "read table",
"spreadsheetId": "1abc...",
"sheet": "Projects",
"result": {
"headers": ["Name", "Status", "Date"],
"rows": [{"Name": "Alpha", "Status": "Active", "Date": "2025-01-15"}],
"headerRow": 1
}
}Errors:
{
"ok": false,
"cmd": "update key",
"error": {"code": "VALIDATION_ERROR", "message": "...", "details": {}}
}| Code | Meaning |
|---|---|
0 |
Success |
10 |
Validation error |
20 |
Auth error |
30 |
Permission error |
40 |
API/transient error |
# Claude Code
sheets-cli install-skill # Project: ./.claude/skills/sheets-cli/SKILL.md
sheets-cli install-skill --global # Personal: ~/.claude/skills/sheets-cli/SKILL.md
# OpenAI Codex
sheets-cli install-skill --codex # ~/.codex/skills/sheets-cli/SKILL.mdInstalls an Agent Skill that teaches the agent how to use sheets-cli. After installing, the agent automatically discovers sheets-cli when you mention spreadsheets, Google Sheets, or sheet names.
Codex: Requires
skills = truein~/.codex/config.tomlunder[features]. VS Code: Agent Skills support is in preview and only available in VS Code Insiders. Enablechat.useAgentSkillsto use Agent Skills.
Restart the agent after installing to load the skill.
Follow read → decide → dry-run → apply:
# 1. Understand current state
sheets-cli read table --sheet "Tasks" --limit 100
# 2. Dry-run
sheets-cli update key --sheet "Tasks" --key-col "ID" --key "TASK-42" --set '{"Status":"Complete"}' --dry-run
# 3. Apply
sheets-cli update key --sheet "Tasks" --key-col "ID" --key "TASK-42" --set '{"Status":"Complete"}'- Use
sheets findto get spreadsheet ID from name - Prefer key-based updates over row indices—rows shift on insert/delete
- Always dry-run before writes
- Check
okfield before proceeding - Batch related operations for atomicity
- Column names match case-insensitively with normalized whitespace
- Header row auto-detects—skips empty rows to find first row with data
- Headerless sheets:
read tablereturns columns asA,B, ...; use column letters for--set/--key-col - Empty sheets:
appendcan bootstrap by writing a header row from JSON keys read table --rangeacceptsA1:Z(auto-prefixed with the sheet)--spreadsheetaccepts URLs—paste full Google Sheets URL directly
bun run dev # Hot-reload
bun run build # Compile binary
bun run typecheck # Type check
bun run lint # Lint
bun run test # TestsMIT
Built with Bun • Styled for machines and humans alike