| name | hour-meter |
| description | Track elapsed time from a set epoch with tamper-evident locking. Like an analog Hobbs meter but digital. Use for tracking uptime, service hours, time since events, sobriety counters, project duration, equipment runtime. Supports create, lock (seal), check, verify against external hash, list, and export operations. |
Hour Meter
Life event tracker with three modes, milestone notifications, and tamper-evident verification.
Three Modes
COUNT UP ā Time since an event
meter.py create smoke-free --start "2025-06-15T08:00:00Z" -d "Last cigarette"
meter.py milestone smoke-free -t hours -v 720 -m "š 30 days smoke-free!"
meter.py lock smoke-free
COUNT DOWN ā Time until an event
meter.py create baby --start "2026-01-15" --end "2026-10-15" --mode down -d "Baby arriving!"
meter.py milestone baby -t percent -v 33 -m "š¶ First trimester complete!"
COUNT BETWEEN ā Journey from start to end
meter.py create career --start "1998-05-15" --end "2038-05-15" -d "40-year career"
meter.py milestone career -t percent -v 50 -m "š Halfway through career!"
meter.py career --meter career --rate 85 --raise-pct 2.5
Tamper-Evident Persistence
When you lock a meter, you get a paper code ā a short, checksummed code you can write on paper:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PAPER CODE (write this down): ā
ā 318B-3229-C523-2F9C-V ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Four Ways to Save (Non-Technical)
1ļøā£ PAPER ā Write the code on paper/sticky note
- 20 characters with dashes, easy to copy
- Built-in checksum catches typos when verifying
- Keep in wallet, safe, or taped to equipment
2ļøā£ PHOTO ā Screenshot or photograph the lock screen
- Store in camera roll, cloud photos
- Visual backup, no typing required
3ļøā£ WITNESS FILE ā Auto-saved to ~/.openclaw/meter-witness.txt
- Append-only log of all locked meters
- Sync folder to Dropbox/iCloud/Google Drive for cloud backup
- Contains paper code + full hash + timestamp
4ļøā£ EMAIL TO SELF ā Click the mailto: link or copy the one-liner
- Opens your email client with pre-filled subject and body
- Or copy the compact message:
š my-meter | Code: XXXX-XXXX-XXXX-XXXX-C | Locked: 2026-02-02
- Send to yourself, search inbox later to verify
5ļøā£ SENDGRID EMAIL ā Auto-send verification email on lock
export SENDGRID_API_KEY=SG.xxxxx
export SENDGRID_FROM_EMAIL=verified@yourdomain.com
meter.py lock my-meter --email you@example.com
- Sends a beautifully formatted HTML email with paper code
- Requires a verified sender in SendGrid (see SendGrid docs)
- Great for automated workflows
Verifying Later
meter.py verify my-meter "318B-3229-C523-2F9C-V"
Milestones
meter.py milestone <name> --type hours --value 1000 --message "1000 hours!"
meter.py milestone <name> --type percent --value 50 --message "Halfway!"
meter.py check-milestones
Email Milestone Notifications (v1.3.0)
Get milestone notifications sent directly to your email:
meter.py create my-meter \
--notify-email you@example.com \
--from-email verified@yourdomain.com \
-d "My tracked event"
meter.py milestone my-meter -t hours -v 24 -m "š 24 hours complete!"
meter.py check-milestones
Email includes:
- šÆ Milestone message
- ā±ļø Current elapsed time
- š Meter description
Requires SENDGRID_API_KEY environment variable.
Milestone Notifications: Heartbeat vs Cron
Recommended: HEARTBEAT (~30 min resolution)
- Add to
HEARTBEAT.md: Run meter.py check-milestones and notify triggered
- Batches with other periodic checks
- Cost-efficient: shares token usage with other heartbeat tasks
- Good for most use cases (quit tracking, career milestones, etc.)
ACTION: Triggers (Agent Automation)
Prefix milestone messages with ACTION: to trigger agent execution instead of just posting:
meter.py milestone my-meter -t hours -v 24 -m "š 24 hours complete!"
meter.py milestone my-meter -t hours -v 24 -m "ACTION: Check the weather and post a summary"
Configure in HEARTBEAT.md:
- If message starts with "ACTION:", execute it as an instruction
- Otherwise, post the message to the configured channel
Alternative: CRON (precise timing)
- Use when exact timing matters (e.g., countdown to event)
- ā ļø Cost warning: Cron at 1-minute intervals = 1,440 API calls/day = expensive!
- If using cron, keep intervals ā„15 minutes to manage costs
- Best for one-shot reminders, not continuous monitoring
Rule of thumb: If 30-minute resolution is acceptable, use heartbeat. Save cron for precision timing.
Quick Reference
meter.py create <name> [--start T] [--end T] [--mode up|down|between] [-d DESC]
meter.py lock <name>
meter.py verify <name> <code>
meter.py check <name>
meter.py milestone <name> -t hours|percent -v N -m "..."
meter.py check-milestones
meter.py witness [--show] [--path]
meter.py list
meter.py career [--meter M] [--rate R] [--raise-pct P]
meter.py export [name]
SendGrid Email Webhook Server
Receive real-time notifications when recipients open, click, bounce, or unsubscribe from your meter verification emails.
Setup
python sendgrid_webhook.py --port 8089 --discord-webhook https://discord.com/api/webhooks/xxx/yyy
python sendgrid_webhook.py --process-events
python sendgrid_webhook.py --process-events --json
Discord Webhook Setup (Recommended)
- In your Discord channel, go to Settings > Integrations > Webhooks
- Click New Webhook, copy the URL
- Pass to
--discord-webhook or set DISCORD_WEBHOOK_URL env var
SendGrid Setup
- Go to SendGrid > Settings > Mail Settings > Event Webhook
- Click "Create new webhook" (or edit existing)
- Set HTTP POST URL to:
https://your-domain.com/webhooks/sendgrid
- Select all event types under Actions to be posted:
- Engagement data: Opened, Clicked, Unsubscribed, Spam Reports, Group Unsubscribes, Group Resubscribes
- Deliverability Data: Processed, Dropped, Deferred, Bounced, Delivered
- Account Data: Account Status Change
- Click "Test Integration" to verify - this fires all event types to your webhook
- Important: Click Save to enable the webhook!
- (Optional) Enable Signed Event Webhook for security and set
SENDGRID_WEBHOOK_PUBLIC_KEY

Event Types
| Event | Emoji | Description |
|---|
| delivered | ā
| Email reached recipient |
| open | š | Recipient opened email |
| click | š | Recipient clicked a link |
| bounce | ā ļø | Email bounced |
| unsubscribe | š | Recipient unsubscribed |
| spamreport | šØ | Marked as spam |
Environment Variables
SENDGRID_WEBHOOK_PUBLIC_KEY
SENDGRID_WEBHOOK_MAX_AGE_SECONDS
WEBHOOK_PORT
DISCORD_WEBHOOK_URL
WEBHOOK_LOG_FILE
The 80,000 Hours Concept
Career as finite inventory: 40 years Ć 2,000 hrs/year = 80,000 hours.
meter.py career --hours-worked 56000 --rate 85 --raise-pct 2.5