Install
$ agentstack add skill-jtsternberg-claude-plugins-md-to-google-doc ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Markdown to Google Doc
Upload local markdown files to Google Drive as formatted Google Docs. The best available path depends on which account can authenticate and how much fidelity the target doc needs.
Source routing
gws only authenticates your personal Google account. If the target doc's account isn't reachable by gws, route through the next rung — try in order, and only read/run the next rung's setup on fallthrough.
Rung 1 — gws (default, unchanged)
Use the existing workflow below: full create, update-in-place, tab management, section linking. This is the primary path whenever gws can authenticate to the target account.
Fall through to rung 2 if gws auth status fails or the target account can't reach the destination folder/doc (403/404 for that account, not an expired-token case).
Rung 2 — gcloud ADC (create + update-in-place, for accounts gws can't reach)
Direct Drive/Docs API access via ADC. Supports create (files.create with a markdown upload — same server-side importer gws uses) and update-in-place: a state file (~/.config/gws-md-to-gdoc/rendered.json) remembers which source file produced which doc ID, so reruns update instead of duplicating. Sets PAGELESS via a batchUpdate after create, same as gws.
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/adc-check.sh" # fast preflight; exit 0 = configured
bash "$SKILL_DIR/scripts/adc-create.sh" [folder-id-or-url] [--title "Title"] [--new]
--new forces a fresh doc even if the state file has a prior entry for this source file. If adc-check.sh fails, it prints an actionable one-line reason — only then read [references/adc-setup.md](references/adc-setup.md) for the full gcloud setup steps. If ADC isn't configured and setting it up isn't worth it right now, fall through to rung 3.
Not supported on this rung yet: tab-scoped publishing, section linking (both gws-only for now).
Rung 3 — claude.ai Google Drive connector (zero setup, create-only)
Use when neither gws nor ADC can reach the target account, and the mcp__claude_ai_Google_Drive__* tools are available in this session. Verified live (2026-07-14): create_file with contentMimeType: "text/markdown" produces a real formatted Google Doc (headings, bold, links, lists, tables) — no de-escaping needed on this direction, this is the connector's write path, not its read path.
- Clean the markdown (same as rung 1/2):
``bash # Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md. SKILL_DIR="${CLAUDE_SKILL_DIR}" bash "$SKILL_DIR/scripts/clean.sh" /tmp/cleaned-$$.md ``
- Derive the title if
--titlewasn't given (H1 heading, else filename). - Call
mcp__claude_ai_Google_Drive__create_file:
title: derived/given titletextContent: the cleaned markdown contentcontentMimeType:"text/markdown"parentId: target folder ID if--folderwas given (strip a full
.../folders/FOLDER_ID URL the same way as folder-ID extraction elsewhere in this skill)
- leave
disableConversionToGoogleTypeunset — default conversion is
what formats the Doc.
- Report the URL:
https://docs.google.com/document/d//edit(theid
field create_file returns).
- Clean up the temp file.
Refuse update requests on this rung. The connector's update_file tool only renames/moves — it cannot replace content, and trash_file fails with a permission error (confirmed live — no delete means no throwaway-temp-doc tricks either). If asked to update an existing doc and only this rung is available, say so and point at rung 2 (ADC update-in-place) instead of silently creating a duplicate doc.
Known limits (confirmed live): no bold/italic/inline emphasis inside table cells (stays literal **text**); no way to set PAGELESS mode (new docs land in Drive's default paged mode); same unsupported set as gws (images, footnotes, smart chips) presumed but not individually verified on this rung.
If none of the three rungs can reach the destination, fail with a clear message naming which rungs were tried and why each failed.
Prerequisites (rung 1)
```!
Codex: this path resolves under Claude Code; substitute the directory containing this plugin.
PLUGINROOT="${CLAUDEPLUGINROOT}" bash "$PLUGINROOT/scripts/auth-preflight.sh"
## Task (rung 1)
Run the entrypoint script, passing all arguments through. It auto-detects
create vs update based on whether the destination looks like a doc URL/ID
or a folder ID:
```bash
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/gdoc.sh" $ARGUMENTS
If no arguments were provided, ask the user for the file path and destination.
Optional flags: --title "Custom Title" overrides the auto-derived title.
Script Details
Creating a New Google Doc
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/upload.sh" ./file.md FOLDER_ID
The folder may be a bare ID or a full Drive folder URL, and may be passed positionally or via --folder. All of these are equivalent:
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/upload.sh" ./file.md FOLDER_ID
bash "$SKILL_DIR/scripts/upload.sh" ./file.md "https://drive.google.com/drive/u/0/folders/FOLDER_ID"
bash "$SKILL_DIR/scripts/upload.sh" ./file.md --folder "https://drive.google.com/drive/u/0/folders/FOLDER_ID"
With a custom title:
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/upload.sh" ./file.md FOLDER_ID --title "My Document"
The script handles: frontmatter stripping, Obsidian callout cleanup, title derivation, upload, verification, and temp file cleanup. It prints the Google Doc URL on success.
Important: The gws CLI requires --upload paths within the current working directory. The script creates a temp file in cwd automatically, but the source file must be accessible from cwd (use relative paths or copy first).
Extracting a Folder ID
The folder ID is the last path segment of a Drive folder URL: https://drive.google.com/drive/u/0/folders/FOLDER_ID_HERE
Title Derivation
When no --title is given, the script:
- Looks for an
# H1 headingin the file (after frontmatter) - Falls back to the filename with
.mdstripped and hyphens replaced by spaces
Updating an Existing Google Doc
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/update.sh" ./file.md DOC_ID
Also accepts a full Google Doc URL instead of a bare doc ID:
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/update.sh" ./file.md "https://docs.google.com/document/d/DOC_ID/edit"
Updating a Single Tab (multi-tab docs)
Google Docs supports native tabs (left-sidebar). update.sh replaces the ENTIRE document and deletes every tab except the first — it refuses to run against a multi-tab doc unless you pass --force.
To publish markdown into one tab while preserving the others:
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/tab-update.sh" ./file.md DOC_ID --tab "Tab Title"
bash "$SKILL_DIR/scripts/tab-update.sh" ./file.md DOC_ID --tab t.abc123
How it works: the markdown is converted server-side via a throwaway temp doc (auto-trashed), whose structure is replayed into the target tab with tab-scoped batchUpdate requests. Supported: headings, bold/italic/links, nested bullet & numbered lists, tables, and horizontal rules (---, rendered as a paragraph bottom border). Not supported (skipped with a warning): images, footnotes, smart chips.
Linking Section References Across Tabs
After publishing, turn §N / §NB references into clickable links to the matching section heading in another tab (e.g. a "Next Steps" tab linking into the main findings tab):
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/link-sections.sh" DOC_ID
bash "$SKILL_DIR/scripts/link-sections.sh" DOC_ID --target-tab "Findings" --from-tab "Next Steps"
By default the target is the tab with the most numbered-section headings and all other tabs are scanned. Idempotent — safe to re-run after edits. Run it after the tabs are published so the target headings exist.
Managing Tabs
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/tabs.sh" list DOC_ID
bash "$SKILL_DIR/scripts/tabs.sh" add DOC_ID "Next Steps" --emoji "⭐" --index 1
bash "$SKILL_DIR/scripts/tabs.sh" rename DOC_ID "Next Steps" "Action Items"
bash "$SKILL_DIR/scripts/tabs.sh" delete DOC_ID t.abc123 --yes
Batch Uploads
When uploading multiple files to the same folder, run upload commands in parallel for efficiency:
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/upload.sh" ./file1.md FOLDER_ID &
bash "$SKILL_DIR/scripts/upload.sh" ./file2.md FOLDER_ID &
wait
Additional Resources
If the bundled scripts are unavailable, see [MANUAL.md](references/MANUAL.md) for the step-by-step manual workflow.
Troubleshooting
Auth expired: Run gws auth login to re-authenticate. Wrong account: Run gws auth status to check which account is active. Account mismatch: If the doc/folder belongs to a different Google account, the script will tell you which account you're authenticated as and suggest sharing or switching accounts. Run gws auth status to check. Upload path error: The gws CLI rejects absolute paths outside cwd. Copy the file into cwd first, or cd to the file's directory before uploading. "This doc has N native tabs" error: the doc uses native tabs; use tab-update.sh (see "Updating a Single Tab") or pass --force to intentionally flatten the doc to one tab. "File not found" / 404 on a folder or doc that exists (shared drives): the resource lives on a shared drive — Drive v3 file calls need supportsAllDrives=true in their query params to see it. All bundled scripts (both the gws and ADC rungs) pass it on every Drive v3 create/get/update; if you call the Drive API by hand, add it to --params. Note the exceptions that work on shared-drive docs without the flag: files.export, and the Docs API (documents.get / documents.batchUpdate) — the flag is for Drive file operations only. "remembered doc … is not readable" warning (ADC rung): the doc in the state file returned 404, so a new doc was created. Drive returns 404 both for a deleted doc and for one you've lost access to, so check whether it was a sharing change before keeping the copy — the original may still exist. "no write access to doc … insufficientFilePermissions" (ADC rung): you can read the doc but not edit it. Request edit access from the owner, or pass --new to render to a fresh doc you own.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jtsternberg
- Source: jtsternberg/claude-plugins
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.