Google Docs Skill

v0.1.0

Integrate with Google Docs API to create, read, update, format, and manage documents using OAuth 2.0 authentication.

0· 1.2k·4 current·4 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for zagran/google-docs-skill.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "Google Docs Skill" (zagran/google-docs-skill) from ClawHub.
Skill page: https://clawhub.ai/zagran/google-docs-skill
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Canonical install target

openclaw skills install zagran/google-docs-skill

ClawHub CLI

Package manager switcher

npx clawhub@latest install google-docs-skill
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Suspicious
high confidence
Purpose & Capability
The instructions and example code clearly implement Google Docs API access via OAuth2 (creating documents, batchUpdate, etc.), which matches the skill name. However the registry metadata provides no description, homepage, or declared required environment variables even though the SKILL.md explicitly expects GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN.
Instruction Scope
The SKILL.md stays within the scope of Google Docs integration: it shows obtaining OAuth tokens, refreshing access tokens, and calling docs.googleapis.com endpoints. It uses a local redirect (HTTPServer on localhost) and opens a browser to obtain consent — both normal for OAuth desktop/web flows. The instructions do not request unrelated system files or external endpoints beyond Google.
Install Mechanism
There is no install spec and no code files beyond the SKILL.md, so nothing is written to disk or downloaded by the skill packaging itself. This lowers installation risk. The provided Python snippets are for the user to run locally.
!
Credentials
The SKILL.md instructs users to set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN (sensitive secrets) but the skill metadata declares no required environment variables or primary credential. That mismatch is incoherent: the skill needs secrets to operate but does not declare them in metadata, preventing reviewers/hosts from seeing what will be requested. Requesting those three variables is proportionate to an OAuth-based Google Docs integration, but the omission from metadata is a security/process concern.
Persistence & Privilege
The skill is not always-enabled and does not request persistent system privileges or modify other skills/settings. It also does not include install scripts that would persist on disk. Autonomous invocation is allowed (platform default) but is not combined with other high privilege requests.
What to consider before installing
What to consider before installing: - Metadata mismatch: the SKILL.md requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN, but the skill metadata does not declare any required env vars. Ask the publisher to update metadata to list required credentials and to provide a homepage/source repo for provenance. - Secrets handling: these environment values are sensitive. Only use credentials you control, and avoid pasting client secrets or refresh tokens into shared/managed environments. Prefer running the OAuth script locally on a trusted machine rather than letting any automated agent execute it. - Verify endpoints and scope: the code calls docs.googleapis.com and oauth2.googleapis.com with the scope https://www.googleapis.com/auth/documents — confirm the scope is appropriate and consent screens are configured correctly. - Source/verifiability: there is no homepage or repository. If you need assurance, request the skill source or a published repository and review the full SKILL.md (it was truncated in the package) before installing. - If you proceed: require the publisher to declare required env vars in the skill metadata, and consider creating a dedicated Google OAuth client (with limited scope) and rotating credentials after use. If the publisher cannot or will not provide provenance and corrected metadata, treat this package as untrusted and do not install.

Like a lobster shell, security has layers — review code before you run it.

latestvk971f0t751fp83cz86q7nqed9s80t7n4
1.2kdownloads
0stars
1versions
Updated 1mo ago
v0.1.0
MIT-0

Google Docs API Integration

Direct access to the Google Docs API using OAuth 2.0. Create documents, insert and format text, and manage document content.

Prerequisites

  1. Google Cloud Project Setup

    • Go to Google Cloud Console
    • Create a new project or select existing one
    • Enable the Google Docs API
    • Create OAuth 2.0 credentials (Desktop app or Web application)
    • Download the credentials JSON file
  2. Environment Setup

    export GOOGLE_CLIENT_ID="your-client-id"
    export GOOGLE_CLIENT_SECRET="your-client-secret"
    export GOOGLE_REFRESH_TOKEN="your-refresh-token"
    

Authentication

Getting OAuth Tokens

Use the following Python script to obtain your refresh token (one-time setup):

import urllib.request
import urllib.parse
import json
import webbrowser
from http.server import HTTPServer, BaseHTTPRequestHandler
import os

# OAuth configuration
CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID')
CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET')
REDIRECT_URI = 'http://localhost:8080'
SCOPES = 'https://www.googleapis.com/auth/documents'

# Step 1: Get authorization code
auth_url = (
    f"https://accounts.google.com/o/oauth2/v2/auth?"
    f"client_id={CLIENT_ID}&"
    f"redirect_uri={REDIRECT_URI}&"
    f"response_type=code&"
    f"scope={urllib.parse.quote(SCOPES)}&"
    f"access_type=offline&"
    f"prompt=consent"
)

print(f"Opening browser for authorization...")
webbrowser.open(auth_url)

