bot-process-control

Manage the Gmail Commander bot daemon and scheduled digest via launchd.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "bot-process-control" with this command: npx skills add terrylica/cc-skills/terrylica-cc-skills-bot-process-control

Bot Process Control

Manage the Gmail Commander bot daemon and scheduled digest via launchd.

Mandatory Preflight

Step 1: Check Current Process Status

echo "=== Gmail Commander Processes ===" pgrep -fl "gmail-commander" 2>/dev/null || echo "No processes found"

echo "" echo "=== launchd Status ===" launchctl list | grep gmail-commander 2>/dev/null || echo "No launchd jobs"

echo "" echo "=== PID Files ===" cat /tmp/gmail-commander-bot.pid 2>/dev/null && echo " (bot)" || echo "No bot PID file" cat /tmp/gmail-digest.pid 2>/dev/null && echo " (digest)" || echo "No digest PID file"

Two Services

Service Type Trigger PID File

Bot Daemon KeepAlive Always-on (grammY polling) /tmp/gmail-commander-bot.pid

Digest StartInterval Every 6 hours (21600s) /tmp/gmail-digest.pid

launchd Plist Templates

Bot Daemon — com.terryli.gmail-commander-bot.plist

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.terryli.gmail-commander-bot</string> <key>ProgramArguments</key> <array> <string>{{HOME}}/own/amonic/bin/gmail-commander-bot</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>NetworkState</key> <true/> </dict> <key>StandardOutPath</key> <string>{{HOME}}/.local/state/launchd-logs/gmail-commander-bot/stdout.log</string> <key>StandardErrorPath</key> <string>{{HOME}}/.local/state/launchd-logs/gmail-commander-bot/stderr.log</string> <key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>{{HOME}}/.local/share/mise/shims:/usr/local/bin:/usr/bin:/bin</string> </dict> <key>ThrottleInterval</key> <integer>10</integer> </dict> </plist>

Scheduled Digest — com.terryli.gmail-commander-digest.plist

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.terryli.gmail-commander-digest</string> <key>ProgramArguments</key> <array> <string>{{HOME}}/own/amonic/bin/gmail-commander-digest</string> </array> <key>StartInterval</key> <integer>21600</integer> <key>StandardOutPath</key> <string>{{HOME}}/.local/state/launchd-logs/gmail-commander-digest/stdout.log</string> <key>StandardErrorPath</key> <string>{{HOME}}/.local/state/launchd-logs/gmail-commander-digest/stderr.log</string> <key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>{{HOME}}/.local/share/mise/shims:/usr/local/bin:/usr/bin:/bin</string> </dict> </dict> </plist>

Quick Operations

Start Bot

launchctl load ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist

Stop Bot

launchctl unload ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist

Restart Bot

launchctl unload ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist launchctl load ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist

Force Kill (Emergency)

pkill -f "gmail-commander.*bot.ts" rm -f /tmp/gmail-commander-bot.pid

View Logs

Recent bot output (centralized launchd logs)

tail -50 ~/.local/state/launchd-logs/gmail-commander-bot/stderr.log

Recent digest output

tail -50 ~/.local/state/launchd-logs/gmail-commander-digest/stderr.log

Audit log (NDJSON, app-managed)

cat $PROJECT_DIR/logs/audit/$(date +%Y-%m-%d).ndjson | jq .

OAuth token refresher log

tail -20 ~/.local/state/launchd-logs/gmail-oauth-refresher/stderr.log

System Resources (Expected)

  • Memory: ~20-30 MB RSS (Bun runtime + grammY)

  • CPU: Negligible (idle polling, wakes on message)

  • Network: Minimal (single long-poll connection to Telegram API)

  • Disk: ~1 MB/day audit logs (14-day rotation)

Telegram Commands

Command Description

/inbox Show recent inbox emails

/search Search emails (Gmail query syntax)

/read Read email by ID

/compose Compose a new email

/reply Reply to an email

/abort Cancel current compose/reply action

/drafts List draft emails

/digest Run email digest now

/status Bot status and stats

/help Show all commands

Note: /abort cancels any in-progress compose or reply session. Works at any step in the flow.

OAuth Token Management

Two-Layer Token Architecture

Browser Auth (one-time, interactive) → Google issues: access_token (1h TTL) + refresh_token (7d TTL in Testing mode) → Saved to: ~/.claude/tools/gmail-tokens/<GMAIL_OP_UUID>.json

Silent Refresh (automatic, no browser) → Uses refresh_token to get new access_token → Fails with invalid_grant when refresh_token itself expires

Hourly Token Refresher (launchd)

A compiled Swift binary runs hourly to proactively refresh the access token:

File Path

Source ~/.claude/automation/gmail-token-refresher/main.swift

Binary ~/.claude/automation/gmail-token-refresher/gmail-oauth-token-hourly-refresher

Plist ~/Library/LaunchAgents/com.terryli.gmail-oauth-token-hourly-refresher.plist

Log $PROJECT_DIR/logs/token-refresher.log

Why hourly: Access tokens expire every 1 hour. Refreshing hourly keeps the token perpetually valid. Frequent refresh also increases the chance Google issues a new refresh_token , resetting its 7-day clock.

Verify it's running:

launchctl list | grep gmail-oauth-token tail -5 $PROJECT_DIR/logs/token-refresher.log

Credentials source: GMAIL_OP_UUID item in 1Password Claude Automation vault (fields: client_id , client_secret ). Accessed via service account token — no biometric prompt required.

Diagnosing invalid_grant

invalid_grant means the refresh token itself expired (not just the access token):

Symptom in audit log:

cat $PROJECT_DIR/logs/audit/$(date +%Y-%m-%d).ndjson | jq 'select(.event == "gmail.error")'

→ "Token expired, refreshing...\nError: invalid_grant\n"

Check token file age:

ls -la ~/.claude/tools/gmail-tokens/<GMAIL_OP_UUID>.json

Fix:

1. Delete expired token

rm ~/.claude/tools/gmail-tokens/<GMAIL_OP_UUID>.json

2. Trigger browser re-auth (opens Google consent page)

source $PROJECT_DIR/.env.launchd $PLUGIN_DIR/scripts/gmail-cli/gmail list -n 1

3. Restart bot

launchctl unload ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist launchctl load ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist

Root cause: Google OAuth apps in Testing mode issue refresh tokens with 7-day TTL. Permanent fix: publish the Google Cloud OAuth app (Google Cloud Console → OAuth consent screen → Publish app).

Diagnosing Stale PID Lock

If the bot exits uncleanly, the PID file may block restart:

Symptom: launchctl shows bot loaded but PID is dead

kill -0 $(cat /tmp/gmail-commander-bot.pid) 2>&1

→ "No such process"

Fix: restart via launchctl (acquireLock handles stale PIDs automatically)

launchctl unload ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist launchctl load ~/Library/LaunchAgents/com.terryli.gmail-commander-bot.plist

Post-Change Checklist

  • YAML frontmatter valid (no colons in description)

  • Trigger keywords current

  • Path patterns use $HOME not hardcoded paths

  • launchd plist templates match actual launcher scripts

  • OAuth token refresher launchd service loaded and running

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

chezmoi-workflows

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

multi-agent-e2e-validation

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

doppler-workflows

No summary provided by upstream source.

Repository SourceNeeds Review