Files
galoryber-daedalus/scripts/notify.py
T
Gary Lobermier 813a6a185e feat: Daedalus — autonomous Claude Code agent framework
A framework for running Claude Code as a fully autonomous agent via cron,
with persistent memory, task management, and session handoff.

Core features:
- Checkpoint-based session handoff (save game system)
- File-based state: tasks, worklog, questions, lessons-learned
- Full autonomy with human-in-the-loop via questions + Pushover
- PID management with stale session detection
- 11-step autonomous workflow prompt
- Customizable for any project via CLAUDE.md + ADAPTING.md guide

Named for the myth: Daedalus, the master craftsman who built
autonomous machines that worked tirelessly without supervision.
2026-02-10 16:17:59 -06:00

140 lines
4.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Pushover notification script for Daedalus.
Sends push notifications when Claude needs human input,
resolves questions, or reports session status.
Environment variables required:
PUSHOVER_USER_KEY — Your Pushover user key
PUSHOVER_APP_TOKEN — Your Pushover application token
Usage:
notify.py blocking "Title" "Message body" # High priority, red alert
notify.py question "Title" "Message body" # Normal priority, question
notify.py resolved "Title" "Message body" # Low priority, question resolved
notify.py info "Title" "Message body" # Lowest priority, FYI only
notify.py error "Title" "Message body" # High priority, session error
"""
import sys
import os
import urllib.request
import urllib.parse
import json
from datetime import datetime
PUSHOVER_API_URL = "https://api.pushover.net/1/messages.json"
# Priority levels map to Pushover priority values:
# -2 = lowest, -1 = low, 0 = normal, 1 = high, 2 = emergency (requires ack)
NOTIFICATION_TYPES = {
"blocking": {
"priority": 1,
"sound": "persistent",
"prefix": "🛑 BLOCKING",
"description": "Work is stopped — response needed",
},
"question": {
"priority": 0,
"sound": "pushover",
"prefix": "💬 QUESTION",
"description": "Claude has a non-blocking question",
},
"resolved": {
"priority": -1,
"sound": "none",
"prefix": "✅ RESOLVED",
"description": "A previous question is no longer needed",
},
"info": {
"priority": -2,
"sound": "none",
"prefix": "️ INFO",
"description": "Status update, no action needed",
},
"error": {
"priority": 1,
"sound": "siren",
"prefix": "❌ ERROR",
"description": "Session error or unexpected failure",
},
}
def send_notification(notif_type: str, title: str, message: str) -> bool:
"""Send a Pushover notification.
Returns True on success, False on failure (prints error to stderr).
"""
user_key = os.environ.get("PUSHOVER_USER_KEY")
app_token = os.environ.get("PUSHOVER_APP_TOKEN")
if not user_key or not app_token:
print(
"ERROR: PUSHOVER_USER_KEY and PUSHOVER_APP_TOKEN must be set.",
file=sys.stderr,
)
return False
if notif_type not in NOTIFICATION_TYPES:
print(
f"ERROR: Unknown notification type '{notif_type}'. "
f"Valid types: {', '.join(NOTIFICATION_TYPES.keys())}",
file=sys.stderr,
)
return False
config = NOTIFICATION_TYPES[notif_type]
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
full_title = f"{config['prefix']}: {title}"
full_message = f"{message}\n\n🕐 {timestamp}"
data = urllib.parse.urlencode({
"token": app_token,
"user": user_key,
"title": full_title,
"message": full_message,
"priority": config["priority"],
"sound": config["sound"],
}).encode("utf-8")
try:
req = urllib.request.Request(PUSHOVER_API_URL, data=data)
with urllib.request.urlopen(req, timeout=10) as response:
result = json.loads(response.read().decode("utf-8"))
if result.get("status") == 1:
print(f"Notification sent: {full_title}")
return True
else:
print(
f"ERROR: Pushover API error: {result}",
file=sys.stderr,
)
return False
except Exception as e:
print(f"ERROR: Failed to send notification: {e}", file=sys.stderr)
return False
def main():
if len(sys.argv) < 4:
print("Usage: notify.py <type> \"Title\" \"Message\"")
print(f"Types: {', '.join(NOTIFICATION_TYPES.keys())}")
print("\nDescriptions:")
for name, config in NOTIFICATION_TYPES.items():
print(f" {name:10s}{config['description']}")
sys.exit(1)
notif_type = sys.argv[1]
title = sys.argv[2]
message = sys.argv[3]
success = send_notification(notif_type, title, message)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()