# Step 2: Capture authorization code
auth_code = None

class OAuthHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global auth_code
        query = urllib.parse.urlparse(self.path).query
        params = urllib.parse.parse_qs(query)
        auth_code = params.get('code', [None])[0]
        
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(b'<html><body><h1>Authorization successful!</h1><p>You can close this window.</p></body></html>')

server = HTTPServer(('localhost', 8080), OAuthHandler)
server.handle_request()

# Step 3: Exchange code for tokens
if auth_code:
    data = urllib.parse.urlencode({
        'code': auth_code,
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
        'redirect_uri': REDIRECT_URI,
        'grant_type': 'authorization_code'
    }).encode()
    
    req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data)
    response = json.load(urllib.request.urlopen(req))
    
    print(f"\nRefresh Token: {response['refresh_token']}")
    print(f"Access Token: {response['access_token']}")
    print(f"\nSet your refresh token:")
    print(f"export GOOGLE_REFRESH_TOKEN=\"{response['refresh_token']}\"")

Getting Access Token

Before making API calls, get a fresh access token:

import urllib.request
import urllib.parse
import json
import os

def get_access_token():
    """Get a fresh access token using refresh token"""
    data = urllib.parse.urlencode({
        'client_id': os.environ['GOOGLE_CLIENT_ID'],
        'client_secret': os.environ['GOOGLE_CLIENT_SECRET'],
        'refresh_token': os.environ['GOOGLE_REFRESH_TOKEN'],
        'grant_type': 'refresh_token'
    }).encode()
    
    req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data)
    response = json.load(urllib.request.urlopen(req))
    return response['access_token']

# Store for reuse
access_token = get_access_token()
print(f"Access Token: {access_token}")

Base URL

https://docs.googleapis.com/v1

All requests require the access token in the Authorization header:

Authorization: Bearer {access_token}

Quick Start

import urllib.request
import json
import os

# Get access token first (using function from above)
access_token = get_access_token()

# Get document
req = urllib.request.Request('https://docs.googleapis.com/v1/documents/{documentId}')
req.add_header('Authorization', f'Bearer {access_token}')
doc = json.load(urllib.request.urlopen(req))
print(json.dumps(doc, indent=2))

API Reference

Create Document

import urllib.request
import json

access_token = get_access_token()

data = json.dumps({'title': 'My New Document'}).encode()
req = urllib.request.Request(
    'https://docs.googleapis.com/v1/documents',
    data=data,
    method='POST'
)
req.add_header('Authorization', f'Bearer {access_token}')
req.add_header('Content-Type', 'application/json')

response = json.load(urllib.request.urlopen(req))
doc_id = response['documentId']
print(f"Created document: {doc_id}")
print(f"URL: https://docs.google.com/document/d/{doc_id}/edit")

Get Document

req = urllib.request.Request(
    f'https://docs.googleapis.com/v1/documents/{doc_id}'
)
req.add_header('Authorization', f'Bearer {access_token}')
doc = json.load(urllib.request.urlopen(req))

Batch Update Document

# Insert text at beginning
updates = {
    'requests': [
        {
            'insertText': {
                'location': {'index': 1},
                'text': 'Hello, World!\n\n'
            }
        }
    ]
}

data = json.dumps(updates).encode()
req = urllib.request.Request(
    f'https://docs.googleapis.com/v1/documents/{doc_id}:batchUpdate',
    data=data,
    method='POST'
)
req.add_header('Authorization', f'Bearer {access_token}')
req.add_header('Content-Type', 'application/json')

response = json.load(urllib.request.urlopen(req))

Common Operations

Insert Text

{
    'requests': [
        {
            'insertText': {
                'location': {'index': 1},
                'text': 'Your text here'
            }
        }
    ]
}

Format Text (Bold, Italic, Font Size)

{
    'requests': [
        {
            'updateTextStyle': {
                'range': {
                    'startIndex': 1,
                    'endIndex': 10
                },
                'textStyle': {
                    'bold': True,
                    'italic': True,
                    'fontSize': {
                        'magnitude': 14,
                        'unit': 'PT'
                    }
                },
                'fields': 'bold,italic,fontSize'
            }
        }
    ]
}

Insert Table

{
    'requests': [
        {
            'insertTable': {
                'location': {'index': 1},
                'rows': 3,
                'columns': 4
            }
        }
    ]
}

Replace Text

{
    'requests': [
        {
            'replaceAllText': {
                'containsText': {
                    'text': '{{placeholder}}',
                    'matchCase': True
                },
                'replaceText': 'Actual value'
            }
        }
    ]
}

Delete Content

{
    'requests': [
        {
            'deleteContentRange': {
                'range': {
                    'startIndex': 1,
                    'endIndex': 50
                }
            }
        }
    ]
}

