A command-line tool for searching, reading, creating, and safely editing Google Docs. Designed to be invoked programmatically (e.g. by an AI agent), with structured JSON output and optimistic concurrency control to avoid clobbering concurrent edits.
- Search Google Drive for Docs by full-text query
- Read a document as plain text or raw API JSON
- Create a document from Markdown
- Update a document safely:
- Append text at the end
- Find-and-replace text across the whole document
- Replace the content under a named heading
- All updates use
WriteControl.RequiredRevisionId— if someone else edits the document between your read and write, the API rejects the write rather than silently overwriting it
- .NET 10 SDK
- A Google Cloud project with the Google Docs API and Google Drive API enabled
- An OAuth 2.0 Desktop app credential (client ID + secret)
- Go to Google Cloud Console and select or create a project.
- Enable the APIs:
- APIs & Services → Library → search Google Docs API → Enable
- APIs & Services → Library → search Google Drive API → Enable
- Create credentials:
- APIs & Services → Credentials → Create Credentials → OAuth client ID
- Application type: Desktop app
- Download or note the Client ID and Client Secret
- Set environment variables:
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your-client-secret
cd GDocControl
dotnet buildTo publish a self-contained executable:
dotnet publish -c Release -r win-x64 --self-contained
# Binary: bin/Release/net10.0/win-x64/publish/gdoc-control.exeOn first run, the tool opens a browser tab asking you to sign in to Google and grant access to Docs and Drive. After you authorize, the OAuth tokens are saved locally and reused automatically on subsequent runs — no browser prompt needed again unless the token is revoked or expires.
Token storage location:
- Windows:
%APPDATA%\GoogleApis\gdoc-cli\ - Linux/macOS:
~/.local/share/google-filedatastore/gdoc-cli/
To force re-authentication, delete the token files from that directory.
gdoc-control search "project proposal"
gdoc-control search "meeting notes" --limit 20{
"files": [
{
"id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"name": "Q3 Project Proposal",
"modifiedTime": "2024-11-01T14:32:00.000Z",
"url": "https://docs.google.com/document/d/1BxiM.../edit"
}
]
}gdoc-control get 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upmsBody is returned as extracted plain text. Headings are prefixed with #/##/etc. for readability.
{
"documentId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"title": "Q3 Project Proposal",
"revisionId": "AOV_f48abc123...",
"body": "# Q3 Project Proposal\nIntroduction text.\n\n## Goals\nGoal one.\n\n## Timeline\nSee below."
}Add --structured to get the raw Google Docs API body JSON instead (useful for debugging or inspecting element indices):
gdoc-control get <id> --structuredgdoc-control create "Meeting Notes" --content "# Meeting Notes
## Attendees
- Alice
- Bob
## Action Items
- Follow up by Friday"Or pipe from a file:
gdoc-control create "Design Doc" --stdin < design.md{
"documentId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"title": "Meeting Notes",
"url": "https://docs.google.com/document/d/1BxiM.../edit"
}gdoc-control update append <id> --text "New paragraph added at the end."gdoc-control update replace-text <id> --search "Q3" --replacement "Q4"Replaces all occurrences. Match is case-sensitive.
gdoc-control update replace-section <id> --heading "Timeline" --content "Phase 1: March
Phase 2: April"Or pipe content from stdin:
gdoc-control update replace-section <id> --heading "Action Items" --stdin < action-items.txtThe heading text must match exactly (case-sensitive) as it appears in the document — without the # prefix. The heading paragraph itself is kept; only the content beneath it is replaced, up to the next heading of the same or higher level.
All update commands (append, replace-text, replace-section) use the Google Docs API's WriteControl.RequiredRevisionId mechanism:
- The command reads the document to get the current
revisionId. - It submits the edit with that
revisionIdinWriteControl.RequiredRevisionId. - If anyone else edited the document in the meantime, Google rejects the write with HTTP 412.
On a 412 error, the tool outputs:
{ "error": "Concurrent edit conflict — re-read the document and retry", "status": 412 }Simply re-run the command. The second attempt will read the updated document and apply the edit on top of the latest revision.
Errors are written to stderr as JSON with exit code 1:
{ "error": "The caller does not have permission", "status": 403 }
{ "error": "Requested entity was not found.", "status": 404 }
{ "error": "Heading not found: \"Timeline\"" }
{ "error": "GOOGLE_CLIENT_ID environment variable is not set." }GDocControl/
├── Program.cs — CLI commands (System.CommandLine 2.0.3)
├── GoogleAuth.cs — OAuth Installed App flow
├── DocsClient.cs — Google Docs / Drive API operations
├── Models.cs — JSON output record types
└── GDocControl.csproj
Dependencies: Google.Apis.Docs.v1, Google.Apis.Drive.v3, Markdig, System.CommandLine