google-credentials-fallback
Use already-installed `gws` or `gog` (gogcli) credentials when Hermes's own Google OAuth setup has not been run. Reuses the refresh token from the working CLI to call Gmail/Calendar/Drive APIs directly via curl, avoiding a full OAuth re-setup. Activates when `$GSETUP --check` returns NOT_AUTHENTICATED but `gws` or `gog` is on PATH and authenticated. Also handles user-shared Google Doc URLs (docs.google.com/document/d/<ID>/edit) — `gws` 403s on personal shared Docs (service-account auth), use `gog docs info` + `gog docs export --format txt` instead.
- License 许可
- MIT
- 最近更新
- 2026-11-14
- Stars
- ★ 6
- Forks
- 0
01 前言 Front Matter
- name
- google-credentials-fallback
- author
- Hermes Agent
- license
- MIT
- version
- 1.1.0
- metadata
- { "hermes": { "tags": [ "google", "gmail", "calendar", "drive", "docs", "oauth", "gws", "gog", "gogcli", "fallback" ], "related_skills": [ "productivity/google-workspace" ] } }
- platforms
- [ "macos", "linux" ]
- description
- Use already-installed `gws` or `gog` (gogcli) credentials when Hermes's own Google OAuth setup has not been run. Reuses the refresh token from the working CLI to call Gmail/Calendar/Drive APIs directly via curl, avoiding a full OAuth re-setup. Activates when `$GSETUP --check` returns NOT_AUTHENTICATED but `gws` or `gog` is on PATH and authenticated. Also handles user-shared Google Doc URLs (docs.google.com/document/d/<ID>/edit) — `gws` 403s on personal shared Docs (service-account auth), use `gog docs info` + `gog docs export --format txt` instead.
02 资源文件 2
03 SKILL.md 正文
google-credentials-fallback
Reuse an already-authenticated gws or gog (gogcli) install on the same machine instead of running the full Hermes Google Workspace OAuth setup. Useful when:
$GSETUP --checkreturnsNOT_AUTHENTICATED.- The user already has
gwsorgoginstalled and authorized (likely from prior work). - You only need to read Gmail, list calendar events, or fetch a Drive/Docs file from a cron job or short-lived agent session.
References
references/daily-digest-cron-recipe.md— verified working recipe for a Gmail + Calendar → Slack morning digest cron (gog v0.10.0,--from/--toworkaround for the--daysbug).
When this skill applies, the bundled productivity/google-workspace skill's $GAPI wrapper cannot be used because no ~/.hermes/google_token.json exists. This skill provides the working alternative.
Verified 2026-07-09 against a host with both CLIs:
gwsis authorized asfirebase-adminsdk-…@worldarchitecture-ai.iam.gserviceaccount.com(service account, empty calendar).gogis authorized as$USER@gmail.com(personal OAuth).
When to activate
Trigger condition: any task needs Gmail / Calendar / Drive / Docs / Sheets and $GSETUP --check says NOT_AUTHENTICATED. Before running the 5-step OAuth setup, check if a working CLI is already on the host:
which gws gog 2>&1
gog auth list 2>&1 # shows accounts + auth type (oauth / service-account)
If gog auth list shows the user account you need, skip Hermes OAuth setup and read the existing credentials file directly.
1. Locate the working credentials
# gws
ls -la ~/.config/gws/ 2>&1
ls -la ~/Library/Application\ Support/gwscli/ 2>&1
# gog (gogcli)
ls -la ~/Library/Application\ Support/gogcli/ 2>&1
cat ~/Library/Application\ Support/gogcli/credentials.json
cat ~/Library/Application\ Support/gogcli/config.json
gog's credentials.json contains client_id, client_secret, and a refresh_token directly in the JSON — no keychain unlock required. Treat as a secret; never paste in chat/logs.
The services array and scopes array tell you which APIs the refresh token can hit (commonly: gmail, calendar, drive, docs).
2. Try the native CLIs first (fastest)
gog (gogcli) — preferred for personal Gmail/Calendar
GOG="-a $USER@gmail.com --enable-commands=gmail calendar"
$gog $GOG gmail search "is:unread newer_than:3d" --max 15
$gog $GOG calendar calendars # list calendar IDs and roles
gws (googleworkspace/cli) — preferred for Workspace / service-account
gws calendar events list --params '{"calendarId": "primary", "timeMin": "…", "timeMax": "…", "maxResults": 10}'
gws gmail users messages list --params '{"userId": "me", "q": "is:unread newer_than:3d", "maxResults": 10}'
Known bugs (work around with curl below)
gog ≤ 0.10.0:gog calendar events list --days 2returnsGoogle API error (404 notFound)against the OAuth default account.gws (2026-07):gws gmail users messages listreturnsPrecondition check failedfor personal Gmail.- gog positional arg parsing treats
primaryas a subcommand, not a calendarId.
Working alternative for the calendar bug (verified 2026-07-12): use --from / --to with RFC3339 UTC timestamps instead of --days N. Example:
FROM_TS=$(date -u -v+0H "+%Y-%m-%dT%H:%M:%SZ")
TO_TS=$(date -u -v+24H "+%Y-%m-%dT%H:%M:%SZ")
gog calendar list --from "$FROM_TS" --to "$TO_TS" --json --results-only -a $USER@gmail.com
This works on gog v0.10.0 against personal Gmail and returns full event details (id, summary, start/end, attendees, hangoutLink).
When gog calendar list still fails, jump straight to curl — the Google REST API is unaffected.
3. Direct curl with the stored refresh token
The bypass that always works. Same refresh_token as gog, direct to Google's REST endpoints.
Get an access token
ACCESS_TOKEN=$(curl -s -X POST https://oauth2.googleapis.com/token \
-d "client_id=$(jq -r .client_id ~/Library/Application\ Support/gogcli/credentials.json)" \
-d "client_secret=$(jq -r .client_secret ~/Library/Application\ Support/gogcli/credentials.json)" \
-d "refresh_token=$(jq -r .refresh_token ~/Library/Application\ Support/gogcli/credentials.json)" \
-d "grant_type=refresh_token" \
-d "scope=https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/gmail.modify openid" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token','ERROR'))")
# Always verify length, never log the value
[ "${#ACCESS_TOKEN}" -gt 50 ] && echo "got token len=${#ACCESS_TOKEN}"
Gmail: list + get metadata
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=20&q=is:unread+newer_than:2d+-category:promotions" \
| python3 -m json.tool
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/<MESSAGE_ID>?format=metadata&metadataHeaders=From&metadataHeaders=Subject&metadataHeaders=Date"
Calendar: list events (include or exclude to scope a window)
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/$USER%40gmail.com/events?timeMin=2026-07-09T16:00:00Z&timeMax=2026-07-10T16:00:00Z&singleEvents=true&orderBy=startTime&maxResults=25" \
| python3 -m json.tool
calendarId in the URL must be URL-encoded (@ → %40). Use gog calendar calendars to enumerate the IDs the user can see; the OAuth default may be a service-account calendar that is empty by design — that's not a bug, just not the one you want.
Drive / Docs / Sheets / People
Same Bearer token + the appropriate https://www.googleapis.com/{drive|docs|sheets|people}/v3/... endpoints, provided the refresh token's services list includes them (it usually does for drive, docs).
4. When to write back to Hermes-managed credentials
Use the curl/gog path as a stopgap. If the user wants long-term canonical access from Hermes, run the bundled google-workspace skill's first-time setup once (Steps 2–5 in that skill's SKILL.md). You can speed it up by reusing the client_id/client_secret/refresh_token triple already in ~/Library/Application Support/gogcli/credentials.json — write it as a Desktop OAuth JSON, then $GSETUP --client-secret … against it. Step 4 (auth-code exchange) may not even be needed if the refresh token was minted against the same client_id.
After this, $GAPI gmail search … and $GAPI calendar list … work normally and should be used in place of the curl fallback.
5. Drive corpus search (find + read a constellation of Google Docs)
When the user asks "find all my old X campaigns / Y documents / Z shared notes" and the answer lives as a constellation of Google Docs (not a single one), use the Drive search pattern. Verified 2026-07-20 against $USER@gmail.com while finding the god-of-tyranny Aizen campaign corpus (16+ Google Docs spanning 2025-06-14 → 2026-02-09).
Drive search — the right gog subcommand is drive search, NOT docs search
# CORRECT — gog 0.10.0 has no `gog docs search` subcommand
gog drive search --query "Aizen god" --max 20 -a $USER@gmail.com --json
gog drive search --query "campaign nocturne" --max 50 -a $USER@gmail.com --json
# WRONG — returns "unknown subcommand" or empty
gog docs search "Aizen god"
gog drive docs search "Aizen god"
Drive download — Google Docs export as PDF is automatic
# Google Doc → auto-exports as PDF (preserves structure, fonts, images)
gog drive download --file-id 1L1sOStC7rVjCzE8KHhpMf55TarmvNXNuEx-RH2vwO-0 \
--output ~/tmp/aizen_origin.pdf -a $USER@gmail.com
# Plain text file (.txt) → downloads as .txt directly
gog drive download --file-id 1HnRXmsl8rcB5_ZNmO7WkDIxhTTZCZw0n \
--output ~/tmp/aizen_godhood_3.txt -a $USER@gmail.com
# PDF (e.g. "Aizen god campaign book 2.pdf") → downloads as PDF
gog drive download --file-id 1hkW_iJZCy7CoOZfCuuj__Q_qz6IGPEE7 \
--output ~/tmp/aizen_book2.pdf -a $USER@gmail.com
Then pdftotext <pdf> - to extract text. Some PDFs (especially older Google Docs) fail pdftotext cleanly — fall back to pdfinfo for metadata + the Drive webViewLink for manual read.
Field reference for gog drive search --json output
| Field | What it is | Use it for |
|---|---|---|
id |
Drive file ID (used in drive download) |
Dedupe, pass to download |
name |
Filename incl. extension | Sort, filter, group by topic |
mimeType |
application/vnd.google-apps.document = Google Doc; text/plain = raw text; application/pdf = PDF |
Decide download path (Google Docs auto-export PDF; raw text stays text) |
modifiedTime |
RFC3339 UTC | Sort newest-first / filter by date range |
size |
Bytes (only set for non-Google-Docs native files) | Estimate download time |
webViewLink |
https://drive.google.com/file/d/<id>/view |
Share-able URL for the user |
Recipe: "find and inventory" a constellation
# 1. Multi-keyword fan-out
for kw in "Aizen god" "campaign nocturne" "noctune bg3" "campaign aizen"; do
gog drive search --query "$kw" --max 50 -a $USER@gmail.com --json \
| jq -r '.files[]? | [.id, .name, .mimeType, .modifiedTime] | @tsv'
done | sort -u -k1,1 # dedupe by file id
# 2. For each hit, dump metadata to a manifest
# (id, name, mimeType, modifiedTime, size, webViewLink)
gog drive info --file-id <id> -a $USER@gmail.com --json
# 3. Download all in parallel (gog supports it; xargs -P 4 is fine)
cat ids.txt | xargs -P 4 -I{} sh -c 'gog drive download --file-id {} --output ~/tmp/{}.pdf -a $USER@gmail.com'
# 4. Extract text from PDFs (parallel)
find ~/tmp -name "*.pdf" | xargs -P 4 -I{} pdftotext {} -
Why this exists
gws 403s on personal shared Google Docs (it's authorized to a service account). gog drive search + gog drive download is the working path for the user's own account, and it preserves the Google-Doc-as-PDF auto-export behavior. Verifying a Google Doc was correctly retrieved means: download size > 50 KB (Google Docs PDFs are ~100KB+ even for short docs) + pdftotext returns non-empty output + first paragraph matches the user's recollection.
Pitfalls
- Multiple-account confusion.
gwsandgogmay be authorized to different accounts on the same host (gws → service account; gog → personal). The active credentials vary by CLI. Confirm which account answered by reading the response'suser.email(Gmail) orsummary(Calendar) field, not by which CLI you invoked. - Service-account calendars show no events. A "calendarId=primary" call to gws returning
items: []and afirebase-adminsdk-…summary is the service account's empty calendar — not an auth error. Query the user's personal$USER@gmail.comcalendar instead. - gog positional arg gotcha.
gog calendar events list primary --days 2parsesprimaryas a subcommand. Use no positional arg for the default, or pass--calendar=…if supported; fall back to curl. - Don't paste the access token into logs. Echo
ACCESS_TOKEN_LEN=${#ACCESS_TOKEN}instead of the raw value when running through a logged tool. - Don't run the full 5-step OAuth setup if gog/gws already work — read the stored credentials and use them. The user shouldn't have to re-authorize twice.
- The bundled
google-workspaceskill is locked. You cannot patch itsSKILL.mdfrom a cron session. This skill is the user-editable mirror for the "Hermes not set up but a CLI is" branch.
04 同仓库更多 Skills
instinct
Use when the user invokes /instinct or /in, or asks to "message Instinct", "ask Instinct", "tell Instinct", "check Instinct", or read replies from the configured peer assistant. iMessage first, WhatsApp Web as fallback.
hr
Compatibility alias for history-resume; produce the same fresh-state, provenance-aware resume briefing.
history-resume
Resume briefing for the current repository branch or worktree using fresh state and admitted history sources.
grok
Alias for the canonical grokbot browser conversation workflow; /grok talks to the Grok bot the same way /dot talks to Dot.
codexa-bench
Compare `codexa` and normal `codex` on a fair split of comparable work by default; use `--strict` for duplicate runs of the exact same task.
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.