Insert Page Break

{
    'requests': [
        {
            'insertPageBreak': {
                'location': {'index': 1}
            }
        }
    ]
}

Update Paragraph Style

{
    'requests': [
        {
            'updateParagraphStyle': {
                'range': {
                    'startIndex': 1,
                    'endIndex': 50
                },
                'paragraphStyle': {
                    'namedStyleType': 'HEADING_1',
                    'alignment': 'CENTER'
                },
                'fields': 'namedStyleType,alignment'
            }
        }
    ]
}

Complete Example: Create and Format Document

import urllib.request
import urllib.parse
import json
import os

def get_access_token():
    """Get fresh access token"""
    data = urllib.parse.urlencode({
        'client_id': os.environ['GOOGLE_CLIENT_ID'],
        'client_secret': os.environ['GOOGLE_CLIENT_SECRET'],
        'refresh_token': os.environ['GOOGLE_REFRESH_TOKEN'],
        'grant_type': 'refresh_token'
    }).encode()
    
    req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data)
    response = json.load(urllib.request.urlopen(req))
    return response['access_token']

def create_document(title, access_token):
    """Create a new document"""
    data = json.dumps({'title': title}).encode()
    req = urllib.request.Request(
        'https://docs.googleapis.com/v1/documents',
        data=data,
        method='POST'
    )
    req.add_header('Authorization', f'Bearer {access_token}')
    req.add_header('Content-Type', 'application/json')
    
    response = json.load(urllib.request.urlopen(req))
    return response['documentId']

def batch_update(doc_id, requests, access_token):
    """Apply batch updates to document"""
    data = json.dumps({'requests': requests}).encode()
    req = urllib.request.Request(
        f'https://docs.googleapis.com/v1/documents/{doc_id}:batchUpdate',
        data=data,
        method='POST'
    )
    req.add_header('Authorization', f'Bearer {access_token}')
    req.add_header('Content-Type', 'application/json')
    
    return json.load(urllib.request.urlopen(req))

# Main workflow
access_token = get_access_token()

# Create document
doc_id = create_document('Project Report', access_token)
print(f"Created: https://docs.google.com/document/d/{doc_id}/edit")

# Add content with formatting
requests = [
    # Insert title
    {
        'insertText': {
            'location': {'index': 1},
            'text': 'Project Report\n\n'
        }
    },
    # Format title as heading
    {
        'updateParagraphStyle': {
            'range': {'startIndex': 1, 'endIndex': 15},
            'paragraphStyle': {'namedStyleType': 'HEADING_1'},
            'fields': 'namedStyleType'
        }
    },
    # Add body text
    {
        'insertText': {
            'location': {'index': 16},
            'text': 'Executive Summary\n\nThis report covers...\n\n'
        }
    },
    # Format section header
    {
        'updateParagraphStyle': {
            'range': {'startIndex': 16, 'endIndex': 34},
            'paragraphStyle': {'namedStyleType': 'HEADING_2'},
            'fields': 'namedStyleType'
        }
    }
]

batch_update(doc_id, requests, access_token)
print("Document updated successfully!")

Key Concepts

Document Indexing

  • Indices are 1-based (document starts at index 1)
  • Each character (including newlines) occupies one index
  • To append at the end, use endOfSegmentLocation

Batch Updates

  • Multiple requests are applied atomically
  • Requests are processed in order
  • Get the document first to determine correct indices

Field Masks

  • When updating styles, specify which fields to update
  • Use comma-separated field names: 'fields': 'bold,fontSize,foregroundColor'

Error Handling

Status CodeMeaning
400Bad Request - Invalid request format
401Unauthorized - Invalid or expired access token
403Forbidden - Insufficient permissions
404Not Found - Document doesn't exist
429Rate Limited - Too many requests

Token Refresh

Access tokens expire after 1 hour. If you get a 401 error, refresh the token:

try:
    # Make API call
    req = urllib.request.Request(url)
    req.add_header('Authorization', f'Bearer {access_token}')
    response = urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    if e.code == 401:
        # Refresh token and retry
        access_token = get_access_token()
        req = urllib.request.Request(url)
        req.add_header('Authorization', f'Bearer {access_token}')
        response = urllib.request.urlopen(req)

Best Practices

  1. Token Management

    • Cache access tokens (valid for 1 hour)
    • Store refresh token securely
    • Implement automatic token refresh
  2. Batch Operations

    • Combine multiple updates into single batch call
    • Reduces API calls and improves performance
  3. Index Calculation

    • Always get current document state before updates
    • Account for newline characters (\n)
    • Use endOfSegmentLocation for appending
  4. Rate Limits

    • Google Docs API has quotas (check Cloud Console)
    • Implement exponential backoff for rate limit errors

Resources

Comments

Loading comments...