214 lines
7.5 KiB
Bash
Executable File
214 lines
7.5 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# PATH for systemd (claude, uv, npx, node)
|
|
export PATH="$HOME/.local/bin:$HOME/.nvm/versions/node/$(ls "$HOME/.nvm/versions/node/" 2>/dev/null | tail -1)/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
|
|
|
|
# Paths — auto-detect from script location
|
|
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VAULT_DIR="$PROJECT_DIR/vault"
|
|
ENV_FILE="$PROJECT_DIR/.env"
|
|
|
|
# Load environment variables
|
|
if [ -f "$ENV_FILE" ]; then
|
|
export $(grep -v '^#' "$ENV_FILE" | xargs)
|
|
fi
|
|
|
|
# Check token
|
|
if [ -z "$TELEGRAM_BOT_TOKEN" ]; then
|
|
echo "ERROR: TELEGRAM_BOT_TOKEN not set"
|
|
exit 1
|
|
fi
|
|
|
|
# Timezone (configure in .env: TZ=Your/Timezone)
|
|
export TZ="${TZ:-UTC}"
|
|
|
|
# Date and chat_id
|
|
TODAY=$(date +%Y-%m-%d)
|
|
CHAT_ID="${ALLOWED_USER_IDS//[\[\]]/}" # remove brackets from [123456]
|
|
|
|
echo "=== d-brain processing for $TODAY ==="
|
|
|
|
# ── ORIENT PHASE: pre-flight checks ──
|
|
DAILY_FILE="$VAULT_DIR/daily/$TODAY.md"
|
|
HANDOFF_FILE="$VAULT_DIR/.session/handoff.md"
|
|
GRAPH_FILE="$VAULT_DIR/.graph/vault-graph.json"
|
|
|
|
# Check daily file exists and has content
|
|
if [ ! -f "$DAILY_FILE" ]; then
|
|
echo "ORIENT: daily/$TODAY.md not found — creating empty file"
|
|
echo "# $TODAY" > "$DAILY_FILE"
|
|
fi
|
|
|
|
DAILY_SIZE=$(wc -c < "$DAILY_FILE" 2>/dev/null || echo "0")
|
|
if [ "$DAILY_SIZE" -lt 50 ]; then
|
|
echo "ORIENT: daily/$TODAY.md is empty ($DAILY_SIZE bytes) — skipping Claude processing"
|
|
echo "ORIENT: Running graph rebuild only"
|
|
|
|
# Still rebuild graph and commit
|
|
cd "$VAULT_DIR"
|
|
uv run .claude/skills/graph-builder/scripts/analyze.py || echo "Graph rebuild failed (non-critical)"
|
|
cd "$PROJECT_DIR"
|
|
|
|
git add -A
|
|
git commit -m "chore: process daily $TODAY" || true
|
|
git push || true
|
|
echo "=== Done (empty daily, graph-only) ==="
|
|
exit 0
|
|
fi
|
|
|
|
# Check handoff exists
|
|
if [ ! -f "$HANDOFF_FILE" ]; then
|
|
echo "ORIENT: handoff.md not found — creating stub"
|
|
mkdir -p "$VAULT_DIR/.session"
|
|
echo -e "---\nupdated: $(date -Iseconds)\n---\n\n## Last Session\n(none)\n\n## Observations" > "$HANDOFF_FILE"
|
|
fi
|
|
|
|
# Check graph freshness (warn if >7 days old)
|
|
if [ -f "$GRAPH_FILE" ]; then
|
|
GRAPH_AGE=$(( ($(date +%s) - $(stat -c %Y "$GRAPH_FILE" 2>/dev/null || stat -f %m "$GRAPH_FILE" 2>/dev/null || echo 0)) / 86400 ))
|
|
if [ "$GRAPH_AGE" -gt 7 ]; then
|
|
echo "ORIENT: vault-graph.json is $GRAPH_AGE days old (>7)"
|
|
fi
|
|
fi
|
|
|
|
echo "ORIENT: daily=$DAILY_SIZE bytes, handoff=OK, graph=OK"
|
|
# ── END ORIENT PHASE ──
|
|
|
|
# MCP timeout for stdio server (default 5 sec is too short)
|
|
export MCP_TIMEOUT=30000
|
|
export MAX_MCP_OUTPUT_TOKENS=50000
|
|
|
|
# ── 3-PHASE PIPELINE ──
|
|
# Phase 1: CAPTURE (classify entries → JSON)
|
|
# Phase 2: EXECUTE (create tasks, save thoughts → JSON)
|
|
# Phase 3: REFLECT (generate HTML report, update MEMORY)
|
|
# Each phase = fresh Claude context for better quality.
|
|
|
|
SESSION_DIR="$VAULT_DIR/.session"
|
|
mkdir -p "$SESSION_DIR"
|
|
CAPTURE_FILE="$SESSION_DIR/capture.json"
|
|
EXECUTE_FILE="$SESSION_DIR/execute.json"
|
|
|
|
# Find yearly goals file (auto-detect year)
|
|
YEARLY_GOALS=$(ls "$VAULT_DIR/goals/1-yearly-"*.md 2>/dev/null | tail -1)
|
|
YEARLY_GOALS_NAME=$(basename "$YEARLY_GOALS" 2>/dev/null || echo "1-yearly.md")
|
|
|
|
# MCP config (needed for Phase 2)
|
|
MCP_PROMPT="CRITICAL: MCP loads in 10-30 seconds. You are NOT in subprocess.
|
|
Algorithm: 1) Call. 2) Error? Wait 10 sec. 3) Call again. 4) Wait 20 sec. 5) Call — GUARANTEED to work.
|
|
DO NOT say MCP unavailable. It is available. Just wait and call."
|
|
|
|
cd "$VAULT_DIR"
|
|
|
|
# ── Phase 1: CAPTURE ──
|
|
echo "=== Phase 1: CAPTURE ==="
|
|
CAPTURE=$(claude --print --dangerously-skip-permissions \
|
|
-p "Today is $TODAY. Read .claude/skills/dbrain-processor/phases/capture.md and execute Phase 1.
|
|
Read daily/$TODAY.md, goals/3-weekly.md, goals/2-monthly.md, goals/$YEARLY_GOALS_NAME.
|
|
Classify each entry. Return ONLY JSON." \
|
|
2>&1) || true
|
|
|
|
# Extract JSON from output (Claude may add text around it)
|
|
echo "$CAPTURE" | grep -o '{.*}' | python3 -c "
|
|
import sys, json
|
|
for line in sys.stdin:
|
|
try:
|
|
data = json.loads(line.strip())
|
|
json.dump(data, sys.stdout, ensure_ascii=False, indent=2)
|
|
sys.exit(0)
|
|
except: pass
|
|
# Fallback: save raw output
|
|
sys.stdout.write('{\"error\": \"failed to parse capture output\", \"raw\": \"see capture.log\"}')
|
|
" > "$CAPTURE_FILE" 2>/dev/null || echo '{"error": "capture failed"}' > "$CAPTURE_FILE"
|
|
|
|
echo "Capture saved: $(wc -c < "$CAPTURE_FILE") bytes"
|
|
|
|
# Check if capture produced valid entries
|
|
if grep -q '"error"' "$CAPTURE_FILE"; then
|
|
echo "WARN: Capture phase had issues, falling back to monolith mode"
|
|
# Fallback to monolith processing
|
|
REPORT=$(claude --print --dangerously-skip-permissions \
|
|
--mcp-config "$PROJECT_DIR/mcp-config.json" \
|
|
-p "Today is $TODAY. Execute daily processing according to dbrain-processor skill.
|
|
$MCP_PROMPT" \
|
|
2>&1) || true
|
|
else
|
|
# ── Phase 2: EXECUTE ──
|
|
echo "=== Phase 2: EXECUTE ==="
|
|
EXECUTE=$(claude --print --dangerously-skip-permissions \
|
|
--mcp-config "$PROJECT_DIR/mcp-config.json" \
|
|
-p "Today is $TODAY. Read .claude/skills/dbrain-processor/phases/execute.md and execute Phase 2.
|
|
Read .session/capture.json for input data.
|
|
Read business/_index.md and projects/_index.md for context.
|
|
Create tasks in Todoist, save thoughts, update CRM. Return ONLY JSON.
|
|
$MCP_PROMPT" \
|
|
2>&1) || true
|
|
|
|
echo "$EXECUTE" | grep -o '{.*}' | python3 -c "
|
|
import sys, json
|
|
for line in sys.stdin:
|
|
try:
|
|
data = json.loads(line.strip())
|
|
json.dump(data, sys.stdout, ensure_ascii=False, indent=2)
|
|
sys.exit(0)
|
|
except: pass
|
|
sys.stdout.write('{\"error\": \"failed to parse execute output\"}')
|
|
" > "$EXECUTE_FILE" 2>/dev/null || echo '{"error": "execute failed"}' > "$EXECUTE_FILE"
|
|
|
|
echo "Execute saved: $(wc -c < "$EXECUTE_FILE") bytes"
|
|
|
|
# ── Phase 3: REFLECT ──
|
|
echo "=== Phase 3: REFLECT ==="
|
|
REPORT=$(claude --print --dangerously-skip-permissions \
|
|
-p "Today is $TODAY. Read .claude/skills/dbrain-processor/phases/reflect.md and execute Phase 3.
|
|
Read .session/capture.json and .session/execute.json for input data.
|
|
Read MEMORY.md, .session/handoff.md, .graph/health-history.json.
|
|
Generate HTML report, update MEMORY, record observations.
|
|
Return ONLY RAW HTML (for Telegram)." \
|
|
2>&1) || true
|
|
fi
|
|
|
|
cd "$PROJECT_DIR"
|
|
|
|
echo "=== Claude output ==="
|
|
echo "$REPORT"
|
|
echo "===================="
|
|
|
|
# Remove HTML comments (break Telegram HTML parser)
|
|
REPORT_CLEAN=$(echo "$REPORT" | sed '/<!--/,/-->/d')
|
|
|
|
# Rebuild vault graph (keeps structure up to date)
|
|
echo "=== Rebuilding vault graph ==="
|
|
cd "$VAULT_DIR"
|
|
uv run .claude/skills/graph-builder/scripts/analyze.py || echo "Graph rebuild failed (non-critical)"
|
|
|
|
# Memory decay (update relevance scores and tiers)
|
|
echo "=== Memory decay ==="
|
|
uv run .claude/skills/agent-memory/scripts/memory-engine.py decay . || echo "Memory decay failed (non-critical)"
|
|
cd "$PROJECT_DIR"
|
|
|
|
# Git commit
|
|
git add -A
|
|
git commit -m "chore: process daily $TODAY" || true
|
|
git push || true
|
|
|
|
# Send to Telegram
|
|
if [ -n "$REPORT_CLEAN" ] && [ -n "$CHAT_ID" ]; then
|
|
echo "=== Sending to Telegram ==="
|
|
RESULT=$(curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
|
|
-d "chat_id=$CHAT_ID" \
|
|
-d "text=$REPORT_CLEAN" \
|
|
-d "parse_mode=HTML")
|
|
|
|
# If HTML failed, send without formatting
|
|
if echo "$RESULT" | grep -q '"ok":false'; then
|
|
echo "HTML failed: $RESULT"
|
|
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
|
|
-d "chat_id=$CHAT_ID" \
|
|
-d "text=$REPORT_CLEAN"
|
|
fi
|
|
fi
|
|
|
|
echo "=== Done ==="
|