Some checks failed
Lint & Format / detect (push) Failing after 32s
Monorepo Lint / lint-shell (push) Failing after 41s
Monorepo Lint / lint-js (push) Failing after 47s
Lint & Format / js-lint (push) Has been skipped
Lint & Format / py-lint (push) Has been skipped
Lint & Format / sh-lint (push) Has been skipped
Lint & Format / go-lint (push) Has been skipped
RoadChain-SHA2048: 9ea9121d5072e206 RoadChain-Identity: alexa@sovereign RoadChain-Full: 9ea9121d5072e20636efae2a25ea95edbd5085cc1e54e0d83186de315c63efc5aa77bd1e839f9178915aae84c5bd91b5faa0fee960e2dac8440ea1942132c9834a922f7d4d433931c7ea3f9ee4cd4706510ec5ff5be6a3fb55963a6df808b14184cb4517852fcdb8514dc14ca02fe16e318cab78ddf1993e76352bc409570415536bf1664cf5536b54acae7080fdc802508fea911ad7246da47bad7b0d31e6c745e93730ed8593f0221336af7378df60c54e2e86e96986a205641cd3a5f1380d5bd54b68d0a0f779fb152b0d8a5b0dbfd5118fc15d32b115ed581b0d715874460ae4e4420d83156dec9e9051047c95fb3ff4abb56e25bd8fc884a5eeb4d05b0a
339 lines
12 KiB
Bash
Executable File
339 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# BlackRoad Schedule Agent — Reads Google Calendar, posts to Slack, agents work on blocks
|
|
# Uses MCP calendar tools via the Slack Worker API
|
|
# Usage: memory-schedule.sh <command> [args]
|
|
set -e
|
|
|
|
COLLAB_DB="$HOME/.blackroad/collaboration.db"
|
|
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
|
|
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
|
|
SCHEDULE_DB="$HOME/.blackroad/schedule.db"
|
|
|
|
PINK='\033[38;5;205m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
RED='\033[0;31m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
|
|
sql() { sqlite3 "$SCHEDULE_DB" "$@" 2>/dev/null; }
|
|
|
|
post_to_slack() {
|
|
curl -s --max-time 3 --connect-timeout 2 \
|
|
-X POST "$SLACK_API/post" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg t "$1" '{text:$t}')" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
get_session() {
|
|
[[ -f "$SESSION_FILE" ]] && cat "$SESSION_FILE" || echo "unknown"
|
|
}
|
|
|
|
# ── INIT ──
|
|
cmd_init() {
|
|
mkdir -p "$(dirname "$SCHEDULE_DB")"
|
|
sqlite3 "$SCHEDULE_DB" <<'SQL'
|
|
PRAGMA journal_mode=WAL;
|
|
CREATE TABLE IF NOT EXISTS schedule_blocks (
|
|
block_id TEXT PRIMARY KEY,
|
|
date TEXT NOT NULL,
|
|
start_time TEXT NOT NULL,
|
|
end_time TEXT NOT NULL,
|
|
summary TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
copilot_prompt TEXT DEFAULT '',
|
|
claimed_by TEXT DEFAULT '',
|
|
status TEXT DEFAULT 'open',
|
|
gcal_event_id TEXT DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_blocks_date ON schedule_blocks(date);
|
|
CREATE TABLE IF NOT EXISTS block_logs (
|
|
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
block_id TEXT NOT NULL,
|
|
session_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
details TEXT DEFAULT '',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
SQL
|
|
echo -e "${GREEN}Schedule DB initialized${NC}"
|
|
}
|
|
|
|
# ── TODAY ──
|
|
cmd_today() {
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
|
|
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${PINK}║${NC} ${BOLD}Schedule — $today${NC} ${PINK}║${NC}"
|
|
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
# Check if we have today's blocks cached
|
|
local count
|
|
count=$(sql "SELECT count(*) FROM schedule_blocks WHERE date='$today';" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$count" -eq 0 || "$count" == "0" ]]; then
|
|
echo -e " ${YELLOW}No blocks cached for today. Run: $0 sync${NC}"
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
sql "SELECT start_time, end_time, summary, claimed_by, status, copilot_prompt FROM schedule_blocks WHERE date='$today' ORDER BY start_time;" | while IFS='|' read -r start end summary claimed status prompt; do
|
|
local icon="⬜"
|
|
local extra=""
|
|
case "$status" in
|
|
claimed) icon="🔵"; extra=" ${DIM}(${claimed})${NC}" ;;
|
|
done) icon="✅" ;;
|
|
active) icon="🟢"; extra=" ${GREEN}ACTIVE${NC}" ;;
|
|
esac
|
|
echo -e " $icon ${BOLD}${start}-${end}${NC} $summary$extra"
|
|
if [[ -n "$prompt" && "$1" == "--prompts" ]]; then
|
|
echo -e " ${DIM}Prompt: ${prompt:0:120}${NC}"
|
|
fi
|
|
done
|
|
echo ""
|
|
|
|
local open claimed done
|
|
open=$(sql "SELECT count(*) FROM schedule_blocks WHERE date='$today' AND status='open';")
|
|
claimed=$(sql "SELECT count(*) FROM schedule_blocks WHERE date='$today' AND status='claimed';")
|
|
done=$(sql "SELECT count(*) FROM schedule_blocks WHERE date='$today' AND status='done';")
|
|
echo -e " ${GREEN}$done done${NC} | ${BLUE}$claimed claimed${NC} | ${YELLOW}$open open${NC}"
|
|
}
|
|
|
|
# ── SYNC — Import from calendar JSON cache ──
|
|
cmd_sync() {
|
|
cmd_init 2>/dev/null
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
|
|
echo -e "${BLUE}Syncing calendar for $today...${NC}"
|
|
|
|
# Find the latest calendar JSON from MCP tool results
|
|
local json_file
|
|
json_file=$(find "$HOME/.claude/projects" -name "*.json" -path "*/tool-results/*" -newer /tmp/.schedule-sync-marker 2>/dev/null | head -1)
|
|
|
|
if [[ -z "$json_file" ]]; then
|
|
# Try to find any recent calendar result
|
|
json_file=$(find "$HOME/.claude/projects" -name "*.json" -path "*/tool-results/*" -exec grep -l "calendar" {} \; 2>/dev/null | head -1)
|
|
fi
|
|
|
|
if [[ -n "$json_file" ]]; then
|
|
# Parse events from JSON
|
|
python3 -c "
|
|
import json, sys
|
|
with open('$json_file') as f:
|
|
data = json.load(f)
|
|
text = data[0]['text'] if isinstance(data, list) else data
|
|
events_data = json.loads(text) if isinstance(text, str) else text
|
|
events = events_data.get('events', [])
|
|
today = '$today'
|
|
for e in events:
|
|
start = e.get('start', {})
|
|
dt = start.get('dateTime', start.get('date', ''))
|
|
if not dt.startswith(today):
|
|
continue
|
|
start_time = dt[11:16] if len(dt) > 10 else '00:00'
|
|
end_dt = e.get('end', {}).get('dateTime', '')
|
|
end_time = end_dt[11:16] if len(end_dt) > 10 else '23:59'
|
|
summary = e.get('summary', '?').replace(\"'\", \"''\")
|
|
desc = (e.get('description', '') or '').replace(\"'\", \"''\")
|
|
# Extract Copilot prompt if present
|
|
prompt = ''
|
|
if 'Copilot Prompt:' in desc:
|
|
prompt = desc.split('Copilot Prompt:')[1].strip().strip(\"'\").replace(\"'\", \"''\")
|
|
event_id = e.get('id', '')
|
|
block_id = f'{today}-{start_time}'
|
|
print(f\"INSERT OR REPLACE INTO schedule_blocks (block_id, date, start_time, end_time, summary, description, copilot_prompt, gcal_event_id) VALUES ('{block_id}', '{today}', '{start_time}', '{end_time}', '{summary}', '{desc[:500]}', '{prompt[:500]}', '{event_id}');\")
|
|
" 2>/dev/null | sqlite3 "$SCHEDULE_DB" 2>/dev/null
|
|
|
|
local synced
|
|
synced=$(sql "SELECT count(*) FROM schedule_blocks WHERE date='$today';")
|
|
echo -e "${GREEN}Synced $synced blocks for $today${NC}"
|
|
touch /tmp/.schedule-sync-marker
|
|
else
|
|
echo -e "${YELLOW}No calendar data found. Use MCP calendar tools first, then re-run sync.${NC}"
|
|
fi
|
|
}
|
|
|
|
# ── CLAIM ──
|
|
cmd_claim() {
|
|
local time_or_id="$1"
|
|
[[ -z "$time_or_id" ]] && { echo -e "${RED}Usage: $0 claim <start-time or block-id>${NC}"; exit 1; }
|
|
|
|
local session
|
|
session=$(get_session)
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
local now
|
|
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
# Try matching by start_time first
|
|
local block_id
|
|
block_id=$(sql "SELECT block_id FROM schedule_blocks WHERE (start_time='$time_or_id' OR block_id='$time_or_id') AND date='$today' LIMIT 1;")
|
|
|
|
if [[ -z "$block_id" ]]; then
|
|
echo -e "${RED}No block found matching '$time_or_id' for today${NC}"
|
|
return 1
|
|
fi
|
|
|
|
sql "UPDATE schedule_blocks SET claimed_by='$session', status='claimed' WHERE block_id='$block_id';"
|
|
|
|
local summary
|
|
summary=$(sql "SELECT summary FROM schedule_blocks WHERE block_id='$block_id';")
|
|
echo -e "${GREEN}Claimed:${NC} $summary"
|
|
|
|
# Show the Copilot prompt
|
|
local prompt
|
|
prompt=$(sql "SELECT copilot_prompt FROM schedule_blocks WHERE block_id='$block_id';")
|
|
if [[ -n "$prompt" ]]; then
|
|
echo -e "${CYAN}Copilot Prompt:${NC} $prompt"
|
|
fi
|
|
|
|
post_to_slack "🔵 *Block Claimed* [$session]: $summary" &
|
|
sql "INSERT INTO block_logs (block_id, session_id, action, details, created_at) VALUES ('$block_id', '$session', 'claimed', '$summary', '$now');"
|
|
}
|
|
|
|
# ── COMPLETE ──
|
|
cmd_complete() {
|
|
local time_or_id="$1"
|
|
[[ -z "$time_or_id" ]] && { echo -e "${RED}Usage: $0 complete <start-time or block-id>${NC}"; exit 1; }
|
|
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
local now
|
|
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
local session
|
|
session=$(get_session)
|
|
|
|
local block_id
|
|
block_id=$(sql "SELECT block_id FROM schedule_blocks WHERE (start_time='$time_or_id' OR block_id='$time_or_id') AND date='$today' LIMIT 1;")
|
|
|
|
if [[ -z "$block_id" ]]; then
|
|
echo -e "${RED}No block found matching '$time_or_id'${NC}"
|
|
return 1
|
|
fi
|
|
|
|
sql "UPDATE schedule_blocks SET status='done' WHERE block_id='$block_id';"
|
|
local summary
|
|
summary=$(sql "SELECT summary FROM schedule_blocks WHERE block_id='$block_id';")
|
|
echo -e "${GREEN}✅ Completed:${NC} $summary"
|
|
post_to_slack "✅ *Block Done*: $summary" &
|
|
sql "INSERT INTO block_logs (block_id, session_id, action, details, created_at) VALUES ('$block_id', '$session', 'completed', '$summary', '$now');"
|
|
}
|
|
|
|
# ── PROMPT — Show the Copilot prompt for a block ──
|
|
cmd_prompt() {
|
|
local time_or_id="$1"
|
|
[[ -z "$time_or_id" ]] && { echo -e "${RED}Usage: $0 prompt <start-time>${NC}"; exit 1; }
|
|
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
|
|
local result
|
|
result=$(sql "SELECT summary, copilot_prompt, description FROM schedule_blocks WHERE (start_time='$time_or_id' OR block_id='$time_or_id') AND date='$today' LIMIT 1;")
|
|
|
|
if [[ -z "$result" ]]; then
|
|
echo -e "${RED}No block found${NC}"
|
|
return 1
|
|
fi
|
|
|
|
IFS='|' read -r summary prompt desc <<< "$result"
|
|
echo -e "${BOLD}$summary${NC}"
|
|
echo ""
|
|
if [[ -n "$prompt" ]]; then
|
|
echo -e "${CYAN}Copilot Prompt:${NC}"
|
|
echo "$prompt"
|
|
else
|
|
echo -e "${DIM}No Copilot prompt — full description:${NC}"
|
|
echo "$desc"
|
|
fi
|
|
}
|
|
|
|
# ── POST — Post today's schedule to Slack ──
|
|
cmd_post() {
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
|
|
local msg="📅 *Schedule — $today*\n"
|
|
sql "SELECT start_time, end_time, summary, status FROM schedule_blocks WHERE date='$today' ORDER BY start_time;" | while IFS='|' read -r start end summary status; do
|
|
local icon="⬜"
|
|
case "$status" in
|
|
claimed) icon="🔵" ;;
|
|
done) icon="✅" ;;
|
|
active) icon="🟢" ;;
|
|
esac
|
|
msg="${msg}\n${icon} ${start}-${end} ${summary}"
|
|
done
|
|
|
|
post_to_slack "$msg"
|
|
echo -e "${GREEN}Schedule posted to Slack${NC}"
|
|
}
|
|
|
|
# ── NEXT — What block is coming up? ──
|
|
cmd_next() {
|
|
local today
|
|
today=$(date +%Y-%m-%d)
|
|
local now_time
|
|
now_time=$(date +%H:%M)
|
|
|
|
local result
|
|
result=$(sql "SELECT start_time, end_time, summary, copilot_prompt FROM schedule_blocks WHERE date='$today' AND start_time >= '$now_time' AND status='open' ORDER BY start_time LIMIT 1;")
|
|
|
|
if [[ -z "$result" ]]; then
|
|
echo -e "${GREEN}No more open blocks today!${NC}"
|
|
return
|
|
fi
|
|
|
|
IFS='|' read -r start end summary prompt <<< "$result"
|
|
echo -e "${BOLD}Next:${NC} ${start}-${end} ${CYAN}$summary${NC}"
|
|
if [[ -n "$prompt" ]]; then
|
|
echo -e "${DIM}Prompt: ${prompt:0:150}${NC}"
|
|
fi
|
|
}
|
|
|
|
# ── HELP ──
|
|
cmd_help() {
|
|
cat <<EOF
|
|
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
|
|
${PINK}║${NC} ${BOLD}BlackRoad Schedule Agent${NC} ${PINK}║${NC}
|
|
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
|
|
|
|
${BOLD}Commands:${NC}
|
|
${CYAN}today [--prompts]${NC} Show today's schedule (add --prompts for Copilot prompts)
|
|
${CYAN}sync${NC} Import today's events from calendar cache
|
|
${CYAN}next${NC} Show next upcoming open block
|
|
${CYAN}claim <time>${NC} Claim a block (e.g. claim 13:00)
|
|
${CYAN}complete <time>${NC} Mark a block as done
|
|
${CYAN}prompt <time>${NC} Show Copilot prompt for a block
|
|
${CYAN}post${NC} Post schedule to Slack
|
|
|
|
${BOLD}Examples:${NC}
|
|
$0 sync # Import from Google Calendar
|
|
$0 today --prompts # Show schedule with AI prompts
|
|
$0 claim 13:00 # Claim the 1pm block
|
|
$0 prompt 16:00 # Show the Copilot prompt for 4pm
|
|
$0 complete 13:00 # Mark 1pm block done
|
|
EOF
|
|
}
|
|
|
|
case "${1:-help}" in
|
|
init) cmd_init ;;
|
|
today|t) shift 2>/dev/null || true; cmd_today "$@" ;;
|
|
sync|s) cmd_sync ;;
|
|
claim|c) shift; cmd_claim "$@" ;;
|
|
complete|done|d) shift; cmd_complete "$@" ;;
|
|
prompt|p) shift; cmd_prompt "$@" ;;
|
|
post) cmd_post ;;
|
|
next|n) cmd_next ;;
|
|
help|--help|-h) cmd_help ;;
|
|
*)
|
|
echo -e "${RED}Unknown: $1${NC}"
|
|
cmd_help
|
|
exit 1
|
|
;;
|
|
esac
|