sync: 2026-03-16 17:30 — 21 files from Alexandria
Some checks failed
Lint & Format / detect (push) Has been cancelled
Lint & Format / js-lint (push) Has been cancelled
Lint & Format / py-lint (push) Has been cancelled
Lint & Format / sh-lint (push) Has been cancelled
Lint & Format / go-lint (push) Has been cancelled
Monorepo Lint / lint-shell (push) Has been cancelled
Monorepo Lint / lint-js (push) Has been cancelled
Some checks failed
Lint & Format / detect (push) Has been cancelled
Lint & Format / js-lint (push) Has been cancelled
Lint & Format / py-lint (push) Has been cancelled
Lint & Format / sh-lint (push) Has been cancelled
Lint & Format / go-lint (push) Has been cancelled
Monorepo Lint / lint-shell (push) Has been cancelled
Monorepo Lint / lint-js (push) Has been cancelled
RoadChain-SHA2048: 2bb8ae01244552ce RoadChain-Identity: alexa@sovereign RoadChain-Full: 2bb8ae01244552cecab2d00dcd4d9be800c7f72efd48c0042d181cbe095735be688df2988e3e51f8e6ea2562aaf6e4a842be17389c27b9c02b0680dec7fb1a89a8117868ffa1457a937dccb40674e118d9394e4131c5000ee0e516109be0623b37d46fa3eb1f2c8916755e5060648c3dee42a48ca180464f284d95844a67c9e1708a1b4da531c5135deaa21f9bbe0de9e1695c09909c55c4f395cbbacde4778f9e4620cbb7b055d7b8b41dbf384076cd3944d7561aebb6dc8720ab432c2720722e162a6a9d08c1decf429b62e1ff12a6517b569ddc8e0664bd785ff1f56c7c9735fef29c60c9899bc1fb55945e9ee015d9cae0ef5023519ac656f29fa8e27a2e
This commit is contained in:
52
operator/memory/COLLABORATION.md
Normal file
52
operator/memory/COLLABORATION.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# BlackRoad Collaboration Protocol
|
||||
|
||||
Every Claude session is part of a team. You are never working alone.
|
||||
|
||||
## On Session Start
|
||||
1. `memory-collaboration.sh inbox` — check for handoffs from previous sessions
|
||||
2. `memory-collaboration.sh register "<what you're working on>"` — announce yourself
|
||||
3. Check codex before solving — don't reinvent what's already solved
|
||||
|
||||
## During Work
|
||||
- `memory-collaboration.sh announce "<update>"` — share progress with the team
|
||||
- `memory-collaboration.sh post "<message>"` — quick Slack message
|
||||
- `memory-agent-dispatch.sh progress "<update>"` — structured progress update
|
||||
- `memory-slack.sh ask <agent> "<question>"` — ask an AI agent for help
|
||||
|
||||
## When Done
|
||||
- `memory-collaboration.sh handoff "<what's next>"` — leave context for the next session
|
||||
- `memory-planner.sh wrap` — auto-generate handoff from session activity
|
||||
- `memory-collaboration.sh complete` — mark session as done
|
||||
|
||||
## Slack Agents
|
||||
| Agent | Specialty | Use for |
|
||||
|-------|-----------|---------|
|
||||
| alice | Network, DNS, security | Infrastructure questions |
|
||||
| cecilia | AI, models, embedding | ML/AI questions |
|
||||
| octavia | Architecture, Gitea, Docker | Systems design |
|
||||
| lucidia | Web apps, GitHub Actions | Frontend, deployment |
|
||||
| shellfish | Security, vulnerabilities | Security review |
|
||||
| caddy | CI/CD, builds | Build issues |
|
||||
| alexa | CEO, strategy | Business decisions |
|
||||
| road | BlackRoad OS overall | General questions |
|
||||
|
||||
## CLI Shortcuts
|
||||
```bash
|
||||
br collab status # Full collaboration dashboard
|
||||
br collab inbox # Check messages
|
||||
br collab announce "x" # Broadcast update
|
||||
br collab handoff "x" # Leave handoff note
|
||||
br collab ask agent "q" # Ask an AI agent
|
||||
br collab debate "topic"# Multi-agent debate
|
||||
br collab standup # Generate standup
|
||||
br collab roadmap # Project roadmap
|
||||
br collab sprint # Sprint board
|
||||
br collab wrap # End-of-session wrap-up
|
||||
br slack say "message" # Quick Slack post
|
||||
br plan next # What to work on next
|
||||
br dispatch queue # Show all available work
|
||||
br watchdog heartbeat # Health check
|
||||
```
|
||||
|
||||
## Key Principle
|
||||
**Every session leaves the system better than it found it.** Log your work, broadcast your learnings, and leave a clear handoff for the next Claude.
|
||||
371
operator/memory/memory-agent-dispatch.sh
Normal file
371
operator/memory/memory-agent-dispatch.sh
Normal file
@@ -0,0 +1,371 @@
|
||||
#!/bin/bash
|
||||
# BlackRoad Agent Dispatch — Task dispatch, sprint board, work queue
|
||||
# Usage: memory-agent-dispatch.sh <command> [args]
|
||||
set -e
|
||||
|
||||
COLLAB_DB="$HOME/.blackroad/collaboration.db"
|
||||
TASKS_DB="$HOME/.blackroad/memory/tasks.db"
|
||||
PROJECTS_DIR="$HOME/.blackroad/memory/infinite-todos/projects"
|
||||
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
|
||||
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
|
||||
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
|
||||
TODOS="$HOME/blackroad-operator/scripts/memory/memory-infinite-todos.sh"
|
||||
COLLAB="$HOME/blackroad-operator/scripts/memory/memory-collaboration.sh"
|
||||
|
||||
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_collab() { sqlite3 "$COLLAB_DB" "$@" 2>/dev/null; }
|
||||
sql_tasks() { sqlite3 "$TASKS_DB" "$@" 2>/dev/null; }
|
||||
|
||||
get_session() {
|
||||
[[ -f "$SESSION_FILE" ]] && cat "$SESSION_FILE" || echo "unknown"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
log_journal() {
|
||||
local action="$1" entity="$2" details="$3"
|
||||
local ts
|
||||
ts=$(date -u +"%Y-%m-%dT%H:%M:%S.3NZ")
|
||||
local hash
|
||||
hash=$(echo -n "$ts$action$entity$details" | shasum -a 256 | cut -c1-16)
|
||||
printf '{"timestamp":"%s","action":"%s","entity":"%s","details":"%s","hash":"%s"}\n' \
|
||||
"$ts" "$action" "$entity" "$details" "$hash" >> "$JOURNAL" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ── DISPATCH ──
|
||||
cmd_dispatch() {
|
||||
local project_id="$1"
|
||||
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 dispatch <project-id>${NC}"; exit 1; }
|
||||
|
||||
local pf="$PROJECTS_DIR/${project_id}.json"
|
||||
if [[ ! -f "$pf" ]]; then
|
||||
echo -e "${RED}Project not found: $project_id${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local next_todo next_id
|
||||
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text // empty' "$pf")
|
||||
next_id=$(jq -r '[.todos[] | select(.status == "pending")][0].todo_id // empty' "$pf")
|
||||
|
||||
if [[ -z "$next_todo" ]]; then
|
||||
echo -e "${GREEN}Project $project_id has no pending todos!${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
local title
|
||||
title=$(jq -r '.title' "$pf")
|
||||
local session
|
||||
session=$(get_session)
|
||||
|
||||
echo -e "${PINK}[DISPATCH]${NC} ${BOLD}$title${NC}"
|
||||
echo -e " ${YELLOW}→${NC} [$next_id] $next_todo"
|
||||
echo ""
|
||||
|
||||
# Post to Slack
|
||||
post_to_slack "🎯 *Task Dispatched* [$session]\nProject: *$title*\nTodo: $next_todo\nID: $next_id" &
|
||||
|
||||
# Log
|
||||
log_journal "dispatch" "$project_id" "Dispatched: $next_todo ($next_id)"
|
||||
echo -e " ${GREEN}Dispatched and announced${NC}"
|
||||
}
|
||||
|
||||
# ── QUEUE ──
|
||||
cmd_queue() {
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Available Work Queue${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Project todos
|
||||
echo -e " ${BOLD}📋 Project Todos:${NC}"
|
||||
local found=0
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
|
||||
local pending
|
||||
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
[[ "$pending" -eq 0 ]] && continue
|
||||
|
||||
local pid title progress
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
title=$(jq -r '.title' "$f")
|
||||
progress=$(jq -r '.progress' "$f")
|
||||
|
||||
echo -e " ${CYAN}$pid${NC} — $title (${progress}%, $pending pending)"
|
||||
jq -r '[.todos[] | select(.status == "pending")][0:3][] | " ⬜ [\(.todo_id)] \(.text)"' "$f"
|
||||
found=1
|
||||
done
|
||||
[[ "$found" -eq 0 ]] && echo -e " ${GREEN}No pending project todos${NC}"
|
||||
echo ""
|
||||
|
||||
# Marketplace tasks
|
||||
if [[ -f "$TASKS_DB" ]]; then
|
||||
echo -e " ${BOLD}🏪 Marketplace Tasks:${NC}"
|
||||
local available
|
||||
available=$(sql_tasks "SELECT count(*) FROM tasks WHERE status='available';")
|
||||
echo -e " $available available task(s)"
|
||||
|
||||
sql_tasks "SELECT task_id, title FROM tasks WHERE status='available' ORDER BY created_at DESC LIMIT 5;" 2>/dev/null | while IFS='|' read -r tid title; do
|
||||
echo -e " ${BLUE}$tid${NC}: $title"
|
||||
done
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── CLAIM ──
|
||||
cmd_claim() {
|
||||
local task_id="$1"
|
||||
[[ -z "$task_id" ]] && { echo -e "${RED}Usage: $0 claim <task-id>${NC}"; exit 1; }
|
||||
|
||||
if [[ ! -f "$TASKS_DB" ]]; then
|
||||
echo -e "${RED}Task marketplace DB not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
local now
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
sql_tasks "UPDATE tasks SET status='claimed', claimed_by='$session', claimed_at='$now' WHERE task_id='$task_id' AND status='available';"
|
||||
|
||||
local affected
|
||||
affected=$(sql_tasks "SELECT changes();")
|
||||
if [[ "$affected" -gt 0 ]]; then
|
||||
local title
|
||||
title=$(sql_tasks "SELECT title FROM tasks WHERE task_id='$task_id';")
|
||||
echo -e "${GREEN}Claimed:${NC} $title"
|
||||
post_to_slack "📌 *Task Claimed* [$session]: $title" &
|
||||
log_journal "task-claim" "$task_id" "Claimed by $session: $title"
|
||||
else
|
||||
echo -e "${YELLOW}Could not claim $task_id (may not exist or already claimed)${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── PROGRESS ──
|
||||
cmd_progress() {
|
||||
local msg="$*"
|
||||
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 progress <message>${NC}"; exit 1; }
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
local now
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Log to collab DB
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
local msg_id="prog-$(date +%s)-$$"
|
||||
sql_collab "INSERT INTO messages (msg_id, session_id, type, message, created_at) VALUES ('$msg_id', '$session', 'progress', '$(echo "$msg" | sed "s/'/''/g")', '$now');"
|
||||
sql_collab "UPDATE sessions SET last_seen='$now' WHERE session_id='$session';"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Progress:${NC} $msg"
|
||||
post_to_slack "⚡ *Progress* [$session]: $msg" &
|
||||
log_journal "progress" "$session" "$msg"
|
||||
}
|
||||
|
||||
# ── DONE ──
|
||||
cmd_done() {
|
||||
local todo_id="$1"
|
||||
local project_id="$2"
|
||||
|
||||
if [[ -z "$todo_id" ]]; then
|
||||
echo -e "${RED}Usage: $0 done <todo-id> [project-id]${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If project not specified, search for it
|
||||
if [[ -z "$project_id" ]]; then
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
if jq -e ".todos[] | select(.todo_id == \"$todo_id\")" "$f" >/dev/null 2>&1; then
|
||||
project_id=$(jq -r '.project_id' "$f")
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$project_id" ]]; then
|
||||
echo -e "${RED}Could not find todo $todo_id in any project${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Complete via infinite todos
|
||||
"$TODOS" complete-todo "$project_id" "$todo_id" 2>/dev/null || true
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
echo -e "${GREEN}Completed:${NC} $todo_id in $project_id"
|
||||
post_to_slack "✅ *Todo Done* [$session]: $todo_id in $project_id" &
|
||||
log_journal "todo-complete" "$project_id" "Completed $todo_id"
|
||||
|
||||
# Show next todo
|
||||
local pf="$PROJECTS_DIR/${project_id}.json"
|
||||
if [[ -f "$pf" ]]; then
|
||||
local next
|
||||
next=$(jq -r '[.todos[] | select(.status == "pending")][0].text // empty' "$pf")
|
||||
if [[ -n "$next" ]]; then
|
||||
echo -e "${YELLOW}Next:${NC} $next"
|
||||
else
|
||||
echo -e "${GREEN}All todos complete for $project_id!${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── SPRINT ──
|
||||
cmd_sprint() {
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Sprint Board${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# In Progress — active sessions with focus
|
||||
echo -e " ${BOLD}🔄 In Progress:${NC}"
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
sql_collab "SELECT session_id, focus FROM sessions WHERE status='active' AND focus != '' ORDER BY last_seen DESC LIMIT 10;" | while IFS='|' read -r sid focus; do
|
||||
echo -e " ${GREEN}●${NC} ${CYAN}${sid}${NC} → $focus"
|
||||
done
|
||||
local no_focus
|
||||
no_focus=$(sql_collab "SELECT count(*) FROM sessions WHERE status='active' AND (focus='' OR focus IS NULL);")
|
||||
[[ "$no_focus" -gt 0 ]] && echo -e " ${DIM}+ $no_focus session(s) without focus${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Claimed marketplace tasks
|
||||
if [[ -f "$TASKS_DB" ]]; then
|
||||
local claimed
|
||||
claimed=$(sql_tasks "SELECT count(*) FROM tasks WHERE status='claimed';")
|
||||
if [[ "$claimed" -gt 0 ]]; then
|
||||
echo -e " ${BOLD}📌 Claimed Tasks:${NC}"
|
||||
sql_tasks "SELECT task_id, title, claimed_by FROM tasks WHERE status='claimed' ORDER BY claimed_at DESC LIMIT 5;" | while IFS='|' read -r tid title by; do
|
||||
echo -e " ${BLUE}$tid${NC}: $title ${DIM}(${by})${NC}"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Up Next — pending todos from top priority projects
|
||||
echo -e " ${BOLD}📋 Up Next:${NC}"
|
||||
local count=0
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
[[ "$count" -ge 5 ]] && break
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
local pending
|
||||
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
[[ "$pending" -eq 0 ]] && continue
|
||||
|
||||
local pid next_todo
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
|
||||
echo -e " ⬜ ${CYAN}$pid${NC}: $next_todo"
|
||||
((count++))
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Done today
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
local done_today
|
||||
done_today=$(sql_collab "SELECT count(*) FROM messages WHERE type='progress' AND created_at > datetime('now', '-24 hours');")
|
||||
echo -e " ${BOLD}✅ Done today:${NC} $done_today progress updates"
|
||||
sql_collab "SELECT message FROM messages WHERE type='progress' AND created_at > datetime('now', '-24 hours') ORDER BY created_at DESC LIMIT 5;" | while read -r msg; do
|
||||
echo -e " ✓ $msg"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ── AUTOQUEUE ──
|
||||
cmd_autoqueue() {
|
||||
echo -e "${PINK}[DISPATCH]${NC} ${BOLD}Auto-generating tasks from projects...${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ ! -f "$TASKS_DB" ]]; then
|
||||
echo -e "${RED}Task marketplace DB not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local added=0
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
|
||||
local pid title
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
title=$(jq -r '.title' "$f")
|
||||
|
||||
# Get pending todos not already in marketplace
|
||||
jq -r '.todos[] | select(.status == "pending") | "\(.todo_id)|\(.text)"' "$f" | while IFS='|' read -r tid text; do
|
||||
local existing
|
||||
existing=$(sql_tasks "SELECT count(*) FROM tasks WHERE task_id='${pid}-${tid}';")
|
||||
if [[ "$existing" -eq 0 ]]; then
|
||||
local now
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
sql_tasks "INSERT INTO tasks (task_id, title, description, status, priority, category, created_at) VALUES ('${pid}-${tid}', '$(echo "$text" | sed "s/'/''/g")', 'From project: $title', 'available', 'medium', '$pid', '$now');" 2>/dev/null || true
|
||||
echo -e " ${GREEN}+${NC} ${CYAN}${pid}-${tid}${NC}: $text"
|
||||
((added++)) || true
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Added $added task(s) to marketplace${NC}"
|
||||
}
|
||||
|
||||
# ── HELP ──
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
|
||||
${PINK}║${NC} ${BOLD}BlackRoad Agent Dispatch${NC} ${PINK}║${NC}
|
||||
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
|
||||
|
||||
${BOLD}Task Management:${NC}
|
||||
${CYAN}dispatch <project>${NC} Dispatch next todo for a project
|
||||
${CYAN}queue${NC} Show all available work
|
||||
${CYAN}claim <task-id>${NC} Claim a marketplace task
|
||||
${CYAN}done <todo-id>${NC} Mark a todo as complete
|
||||
|
||||
${BOLD}Progress:${NC}
|
||||
${CYAN}progress <msg>${NC} Post progress update (DB + Slack)
|
||||
${CYAN}sprint${NC} Sprint board: in-progress, next, done
|
||||
|
||||
${BOLD}Automation:${NC}
|
||||
${CYAN}autoqueue${NC} Auto-generate marketplace tasks from projects
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
dispatch|dis) shift; cmd_dispatch "$@" ;;
|
||||
queue|q) cmd_queue ;;
|
||||
claim|c) shift; cmd_claim "$@" ;;
|
||||
progress|prog) shift; cmd_progress "$@" ;;
|
||||
done|complete) shift; cmd_done "$@" ;;
|
||||
sprint|sp) cmd_sprint ;;
|
||||
autoqueue|auto) cmd_autoqueue ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*)
|
||||
echo -e "${RED}Unknown: $1${NC}"
|
||||
cmd_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
444
operator/memory/memory-planner.sh
Normal file
444
operator/memory/memory-planner.sh
Normal file
@@ -0,0 +1,444 @@
|
||||
#!/bin/bash
|
||||
# BlackRoad Planner — Project planning, focus tracking, standups, wrap-ups
|
||||
# Usage: memory-planner.sh <command> [args]
|
||||
set -e
|
||||
|
||||
COLLAB_DB="$HOME/.blackroad/collaboration.db"
|
||||
PROJECTS_DIR="$HOME/.blackroad/memory/infinite-todos/projects"
|
||||
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
|
||||
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
|
||||
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
|
||||
TODOS="$HOME/blackroad-operator/scripts/memory/memory-infinite-todos.sh"
|
||||
COLLAB="$HOME/blackroad-operator/scripts/memory/memory-collaboration.sh"
|
||||
|
||||
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 "$COLLAB_DB" "$@" 2>/dev/null; }
|
||||
|
||||
get_session() {
|
||||
[[ -f "$SESSION_FILE" ]] && cat "$SESSION_FILE" || echo ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# ── PLAN ──
|
||||
cmd_plan() {
|
||||
local project_id="$1"
|
||||
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 plan <project-id>${NC}"; exit 1; }
|
||||
|
||||
local project_file="$PROJECTS_DIR/${project_id}.json"
|
||||
if [[ ! -f "$project_file" ]]; then
|
||||
echo -e "${RED}Project not found: $project_id${NC}"
|
||||
echo -e "${YELLOW}Available:${NC}"
|
||||
ls "$PROJECTS_DIR"/*.json 2>/dev/null | while read -r f; do
|
||||
local pid
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
local title
|
||||
title=$(jq -r '.title' "$f")
|
||||
local prog
|
||||
prog=$(jq -r '.progress' "$f")
|
||||
echo -e " ${CYAN}$pid${NC} — $title (${prog}%)"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local title cadence status progress
|
||||
title=$(jq -r '.title' "$project_file")
|
||||
cadence=$(jq -r '.cadence' "$project_file")
|
||||
status=$(jq -r '.status' "$project_file")
|
||||
progress=$(jq -r '.progress' "$project_file")
|
||||
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}$title${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}ID:${NC} ${CYAN}${project_id}${NC} | ${BOLD}Cadence:${NC} ${cadence} | ${BOLD}Status:${NC} ${status}"
|
||||
|
||||
# Progress bar
|
||||
local bar_len=40
|
||||
local filled=$(( progress * bar_len / 100 ))
|
||||
local empty=$(( bar_len - filled ))
|
||||
local bar="${GREEN}"
|
||||
for ((i=0; i<filled; i++)); do bar+="█"; done
|
||||
bar+="${DIM}"
|
||||
for ((i=0; i<empty; i++)); do bar+="░"; done
|
||||
bar+="${NC}"
|
||||
echo -e " ${BOLD}Progress:${NC} $bar ${progress}%"
|
||||
echo ""
|
||||
|
||||
# Todos
|
||||
local total_todos done_todos pending_todos
|
||||
total_todos=$(jq '.todos | length' "$project_file")
|
||||
done_todos=$(jq '[.todos[] | select(.status == "completed")] | length' "$project_file")
|
||||
pending_todos=$(jq '[.todos[] | select(.status == "pending")] | length' "$project_file")
|
||||
|
||||
echo -e " ${BOLD}Todos:${NC} $done_todos/$total_todos done, $pending_todos pending"
|
||||
echo ""
|
||||
|
||||
# Show completed
|
||||
if [[ "$done_todos" -gt 0 ]]; then
|
||||
echo -e " ${GREEN}Completed:${NC}"
|
||||
jq -r '.todos[] | select(.status == "completed") | " ✅ \(.text)"' "$project_file"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Show pending
|
||||
if [[ "$pending_todos" -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}Pending:${NC}"
|
||||
jq -r '.todos[] | select(.status == "pending") | " ⬜ [\(.todo_id)] \(.text)"' "$project_file"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── NEXT ──
|
||||
cmd_next() {
|
||||
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Finding next action...${NC}"
|
||||
echo ""
|
||||
|
||||
local best_project="" best_todo="" best_priority=999 best_progress=999
|
||||
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
|
||||
local progress pending_count cadence pid title
|
||||
progress=$(jq -r '.progress' "$f")
|
||||
pending_count=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
[[ "$pending_count" -eq 0 ]] && continue
|
||||
|
||||
cadence=$(jq -r '.cadence' "$f")
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
title=$(jq -r '.title' "$f")
|
||||
|
||||
# Priority: weekly=1, monthly=2, forever=3
|
||||
local priority=3
|
||||
case "$cadence" in
|
||||
weekly) priority=1 ;;
|
||||
monthly) priority=2 ;;
|
||||
esac
|
||||
|
||||
if [[ "$priority" -lt "$best_priority" ]] || \
|
||||
[[ "$priority" -eq "$best_priority" && "$progress" -lt "$best_progress" ]]; then
|
||||
best_priority=$priority
|
||||
best_progress=$progress
|
||||
best_project="$pid"
|
||||
best_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$best_project" ]]; then
|
||||
echo -e " ${BOLD}Next up:${NC} ${CYAN}$best_project${NC} (${best_progress}%)"
|
||||
echo -e " ${YELLOW}→${NC} $best_todo"
|
||||
echo ""
|
||||
echo -e " ${DIM}Set focus: $0 focus $best_project${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}All projects complete or no pending todos!${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── FOCUS ──
|
||||
cmd_focus() {
|
||||
local project_id="$1"
|
||||
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 focus <project-id>${NC}"; exit 1; }
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
if [[ -z "$session" ]]; then
|
||||
echo -e "${RED}No active session. Run: memory-collaboration.sh register${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update session focus
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
sql "UPDATE sessions SET focus='$project_id' WHERE session_id='$session';"
|
||||
fi
|
||||
|
||||
# Get project title
|
||||
local title=""
|
||||
local pf="$PROJECTS_DIR/${project_id}.json"
|
||||
[[ -f "$pf" ]] && title=$(jq -r '.title' "$pf")
|
||||
|
||||
echo -e "${GREEN}Focus set:${NC} ${CYAN}${project_id}${NC}${title:+ — $title}"
|
||||
|
||||
# Announce to Slack
|
||||
"$COLLAB" announce "Focus: $project_id${title:+ — $title}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ── STANDUP ──
|
||||
cmd_standup() {
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Standup Report${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# What was done (last handoff picked up by this session)
|
||||
echo -e " ${BOLD}✅ What was done (last handoff):${NC}"
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
local last_handoff
|
||||
last_handoff=$(sql "SELECT message FROM handoffs WHERE picked_up_by != '' ORDER BY picked_up_at DESC LIMIT 1;")
|
||||
if [[ -n "$last_handoff" ]]; then
|
||||
echo -e " $last_handoff"
|
||||
else
|
||||
echo -e " ${DIM}No handoff found${NC}"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# What's planned (next action)
|
||||
echo -e " ${BOLD}🎯 What's planned:${NC}"
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
local pending
|
||||
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
[[ "$pending" -eq 0 ]] && continue
|
||||
local pid next_todo
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
|
||||
echo -e " ${CYAN}$pid${NC}: $next_todo"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Blockers
|
||||
echo -e " ${BOLD}🚧 Blockers:${NC}"
|
||||
echo -e " ${DIM}(add manually or check memory for known issues)${NC}"
|
||||
echo ""
|
||||
|
||||
# Post to Slack
|
||||
echo -e " ${DIM}Post to Slack: $0 standup --slack${NC}"
|
||||
if [[ "$1" == "--slack" ]]; then
|
||||
local msg="📋 *Standup Report*\n"
|
||||
msg="${msg}✅ Done: $(sql "SELECT message FROM handoffs WHERE picked_up_by != '' ORDER BY picked_up_at DESC LIMIT 1;" 2>/dev/null || echo 'N/A')\n"
|
||||
msg="${msg}🎯 Next: $(cmd_next 2>/dev/null | grep "→" | head -1 || echo 'checking...')"
|
||||
post_to_slack "$msg"
|
||||
echo -e " ${GREEN}Posted to Slack${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── ROADMAP ──
|
||||
cmd_roadmap() {
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}BlackRoad Project Roadmap${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
for cadence in weekly monthly forever; do
|
||||
local label icon
|
||||
case "$cadence" in
|
||||
weekly) label="WEEKLY"; icon="📆" ;;
|
||||
monthly) label="MONTHLY"; icon="📊" ;;
|
||||
forever) label="FOREVER"; icon="♾️" ;;
|
||||
esac
|
||||
|
||||
local found=0
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local fc
|
||||
fc=$(jq -r '.cadence' "$f")
|
||||
[[ "$fc" != "$cadence" ]] && continue
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
echo -e " ${BOLD}$icon $label${NC}"
|
||||
found=1
|
||||
fi
|
||||
|
||||
local pid title progress status pending
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
title=$(jq -r '.title' "$f")
|
||||
progress=$(jq -r '.progress' "$f")
|
||||
status=$(jq -r '.status' "$f")
|
||||
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
|
||||
local color="$GREEN"
|
||||
[[ "$progress" -lt 50 ]] && color="$YELLOW"
|
||||
[[ "$progress" -lt 20 ]] && color="$RED"
|
||||
[[ "$progress" -eq 100 ]] && color="$GREEN"
|
||||
|
||||
local bar=""
|
||||
local filled=$(( progress / 10 ))
|
||||
for ((i=0; i<filled; i++)); do bar+="█"; done
|
||||
for ((i=filled; i<10; i++)); do bar+="░"; done
|
||||
|
||||
echo -e " ${color}${bar}${NC} ${progress}% ${CYAN}${pid}${NC} — $title ($pending pending)"
|
||||
done
|
||||
[[ "$found" -gt 0 ]] && echo ""
|
||||
done
|
||||
}
|
||||
|
||||
# ── REVIEW ──
|
||||
cmd_review() {
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Session Review${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
echo -e " ${BOLD}Session:${NC} ${CYAN}${session:-unknown}${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -f "$COLLAB_DB" && -n "$session" ]]; then
|
||||
# Messages sent this session
|
||||
echo -e " ${BOLD}Messages sent:${NC}"
|
||||
sql "SELECT type, message, created_at FROM messages WHERE session_id='$session' ORDER BY created_at;" | while IFS='|' read -r type msg created; do
|
||||
echo -e " [${created:11:8}] ${CYAN}${type}${NC}: ${msg:0:70}"
|
||||
done
|
||||
|
||||
# Handoffs created
|
||||
echo ""
|
||||
echo -e " ${BOLD}Handoffs left:${NC}"
|
||||
sql "SELECT message FROM handoffs WHERE from_session='$session';" | while read -r msg; do
|
||||
echo -e " 🤝 $msg"
|
||||
done
|
||||
fi
|
||||
|
||||
# Recent journal entries
|
||||
if [[ -f "$JOURNAL" ]]; then
|
||||
echo ""
|
||||
echo -e " ${BOLD}Recent journal activity:${NC}"
|
||||
tail -10 "$JOURNAL" | while read -r line; do
|
||||
local action entity ts
|
||||
action=$(echo "$line" | jq -r '.action')
|
||||
entity=$(echo "$line" | jq -r '.entity')
|
||||
ts=$(echo "$line" | jq -r '.timestamp' | cut -dT -f2 | cut -d. -f1)
|
||||
echo -e " [${ts}] ${action} → ${entity}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ── SUGGEST ──
|
||||
cmd_suggest() {
|
||||
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Analyzing projects...${NC}"
|
||||
echo ""
|
||||
|
||||
local suggestions=()
|
||||
|
||||
for f in "$PROJECTS_DIR"/*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local status
|
||||
status=$(jq -r '.status' "$f")
|
||||
[[ "$status" != "active" ]] && continue
|
||||
|
||||
local pid progress pending cadence title
|
||||
pid=$(jq -r '.project_id' "$f")
|
||||
progress=$(jq -r '.progress' "$f")
|
||||
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
|
||||
cadence=$(jq -r '.cadence' "$f")
|
||||
title=$(jq -r '.title' "$f")
|
||||
|
||||
[[ "$pending" -eq 0 ]] && continue
|
||||
|
||||
local reason=""
|
||||
if [[ "$cadence" == "weekly" && "$progress" -lt 100 ]]; then
|
||||
reason="⚡ Weekly deadline — ${progress}% done"
|
||||
elif [[ "$progress" -lt 20 ]]; then
|
||||
reason="🔴 Barely started — needs attention"
|
||||
elif [[ "$progress" -lt 50 ]]; then
|
||||
reason="🟡 Under halfway — good opportunity"
|
||||
fi
|
||||
|
||||
if [[ -n "$reason" ]]; then
|
||||
echo -e " ${CYAN}$pid${NC} — $title"
|
||||
echo -e " $reason ($pending pending todos)"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ── WRAP ──
|
||||
cmd_wrap() {
|
||||
local session
|
||||
session=$(get_session)
|
||||
if [[ -z "$session" ]]; then
|
||||
echo -e "${RED}No active session to wrap${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Wrapping up session...${NC}"
|
||||
|
||||
# Gather session activity
|
||||
local msgs=""
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
msgs=$(sql "SELECT type, message FROM messages WHERE session_id='$session' ORDER BY created_at;" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Auto-generate handoff
|
||||
local handoff_msg="Session $session wrap-up:"
|
||||
if [[ -n "$msgs" ]]; then
|
||||
handoff_msg="$handoff_msg | Activity: $(echo "$msgs" | wc -l | tr -d ' ') messages"
|
||||
local last_announce
|
||||
last_announce=$(sql "SELECT message FROM messages WHERE session_id='$session' AND type='announce' ORDER BY created_at DESC LIMIT 1;" 2>/dev/null)
|
||||
[[ -n "$last_announce" ]] && handoff_msg="$handoff_msg | Last: $last_announce"
|
||||
fi
|
||||
|
||||
# Get current focus
|
||||
local focus
|
||||
focus=$(sql "SELECT focus FROM sessions WHERE session_id='$session';" 2>/dev/null)
|
||||
[[ -n "$focus" ]] && handoff_msg="$handoff_msg | Focus was: $focus"
|
||||
|
||||
echo -e " ${BOLD}Handoff:${NC} $handoff_msg"
|
||||
echo ""
|
||||
|
||||
# Create handoff
|
||||
"$COLLAB" handoff "$handoff_msg" 2>/dev/null || true
|
||||
|
||||
# Mark session complete
|
||||
"$COLLAB" complete 2>/dev/null || true
|
||||
|
||||
echo -e "${GREEN}Session wrapped up and handed off!${NC}"
|
||||
}
|
||||
|
||||
# ── HELP ──
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
|
||||
${PINK}║${NC} ${BOLD}BlackRoad Planner${NC} ${PINK}║${NC}
|
||||
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
|
||||
|
||||
${BOLD}Planning:${NC}
|
||||
${CYAN}plan <project-id>${NC} Show full project plan with todos
|
||||
${CYAN}next${NC} What to work on next (auto-prioritized)
|
||||
${CYAN}roadmap${NC} All projects grouped by cadence
|
||||
${CYAN}suggest${NC} AI-suggested priorities
|
||||
|
||||
${BOLD}Session:${NC}
|
||||
${CYAN}focus <project-id>${NC} Set session focus + announce
|
||||
${CYAN}standup [--slack]${NC} Generate standup report
|
||||
${CYAN}review${NC} Review this session's activity
|
||||
${CYAN}wrap${NC} End session: auto-handoff + complete
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
plan) shift; cmd_plan "$@" ;;
|
||||
next) cmd_next ;;
|
||||
focus) shift; cmd_focus "$@" ;;
|
||||
standup) shift 2>/dev/null || true; cmd_standup "$@" ;;
|
||||
roadmap|map) cmd_roadmap ;;
|
||||
review|rev) cmd_review ;;
|
||||
suggest|sug) cmd_suggest ;;
|
||||
wrap|end) cmd_wrap ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*)
|
||||
echo -e "${RED}Unknown: $1${NC}"
|
||||
cmd_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
283
operator/memory/memory-slack.sh
Normal file
283
operator/memory/memory-slack.sh
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/bin/bash
|
||||
# BlackRoad Slack CLI — Direct agent communication bridge
|
||||
# Usage: memory-slack.sh <command> [args]
|
||||
set -e
|
||||
|
||||
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
|
||||
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
|
||||
|
||||
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'
|
||||
NC='\033[0m'
|
||||
|
||||
get_session() {
|
||||
if [[ -f "$SESSION_FILE" ]]; then
|
||||
cat "$SESSION_FILE"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── SAY ──
|
||||
cmd_say() {
|
||||
local msg="$*"
|
||||
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 say <message>${NC}"; exit 1; }
|
||||
local session
|
||||
session=$(get_session)
|
||||
local text="[${session}] ${msg}"
|
||||
curl -s --max-time 3 --connect-timeout 2 \
|
||||
-X POST "$SLACK_API/post" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$text" '{text:$t}')" >/dev/null 2>&1 || true
|
||||
echo -e "${GREEN}Sent to Slack:${NC} $msg"
|
||||
}
|
||||
|
||||
# ── ASK ──
|
||||
cmd_ask() {
|
||||
local agent="$1"
|
||||
shift || true
|
||||
local msg="$*"
|
||||
[[ -z "$agent" || -z "$msg" ]] && { echo -e "${RED}Usage: $0 ask <agent> <message>${NC}"; exit 1; }
|
||||
|
||||
echo -e "${BLUE}Asking ${CYAN}${agent}${BLUE}...${NC}"
|
||||
local response
|
||||
response=$(curl -s --max-time 15 --connect-timeout 3 \
|
||||
-X POST "$SLACK_API/ask" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg a "$agent" --arg m "$msg" '{agent:$a, message:$m, slack:true}')" 2>/dev/null)
|
||||
|
||||
if [[ -n "$response" ]]; then
|
||||
local name reply
|
||||
name=$(echo "$response" | jq -r '.agent // "?"')
|
||||
reply=$(echo "$response" | jq -r '.reply // "no response"')
|
||||
echo -e "${GREEN}${name}:${NC} ${reply}"
|
||||
else
|
||||
echo -e "${RED}No response — agent may be offline${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── DEBATE ──
|
||||
cmd_debate() {
|
||||
local topic="$*"
|
||||
[[ -z "$topic" ]] && { echo -e "${RED}Usage: $0 debate <topic>${NC}"; exit 1; }
|
||||
|
||||
echo -e "${BLUE}Starting debate: ${CYAN}${topic}${NC}"
|
||||
echo -e "${BLUE}Agents: alice, cecilia, octavia, lucidia × 2 rounds${NC}"
|
||||
echo ""
|
||||
|
||||
local response
|
||||
response=$(curl -s --max-time 60 --connect-timeout 3 \
|
||||
-X POST "$SLACK_API/group" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$topic" '{topic:$t, agents:["alice","cecilia","octavia","lucidia"], rounds:2}')" 2>/dev/null)
|
||||
|
||||
if [[ -n "$response" ]]; then
|
||||
echo "$response" | jq -r '.transcript[]? | "\(.emoji) \(.agent) (round \(.round + 1)): \(.reply)"' 2>/dev/null
|
||||
else
|
||||
echo -e "${RED}No response — debate failed${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── COUNCIL ──
|
||||
cmd_council() {
|
||||
local topic="$*"
|
||||
[[ -z "$topic" ]] && { echo -e "${RED}Usage: $0 council <topic>${NC}"; exit 1; }
|
||||
|
||||
echo -e "${BLUE}Full council: ${CYAN}${topic}${NC}"
|
||||
echo ""
|
||||
|
||||
local response
|
||||
response=$(curl -s --max-time 60 --connect-timeout 3 \
|
||||
-X POST "$SLACK_API/group" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$topic" '{topic:$t, agents:["alice","cecilia","octavia","lucidia","shellfish","caddy"], rounds:1}')" 2>/dev/null)
|
||||
|
||||
if [[ -n "$response" ]]; then
|
||||
echo "$response" | jq -r '.transcript[]? | "\(.emoji) \(.agent): \(.reply)"' 2>/dev/null
|
||||
else
|
||||
echo -e "${RED}No response — council failed${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── ALERT ──
|
||||
cmd_alert() {
|
||||
local msg="$*"
|
||||
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 alert <message>${NC}"; exit 1; }
|
||||
|
||||
curl -s --max-time 3 --connect-timeout 2 \
|
||||
-X POST "$SLACK_API/alert" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$msg" '{text:$t}')" >/dev/null 2>&1 || true
|
||||
echo -e "${RED}🚨 Alert sent:${NC} $msg"
|
||||
}
|
||||
|
||||
# ── DEPLOY ──
|
||||
cmd_deploy() {
|
||||
local msg="$*"
|
||||
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 deploy <message>${NC}"; exit 1; }
|
||||
|
||||
curl -s --max-time 3 --connect-timeout 2 \
|
||||
-X POST "$SLACK_API/deploy" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$msg" '{text:$t}')" >/dev/null 2>&1 || true
|
||||
echo -e "${GREEN}🚀 Deploy notification sent:${NC} $msg"
|
||||
}
|
||||
|
||||
# ── HEALTH ──
|
||||
cmd_health() {
|
||||
local response
|
||||
response=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/health" 2>/dev/null)
|
||||
if [[ -n "$response" ]]; then
|
||||
local status agents
|
||||
status=$(echo "$response" | jq -r '.status')
|
||||
agents=$(echo "$response" | jq -r '.agents')
|
||||
echo -e "${GREEN}Slack Worker:${NC} $status — $agents agents"
|
||||
else
|
||||
echo -e "${RED}Slack Worker: unreachable${NC}"
|
||||
fi
|
||||
|
||||
local full_status
|
||||
full_status=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/status" 2>/dev/null)
|
||||
if [[ -n "$full_status" ]]; then
|
||||
local webhook bot
|
||||
webhook=$(echo "$full_status" | jq -r '.webhook')
|
||||
bot=$(echo "$full_status" | jq -r '.bot_token')
|
||||
echo -e " Webhook: ${GREEN}$webhook${NC}"
|
||||
echo -e " Bot token: ${YELLOW}$bot${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── AGENTS ──
|
||||
cmd_agents() {
|
||||
local response
|
||||
response=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/agents" 2>/dev/null)
|
||||
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}BlackRoad Slack Agents${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ -n "$response" ]]; then
|
||||
echo "$response" | jq -r '.agents[]? | " \(.emoji) \(.name|@text) — \(.role)"' 2>/dev/null
|
||||
else
|
||||
echo -e " ${RED}Could not fetch agents${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── BROADCAST ──
|
||||
cmd_broadcast() {
|
||||
local msg="$*"
|
||||
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 broadcast <message>${NC}"; exit 1; }
|
||||
|
||||
local session
|
||||
session=$(get_session)
|
||||
|
||||
# Post to Slack
|
||||
curl -s --max-time 3 --connect-timeout 2 \
|
||||
-X POST "$SLACK_API/collab" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "announce" --arg m "$msg" --arg s "$session" \
|
||||
'{type:$t, message:$m, session_id:$s}')" >/dev/null 2>&1 || true
|
||||
|
||||
# Also log to collab DB if available
|
||||
if [[ -f "$HOME/.blackroad/collaboration.db" ]]; then
|
||||
local msg_id="msg-$(date +%s)-$$"
|
||||
local now
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
sqlite3 "$HOME/.blackroad/collaboration.db" "INSERT INTO messages (msg_id, session_id, type, message, created_at) VALUES ('$msg_id', '$session', 'announce', '$(echo "$msg" | sed "s/'/''/g")', '$now');" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}📢 Broadcast:${NC} $msg"
|
||||
}
|
||||
|
||||
# ── THREAD ──
|
||||
cmd_thread() {
|
||||
local agent="$1"
|
||||
shift || true
|
||||
local initial="$*"
|
||||
[[ -z "$agent" || -z "$initial" ]] && { echo -e "${RED}Usage: $0 thread <agent> <message>${NC}"; exit 1; }
|
||||
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Thread with ${agent}${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
local context=""
|
||||
local prompts=("$initial" "Can you elaborate on that?" "What should we do about it?")
|
||||
|
||||
for i in 0 1 2; do
|
||||
local prompt="${prompts[$i]}"
|
||||
if [[ "$i" -gt 0 ]]; then
|
||||
prompt="Previous context: ${context}. Follow-up: ${prompts[$i]}"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}You:${NC} ${prompts[$i]}"
|
||||
local response
|
||||
response=$(curl -s --max-time 15 --connect-timeout 3 \
|
||||
-X POST "$SLACK_API/ask" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg a "$agent" --arg m "$prompt" '{agent:$a, message:$m}')" 2>/dev/null)
|
||||
|
||||
if [[ -n "$response" ]]; then
|
||||
local reply
|
||||
reply=$(echo "$response" | jq -r '.reply // "..."')
|
||||
echo -e "${GREEN}${agent}:${NC} ${reply}"
|
||||
context="${context} ${reply}"
|
||||
else
|
||||
echo -e "${RED}(no response)${NC}"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
# ── HELP ──
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
|
||||
${PINK}║${NC} ${BOLD}BlackRoad Slack CLI${NC} ${PINK}║${NC}
|
||||
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
|
||||
|
||||
${BOLD}Messaging:${NC}
|
||||
${CYAN}say <message>${NC} Post to Slack with session prefix
|
||||
${CYAN}broadcast <message>${NC} Broadcast via /collab + log to DB
|
||||
${CYAN}alert <message>${NC} Send alert to Slack
|
||||
${CYAN}deploy <message>${NC} Send deploy notification
|
||||
|
||||
${BOLD}Agents:${NC}
|
||||
${CYAN}ask <agent> <msg>${NC} Ask an AI agent
|
||||
${CYAN}debate <topic>${NC} 4-agent debate (2 rounds)
|
||||
${CYAN}council <topic>${NC} 6-agent council (1 round)
|
||||
${CYAN}thread <agent> <msg>${NC} 3-exchange conversation with agent
|
||||
${CYAN}agents${NC} List all available agents
|
||||
|
||||
${BOLD}System:${NC}
|
||||
${CYAN}health${NC} Check Slack Worker health
|
||||
|
||||
${BOLD}Agents:${NC} alice, cecilia, octavia, aria, lucidia, shellfish, caddy, alexa, road
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
say|s) shift; cmd_say "$@" ;;
|
||||
ask|a) shift; cmd_ask "$@" ;;
|
||||
debate|d) shift; cmd_debate "$@" ;;
|
||||
council|c) shift; cmd_council "$@" ;;
|
||||
alert) shift; cmd_alert "$@" ;;
|
||||
deploy) shift; cmd_deploy "$@" ;;
|
||||
health|h) cmd_health ;;
|
||||
agents|list) cmd_agents ;;
|
||||
broadcast|bc) shift; cmd_broadcast "$@" ;;
|
||||
thread|t) shift; cmd_thread "$@" ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*)
|
||||
echo -e "${RED}Unknown: $1${NC}"
|
||||
cmd_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
308
operator/memory/memory-watchdog.sh
Normal file
308
operator/memory/memory-watchdog.sh
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/bin/bash
|
||||
# BlackRoad Collaboration Watchdog — health monitoring, cleanup, digests
|
||||
# Usage: memory-watchdog.sh <command> [args]
|
||||
set -e
|
||||
|
||||
COLLAB_DB="$HOME/.blackroad/collaboration.db"
|
||||
HANDOFF_DIR="$HOME/.blackroad/memory/handoffs"
|
||||
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
|
||||
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
|
||||
|
||||
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'
|
||||
NC='\033[0m'
|
||||
|
||||
sql() { sqlite3 "$COLLAB_DB" "$@" 2>/dev/null; }
|
||||
|
||||
post_to_slack() {
|
||||
local text="$1"
|
||||
curl -s --max-time 3 --connect-timeout 2 \
|
||||
-X POST "$SLACK_API/post" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg t "$text" '{text:$t}')" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
check_db() {
|
||||
if [[ ! -f "$COLLAB_DB" ]]; then
|
||||
echo -e "${RED}Collaboration DB not found. Run: memory-collaboration.sh init${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── SWEEP — Clean stale data ──
|
||||
cmd_sweep() {
|
||||
check_db
|
||||
echo -e "${PINK}[WATCHDOG]${NC} ${BOLD}Sweeping stale data...${NC}"
|
||||
|
||||
# Mark stale sessions as abandoned (>2h, still active)
|
||||
local stale
|
||||
stale=$(sql "SELECT count(*) FROM sessions WHERE status='active' AND last_seen < datetime('now', '-2 hours');")
|
||||
if [[ "$stale" -gt 0 ]]; then
|
||||
sql "UPDATE sessions SET status='abandoned' WHERE status='active' AND last_seen < datetime('now', '-2 hours');"
|
||||
echo -e " ${YELLOW}Abandoned $stale stale session(s)${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}No stale sessions${NC}"
|
||||
fi
|
||||
|
||||
# Archive old messages (>7 days)
|
||||
local old_msgs
|
||||
old_msgs=$(sql "SELECT count(*) FROM messages WHERE created_at < datetime('now', '-7 days');")
|
||||
if [[ "$old_msgs" -gt 0 ]]; then
|
||||
sql "DELETE FROM messages WHERE created_at < datetime('now', '-7 days');"
|
||||
echo -e " ${YELLOW}Archived $old_msgs old message(s)${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}No old messages to archive${NC}"
|
||||
fi
|
||||
|
||||
# Clean picked-up handoff files (>3 days)
|
||||
local cleaned=0
|
||||
for f in "$HANDOFF_DIR"/handoff-*.json; do
|
||||
[[ ! -f "$f" ]] && continue
|
||||
local pbu
|
||||
pbu=$(jq -r '.picked_up_by // ""' "$f" 2>/dev/null)
|
||||
if [[ -n "$pbu" && "$pbu" != "" && "$pbu" != "null" ]]; then
|
||||
local created
|
||||
created=$(jq -r '.created_at' "$f" 2>/dev/null)
|
||||
local file_age
|
||||
file_age=$(( $(date +%s) - $(date -jf "%Y-%m-%dT%H:%M:%SZ" "$created" +%s 2>/dev/null || echo $(date +%s)) ))
|
||||
if [[ "$file_age" -gt 259200 ]]; then # 3 days
|
||||
rm -f "$f"
|
||||
((cleaned++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
[[ "$cleaned" -gt 0 ]] && echo -e " ${YELLOW}Cleaned $cleaned old handoff file(s)${NC}"
|
||||
|
||||
# Report orphaned handoffs
|
||||
local orphaned
|
||||
orphaned=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours');")
|
||||
if [[ "$orphaned" -gt 0 ]]; then
|
||||
echo -e " ${RED}$orphaned orphaned handoff(s) — never picked up (>24h)${NC}"
|
||||
fi
|
||||
|
||||
# DB size
|
||||
local db_size
|
||||
db_size=$(du -h "$COLLAB_DB" | cut -f1)
|
||||
echo -e " ${BLUE}DB size: $db_size${NC}"
|
||||
|
||||
# Total counts
|
||||
local sessions msgs handoffs
|
||||
sessions=$(sql "SELECT count(*) FROM sessions;")
|
||||
msgs=$(sql "SELECT count(*) FROM messages;")
|
||||
handoffs=$(sql "SELECT count(*) FROM handoffs;")
|
||||
echo -e " ${BLUE}Totals: $sessions sessions, $msgs messages, $handoffs handoffs${NC}"
|
||||
}
|
||||
|
||||
# ── HEARTBEAT — Quick health pulse ──
|
||||
cmd_heartbeat() {
|
||||
local slack_flag="${1:-}"
|
||||
local issues=()
|
||||
|
||||
# Check DB
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
local integrity
|
||||
integrity=$(sql "PRAGMA integrity_check;" 2>/dev/null)
|
||||
if [[ "$integrity" != "ok" ]]; then
|
||||
issues+=("DB integrity: $integrity")
|
||||
fi
|
||||
else
|
||||
issues+=("Collaboration DB missing")
|
||||
fi
|
||||
|
||||
# Check Slack
|
||||
local slack_status
|
||||
slack_status=$(curl -s --max-time 2 --connect-timeout 1 "$SLACK_API/health" 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unreachable")
|
||||
if [[ "$slack_status" != "alive" ]]; then
|
||||
issues+=("Slack Worker: $slack_status")
|
||||
fi
|
||||
|
||||
# Check journal
|
||||
if [[ -f "$JOURNAL" ]]; then
|
||||
local last_entry
|
||||
last_entry=$(tail -1 "$JOURNAL" 2>/dev/null)
|
||||
echo "$last_entry" | jq . >/dev/null 2>&1 || issues+=("Journal: last entry not valid JSON")
|
||||
else
|
||||
issues+=("Journal file missing")
|
||||
fi
|
||||
|
||||
# Check disk space
|
||||
local disk_used
|
||||
disk_used=$(du -sm "$HOME/.blackroad/" 2>/dev/null | cut -f1)
|
||||
if [[ "${disk_used:-0}" -gt 500 ]]; then
|
||||
issues+=("Disk: ~/.blackroad/ is ${disk_used}MB")
|
||||
fi
|
||||
|
||||
# Active sessions
|
||||
local active=0
|
||||
if [[ -f "$COLLAB_DB" ]]; then
|
||||
active=$(sql "SELECT count(*) FROM sessions WHERE status='active';" 2>/dev/null || echo 0)
|
||||
fi
|
||||
|
||||
if [[ ${#issues[@]} -eq 0 ]]; then
|
||||
echo -e "${GREEN}✅ All systems nominal${NC} — $active active session(s), Slack connected, DB healthy"
|
||||
if [[ "$slack_flag" == "--slack" ]]; then
|
||||
post_to_slack "💚 Watchdog heartbeat: all systems nominal — $active active session(s)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}⚠️ Issues detected:${NC}"
|
||||
for issue in "${issues[@]}"; do
|
||||
echo -e " ${RED}• $issue${NC}"
|
||||
done
|
||||
if [[ "$slack_flag" == "--slack" ]]; then
|
||||
local msg="⚠️ Watchdog heartbeat: ${#issues[@]} issue(s)"
|
||||
for issue in "${issues[@]}"; do
|
||||
msg="$msg\n • $issue"
|
||||
done
|
||||
post_to_slack "$msg"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── DIGEST — Collaboration digest ──
|
||||
cmd_digest() {
|
||||
check_db
|
||||
local slack_flag="${1:-}"
|
||||
|
||||
local sessions_24h msgs_24h handoffs_24h handoffs_picked
|
||||
sessions_24h=$(sql "SELECT count(*) FROM sessions WHERE started_at > datetime('now', '-24 hours');")
|
||||
msgs_24h=$(sql "SELECT count(*) FROM messages WHERE created_at > datetime('now', '-24 hours');")
|
||||
handoffs_24h=$(sql "SELECT count(*) FROM handoffs WHERE created_at > datetime('now', '-24 hours');")
|
||||
handoffs_picked=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_at > datetime('now', '-24 hours') AND picked_up_by != '';")
|
||||
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Collaboration Digest (24h)${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e " Sessions: ${GREEN}${sessions_24h}${NC}"
|
||||
echo -e " Messages: ${BLUE}${msgs_24h}${NC}"
|
||||
echo -e " Handoffs: ${CYAN}${handoffs_24h} created${NC}, ${GREEN}${handoffs_picked} picked up${NC}"
|
||||
echo ""
|
||||
|
||||
# Recent sessions
|
||||
echo -e " ${BOLD}Sessions:${NC}"
|
||||
sql "SELECT session_id, status, focus FROM sessions WHERE started_at > datetime('now', '-24 hours') ORDER BY started_at DESC LIMIT 10;" | while IFS='|' read -r sid status focus; do
|
||||
local icon="●"
|
||||
case "$status" in
|
||||
active) icon="${GREEN}●${NC}" ;;
|
||||
completed) icon="${BLUE}✓${NC}" ;;
|
||||
abandoned) icon="${YELLOW}⊘${NC}" ;;
|
||||
esac
|
||||
echo -e " $icon ${CYAN}${sid}${NC}${focus:+ — $focus}"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Top message types
|
||||
echo -e " ${BOLD}Message Types:${NC}"
|
||||
sql "SELECT type, count(*) as cnt FROM messages WHERE created_at > datetime('now', '-24 hours') GROUP BY type ORDER BY cnt DESC;" | while IFS='|' read -r type cnt; do
|
||||
echo -e " ${type}: ${GREEN}${cnt}${NC}"
|
||||
done
|
||||
|
||||
if [[ "$slack_flag" == "--slack" ]]; then
|
||||
local msg="📊 *Collaboration Digest (24h)*\n"
|
||||
msg="${msg}Sessions: ${sessions_24h} | Messages: ${msgs_24h} | Handoffs: ${handoffs_24h} created, ${handoffs_picked} picked up"
|
||||
post_to_slack "$msg"
|
||||
echo ""
|
||||
echo -e " ${GREEN}Posted to Slack${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── AUDIT — Full system audit ──
|
||||
cmd_audit() {
|
||||
check_db
|
||||
|
||||
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${PINK}║${NC} ${BOLD}Collaboration System Audit${NC} ${PINK}║${NC}"
|
||||
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# DB integrity
|
||||
local integrity
|
||||
integrity=$(sql "PRAGMA integrity_check;")
|
||||
echo -e " ${BOLD}DB Integrity:${NC} ${integrity}"
|
||||
|
||||
# All sessions
|
||||
echo ""
|
||||
echo -e " ${BOLD}All Sessions:${NC}"
|
||||
sql "SELECT session_id, status, started_at, last_seen, focus FROM sessions ORDER BY started_at DESC;" | while IFS='|' read -r sid status started last focus; do
|
||||
local icon
|
||||
case "$status" in
|
||||
active) icon="${GREEN}●${NC}" ;;
|
||||
completed) icon="${BLUE}✓${NC}" ;;
|
||||
abandoned) icon="${YELLOW}⊘${NC}" ;;
|
||||
*) icon="?" ;;
|
||||
esac
|
||||
echo -e " $icon ${CYAN}${sid}${NC} [${status}] ${started:0:19}${focus:+ — $focus}"
|
||||
done
|
||||
|
||||
# Orphaned handoffs
|
||||
echo ""
|
||||
local orphaned
|
||||
orphaned=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours');")
|
||||
echo -e " ${BOLD}Orphaned Handoffs (>24h, never picked up):${NC} ${orphaned}"
|
||||
if [[ "$orphaned" -gt 0 ]]; then
|
||||
sql "SELECT handoff_id, from_session, message FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours') LIMIT 5;" | while IFS='|' read -r hid from msg; do
|
||||
echo -e " ${RED}⚠${NC} ${hid} from ${CYAN}${from}${NC}: ${msg:0:60}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Duplicate session check
|
||||
echo ""
|
||||
local dupes
|
||||
dupes=$(sql "SELECT session_id, count(*) as cnt FROM sessions GROUP BY session_id HAVING cnt > 1;" 2>/dev/null)
|
||||
if [[ -n "$dupes" ]]; then
|
||||
echo -e " ${RED}Duplicate sessions found:${NC}"
|
||||
echo "$dupes" | while IFS='|' read -r sid cnt; do
|
||||
echo -e " ${RED}$sid: $cnt entries${NC}"
|
||||
done
|
||||
else
|
||||
echo -e " ${GREEN}No duplicate sessions${NC}"
|
||||
fi
|
||||
|
||||
# DB stats
|
||||
echo ""
|
||||
local db_size pages
|
||||
db_size=$(du -h "$COLLAB_DB" | cut -f1)
|
||||
pages=$(sql "PRAGMA page_count;")
|
||||
local page_size
|
||||
page_size=$(sql "PRAGMA page_size;")
|
||||
echo -e " ${BOLD}DB Stats:${NC} $db_size, $pages pages × ${page_size}B"
|
||||
echo -e " ${BOLD}Journal mode:${NC} $(sql 'PRAGMA journal_mode;')"
|
||||
}
|
||||
|
||||
# ── HELP ──
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
|
||||
${PINK}║${NC} ${BOLD}BlackRoad Collaboration Watchdog${NC} ${PINK}║${NC}
|
||||
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
|
||||
|
||||
${BOLD}Commands:${NC}
|
||||
${CYAN}heartbeat [--slack]${NC} Quick health check (post to Slack with --slack)
|
||||
${CYAN}sweep${NC} Clean stale sessions, old messages, orphaned handoffs
|
||||
${CYAN}digest [--slack]${NC} 24h collaboration digest (post to Slack with --slack)
|
||||
${CYAN}audit${NC} Full system audit: integrity, sessions, orphans
|
||||
|
||||
${BOLD}Cron schedule:${NC}
|
||||
*/30 heartbeat
|
||||
3am sweep
|
||||
9am digest --slack
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
heartbeat|hb) shift 2>/dev/null || true; cmd_heartbeat "$@" ;;
|
||||
sweep|clean) cmd_sweep ;;
|
||||
digest|report) shift 2>/dev/null || true; cmd_digest "$@" ;;
|
||||
audit) cmd_audit ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*)
|
||||
echo -e "${RED}Unknown: $1${NC}"
|
||||
cmd_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user