From gmail-commander
Access Gmail via CLI with 1Password OAuth. Use when user wants to read emails, search inbox, export messages, create drafts, or.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gmail-commander:gmail-accessThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Read and search Gmail programmatically via Claude Code.
Read and search Gmail programmatically via Claude Code.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
CRITICAL: You MUST complete this preflight checklist before running any Gmail commands. Do NOT skip steps.
ls -la "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail" 2>/dev/null || echo "BINARY_NOT_FOUND"
If BINARY_NOT_FOUND: Build it first:
cd ~/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli && bun install && bun run build
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
If NOT_SET: You MUST run the Setup Flow below. Do NOT proceed to Gmail commands.
ALWAYS verify you're accessing the correct email account for the current project.
# Show current project context
echo "=== Gmail Account Context ==="
echo "Working directory: $(pwd)"
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID}"
# Check where GMAIL_OP_UUID is defined (mise hierarchy)
echo ""
echo "=== mise Config Source ==="
grep -l "GMAIL_OP_UUID" .mise.local.toml .mise.toml ~/.config/mise/config.toml 2>/dev/null || echo "Not found in standard locations"
# Quick connectivity test — shows the account email from a real email
echo ""
echo "=== Account Verification ==="
$GMAIL_CLI list -n 1 2>&1 | head -5
STOP and confirm with user before proceeding:
list -n 1 output shows the account's inbox — verify this matches the project's intended email.mise.local.toml sets GMAIL_OP_UUID in the mise hierarchyMulti-account disambiguation (when GMAIL_OP_UUID is NOT_SET but tokens exist).
There is no whoami subcommand; map each cached token UUID to its mailbox by
probing, then pick the one that fits the project:
# Which accounts are cached, and which mailbox does each resolve to?
for f in ~/.claude/tools/gmail-tokens/*.json; do
case "$(basename "$f")" in *.app-credentials.json|'*.json') continue ;; esac
uuid=$(basename "$f" .json)
who=$(GMAIL_OP_UUID="$uuid" $GMAIL_CLI list -n 1 --json 2>/dev/null \
| jq -r '.[0].to // "(probe failed / token expired)"')
echo "$uuid → $who"
done
A probe that returns invalid_grant means that account's refresh token is dead
(see "Diagnosing invalid_grant"). Pick the working UUID whose mailbox matches
the project, pin it in .mise.local.toml, and confirm it's gitignored. A child
project often needs a DIFFERENT account than its parent — verify, never assume
the parent's UUID.
# Check cached token exists and is not expired
TOKEN_FILE="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.json"
APP_CREDS="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.app-credentials.json"
echo "Token file: $([ -f "$TOKEN_FILE" ] && echo "EXISTS" || echo "MISSING")"
echo "App credentials: $([ -f "$APP_CREDS" ] && echo "CACHED" || echo "MISSING — will need 1Password on first run")"
If token file is MISSING: First run will open a browser for OAuth consent. This is expected.
If app credentials are MISSING: 1Password will be called once to cache client_id/client_secret, then never again.
Follow these steps IN ORDER. Use AskUserQuestion at decision points.
command -v op && echo "OP_CLI_INSTALLED" || echo "OP_CLI_MISSING"
If OP_CLI_MISSING: Stop and inform user:
1Password CLI is required. Install with:
brew install 1password-cli
# Try common vaults — "Claude Automation" for service accounts, "Employee" for interactive
for VAULT in "Claude Automation" "Employee" "Personal"; do
ITEMS=$(op item list --vault "$VAULT" --format json 2>/dev/null | jq -r '.[] | select(.title | test("gmail|oauth|google"; "i")) | "\(.id)\t\(.title)"')
[ -n "$ITEMS" ] && echo "=== Vault: $VAULT ===" && echo "$ITEMS"
done
Parse the output and proceed based on results:
If items found, use AskUserQuestion with discovered items:
AskUserQuestion({
questions: [{
question: "Which 1Password item contains your Gmail OAuth credentials?",
header: "Gmail OAuth",
options: [
// POPULATE FROM op item list RESULTS - example:
{ label: "Gmail API - dental-quizzes (56peh...)", description: "OAuth client in Employee vault" },
{ label: "Gmail API - personal (abc12...)", description: "Personal OAuth client" },
],
multiSelect: false
}]
})
If NO items found, use AskUserQuestion to guide setup:
AskUserQuestion({
questions: [{
question: "No Gmail OAuth credentials found in 1Password. How would you like to proceed?",
header: "Setup",
options: [
{ label: "Create new OAuth credentials (Recommended)", description: "I'll guide you through Google Cloud Console setup" },
{ label: "I have credentials elsewhere", description: "Help me add them to 1Password" },
{ label: "Skip for now", description: "I'll set this up later" }
],
multiSelect: false
}]
})
After user selects an item (with UUID), use AskUserQuestion:
AskUserQuestion({
questions: [{
question: "Add GMAIL_OP_UUID to .mise.local.toml in current project?",
header: "Configure",
options: [
{ label: "Yes, add to .mise.local.toml (Recommended)", description: "Creates/updates gitignored config file" },
{ label: "Show me the config only", description: "I'll add it manually" }
],
multiSelect: false
}]
})
If "Yes, add to .mise.local.toml":
.mise.local.toml existsGMAIL_OP_UUID to [env] section[env]
GMAIL_OP_UUID = "<selected-uuid>"
.mise.local.toml is in .gitignoreIf "Show me the config only": Output the TOML for user to add manually.
mise trust 2>/dev/null || true
cd . && echo "GMAIL_OP_UUID after reload: ${GMAIL_OP_UUID:-NOT_SET}"
If still NOT_SET: Inform user to restart their shell or run source ~/.zshrc.
GMAIL_OP_UUID="${GMAIL_OP_UUID}" $HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail list -n 1
If OAuth prompt appears: This is expected on first run. Browser will open for Google consent.
GMAIL_CLI="$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail"
# List recent emails
$GMAIL_CLI list -n 10
# Search emails
$GMAIL_CLI search "from:[email protected]" -n 20
# Search with date range
$GMAIL_CLI search "from:phoebe after:2026/01/27" -n 10
# Read specific email with full body
$GMAIL_CLI read <message_id>
# Read and download inline images (copy-pasted screenshots in compose)
$GMAIL_CLI read <message_id> --save-images
# Download inline images to a specific directory
$GMAIL_CLI read <message_id> --save-images --image-dir ./attachments/my-folder/
# Shorthand: --image-dir implies --save-images
$GMAIL_CLI read <message_id> --image-dir ./attachments/my-folder/
# JSON output with image metadata and saved paths
$GMAIL_CLI read <message_id> --save-images --json
# Download REAL file attachments (PDF, docx, csv, …) — distinct from inline images
$GMAIL_CLI read <message_id> --save-attachments
# Download attachments to a specific directory (implies --save-attachments)
$GMAIL_CLI read <message_id> --attachment-dir ./files/case-17402939/
# Export search results to JSON (full body + inlineImages + attachments metadata per message)
$GMAIL_CLI export -q "label:inbox" -o emails.json -n 100
# JSON output (for parsing)
$GMAIL_CLI list -n 10 --json
# Create a draft email
$GMAIL_CLI draft --to "[email protected]" --subject "Hello" --body "Message body"
# Create a draft reply (threads into existing conversation)
$GMAIL_CLI draft --to "[email protected]" --subject "Re: Hello" --body "Reply text" --reply-to <message_id>
# Draft with body loaded from a file (multi-paragraph bodies are awkward via --body)
$GMAIL_CLI draft --to "[email protected]" --subject "Report" --body-file ./email-body.txt
# Draft with file attachments (--attach is repeatable; MIME type guessed from extension)
$GMAIL_CLI draft --to "[email protected]" --subject "Q1 Report" \
--body-file ./email-body.txt \
--attach ./report.pdf \
--attach ./screenshot.png
# Draft reply with both body-file and multiple attachments
$GMAIL_CLI draft --to "[email protected]" --subject "Re: Project" \
--reply-to <message_id> \
--body-file ./reply.txt \
--attach ./diagram.pdf
Emails often contain copy-pasted screenshots (inline images embedded in the HTML body, not file attachments). These appear as [image: image.png] placeholders in plain text but contain real image data accessible via the Gmail API.
| Flag | Effect |
|---|---|
--save-images | Download all inline images to disk (default: ~/.claude/tools/gmail-images/<message_id>/) |
--image-dir <path> | Custom output directory (implies --save-images) |
| No flag | Shows image metadata (count, filenames, sizes) but does NOT download |
--- Inline Images (3) ---
image.png image/png 245.3 KB
image.png image/png 512.1 KB
photo.jpg image/jpeg 89.7 KB
--- Saved to Disk ---
./attachments/01_image.png (251,234 B)
./attachments/02_image.png (524,001 B)
./attachments/03_photo.jpg (91,852 B)
--- Markdown References ---



These are two disjoint channels, surfaced and downloaded separately:
| Channel | MIME parts | Metadata field | Download flag |
|---|---|---|---|
| Inline images | image/* with attachmentId (copy-pasted screenshots) | inlineImages[] | --save-images / --image-dir |
| File attachments | non-image parts with a filename + attachmentId (PDF, docx, csv, …) | attachments[] | --save-attachments / --attachment-dir |
A plain gmail read <id> (no flags) shows both as metadata blocks (filename, MIME, size) without downloading — so you can see a PDF exists before pulling it. export and read --json both carry attachments[] (and inlineImages[]) in their JSON.
has:attachment matches real file attachments but NOT inline images. Gmail search has no operator for inline images. To discover emails with inline images, you must read the email and check the MIME tree.
Strategy for finding emails with inline images:
# Search by sender/date, then read each to check for images
$GMAIL_CLI search "from:[email protected] after:2026/02/01" -n 10 --json | \
jq -r '.[].id' | while read id; do
COUNT=$($GMAIL_CLI read "$id" --json | jq '.inlineImages | length')
[ "$COUNT" -gt 0 ] && echo "$id has $COUNT inline image(s)"
done
When downloading images from a thread (multiple reply emails), later replies include all prior inline images. The last email in a thread is typically the superset.
Recommendation: For threaded conversations, download images from the latest reply only to avoid duplicates. Compare by file size if unsure.
Copy-pasted screenshots often all share the generic filename image.png. The CLI prefixes a zero-padded index: 01_image.png, 02_image.png, etc. These machine-generated names should be renamed to descriptive names for correspondence archival.
When inline images contain handwritten annotations (circles, arrows, written text overlaid on screenshots), perform a systematic two-level analysis:
Format annotations as blockquote captions beneath each image in markdown:

> **Annotation transcription**: [Detailed description of visual markup.]
> Handwritten text reads: _"exact transcription here"_
> [Interpretation of what the annotator is requesting.]
Do NOT defer annotation transcription to a second pass. Capture all annotations on the first image examination to avoid redundant re-reads.
Real file attachments (PDF, docx, csv, …) are surfaced in attachments[] and
downloaded with --save-attachments / --attachment-dir. Same fetch path as
inline images, different metadata field.
# See what a message carries (no download) — both metadata blocks print
$GMAIL_CLI read <id> # → "--- Attachments (1) --- foo.pdf application/pdf 192.5 KB"
# Download to a chosen dir; files are index-prefixed + sanitized
$GMAIL_CLI read <id> --attachment-dir ./files/
# Discover which messages in a corpus actually carry attachments
$GMAIL_CLI search "from:[email protected] has:attachment" -n 20 --json | \
jq -r '.[].id' | while IFS= read -r id; do
N=$($GMAIL_CLI read "$id" --json | jq '.attachments | length')
[ "$N" -gt 0 ] && echo "$id → $N attachment(s)"
done
Why this matters for archival: in clinical/legal/operational mail the
attached PDF (a protocol, a vendor form, a signed consent) is often the most
important payload. A body-only export silently loses it. Always check
attachments[] when archiving a correspondence thread.
The canonical pattern for archiving a whole correspondence (verified on a 27-message, 15-thread clinical corpus):
"Curve") returns mostly newsletter noise. Prefer:
curvedental.com (matches from/to/cc on the org)from:[email protected] OR to:…1233V)search --json (snippet-only) and curate the
in-scope set out of the noise.read --json loop (one file per message).threadId client-side — Gmail's list/search APIs return
individual messages, not threads; you reconstruct threads yourself.Date and strip quoted history
(drop >-prefixed lines and everything after On … wrote:) to expose each
message's new content.attachments[] is non-empty.# Robust batch fetch. NOTE: in zsh `for id in $VAR` does NOT word-split —
# always loop with `while IFS= read -r` over newline-delimited IDs.
printf '%s\n' $IDS | while IFS= read -r id; do
[ -n "$id" ] && $GMAIL_CLI read "$id" --json > "raw/$id.json"
done
export is the one-call shortcut (fixed)gmail export -q "<query>" -o out.json -n N writes one JSON array with full
body + inlineImages[] + attachments[] per message — the batch-fetch
shortcut when a single query captures your set. (Historical note: before the
fix, export printed "Exported N emails to <path>" but wrote no file —
outputPath was an unused parameter. If you see that symptom, the binary is
stale; rebuild it.) export does not download attachment bytes — it only
carries the metadata; use read --save-attachments per message for the files.
The draft command creates emails in your Gmail Drafts folder for review before sending.
Required options:
--to - Recipient email address--subject - Email subject line--body OR --body-file - Email body text (one of the two)Optional:
--body-file - Read body from a file instead of --body. Useful for multi-paragraph bodies that are awkward to pass on the shell. Mutually exclusive with --body; if both are passed, --body wins with a stderr warning.--attach - File path to attach. Repeatable for multiple attachments. MIME type is guessed from extension (PDF, PNG, JPEG, DOCX, XLSX, ZIP, MD, JSON, etc. → mapped; unknown → application/octet-stream). Total message size ≤ 25 MB (Gmail limit; the CLI surfaces a 413 with a helpful hint if you exceed it).--from - Sender email alias (auto-detected when replying, see Sender Alignment below)--reply-to - Message ID to reply to (creates threaded reply with proper headers)--json - Output draft details as JSONThe user has multiple Send As aliases configured in Gmail. The From address MUST match correctly or the recipient sees a reply from the wrong identity.
Rule 1 - Replies (--reply-to is set): The CLI auto-detects the correct sender by reading the original email's To/Cc/Delivered-To headers and matching against the user's Send As aliases. No manual intervention needed. The CLI will print:
From: [email protected] (auto-detected from original email)
If auto-detection fails (e.g., the email was BCC'd), explicitly pass --from.
Rule 2 - New emails (no --reply-to): When drafting a brand new email (not a reply), you MUST use AskUserQuestion to confirm which sender alias to use BEFORE creating the draft. Never assume the default.
AskUserQuestion({
questions: [{
question: "Which email address should this be sent from?",
header: "Send As",
options: [
// Populate from known aliases or let user specify
{ label: "[email protected]", description: "Personal Gmail" },
{ label: "[email protected]", description: "Work email" },
],
multiSelect: false
}]
})
Then pass the selected address via --from:
$GMAIL_CLI draft --to "[email protected]" --from "[email protected]" --subject "Hello" --body "Message"
Rule 3 - Always verify in output: After draft creation, confirm the From address is shown in the output. If it's missing or wrong, delete the draft and recreate.
After EVERY draft creation, you MUST present the user with a direct Gmail link to review the draft. This is critical because drafts should always be visually confirmed before sending.
Always output this after creating a draft:
Draft created! Review it here:
https://mail.google.com/mail/u/0/#drafts
From: <sender_address>
Never skip this step. The user must be able to click through to Gmail and visually verify the draft content, sender, recipients, and threading before sending.
# 1. Find the message to reply to
$GMAIL_CLI search "from:[email protected] subject:meeting" -n 5 --json
# 2. Create draft reply - From is auto-detected from original email's To header
$GMAIL_CLI draft \
--to "[email protected]" \
--subject "Re: Meeting tomorrow" \
--body "Thanks for the update. I'll be there at 2pm." \
--reply-to "19c1e6a97124aed8"
# 3. ALWAYS present the review link + From address to user
# 1. Ask user which alias to send from (AskUserQuestion)
# 2. Create draft with explicit --from
$GMAIL_CLI draft \
--to "[email protected]" \
--from "[email protected]" \
--subject "Hello" \
--body "Message body"
# 3. ALWAYS present the review link + From address to user
Note: After creating drafts, users need to re-authenticate if they previously only had read access. The CLI will prompt for OAuth consent to add the gmail.compose scope.
| Query | Description |
|---|---|
from:[email protected] | From specific sender |
to:[email protected] | To specific recipient |
subject:keyword | Subject contains keyword |
after:2026/01/01 | After date |
before:2026/02/01 | Before date |
label:inbox | Has label |
is:unread | Unread emails |
has:attachment | Has file attachment (does NOT match inline images — see Inline Image Extraction) |
Reference: https://support.google.com/mail/answer/7190
| Variable | Required | Description |
|---|---|---|
GMAIL_OP_UUID | Yes | 1Password item UUID for OAuth credentials |
GMAIL_OP_VAULT | No | 1Password vault (default: Employee) |
HTTPS_PROXY | No | Honored by the underlying gaxios library, BUT the CLI auto-injects *.googleapis.com into NO_PROXY at startup so corporate proxies don't break Gmail traffic. See "Proxy Auto-Bypass" below. |
If HTTPS_PROXY (or HTTP_PROXY) is set in the environment, the CLI automatically injects the following hosts into NO_PROXY at module load — before any auth or API call is made:
.googleapis.com (covers gmail.googleapis.com, oauth2.googleapis.com, etc.).google.comaccounts.google.comoauth2.googleapis.comWhy this exists: many corporate networks (Cloudflare WARP, mitmproxy local interceptors, ITP-style local proxies) can't tunnel CONNECT to Google API hosts. When the proxy fails, the response surface returns HTTP 502 with an empty error body — the googleapis library throws a gaxios error whose .message is empty, which used to render as a useless empty Error: in stderr.
By force-bypassing the proxy for Google hosts, end-users with a corporate proxy can run the CLI without manually setting NO_PROXY or unsetting HTTPS_PROXY per-command.
Idempotent: the injection only adds entries that aren't already present in NO_PROXY. If you've manually configured NO_PROXY=.googleapis.com, the CLI leaves it alone.
Diagnosing remaining proxy issues (rare): if you still see HTTP 5xx errors, the CLI's new error formatter prints the full URL + response body + a hint. Check that HTTPS_PROXY was set BEFORE the CLI started (env-var detection is one-shot at module load).
The CLI's top-level error handler renders unknown errors as structured messages with HTTP status, request URL, response body snippet, and a category-specific hint. Example for a 404 on a bogus draft ID:
Error: HTTP 404 Not Found DELETE https://gmail.googleapis.com/gmail/v1/users/me/drafts/r-doesnotexist123
body: {"error":{"code":404,"message":"Requested entity was not found.",...}}
hint: message / draft ID not found. List existing drafts with `gmail drafts` first.
Hint categories:
| Status | Hint |
|---|---|
| 401 | Token expired/revoked. Delete ~/.claude/tools/gmail-tokens/$GMAIL_OP_UUID.json to force re-auth. |
| 403 | OAuth scope insufficient (drafts/attachments need gmail.compose) or Send As alias not configured. |
| 404 | Message / draft ID not found. List existing drafts with gmail drafts first. |
| 413 | Attachment(s) exceed Gmail's 25 MB per-message limit. Split or use Drive links. |
| 502/503/504 | Gateway error — usually a proxy that can't tunnel to Google. The auto-bypass should prevent this; check that HTTPS_PROXY was set BEFORE the CLI started. |
Network-layer errors (ECONNREFUSED, ENOTFOUND, etc.) are surfaced with their error code instead of the empty Error: of the prior implementation.
~/.claude/tools/gmail-tokens/
├── <uuid>.json # OAuth token (access + refresh), refreshed hourly
└── <uuid>.app-credentials.json # client_id + client_secret (static, cached from 1Password)
client_id/client_secret → cached to <uuid>.app-credentials.json<uuid>.jsonTo force a fresh 1Password lookup (e.g., after rotating OAuth app credentials):
rm ~/.claude/tools/gmail-tokens/<uuid>.app-credentials.json
invalid_grantA refresh token in a Google OAuth app whose publishing status is "Testing" expires after 7 days — period. The hourly refresher renews the access token but does NOT extend the refresh token's 7-day clock, so a Testing-mode account dies roughly weekly and can only be revived by a browser re-consent. (An app in "In production" status issues long-lived refresh tokens that don't expire on that clock.)
Recovery (re-consent): Delete the expired token file and re-authorize via browser:
# 1. Back up and remove the expired token
mv ~/.claude/tools/gmail-tokens/<uuid>.json ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# 2. Run any gmail command — browser will open for OAuth consent
# (sign in with the SPECIFIC account that <uuid> maps to — see accounts.json labels)
$GMAIL_CLI list -n 1
# 3. Verify the hourly refresher picks up the new token
~/.claude/automation/gmail-token-refresher/gmail-oauth-token-refresher 2>&1
# 4. Clean up backup
rm ~/.claude/tools/gmail-tokens/<uuid>.json.expired
Durable fix (stop the weekly death — "keep everything re-auth"): publish the OAuth app to Production so refresh tokens stop expiring on the 7-day clock.
accounts.json vault per uuid). Only the
dying one is stuck in Testing.client_id prefix is the project number; the CLI prints the full
client_id in the consent URL during re-auth).# Check all accounts at once
for f in ~/.claude/tools/gmail-tokens/*.json; do
[ "$(basename "$f")" = "*.json" ] && continue
case "$(basename "$f")" in *.app-credentials.json) continue ;; esac
UUID=$(basename "$f" .json)
python3 -c "
import json, datetime
t = json.load(open('$f'))
exp = datetime.datetime.fromtimestamp(t.get('expiry_date',0)/1000)
delta = (exp - datetime.datetime.now()).total_seconds()
status = 'VALID' if delta > 0 else 'EXPIRED'
print(f' {\"$UUID\"}: {status} (expires in {int(delta/60)}m)' if delta > 0 else f' {\"$UUID\"}: EXPIRED ({int(-delta/3600)}h ago)')
" 2>/dev/null
done
text/plain with no wrapping guidance, so clients hard-wrap long paragraphs.format=flowed: emitted RFC 3676 format=flowed with 72-col soft breaks. Gmail does NOT reliably reflow format=flowed, and the 72-col breaks are shown literally in Gmail's compose/draft view — so the drafts still looked wrapped/"truncated". Do not use format=flowed for Gmail-destined mail.multipart/alternative: buildRawMessage now sends multipart/alternative with a text/plain part (the body UNWRAPPED — long lines, no 72-col chopping) and a text/html part (toHtmlBody() + escapeHtml()). The HTML wraps the body in a <div> and turns ONLY the author's own newlines into <br> — running text carries no inserted breaks, so the browser reflows long paragraphs to the reader's window and never shows fixed short lines. List items / Best,\nname sign-offs keep their breaks via <br>. With attachments, the alternative body nests inside multipart/mixed.read --json plain part now has a 329-char single paragraph line (unwrapped — proving the new path ran); the HTML part reflows in Gmail. Rebuilt binary (bun run build), tsgo clean. NOTE: the gmail binary is compiled — a TS edit needs bun run build; drafts made before a fix must be recreated.gmail export -o <path> printed "Exported N emails to <path>" but wrote nothing (exportEmails returned the array, never wrote outputPath). Separately, the CLI surfaced no file attachments (only inlineImages), so 11 messages' attached PDFs (a vendor certification form, protocols) were silently dropped.exportEmails now writeFiles the JSON. (2) Added extractAttachments + attachments[] metadata in formatMessage, saveAttachments() in gmail-images.ts, --save-attachments/--attachment-dir flags, and an Attachments metadata block in printEmails. Documented the inline-image-vs-attachment split, the bulk thread-archival pipeline, the multi-account UUID→mailbox probe, and the zsh while read batch-loop gotcha.export -q curvedental.com -o /tmp/x.json now writes 3 emails with full bodies; read <id> --attachment-dir pulled CDAnet Software Vendor Certification Application form.pdf (197,168 B, valid PDF 1.7, 3 pages). Rebuilt binary, tsc --noEmit clean.After this skill completes, reflect before closing the task:
Do NOT defer. The next invocation inherits whatever you leave behind.
npx claudepluginhub terrylica/cc-skills --plugin gmail-commanderGuides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Forks private projects, strips secrets/internal references, verifies cleanliness, and packages them with CLAUDE.md, README, and setup.sh for safe public release.