From e33b5cbac6e4c163483793e98830bbf98bd7c774 Mon Sep 17 00:00:00 2001 From: zlei9 Date: Sun, 29 Mar 2026 13:04:57 +0800 Subject: [PATCH] Initial commit with translated description --- README.md | 135 ++++++++++++++ SKILL.md | 147 +++++++++++++++ _meta.json | 6 + references/usage.md | 202 ++++++++++++++++++++ scripts/monitor_task.py | 273 ++++++++++++++++++++++++++++ scripts/send_status.py | 180 ++++++++++++++++++ scripts/send_status_websocket.py | 113 ++++++++++++ scripts/send_status_with_logging.py | 215 ++++++++++++++++++++++ scripts/test_send_status.py | 62 +++++++ 9 files changed, 1333 insertions(+) create mode 100644 README.md create mode 100644 SKILL.md create mode 100644 _meta.json create mode 100644 references/usage.md create mode 100644 scripts/monitor_task.py create mode 100644 scripts/send_status.py create mode 100644 scripts/send_status_websocket.py create mode 100644 scripts/send_status_with_logging.py create mode 100644 scripts/test_send_status.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..634ed9d --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Task Status Skill + +A Clawdbot skill for sending short status descriptions in chat, with automatic periodic monitoring that updates every 5 seconds. + +## Quick Start + +### Install into Clawdbot +```bash +# Copy to Clawdbot skills directory +copy task-status "C:\Users\Luffy\AppData\Roaming\npm\node_modules\clawdbot\skills\task-status" +``` + +### Usage + +#### Manual Updates (Single Status Messages) +```bash +python scripts/send_status.py "Still working..." "progress" "task_name" +python scripts/send_status.py "Complete!" "success" "task_name" +python scripts/send_status.py "Error!" "error" "task_name" +``` + +#### Automatic Periodic Monitoring (Every 5 seconds) +```bash +# Start monitoring before your long task +python scripts/monitor_task.py start "my_long_task" "progress" + +# Your long running task/process here... +# Monitor sends "Still working..." updates every 5 seconds automatically + +# Stop monitoring with final status +python scripts/monitor_task.py stop "my_long_task" "success" "Task complete!" +``` + +## Features + +- **Manual Status Updates**: Send one-off status messages +- **Automatic Monitoring**: Periodic "heartbeat" updates every 5 seconds +- **ASCII Fallback**: Uses ASCII symbols (->, OK, !, ?) on Windows CMD +- **Emoji Support**: Uses emojis (๐Ÿ”„, โœ…, โŒ, โš ๏ธ) on Windows Terminal/PowerShell +- **Background Monitoring**: Runs independently until stopped +- **State Management**: Tracks active monitors in `.task_status_state.json` + +## Status Types + +| Type | Emoji | ASCII | Use Case | +|------|-------|-------|----------| +| progress | ๐Ÿ”„ | -> | Ongoing work | +| success | โœ… | OK | Completed successfully | +| error | โŒ | ! | Failed, cannot continue | +| warning | โš ๏ธ | ? | Issue but continuing | + +## Examples + +### Long File Processing +```bash +# Start monitor +python monitor_task.py start "video_convert" "progress" + +# Convert video (takes 5 minutes) +ffmpeg -i input.mp4 output.mp4 + +# Stop monitor +python monitor_task.py stop "video_convert" "success" "Conversion complete" +``` + +### Database Migration +```bash +# Start monitor with 10-second interval +python monitor_task.py start "db_migration" "progress" --interval 10 + +# Run migration +python migrate_db.py + +# Stop monitor +python monitor_task.py stop "db_migration" "success" "Migrated 50,000 records" +``` + +### API Rate Limiting +```bash +# Start monitor +python monitor_task.py start "api_sync" "progress" + +# Make 1000 API calls (takes 10 minutes) +python sync_api.py + +# Stop monitor +python monitor_task.py stop "api_sync" "success" "All calls successful" +``` + +## Monitoring Commands + +- `monitor_task.py start [--interval ]` - Start monitoring +- `monitor_task.py stop ` - Stop and send final status +- `monitor_task.py status` - View active monitors +- `monitor_task.py cancel_all` - Cancel all monitors (no final status) + +## File Structure + +``` +task-status/ +โ”œโ”€โ”€ SKILL.md # Skill metadata and documentation +โ”œโ”€โ”€ references/ +โ”‚ โ””โ”€โ”€ usage.md # Detailed usage guide +โ”œโ”€โ”€ scripts/ +โ”‚ โ”œโ”€โ”€ send_status.py # Manual status updates +โ”‚ โ””โ”€โ”€ monitor_task.py # Automatic periodic monitoring +โ”œโ”€โ”€ .task_status_state.json # Active monitor state (generated) +โ””โ”€โ”€ README.md # This file +``` + +## Integration with Clawdbot + +Add to your workspace in `AGENTS.md` or `TOOLS.md`: + +```markdown +### Task Status +- Manual updates: `python scripts/send_status.py "message" "type" "step"` +- Auto monitoring: `python monitor_task.py start "task" "progress"` +- Periodic updates: Every 5 seconds automatically +``` + +## Tips + +1. **Short Messages**: Keep status messages under 140 characters +2. **Specific Names**: Use descriptive task names for clarity +3. **Always Stop**: Remember to stop the monitor with final status +4. **Check Status**: Use `monitor_task.py status` to see active monitors +5. **Cleanup**: Use `cancel_all` if monitors get stuck + +## Troubleshooting + +- **Monitor stuck**: Run `python monitor_task.py cancel_all` +- **No output**: Check if monitor is running with `status` command +- **Encoding issues**: ASCII fallback will be used automatically +- **Task done but monitor still running**: Stop it manually with `stop` command \ No newline at end of file diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..b7086c8 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,147 @@ +--- +name: task-status +description: "ไธบ้•ฟๆ—ถ้—ด่ฟ่กŒ็š„ไปปๅŠกๅœจ่Šๅคฉไธญๅ‘้€็ฎ€็Ÿญ็Šถๆ€ๆ่ฟฐใ€‚" +--- + +# Task Status Skill + +## Quick Start + +### Manual Status Updates +```bash +python scripts/send_status.py "Starting data fetch..." "progress" "step1" +python scripts/send_status.py "Processing complete" "success" "final" +python scripts/send_status.py "Error: Missing API key" "error" "auth" +``` + +### Automatic Periodic Monitoring (Every 5 seconds) +```bash +# Start monitoring a long-running task +python scripts/monitor_task.py start "My Long Task" "processing" + +# Monitor will send "Still working..." updates every 5 seconds +# When task completes, report final status +python scripts/monitor_task.py stop "My Long Task" "success" "Completed successfully!" +``` + +## Status Types + +- **progress**: Ongoing work (shows ๐Ÿ”„ or ->) +- **success**: Task complete (shows โœ… or OK) +- **error**: Failed task (shows โŒ or !) +- **warning**: Issue but continuing (shows โš ๏ธ or ?) + +## Periodic Monitoring + +The `monitor_task.py` script provides automatic updates: + +### Starting Monitor +```bash +python scripts/monitor_task.py start "" "" [--interval ] +``` + +- Automatically sends "Still working..." updates every 5 seconds +- Runs in background until stopped +- Can be customized with different intervals + +### Stopping Monitor +```bash +python scripts/monitor_task.py stop "" "" "" +``` + +### Example: Long File Processing +```bash +# Start monitoring +python scripts/monitor_task.py start "video_processing" "progress" + +# ... long processing happens here ... + +# Stop with final status +python scripts/monitor_task.py stop "video_processing" "success" "Processing complete!" +``` + +## Manual Updates (Quick Status) + +For single status updates without monitoring: + +```bash +python scripts/send_status.py "Still fetching data..." "progress" "fetch" +python scripts/send_status.py "Processing records: 250/1000" "progress" "process" +python scripts/send_status.py "Complete! 3 files ready" "success" "final" +python scripts/send_status.py "Error: Connection timeout" "error" "api" +``` + +## When to Use Each Method + +### Use Manual Updates When: +- Task is short (under 30 seconds) +- You want control over when updates are sent +- Task has discrete, meaningful milestones + +### Use Periodic Monitoring When: +- Task is long-running (over 1 minute) +- You want consistent "heartbeat" updates every 5 seconds +- Task has long periods of quiet work +- You want to reassure user that work is ongoing + +## Message Guidelines + +Keep status messages under 140 characters. Examples: + +- **Progress**: "Still fetching data..." or "Processing records: 250/1000" +- **Success**: "Complete! 3 files ready" or "Task finished successfully" +- **Error**: "Error: Connection timeout" or "Failed: Missing API key" +- **Warning**: "Continuing despite timeout" or "Partial success: 5/10 files" + +## Advanced Usage + +### With Additional Details +```bash +python scripts/send_status.py "Uploading..." "progress" "upload" --details "File: report.pdf (2.4MB)" +``` + +### Different Intervals +```bash +python scripts/monitor_task.py start "data_sync" "progress" --interval 10 +``` + +### Importing for Python Scripts +```python +from send_status import send_status + +def long_task(): + send_status("Starting...", "progress", "step1") + # ... work + send_status("Step complete", "success", "step1") +``` + +## Automation with Clawdbot Cron + +For scheduled tasks, use Clawdbot's cron feature: + +```python +# In a script or session +from cron import add + +# Every 5 seconds, check status +job = { + "text": "Check status update", + "interval": "5s", + "enabled": True +} +add(job) +``` + +This allows status updates even when you're not actively watching. + +## Installation + +To use this skill, copy the `task-status` folder into your Clawdbot skills directory: + +``` +C:\Users\Luffy\AppData\Roaming\npm\node_modules\clawdbot\skills\task-status +``` + +Or add it to your workspace and reference it from `AGENTS.md` or `TOOLS.md`. + +Once installed, the skill will be available for any task where you need periodic status updates. \ No newline at end of file diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..5212f01 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn7e5frkfxx996cjqrkhryj95x7zx5we", + "slug": "task-status", + "version": "1.0.0", + "publishedAt": 1769504488946 +} \ No newline at end of file diff --git a/references/usage.md b/references/usage.md new file mode 100644 index 0000000..99f2384 --- /dev/null +++ b/references/usage.md @@ -0,0 +1,202 @@ +# Task Status Usage Guide + +## Quick Examples + +### Manual Status Updates (Single Messages) +```bash +# Progress update +python send_status.py "Fetching data..." "progress" "fetch" + +# Success update +python send_status.py "Done! 150 records processed" "success" "process" + +# Error update +python send_status.py "Failed to connect to API" "error" "api_call" + +# Warning update +python send_status.py "Continuing despite timeout" "warning" "timeout" +``` + +### Automated Periodic Monitoring (Every 5 seconds) +```bash +# Start monitoring a long task +python monitor_task.py start "video_processing" "progress" + +# Monitor runs in background, sending "Still working..." updates every 5 seconds + +# Stop monitoring with final status +python monitor_task.py stop "video_processing" "success" "Processing complete!" + +# Or with an error +python monitor_task.py stop "video_processing" "error" "Failed: Corrupt file" +``` + +### Python Script +```python +from send_status import send_status + +def process_data(): + send_status("Reading files...", "progress", "read") + # ... work + send_status("Processing complete", "success", "process") +``` + +### Shell Script +```bash +#!/bin/bash +python send_status.py "Starting backup..." "progress" "backup" +# ... backup command +python send_status.py "Backup complete" "success" "backup" +``` + +## Status Types + +| Type | Emoji | ASCII | When to Use | +|------|-------|-------|-------------| +| progress | ๐Ÿ”„ | -> | Ongoing work, "still working on it" | +| success | โœ… | OK | Task completed successfully | +| error | โŒ | ! | Task failed, cannot continue | +| warning | โš ๏ธ | ? | Issue but continuing | + +## When to Use Each Method + +### Use Manual Updates When: +- Task is short (under 30 seconds) +- You want control over when updates are sent +- Task has discrete, meaningful milestones + +### Use Periodic Monitoring When: +- Task is long-running (over 1 minute) +- You want consistent "heartbeat" updates every 5 seconds +- Task has long periods of quiet work +- You want to reassure user that work is ongoing + +## Periodic Monitoring Details + +### Starting a Monitor +```bash +python monitor_task.py start "" "" [--interval ] +``` + +- Sends "Still working..." updates every 5 seconds by default +- Runs in background until stopped +- Can customize interval with `--interval` + +### Viewing Active Monitors +```bash +python monitor_task.py status +``` + +### Cancelling All Monitors (Without Final Status) +```bash +python monitor_task.py cancel_all +``` + +## Best Practices + +1. **Keep messages short** - Under 140 characters +2. **Be specific** - Include step names for clarity +3. **Update periodically** - Every ~4 seconds for long tasks (or use monitoring) +4. **Use details** - Add extra context when needed +5. **End with status** - Always send final success/error + +## Common Patterns + +### Multi-step Task (Manual) +```bash +python send_status.py "Step 1/5: Validating input" "progress" "step1" +# ... step 1 +python send_status.py "Step 2/5: Processing data" "progress" "step2" +# ... step 2 +# ... etc +python send_status.py "All steps complete" "success" "final" +``` + +### Long-Running Task (Automatic Monitoring) +```bash +# Start monitor before starting the task +python monitor_task.py start "data_migration" "progress" + +# Run the actual task (can take minutes/hours) +# Monitor sends "Still working..." updates every 5 seconds + +# When task finishes, stop monitor with final status +python monitor_task.py stop "data_migration" "success" "Migration complete: 5000 records" +``` + +### With Details +```bash +python send_status.py "Uploading..." "progress" "upload" --details "File: report.pdf (2.4MB)" +``` + +### Error Recovery +```bash +python send_status.py "Connection failed, retrying..." "warning" "retry" +# ... retry logic +if success: + python send_status.py "Retry successful" "success" "retry" +else: + python send_status.py "Retry failed, giving up" "error" "retry" +``` + +### Long Task with Manual Control +```bash +# Start monitor +python monitor_task.py start "processing" "progress" + +# ... do work ... + +# Check status periodically +python monitor_task.py status + +# When done, stop monitor +python monitor_task.py stop "processing" "success" "Finished!" +``` + +## Integration + +### Import send_status for Python Scripts +```python +from send_status import send_status + +msg = send_status("Working...", "progress", "work") +print(f"Logged: {msg}") # Output: "-> [work] Working..." +``` + +### Use in Shell Scripts +```bash +#!/bin/bash +send_status() { + python send_status.py "$1" "$2" "$3" +} + +send_status "Starting process" "progress" "main" +# ... process +send_status "Done" "success" "main" +``` + +### Automation with Clawdbot Cron +For scheduled tasks that need periodic status updates, use Clawdbot's cron feature. + +## Monitoring Use Cases + +### File Processing +```bash +python monitor_task.py start "file_proc" "progress" +# Process 1000 files (takes 10 minutes) +python monitor_task.py stop "file_proc" "success" "Processed 1000 files" +``` + +### Data Sync +```bash +python monitor_task.py start "sync" "progress" --interval 10 +# Sync databases (takes 5 minutes) +python monitor_task.py stop "sync" "success" "Sync complete" +``` + +### API Calls +```bash +python monitor_task.py start "api_call" "progress" +# Make 1000 API requests +python monitor_task.py stop "api_call" "success" "All 1000 requests successful" +``` \ No newline at end of file diff --git a/scripts/monitor_task.py b/scripts/monitor_task.py new file mode 100644 index 0000000..70ab99a --- /dev/null +++ b/scripts/monitor_task.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +""" +Monitor a long-running task and send periodic status updates every 5 seconds. + +Usage: + # Start monitoring + python monitor_task.py start "" "" [--interval ] + + # Stop monitoring (sends final status) + python monitor_task.py stop "" "" "" + +Status Types (for final status): + progress - Ongoing work (shows ๐Ÿ”„) + success - Task complete (shows โœ…) + error - Failed task (shows โŒ) + warning - Issue but continuing (shows โš ๏ธ) + +Examples: + # Start monitoring a video processing task + python monitor_task.py start "video_proc" "progress" + + # Later, stop with success + python monitor_task.py stop "video_proc" "success" "Processing complete!" + + # Or with an error + python monitor_task.py stop "video_proc" "error" "Failed: Corrupt file" +""" + +import sys +sys.stdout.reconfigure(encoding='utf-8') + +import time +import json +import threading +from pathlib import Path +from datetime import datetime +from send_status import send_status, can_encode_emoji + +# State file to track active monitors +# Path: C:\Users\Luffy\clawd\task-status\scripts\monitor_task.py +# Parent: C:\Users\Luffy\clawd\task-status\scripts +# Parent.parent: C:\Users\Luffy\clawd\task-status +STATE_FILE = Path(__file__).parent.parent / ".task_status_state.json" + +def load_state(): + """Load active monitors from state file.""" + if STATE_FILE.exists(): + try: + with open(STATE_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + except: + pass + return {} + +def save_state(state): + """Save active monitors to state file.""" + with open(STATE_FILE, 'w', encoding='utf-8') as f: + json.dump(state, f, indent=2) + +def monitor_worker(task_name: str, interval: int = 5): + """ + Background worker that sends periodic updates. + Runs until stopped via state file check. + """ + import sys + sys.stdout.reconfigure(encoding='utf-8') + + print(f"[DEBUG] monitor_worker started for '{task_name}' with {interval}s interval", file=sys.stderr) + sys.stderr.flush() + + last_update = time.time() + print(f"[DEBUG] Initial last_update: {last_update}", file=sys.stderr) + sys.stderr.flush() + + iteration = 0 + while True: + iteration += 1 + + # Check if task still exists in state + state = load_state() + print(f"[DEBUG] Iteration {iteration}: State keys = {list(state.keys())}", file=sys.stderr) + sys.stderr.flush() + + print(f"[DEBUG] Checking if '{task_name}' in state: {task_name in state}", file=sys.stderr) + sys.stderr.flush() + + if task_name not in state: + print(f"[DEBUG] Task '{task_name}' not in state, stopping worker", file=sys.stderr) + sys.stderr.flush() + # Task was stopped + break + + # Send "Still working..." update if enough time has passed + current_time = time.time() + elapsed = current_time - last_update + print(f"[DEBUG] Iteration {iteration}: elapsed={elapsed:.1f}s, interval={interval}s", file=sys.stderr) + sys.stderr.flush() + + if elapsed >= interval: + print(f"[DEBUG] Time to send update! elapsed={elapsed:.1f}s >= interval={interval}s", file=sys.stderr) + sys.stderr.flush() + + # Send progress update + message = f"Still working..." + status = "progress" + + # Try to send status + try: + print(f"[DEBUG] Sending status: {message} {status} {task_name}", file=sys.stderr) + sys.stderr.flush() + send_status(message, status, task_name) + print(f"[DEBUG] Status sent successfully", file=sys.stderr) + sys.stderr.flush() + except Exception as e: + # If send fails, log but continue + print(f"[DEBUG] Monitor warning: {e}", file=sys.stderr) + sys.stderr.flush() + + last_update = current_time + print(f"[DEBUG] Updated last_update: {last_update}", file=sys.stderr) + sys.stderr.flush() + else: + print(f"[DEBUG] Not time yet, sleeping...", file=sys.stderr) + sys.stderr.flush() + + # Sleep briefly to avoid tight loop (reduced from 0.5 to 0.1) + time.sleep(0.1) + + print(f"[DEBUG] monitor_worker finished", file=sys.stderr) + sys.stderr.flush() + +def start_monitor(task_name: str, status_type: str = "progress", interval: int = 5): + """ + Start a new monitor for the given task. + """ + import sys + sys.stdout.reconfigure(encoding='utf-8') + + print(f"[DEBUG] start_monitor called: task_name='{task_name}', status_type='{status_type}', interval={interval}", file=sys.stderr) + + # Validate status type + if status_type not in ["progress", "success", "error", "warning"]: + print(f"Invalid status type: {status_type}", file=sys.stderr) + sys.exit(1) + + # Check if already monitoring + state = load_state() + print(f"[DEBUG] Current state: {state}", file=sys.stderr) + + if task_name in state: + print(f"Already monitoring task '{task_name}'", file=sys.stderr) + sys.exit(1) + + # Add to state + state[task_name] = { + "status_type": status_type, + "interval": interval, + "started_at": datetime.now().isoformat() + } + print(f"[DEBUG] Saving new state: {state}", file=sys.stderr) + save_state(state) + + # Send initial status + print(f"[DEBUG] Sending initial status", file=sys.stderr) + send_status(f"Monitoring started (updates every {interval}s)", status_type, task_name) + + # Start background monitor thread + print(f"[DEBUG] Starting monitor thread", file=sys.stderr) + thread = threading.Thread( + target=monitor_worker, + args=(task_name, interval), + daemon=True + ) + thread.start() + print(f"[DEBUG] Monitor thread started: {thread}", file=sys.stderr) + + print(f"Monitor started for '{task_name}' with {interval}s interval") + +def stop_monitor(task_name: str, final_status: str, final_message: str): + """ + Stop monitoring and send final status. + """ + import sys + sys.stdout.reconfigure(encoding='utf-8') + + print(f"[DEBUG] stop_monitor called: task_name='{task_name}', final_status='{final_status}', final_message='{final_message}'", file=sys.stderr) + + # Check if monitoring + state = load_state() + print(f"[DEBUG] Current state: {state}", file=sys.stderr) + + if task_name not in state: + print(f"No active monitor for task '{task_name}'", file=sys.stderr) + sys.exit(1) + + # Remove from state + print(f"[DEBUG] Removing '{task_name}' from state", file=sys.stderr) + del state[task_name] + print(f"[DEBUG] New state: {state}", file=sys.stderr) + save_state(state) + + # Send final status + print(f"[DEBUG] Sending final status", file=sys.stderr) + send_status(final_message, final_status, task_name) + + print(f"Monitor stopped for '{task_name}'") + print(f"Final status: {final_message}") + +def main(): + if len(sys.argv) < 2: + print(__doc__) + sys.exit(1) + + action = sys.argv[1].lower() + + if action == "start": + if len(sys.argv) < 4: + print("Usage: monitor_task.py start [--interval ]", file=sys.stderr) + sys.exit(1) + + task_name = sys.argv[2] + status_type = sys.argv[3] + interval = 5 + + # Check for --interval flag + if len(sys.argv) > 5 and sys.argv[4] == "--interval": + try: + interval = int(sys.argv[5]) + except ValueError: + print("Interval must be an integer", file=sys.stderr) + sys.exit(1) + + start_monitor(task_name, status_type, interval) + + elif action == "stop": + if len(sys.argv) < 5: + print("Usage: monitor_task.py stop ", file=sys.stderr) + sys.exit(1) + + task_name = sys.argv[2] + final_status = sys.argv[3] + final_message = sys.argv[4] + + stop_monitor(task_name, final_status, final_message) + + elif action == "status": + # Show active monitors + state = load_state() + if not state: + print("No active monitors") + else: + print("Active monitors:") + for task, info in state.items(): + print(f" {task}: {info['status_type']} (interval: {info['interval']}s)") + + elif action == "cancel_all": + # Stop all monitors without sending final status + state = load_state() + if state: + for task_name in list(state.keys()): + del state[task_name] + save_state(state) + print(f"Cancelled all monitors: {list(state.keys())}") + else: + print("No active monitors to cancel") + + else: + print(f"Unknown action: {action}", file=sys.stderr) + print(__doc__) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/send_status.py b/scripts/send_status.py new file mode 100644 index 0000000..bd5c6e7 --- /dev/null +++ b/scripts/send_status.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +Send status messages for long-running tasks. + +Usage: + python send_status.py "" "" "" [--details "
"] + +Status Types: + progress - Ongoing work (shows ๐Ÿ”„) + success - Task complete (shows โœ…) + error - Failed task (shows โŒ) + warning - Issue but continuing (shows โš ๏ธ) + +Example: + python send_status.py "Fetching data..." "progress" "fetch" + python send_status.py "Complete!" "success" "final" + python send_status.py "Error: Missing file" "error" "file_check" +""" + +import sys +sys.stdout.reconfigure(encoding='utf-8') + +import json +import os +import websocket + +# Status type to emoji mapping +STATUS_EMOJIS = { + "progress": "๐Ÿ”„", + "success": "โœ…", + "error": "โŒ", + "warning": "โš ๏ธ" +} + +def can_encode_emoji(text: str, encoding: str = None) -> bool: + """Check if text can be encoded with the given encoding.""" + if encoding is None: + encoding = sys.stdout.encoding + try: + text.encode(encoding) + return True + except (UnicodeEncodeError, LookupError): + return False + +def send_status(message: str, status_type: str, step_name: str, details: str = None): + """ + Format and send a status message to Telegram. + + Args: + message: Short status message (< 140 chars) + status_type: Type of status (progress, success, error, warning) + step_name: Name of the step being reported + details: Optional additional context + """ + if status_type not in STATUS_EMOJIS: + raise ValueError(f"Invalid status_type: {status_type}") + + emoji = STATUS_EMOJIS[status_type] + + # Choose emoji or ASCII based on encoding capability + if can_encode_emoji(emoji): + prefix = emoji + else: + prefix = emoji # Use emoji anyway, most modern terminals support it + + # Build the message + formatted = f"{prefix} [{step_name}] {message}" + + if details: + formatted += f" ({details})" + + # Keep it concise (under 140 chars) + if len(formatted) > 140: + formatted = formatted[:137] + "..." + + # Try WebSocket first (fastest) + gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN") + + if gateway_token: + try: + gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789") + target = os.environ.get("TELEGRAM_TARGET", "7590912486") + ws_url = f"ws://127.0.0.1:{gateway_port}/ws" + + # Connect and send + ws = websocket.create_connection(ws_url, timeout=10) + + # Send message directly (no handshake needed for simple messages) + msg = { + "type": "message", + "action": "send", + "target": target, + "message": formatted, + "channel": "telegram" + } + ws.send(json.dumps(msg)) + + # Try to receive response but don't wait too long + try: + response = ws.recv() + result = json.loads(response) + # If we got a challenge, respond to it + if result.get("event") == "connect.challenge": + # Send handshake with token + handshake = {"type": "handshake", "token": gateway_token} + ws.send(json.dumps(handshake)) + # Try to receive again + response = ws.recv() + result = json.loads(response) + except: + # If we can't receive, assume message was sent + pass + + ws.close() + return formatted + + except Exception as e: + print(f"โš ๏ธ WebSocket failed: {e}", file=sys.stderr) + + # Fallback: try CLI + import subprocess + import shutil + + clawdbot_path = shutil.which("clawdbot") + + if not clawdbot_path: + clawdbot_paths = [ + "C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot.cmd", + "C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot" + ] + for path in clawdbot_paths: + if os.path.exists(path): + clawdbot_path = path + break + + if clawdbot_path: + try: + target = os.environ.get("TELEGRAM_TARGET", "7590912486") + result = subprocess.run( + [ + clawdbot_path, + "message", + "send", + "--target", target, + "--message", formatted, + "--channel", "telegram" + ], + capture_output=True, + text=True, + timeout=20 + ) + if result.returncode == 0: + return formatted + except Exception as e: + print(f"โš ๏ธ CLI failed: {e}", file=sys.stderr) + + # Final fallback: print to console + print(formatted, file=sys.stderr) + + return formatted + +def main(): + if len(sys.argv) < 4: + print(__doc__) + sys.exit(1) + + message = sys.argv[1] + status_type = sys.argv[2] + step_name = sys.argv[3] + details = sys.argv[4] if len(sys.argv) > 4 else None + + try: + result = send_status(message, status_type, step_name, details) + print(f"Status sent: {result}") + except Exception as e: + print(f"Error sending status: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/send_status_websocket.py b/scripts/send_status_websocket.py new file mode 100644 index 0000000..ec7f009 --- /dev/null +++ b/scripts/send_status_websocket.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Send status messages using Clawdbot WebSocket API (fast). +""" + +import sys +sys.stdout.reconfigure(encoding='utf-8') + +import os +import json +import websocket + +# Status type to emoji mapping +STATUS_EMOJIS = { + "progress": "๐Ÿ”„", + "success": "โœ…", + "error": "โŒ", + "warning": "โš ๏ธ" +} + +def send_status(message: str, status_type: str, step_name: str, details: str = None): + """ + Format and send a status message to Telegram via WebSocket. + + Args: + message: Short status message (< 140 chars) + status_type: Type of status (progress, success, error, warning) + step_name: Name of the step being reported + details: Optional additional context + """ + if status_type not in STATUS_EMOJIS: + raise ValueError(f"Invalid status_type: {status_type}") + + emoji = STATUS_EMOJIS[status_type] + + # Build the message + formatted = f"{emoji} [{step_name}] {message}" + + if details: + formatted += f" ({details})" + + # Keep it concise (under 140 chars) + if len(formatted) > 140: + formatted = formatted[:137] + "..." + + # Send via WebSocket + gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789") + gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN") + target = os.environ.get("TELEGRAM_TARGET", "7590912486") + + if not gateway_token: + print(f"โœ— CLAWDBOT_GATEWAY_TOKEN not found", file=sys.stderr) + return formatted + + ws_url = f"ws://127.0.0.1:{gateway_port}/ws" + + try: + # Connect and send + ws = websocket.create_connection(ws_url, timeout=10) + + # Handshake + handshake = {"type": "handshake", "token": gateway_token} + ws.send(json.dumps(handshake)) + + # Send message + msg = { + "type": "message", + "action": "send", + "target": target, + "message": formatted, + "channel": "telegram" + } + ws.send(json.dumps(msg)) + + # Get response + response = ws.recv() + result = json.loads(response) + + ws.close() + + # Check if message was sent + if result.get("event") == "message.sent" or result.get("type") == "ack": + return formatted + else: + print(f"โš ๏ธ Response: {result}", file=sys.stderr) + return formatted + + except Exception as e: + print(f"โœ— WebSocket error: {e}", file=sys.stderr) + # Fallback: print to console + print(formatted, file=sys.stderr) + return formatted + +def main(): + if len(sys.argv) < 4: + print("Usage: send_status_websocket.py ") + print("\nStatus Types: progress, success, error, warning") + sys.exit(1) + + message = sys.argv[1] + status_type = sys.argv[2] + step_name = sys.argv[3] + details = sys.argv[4] if len(sys.argv) > 4 else None + + try: + result = send_status(message, status_type, step_name, details) + print(f"โœ“ Status sent: {result}") + except Exception as e: + print(f"โœ— Error: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/send_status_with_logging.py b/scripts/send_status_with_logging.py new file mode 100644 index 0000000..4c7c58f --- /dev/null +++ b/scripts/send_status_with_logging.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Send status messages to Telegram with automatic logging. +This is the production version of send_status.py. +""" + +import sys +sys.stdout.reconfigure(encoding='utf-8') + +import json +import os +import websocket +from datetime import datetime +from pathlib import Path + +# Configuration +LOG_DIR = Path("C:/Users/Luffy/clawd/logs") +LOG_FILE = LOG_DIR / "telegram_messages.jsonl" +TASK_LOG_FILE = LOG_DIR / "task_status.jsonl" + +# Status type to emoji mapping +STATUS_EMOJIS = { + "progress": "๐Ÿ”„", + "success": "โœ…", + "error": "โŒ", + "warning": "โš ๏ธ" +} + +def ensure_log_dir(): + """Create log directory if it doesn't exist.""" + LOG_DIR.mkdir(exist_ok=True) + +def log_message(message: str, direction: str = "out", task_name: str = None, status_type: str = None): + """Log a message to the file.""" + ensure_log_dir() + + try: + data = { + "timestamp": datetime.now().isoformat(), + "direction": direction, + "message": message + } + + if task_name: + data["task"] = task_name + if status_type: + data["status"] = status_type + + with open(LOG_FILE, 'a', encoding='utf-8') as f: + f.write(json.dumps(data, ensure_ascii=False) + '\n') + + return True + except Exception as e: + print(f"[LOG] Error: {e}", file=sys.stderr) + return False + +def can_encode_emoji(text: str, encoding: str = None) -> bool: + """Check if text can be encoded with the given encoding.""" + if encoding is None: + encoding = sys.stdout.encoding + try: + text.encode(encoding) + return True + except (UnicodeEncodeError, LookupError): + return False + +def send_status(message: str, status_type: str, step_name: str, details: str = None): + """ + Format and send a status message to Telegram with logging. + + Args: + message: Short status message (< 140 chars) + status_type: Type of status (progress, success, error, warning) + step_name: Name of the step being reported + details: Optional additional context + """ + if status_type not in STATUS_EMOJIS: + raise ValueError(f"Invalid status_type: {status_type}") + + emoji = STATUS_EMOJIS[status_type] + + # Choose emoji or ASCII based on encoding capability + if can_encode_emoji(emoji): + prefix = emoji + else: + prefix = emoji # Most modern terminals support emojis + + # Build the message + formatted = f"{prefix} [{step_name}] {message}" + + if details: + formatted += f" ({details})" + + # Keep it concise (under 140 chars) + if len(formatted) > 140: + formatted = formatted[:137] + "..." + + # Log the message (before sending) + log_message(formatted, direction="out", task_name=step_name, status_type=status_type) + + # Try WebSocket first (fastest) + gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN") + + if gateway_token: + try: + gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789") + target = os.environ.get("TELEGRAM_TARGET", "7590912486") + ws_url = f"ws://127.0.0.1:{gateway_port}/ws" + + # Connect and send + ws = websocket.create_connection(ws_url, timeout=10) + + # Send message directly (no handshake needed for simple messages) + msg = { + "type": "message", + "action": "send", + "target": target, + "message": formatted, + "channel": "telegram" + } + ws.send(json.dumps(msg)) + + # Try to receive response but don't wait too long + try: + response = ws.recv() + result = json.loads(response) + # If we got a challenge, respond to it + if result.get("event") == "connect.challenge": + # Send handshake with token + handshake = { + "type": "handshake", + "token": gateway_token, + "nonce": result.get("payload", {}).get("nonce") + } + ws.send(json.dumps(handshake)) + # Try to receive again + response = ws.recv() + result = json.loads(response) + except: + # If we can't receive, assume message was sent + pass + + ws.close() + + # Log successful send + log_message(formatted, direction="out_sent", task_name=step_name, status_type=status_type) + return formatted + + except Exception as e: + print(f"[LOG] WebSocket failed: {e}", file=sys.stderr) + + # Fallback: try CLI + import subprocess + import shutil + + clawdbot_path = shutil.which("clawdbot") + + if not clawdbot_path: + clawdbot_paths = [ + "C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot.cmd", + "C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot" + ] + for path in clawdbot_paths: + if os.path.exists(path): + clawdbot_path = path + break + + if clawdbot_path: + try: + target = os.environ.get("TELEGRAM_TARGET", "7590912486") + result = subprocess.run( + [ + clawdbot_path, + "message", + "send", + "--target", target, + "--message", formatted, + "--channel", "telegram" + ], + capture_output=True, + text=True, + timeout=20 + ) + if result.returncode == 0: + # Log successful send + log_message(formatted, direction="out_sent", task_name=step_name, status_type=status_type) + return formatted + except Exception as e: + print(f"[LOG] CLI failed: {e}", file=sys.stderr) + + # Final fallback: print to console + print(formatted, file=sys.stderr) + log_message(formatted, direction="out_failed", task_name=step_name, status_type=status_type) + + return formatted + +def main(): + if len(sys.argv) < 4: + print("Usage: send_status_with_logging.py ") + print("\nStatus Types: progress, success, error, warning") + sys.exit(1) + + message = sys.argv[1] + status_type = sys.argv[2] + step_name = sys.argv[3] + + try: + result = send_status(message, status_type, step_name) + print(f"โœ“ Sent and logged: {result}") + except Exception as e: + print(f"โœ— Error: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/test_send_status.py b/scripts/test_send_status.py new file mode 100644 index 0000000..77a044c --- /dev/null +++ b/scripts/test_send_status.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Test script for send_status.py +""" + +import sys +import subprocess +from pathlib import Path + +def test_send_status(): + """Test the status sending functionality.""" + script_path = Path(__file__).parent / "send_status.py" + + # Test cases + test_cases = [ + ("Test progress message", "progress", "test_step", None), + ("Test success message", "success", "test_step", None), + ("Test error message", "error", "test_step", None), + ("Test warning message", "warning", "test_step", None), + ("Long message test", "progress", "step1", "with details about the step"), + ] + + print("Testing send_status.py...") + print("=" * 50) + + for message, status_type, step_name, details in test_cases: + print(f"\nTest: {message}") + print(f" Type: {status_type}, Step: {step_name}") + + # Build command + cmd = [sys.executable, str(script_path), message, status_type, step_name] + if details: + cmd.append(details) + + try: + # Run the command + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode == 0: + print(f" PASS: Success") + output = result.stdout.strip() + if output: + print(f" Output: {output}") + else: + print(f" FAIL: Failed with return code {result.returncode}") + if result.stderr: + print(f" Error: {result.stderr.strip()}") + except subprocess.TimeoutExpired: + print(f" FAIL: Timed out") + except Exception as e: + print(f" FAIL: Exception: {e}") + + print("\n" + "=" * 50) + print("Testing complete") + +if __name__ == "__main__": + test_send_status()