Add fullscreen_message skill
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# fullscreen_message Skill
|
||||
|
||||
This skill lets you display a fullscreen message popup on the local machine, triggered from the command line (or by other agents/automations). Great for visual notifications, alerts, or simple messages—works outside of chat.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
python3 fullscreen_message_skill.py --message "Your custom message!" --timeout 5
|
||||
```
|
||||
- `--message` (optional): The text to display (default: "Hello, Adolfo!")
|
||||
- `--timeout` (optional): Auto-dismiss after N seconds (default: wait for key or click)
|
||||
|
||||
You can connect this to a heartbeat or automation to make sure you never miss an alert!
|
||||
|
||||
---
|
||||
|
||||
### To Expand
|
||||
- Color, font size, dynamic sizing
|
||||
- Interactive elements (buttons, input)
|
||||
- Multi-message queue
|
||||
- Two-way local interaction
|
||||
@@ -0,0 +1,37 @@
|
||||
import argparse
|
||||
import pygame
|
||||
import sys
|
||||
import time
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Display a fullscreen message.")
|
||||
parser.add_argument('--message', type=str, default="Hello, Adolfo!", help='Message to display')
|
||||
parser.add_argument('--timeout', type=int, default=0, help='Seconds before auto-dismiss (0 = wait for key)')
|
||||
args = parser.parse_args()
|
||||
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
|
||||
pygame.display.set_caption("Fullscreen Message")
|
||||
font = pygame.font.Font(None, 100)
|
||||
text = font.render(args.message, True, (255, 255, 255))
|
||||
text_rect = text.get_rect(center=(screen.get_width() // 2, screen.get_height() // 2))
|
||||
clock = pygame.time.Clock()
|
||||
start_time = time.time()
|
||||
running = True
|
||||
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type in (pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN):
|
||||
running = False
|
||||
if args.timeout and (time.time() - start_time) > args.timeout:
|
||||
running = False
|
||||
|
||||
screen.fill((0, 0, 0))
|
||||
screen.blit(text, text_rect)
|
||||
pygame.display.flip()
|
||||
clock.tick(30)
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,8 +0,0 @@
|
||||
# macOS Calendar
|
||||
Description: Manage calendar events via the macOS Calendar app using AppleScript.
|
||||
|
||||
## Tools
|
||||
|
||||
### list
|
||||
Description: List calendar events for the next N days (default 7).
|
||||
Command: osascript skills/macos-calendar/fetch_events.scpt {{days}}
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/osascript
|
||||
on run argv
|
||||
set daysToFetch to 7
|
||||
if (count of argv) > 0 then
|
||||
set daysToFetch to (item 1 of argv) as integer
|
||||
end if
|
||||
|
||||
set currentDate to current date
|
||||
set endDate to currentDate + (daysToFetch * days)
|
||||
|
||||
tell application "Calendar"
|
||||
set output to ""
|
||||
set allCalendars to calendars
|
||||
|
||||
repeat with aCal in allCalendars
|
||||
set calName to name of aCal
|
||||
-- Filter out holidays or subscription calendars if they are too noisy, but for now keep all.
|
||||
|
||||
set calEvents to (every event of aCal whose start date is greater than or equal to currentDate and start date is less than or equal to endDate)
|
||||
|
||||
repeat with anEvent in calEvents
|
||||
try
|
||||
set evtSummary to summary of anEvent
|
||||
on error
|
||||
set evtSummary to "No Title"
|
||||
end try
|
||||
|
||||
try
|
||||
set evtStart to start date of anEvent
|
||||
on error
|
||||
set evtStart to "Unknown"
|
||||
end try
|
||||
|
||||
try
|
||||
set evtEnd to end date of anEvent
|
||||
on error
|
||||
set evtEnd to "Unknown"
|
||||
end try
|
||||
|
||||
try
|
||||
set evtLoc to location of anEvent
|
||||
if evtLoc is missing value then set evtLoc to ""
|
||||
on error
|
||||
set evtLoc to ""
|
||||
end try
|
||||
|
||||
set output to output & "Calendar: " & calName & "\nEvent: " & evtSummary & "\nStart: " & evtStart & "\nEnd: " & evtEnd & "\nLocation: " & evtLoc & "\n---\n"
|
||||
end repeat
|
||||
end repeat
|
||||
|
||||
if output is "" then
|
||||
return "No events found for the next " & daysToFetch & " days."
|
||||
else
|
||||
return output
|
||||
end if
|
||||
end tell
|
||||
end run
|
||||
@@ -1,12 +0,0 @@
|
||||
# macOS Mail
|
||||
Description: Manage emails via the macOS Mail app using AppleScript. Can fetch recent emails and send new ones.
|
||||
|
||||
## Tools
|
||||
|
||||
### fetch
|
||||
Description: Get the latest N emails from the inbox.
|
||||
Command: osascript skills/macos-mail/fetch_emails.scpt {{limit}}
|
||||
|
||||
### send
|
||||
Description: Send an email using the default account in macOS Mail.
|
||||
Command: osascript skills/macos-mail/send_email.scpt "{{to}}" "{{subject}}" "{{body}}"
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/osascript
|
||||
on run argv
|
||||
set messageLimit to 10
|
||||
if (count of argv) > 0 then
|
||||
set messageLimit to (item 1 of argv) as integer
|
||||
end if
|
||||
|
||||
tell application "Mail"
|
||||
set output to ""
|
||||
set inboxMessages to (messages of inbox whose read status is false)
|
||||
|
||||
-- Fallback to all messages if no unread, or just take top N
|
||||
if (count of inboxMessages) is 0 then
|
||||
set inboxMessages to messages of inbox
|
||||
end if
|
||||
|
||||
-- Sort logic is hard in pure AppleScript efficiently, usually they come in order.
|
||||
-- We'll just grab the first N items which are usually newest.
|
||||
|
||||
set msgCount to count of inboxMessages
|
||||
if msgCount > messageLimit then
|
||||
set msgCount to messageLimit
|
||||
end if
|
||||
|
||||
repeat with i from 1 to msgCount
|
||||
set msg to item i of inboxMessages
|
||||
set msgSubject to subject of msg
|
||||
set msgSender to sender of msg
|
||||
set msgDate to date received of msg
|
||||
set msgContent to content of msg
|
||||
|
||||
-- Truncate content if too long
|
||||
if (length of msgContent) > 500 then
|
||||
set msgContent to (text 1 thru 500 of msgContent) & "..."
|
||||
end if
|
||||
|
||||
set output to output & "From: " & msgSender & "\nSubject: " & msgSubject & "\nDate: " & msgDate & "\nBody: " & msgContent & "\n---\n"
|
||||
end repeat
|
||||
|
||||
return output
|
||||
end tell
|
||||
end run
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/osascript
|
||||
on run argv
|
||||
if (count of argv) < 2 then
|
||||
return "Usage: send_email.scpt <recipient> <subject> <body>"
|
||||
end if
|
||||
|
||||
set recipientAddress to item 1 of argv
|
||||
set msgSubject to item 2 of argv
|
||||
set msgContent to item 3 of argv
|
||||
|
||||
tell application "Mail"
|
||||
set newMessage to make new outgoing message with properties {subject:msgSubject, content:msgContent, visible:false}
|
||||
tell newMessage
|
||||
make new to recipient at end of to recipients with properties {address:recipientAddress}
|
||||
end tell
|
||||
send newMessage
|
||||
end tell
|
||||
return "Email sent to " & recipientAddress
|
||||
end run
|
||||
@@ -1,12 +0,0 @@
|
||||
# macOS Notes
|
||||
Description: Manage Apple Notes via AppleScript.
|
||||
|
||||
## Tools
|
||||
|
||||
### list
|
||||
Description: List recent notes or search by title.
|
||||
Command: osascript skills/macos-notes/list_notes.scpt "{{query}}"
|
||||
|
||||
### add
|
||||
Description: Create a new note or append to an existing one.
|
||||
Command: osascript skills/macos-notes/add_note.scpt "{{title}}" "{{body}}"
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/osascript
|
||||
on run argv
|
||||
if (count of argv) < 2 then
|
||||
return "Usage: add_note.scpt <title> <body>"
|
||||
end if
|
||||
|
||||
set noteTitle to item 1 of argv
|
||||
set noteBody to item 2 of argv
|
||||
|
||||
-- Apple Notes expects HTML for the body.
|
||||
-- We'll wrap the body in minimal HTML.
|
||||
set htmlBody to "<div>" & noteBody & "</div>"
|
||||
|
||||
-- Replace newlines with <br>
|
||||
set htmlBody to do shell script "echo " & quoted form of htmlBody & " | sed 's/$/<br>/'"
|
||||
|
||||
tell application "Notes"
|
||||
tell default account
|
||||
-- Check if note exists to append, or create new
|
||||
set existingNotes to (notes whose name is noteTitle)
|
||||
if (count of existingNotes) > 0 then
|
||||
set currentNote to item 1 of existingNotes
|
||||
set currentBody to body of currentNote
|
||||
-- Append to existing body
|
||||
set body of currentNote to currentBody & "<br>" & htmlBody
|
||||
return "Appended to note: " & noteTitle
|
||||
else
|
||||
make new note with properties {name:noteTitle, body:htmlBody}
|
||||
return "Created new note: " & noteTitle
|
||||
end if
|
||||
end tell
|
||||
end tell
|
||||
end run
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/osascript
|
||||
on run argv
|
||||
set searchObj to ""
|
||||
if (count of argv) > 0 then
|
||||
set searchObj to item 1 of argv
|
||||
end if
|
||||
|
||||
tell application "Notes"
|
||||
if searchObj is "" then
|
||||
set foundNotes to notes of default account
|
||||
-- limiting to top 10 recent
|
||||
set noteCount to count of foundNotes
|
||||
if noteCount > 10 then set noteCount to 10
|
||||
set subNotes to items 1 thru noteCount of foundNotes
|
||||
else
|
||||
set subNotes to (notes of default account whose name contains searchObj)
|
||||
end if
|
||||
|
||||
set output to ""
|
||||
repeat with aNote in subNotes
|
||||
set nName to name of aNote
|
||||
set nBody to body of aNote -- This is HTML
|
||||
set nModDate to modification date of aNote
|
||||
|
||||
-- Simple HTML cleanup for readability (very basic)
|
||||
set nBodyClean to do shell script "echo " & quoted form of nBody & " | sed -e 's/<[^>]*>//g'"
|
||||
|
||||
set output to output & "Note: " & nName & "\nModified: " & nModDate & "\nBody: " & nBodyClean & "\n---\n"
|
||||
end repeat
|
||||
return output
|
||||
end tell
|
||||
end run
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: pi-ssh-access
|
||||
description: "Remembers and documents how to SSH into the Raspberry Pi (192.168.68.126) as adolforeyna with the current ed25519 key. Use when the user asks for the connection details so Bro can log in from another machine or confirm passwordless access setup."
|
||||
---
|
||||
|
||||
# Raspberry Pi SSH Access
|
||||
|
||||
## Key facts Codex should keep handy
|
||||
|
||||
- **Host:** `192.168.68.126` (Pi on the local LAN)
|
||||
- **Username:** `adolforeyna`
|
||||
- **Default port:** `22` (standard SSH)
|
||||
- **Public key path (local):** `~/.ssh/id_ed25519.pub`
|
||||
- **Private key path (local):** `~/.ssh/id_ed25519`
|
||||
- **SSH key comment:** `adolforeyna@Mac-mini-M4.local`
|
||||
- **Known hosts entry:** Added via `ssh-keyscan -H 192.168.68.126`
|
||||
|
||||
## Steps for Bro to reconnect
|
||||
|
||||
1. Ensure the Pi is online and reachable at `192.168.68.126`.
|
||||
2. Use the generated key automatically: `ssh adolforeyna@192.168.68.126` (no password required).
|
||||
3. If a host key error resurfaces, rerun `ssh-keyscan -H 192.168.68.126 >> ~/.ssh/known_hosts` and reconnect.
|
||||
4. When the user requests, copy the public key again with `ssh-copy-id -i ~/.ssh/id_ed25519.pub adolforeyna@192.168.68.126`.
|
||||
|
||||
## Reminder for maintenance
|
||||
|
||||
- If the key ever changes or is rotated, update this skill and regenerate both `id_ed25519` and `id_ed25519.pub` as needed.
|
||||
- Keep this skill synced with `tools/ssh` notes if you ever keep a separate reference file.
|
||||
@@ -1,8 +0,0 @@
|
||||
# Repo Sync
|
||||
Description: Sync the current workspace to the configured git repository. Use this after making significant changes to files.
|
||||
|
||||
## Tools
|
||||
|
||||
### sync
|
||||
Description: Commit and push all current changes to the remote repository.
|
||||
Command: ./skills/repo-sync/sync.sh
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd /Users/adolforeyna/.openclaw/workspace
|
||||
|
||||
# Check if there are changes
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
git add .
|
||||
git commit -m "Auto-sync: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
git push origin main
|
||||
echo "Synced changes to repo."
|
||||
else
|
||||
echo "No changes to sync."
|
||||
fi
|
||||
@@ -1,23 +0,0 @@
|
||||
# SKILL.md - Say Out Loud
|
||||
|
||||
## Purpose
|
||||
Announce messages out loud through the Mac mini’s built-in speakers. Plays a chime, sets volume to 80%, and uses the macOS default system voice. Triggers on phrases like “talk to us” or “say out loud.”
|
||||
|
||||
## Triggers
|
||||
- “talk to us”
|
||||
- “say out loud”
|
||||
- “announce”
|
||||
- or by explicit command in the interface
|
||||
|
||||
## How it works
|
||||
- Sets output to built-in speakers (if available)
|
||||
- Sets volume to 80%
|
||||
- Plays the macOS "Glass" chime
|
||||
- Speaks the full answer using the system's default voice (can be changed to Alex, David, etc. on request)
|
||||
|
||||
## Usage
|
||||
Just say “talk to us” or any of the trigger phrases and Bro will say the next message out loud for everyone in the room.
|
||||
|
||||
---
|
||||
|
||||
Script: `say_out_loud.sh` (see alongside this file)
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
# say_out_loud.sh
|
||||
# Usage: ./say_out_loud.sh "message to say"
|
||||
|
||||
MESSAGE="$1"
|
||||
|
||||
# Set output to built-in speakers — comment this line if not needed
|
||||
default_output="$(system_profiler SPAudioDataType | grep -B 5 'Built-in Output' | grep 'Device ID' | awk -F ": " '{print $2}' | head -n1)"
|
||||
if [[ -n "$default_output" ]]; then
|
||||
SwitchAudioSource -s "$default_output" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Set system volume to 80%
|
||||
osascript -e 'set volume output volume 80'
|
||||
|
||||
# Play chime
|
||||
afplay /System/Library/Sounds/Glass.aiff
|
||||
|
||||
# Speak the message (default voice)
|
||||
say "$MESSAGE"
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
name: say-voice-replies
|
||||
description: Deliver short, hands-free responses by turning text into macOS `say` voice notes saved in the system temp directory and sending them as Telegram voice messages. Use whenever Adolfo asks for audio answers (e.g., while driving) or you decide speech will land better than text.
|
||||
---
|
||||
|
||||
# Say Voice Replies
|
||||
|
||||
## Overview
|
||||
Use this skill to quickly convert any response into an audio note using the built-in macOS `say` command and deliver it through Telegram as a voice message. The key constraints we uncovered on 2026-02-14 are that the `message` tool refuses files from the workspace and only accepts media written inside the macOS temporary directory, so every workflow below is designed around that.
|
||||
|
||||
## Quick Start
|
||||
1. Draft the spoken script (aim for 10–25 seconds unless the user asked for longer).
|
||||
2. Use the macOS default `say` voice and rate unless Adolfo specifically requests a different vibe.
|
||||
3. Generate the audio in `$TMPDIR` using `say` → send it with the `message` tool using `asVoice: true`.
|
||||
4. (Optional) Delete the temp file once the voice note is confirmed delivered.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Prepare the narration
|
||||
- Keep phrasing conversational and note any emoji you want to read out loud.
|
||||
- If the reply needs structure, say the headings (e.g., “Update one… Update two…”).
|
||||
- Sum up the audio in text when sending the message (“Audio reply: ETA + blockers”) so the user knows what the note contains before tapping it.
|
||||
|
||||
### 2. Generate a temp file that the `message` tool accepts
|
||||
MacOS exposes an agent-specific temp dir via `$TMPDIR`, which resolves to `/var/folders/...` — the only location Telegram uploads accepted during testing.
|
||||
|
||||
```bash
|
||||
TMPFILE=$(mktemp "${TMPDIR:-/tmp}/brovoice_XXXXXX")
|
||||
OUTFILE="${TMPFILE}.m4a"
|
||||
```
|
||||
|
||||
Use `say` to create the audio (AAC keeps the file small and Telegram-friendly):
|
||||
|
||||
```bash
|
||||
say --data-format=aac \
|
||||
"Yo bro, leaving the studio now. Should hit the church in fifteen." \
|
||||
-o "$OUTFILE"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The stock system voice/rate keeps everything consistent; only add `-v <VoiceName>` or `-r <speed>` if Adolfo requests a different vibe.
|
||||
- If you need emphasis, split into multiple `say` calls and concatenate with `afconvert`, but most replies can be cut in one take.
|
||||
- `say` writes synchronously; no extra waits are needed before sending.
|
||||
|
||||
### 3. Send the voice note via Telegram
|
||||
The `message` tool needs three things:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "telegram",
|
||||
"media": "/var/folders/.../brovoice_a1b2c3.m4a",
|
||||
"asVoice": true,
|
||||
"message": "Audio reply: key takeaways + ETA"
|
||||
}
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
- Always set `asVoice: true` so Telegram renders the clip as a push-to-play voice message instead of a regular file.
|
||||
- Keep a short text caption that previews the content (helps when the user can’t tap immediately).
|
||||
- If the user explicitly wants only audio, you can keep the caption minimal (e.g., “Audio-only reply”).
|
||||
|
||||
### 4. Clean up
|
||||
Once Telegram confirms `{ "ok": true }`, remove the temp file:
|
||||
|
||||
```bash
|
||||
rm -f "$OUTFILE"
|
||||
```
|
||||
|
||||
This avoids leaving dozens of `.m4a` files inside `/var/folders/...`.
|
||||
|
||||
## Tips & Variations
|
||||
- **Faster follow-ups:** Capture the temp path and reuse it if you plan to cut a second take immediately; otherwise regenerate with `mktemp`.
|
||||
- **Speaking rate tweaks (optional):** If Adolfo asks for faster/slower delivery, `say -r 160` ≈ relaxed, `-r 190` ≈ energetic; stay under `-r 210` for intelligibility while driving.
|
||||
- **Edge cases:** If `say` errors (rare on headless sessions), fall back to the built-in `tts` tool—it already writes to the same temp tree and produces a `MEDIA:/path/to/file.mp3` string you can feed directly into `message`.
|
||||
- **Captioning:** When summarizing long instructions, include bullet cues in the text reply while the audio carries the nuance.
|
||||
|
||||
Stick to this flow whenever Adolfo asks for a voice response (especially while driving) or when tone-of-voice will land better than plain text.
|
||||
Reference in New Issue
Block a user