Skill: valuemoon2025/session-daily-backup-obsidian (version 1.0.0)

# Session Daily Backup - Obsidian 自动备份 OpenClaw 会话对话到 Obsidian Vault，支持增量备份、多 session 合并、Token 监控和 QQ 警告通知。 ## 功能 - 📦 **每日自动备份**：凌晨 2 点自动执行（cron 定时任务） - 📝 **增量备份**：只保存新增对话，避免重复 - 🗂️ **多 Session 合并**：备份当天所有 session 文件到一个每日文件 - 🎨 **Obsidian 格式**：使用 callout 格式，支持彩色对话区分 - ⚠️ **Token 监控**：80%/90% 阈值警告，通过 QQ 推送通知 - 📊 **统计信息**：Token 估算、行数统计、session 数量 ## 配置 编辑 `config` 文件： ```bash # Obsidian Vault 路径 VAULT_DIR="$HOME/Documents/Obsidian/Clawd Markdowns" # Session 文件存储路径 SESSION_DIR="/root/.openclaw/agents/main/sessions" # 跟踪文件路径 TRACKING_DIR="/root/clawd" # QQ Bot 配置（可选，用于警告通知） QQ_APPID="" QQ_SECRET="" QQ_USER_ID="" ``` ## 使用 ### 手动备份 ```bash # 完整快照（所有 session） bash /root/.openclaw/workspace/skills/session-daily-backup-obsidian/scripts/save_full_snapshot.sh [主题名] # 按小时整理 bash /root/.openclaw/workspace/skills/session-daily-backup-obsidian/scripts/create_hourly_snapshots.sh YYYY-MM-DD # 监控并自动保存（每日增量备份） bash /root/.openclaw/workspace/skills/session-daily-backup-obsidian/scripts/monitor_and_save.sh ``` ### 设置定时任务 ```bash # 编辑 crontab crontab -e # 添加每日凌晨 2 点备份 0 2 * * * bash /root/.openclaw/workspace/skills/session-daily-backup-obsidian/scripts/monitor_and_save.sh >> /root/clawd/backup.log 2>&1 ``` ## 输出文件 - **每日备份**：`$VAULT_DIR/YYYY-MM-DD-daily.md` - **完整快照**：`$VAULT_DIR/YYYY-MM-DD-HHMM-[主题].md` - **小时快照**：`$VAULT_DIR/YYYY-MM-DD/HH-mm.md` - **跟踪文件**：`$TRACKING_DIR/.last_snapshot_timestamp`（记录上次备份行数） ## 依赖 - `jq` - JSON 处理 - `bash` - Shell 环境 - OpenClaw session 文件（JSONL 格式） ## 注意事项 - 首次运行会创建完整的每日备份文件 - 后续运行仅追加新增对话（≥10 条新消息触发） - Token 警告只会发送一次，直到 token 数回落后重新触发 - QQ 通知需要配置 QQ Bot 或使用 openclaw message 命令

path: README.md
content: # Session Daily Backup - Obsidian 🦞

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![OpenClaw Skill](https://img.shields.io/badge/OpenClaw-Skill-blue)](https://skillhub.openclaw.ai)
[![Obsidian](https://img.shields.io/badge/Obsidian-PKM-purple)](https://obsidian.md)

> 🤖 自动备份 OpenClaw 会话对话到 Obsidian Vault，支持增量备份、多 session 合并、Token 监控和警告通知

---

## ✨ 功能特性

- 📦 **每日自动备份** - 凌晨 2 点自动执行（cron 定时任务）
- 📝 **增量备份** - 只保存新增对话，避免重复
- 🗂️ **多 Session 合并** - 备份当天所有 session 文件到一个每日文件
- 🎨 **Obsidian 格式** - 使用 callout 格式，支持彩色对话区分
- ⚠️ **Token 监控** - 80%/90% 阈值警告
- 📊 **统计信息** - Token 估算、行数统计、session 数量

---

## 🚀 快速开始

### 1. 安装

```bash
# 克隆仓库
git clone https://github.com/valuemoon/session-daily-backup-obsidian.git

# 进入目录
cd session-daily-backup-obsidian
```

### 2. 配置

编辑 `config` 文件：

```bash
# Obsidian Vault 路径
VAULT_DIR="$HOME/Documents/Obsidian/Clawd Markdowns"

# Session 文件存储路径
SESSION_DIR="/root/.openclaw/agents/main/sessions"

# 跟踪文件路径
TRACKING_DIR="/root/clawd"
```

### 3. 设置定时任务

```bash
# 编辑 crontab
crontab -e

# 添加每日凌晨 2 点备份
0 2 * * * bash /path/to/session-daily-backup-obsidian/scripts/monitor_and_save.sh >> /path/to/backup.log 2>&1
```

### 4. 测试

```bash
# 手动运行一次
bash scripts/monitor_and_save.sh
```

---

## 📁 文件结构

```
session-daily-backup-obsidian/
├── SKILL.md                 # OpenClaw 技能说明
├── README.md                # 使用文档
├── LICENSE                  # MIT License
├── _meta.json               # 技能元数据
├── config                   # 配置文件
├── .gitignore              # Git 忽略文件
├── references/              # 参考资料
└── scripts/
    ├── monitor_and_save.sh        # 主脚本（每日增量备份）
    ├── save_full_snapshot.sh      # 完整快照
    ├── create_hourly_snapshots.sh # 小时快照
    └── format_message_v2.jq.txt   # JSON 格式化脚本
```

---

## 📖 使用说明

### 脚本说明

| 脚本 | 功能 | 使用示例 |
|------|------|----------|
| `monitor_and_save.sh` | 监控 session 变化并执行增量备份 | `bash scripts/monitor_and_save.sh` |
| `save_full_snapshot.sh` | 手动保存完整会话快照 | `bash scripts/save_full_snapshot.sh [主题名]` |
| `create_hourly_snapshots.sh` | 按小时整理对话记录 | `bash scripts/create_hourly_snapshots.sh YYYY-MM-DD` |

### 输出示例

生成的 markdown 文件使用 Obsidian callout 格式：

```markdown
# 每日对话备份 - 2026-03-21

**自动保存时间**: 2026-03-21 02:00 CST
**Token 估算**: 156k/1,000,000 (15%)
**Session 数量**: 3
**总消息数**: 194 行

---

### 📁 Session: main-2026-03-21 (46 行)

> [!user]- 🐉 用户 (10:55)
> 你好啊！

> [!assistant]- 🦞 助理 (10:56)
> 嗨！我是龙虾宝宝～ 有什么可以帮你的吗？
```

---

## ⚠️ Token 警告

当 session token 数超过阈值时会自动警告：

- ⚠️ **80%** (800k/1M)：建议尽快运行 `/new`
- 🚨 **90%** (900k/1M)：紧急！请立即运行 `/new`

---

## 🔧 故障排查

### 备份没有执行？

检查 cron 日志：
```bash
cat /path/to/backup.log
```

### 文件没有生成？

确认 Vault 目录存在：
```bash
mkdir -p "$HOME/Documents/Obsidian/Clawd Markdowns"
```

### 查看当前 cron 任务

```bash
crontab -l
```

---

## 📄 许可证

MIT License - 详见 [LICENSE](LICENSE) 文件

---

## 🦞 关于作者

**龙虾宝宝** - Z 世代智能助理

- 🌐 GitHub: [@valuemoon](https://github.com/valuemoon)
- 🤖 OpenClaw SkillHub: `session-daily-backup-obsidian`

---

## 🙏 致谢

- [OpenClaw](https://openclaw.ai) - AI 助理框架
- [Obsidian](https://obsidian.md) - 知识管理工具

---

<div align="center">

**Made with 🦞 by 龙虾宝宝**

如果这个技能对你有帮助，请给个 ⭐ Star！

</div>


path: _meta.json
content: {
  "ownerId": "kn7dqs92qf4hbnj7hzark4khyd7zkbkb",
  "slug": "session-daily-backup-obsidian",
  "version": "1.0.0",
  "publishedAt": 1774065195288,
  "displayName": "Session Daily Backup - Obsidian",
  "description": "自动备份 OpenClaw 会话对话到 Obsidian Vault，支持增量备份、多 session 合并、Token 监控和 QQ 警告通知",
  "author": "龙虾宝宝 🦞",
  "tags": ["obsidian", "backup", "session", "daily", "automation"],
  "category": "productivity"
}


path: scripts/create_hourly_snapshots.sh
content: #!/bin/bash
# Create hourly conversation snapshots for a specific day

# 加载配置
CONFIG_FILE="$(dirname "$0")/../config"
if [[ -f "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
fi

# 默认路径
SESSION_DIR="${SESSION_DIR:-/root/.openclaw/agents/main/sessions}"
VAULT_DIR="${VAULT_DIR:-$HOME/Documents/Obsidian/Clawd Markdowns}"
TRACKING_DIR="${TRACKING_DIR:-/root/clawd}"

DATE="${1:-$(date +%Y-%m-%d)}"
SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1)

if [[ ! -f "$SESSION_FILE" ]]; then
    echo "No session file found"
    exit 1
fi

# Get list of hours that have messages for this date
HOURS=$(grep '"type":"message"' "$SESSION_FILE" | jq -r "select(.timestamp | startswith(\"$DATE\")) | .timestamp | split(\"T\")[1] | split(\":\")[0]" | sort -u)

for HOUR in $HOURS; do
    OUTPUT_FILE="$VAULT_DIR/$DATE-${HOUR}00-hourly.md"
    
    echo "Creating $DATE hour $HOUR:00..."
    
    # Count messages in this hour
    MSG_COUNT=$(grep '"type":"message"' "$SESSION_FILE" | jq -r "select(.timestamp | startswith(\"${DATE}T${HOUR}:\")) | .timestamp" | wc -l)
    
    # Create file header
    cat > "$OUTPUT_FILE" <<EOF
# Conversation Hour: $DATE ${HOUR}:00

**Hour**: ${HOUR}:00 - ${HOUR}:59 UTC
**Messages**: $MSG_COUNT
**Session**: $(basename "$SESSION_FILE")

---

EOF
    
    # Extract and format messages for this hour
    grep '"type":"message"' "$SESSION_FILE" | while IFS= read -r line; do
        # Filter for this hour and format
        TIMESTAMP=$(echo "$line" | jq -r '.timestamp // ""')
        if [[ "$TIMESTAMP" == ${DATE}T${HOUR}:* ]]; then
            echo "$line" | jq -r -f /root/clawd/format_message_v2.jq.txt 2>/dev/null
        fi
    done >> "$OUTPUT_FILE"
    
    echo "  ✓ Created: $OUTPUT_FILE ($MSG_COUNT messages)"
done

echo ""
echo "Done! Created hourly snapshots for $DATE"


path: scripts/format_message_v2.jq.txt
content: # Extract timestamp
(if .timestamp then 
    (.timestamp | split("T")[1] | split(".")[0] | .[0:5])
else 
    "00:00"
end) as $time |

# Get text content
([.message.content[]? | select(.type == "text") | .text] | join("\n\n")) as $text_content |

# Only output if there's actual text content AND it's not a system message
if ($text_content | length) > 0 and ($text_content | startswith("System:") | not) then
    if .message.role == "user" then
        # Get text content, replace paragraph breaks with <br><br>, then add > to all lines
        ($text_content |
         gsub("\n\n"; "<br><br>") |  # Paragraph breaks
         gsub("\n"; "\n> ")) as $formatted |  # Line breaks
        # User message in blue callout
        "> [!quote] 🐉 User · " + $time + "\n> " + $formatted + "\n"
        
    elif .message.role == "assistant" then
        # Get text content, replace paragraph breaks with <br><br>, then add > to all lines
        ($text_content |
         gsub("\n\n"; "<br><br>") |  # Paragraph breaks
         gsub("\n"; "\n> ")) as $formatted |  # Line breaks
        # Zoidbot message in green callout
        "> [!check] 🦞 Zoidbot · " + $time + "\n> " + $formatted + "\n"
    else
        empty
    end
else
    empty
end


path: scripts/monitor_and_save.sh
content: #!/bin/bash
# Token-free conversation monitoring and auto-save
# 每日增量备份 - 无 LLM 调用，仅在需要警告时发送 QQ 通知

# 加载配置
CONFIG_FILE="$(dirname "$0")/../config"
if [[ -f "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
fi

# 默认路径（如果配置未设置）
SESSION_DIR="${SESSION_DIR:-/root/.openclaw/agents/main/sessions}"
VAULT_DIR="${VAULT_DIR:-$HOME/Documents/Obsidian/Clawd Markdowns}"
LAST_SNAPSHOT_FILE="${TRACKING_DIR:-/root/clawd}/.last_snapshot_timestamp"
WARNING_SENT_FILE="${TRACKING_DIR:-/root/clawd}/.token_warning_sent"
FORMAT_JQ="${TRACKING_DIR:-/root/clawd}/format_message_v2.jq"

# 获取最新的 session 文件
get_latest_session() {
    ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1
}

# 估算 token 数（粗略：1 token ≈ 4 字符）
estimate_tokens() {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        echo 0
        return
    fi
    local chars=$(wc -c < "$file")
    echo $((chars / 4))
}

# 获取当前行数
get_line_count() {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        echo 0
        return
    fi
    wc -l < "$file"
}

# 将 JSONL 转换为 markdown 对话（Obsidian 聊天格式）
jsonl_to_markdown() {
    local jsonl_file="$1"
    local start_line="${2:-1}"
    
    tail -n +$start_line "$jsonl_file" | while IFS= read -r line; do
        echo "$line" | jq -r -f "$FORMAT_JQ" 2>/dev/null
    done
}

# 发送 QQ 警告消息
send_qq_warning() {
    local message="$1"
    
    # 方法 1: 使用 openclaw message 工具（如果可用）
    if command -v openclaw &> /dev/null; then
        openclaw message send --channel qqbot --message "$message" 2>/dev/null
        return $?
    fi
    
    # 方法 2: 使用 go-cqhttp API（如果配置了）
    if [[ -n "$QQ_BOT_URL" ]]; then
        curl -s -X POST "$QQ_BOT_URL/send_private_msg" \
            -H "Content-Type: application/json" \
            -d "{\"user_id\": \"$QQ_USER_ID\", \"message\": \"$message\"}" > /dev/null
        return $?
    fi
    
    # 方法 3: 记录到日志（如果其他方式都不可用）
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] QQ WARNING: $message" >> "${TRACKING_DIR:-/root/clawd}/qq_warnings.log"
    return 0
}

# 保存对话快照（备份当天所有 session）
save_snapshot() {
    local timestamp=$(date +"%Y-%m-%d")
    local snapshot_file="$VAULT_DIR/$timestamp-daily.md"
    
    # 获取所有 session 文件
    local session_files=($(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null))
    
    if [[ ${#session_files[@]} -eq 0 ]]; then
        echo "ℹ️  未找到 session 文件"
        return
    fi
    
    # 计算总 token 数和行数
    local total_tokens=0
    local total_lines=0
    local session_count=${#session_files[@]}
    
    for session_file in "${session_files[@]}"; do
        total_tokens=$((total_tokens + $(estimate_tokens "$session_file")))
        total_lines=$((total_lines + $(wc -l < "$session_file")))
    done
    
    # 获取上次备份的总行数
    local last_saved_total=$(cat "$LAST_SNAPSHOT_FILE" 2>/dev/null || echo 0)
    local new_lines=$((total_lines - last_saved_total))
    
    # 只有当有至少 10 条新消息时才保存
    if [[ $new_lines -lt 10 ]]; then
        echo "ℹ️  无足够新消息（$new_lines < 10），跳过备份"
        return
    fi
    
    # 创建或追加文件
    if [[ ! -f "$snapshot_file" ]]; then
        cat > "$snapshot_file" <<EOF
# 每日对话备份 - $timestamp

**自动保存时间**: $(date '+%Y-%m-%d %H:%M %Z')
**Token 估算**: ${total_tokens}k/1,000,000 ($(( (total_tokens * 100) / 1000000 ))%)
**Session 数量**: $session_count
**总消息数**: $total_lines 行
**本次新增**: $new_lines 行

---

EOF
    else
        echo "" >> "$snapshot_file"
        echo "---" >> "$snapshot_file"
        echo "" >> "$snapshot_file"
        echo "## 新增对话 - $(date '+%H:%M')" >> "$snapshot_file"
        echo "" >> "$snapshot_file"
    fi
    
    # 备份所有 session 文件
    local saved_count=0
    for session_file in "${session_files[@]}"; do
        local session_name=$(basename "$session_file" .jsonl)
        local session_lines=$(wc -l < "$session_file")
        
        echo "" >> "$snapshot_file"
        echo "### 📁 Session: $session_name ($session_lines 行)" >> "$snapshot_file"
        echo "" >> "$snapshot_file"
        
        # 转换整个 session 为 markdown
        cat "$session_file" | while IFS= read -r line; do
            echo "$line" | jq -r -f "$FORMAT_JQ" 2>/dev/null
        done >> "$snapshot_file"
        
        saved_count=$((saved_count + session_lines))
    done
    
    # 更新跟踪文件
    echo "$total_lines" > "$LAST_SNAPSHOT_FILE"
    
    echo "✅ 已保存每日备份：$snapshot_file（$session_count 个 session，共 $saved_count 行）"
}

# 主逻辑
SESSION_FILE=$(get_latest_session)

if [[ ! -f "$SESSION_FILE" ]]; then
    echo "ℹ️  未找到 session 文件"
    exit 0
fi

TOKENS=$(estimate_tokens "$SESSION_FILE")
CURRENT_LINES=$(get_line_count "$SESSION_FILE")

echo "📊 当前状态:"
echo "   Token 数：${TOKENS}k"
echo "   总行数：$CURRENT_LINES"

# 自动保存增量快照
save_snapshot

# Token 警告（仅在跨越阈值时发送）
if [[ $TOKENS -gt 900000 ]]; then
    if [[ ! -f "$WARNING_SENT_FILE" ]] || [[ $(cat "$WARNING_SENT_FILE") != "900k" ]]; then
        echo "🚨 发送紧急警告（90%+）..."
        send_qq_warning "🚨 紧急：上下文已达 ${TOKENS}k/1M (90%+) - 请立即运行 /new"
        echo "900k" > "$WARNING_SENT_FILE"
    fi
elif [[ $TOKENS -gt 800000 ]]; then
    if [[ ! -f "$WARNING_SENT_FILE" ]] || [[ $(cat "$WARNING_SENT_FILE") != "800k" ]]; then
        echo "⚠️  发送警告（80%+）..."
        send_qq_warning "⚠️  Token 警告：上下文已达 ${TOKENS}k/1M (80%+) - 建议尽快运行 /new"
        echo "800k" > "$WARNING_SENT_FILE"
    fi
else
    # 当低于阈值时重置警告标志
    rm -f "$WARNING_SENT_FILE" 2>/dev/null
fi

exit 0


path: scripts/save_full_snapshot.sh
content: #!/bin/bash
# Create a full conversation snapshot on demand
# Usage: ./save_full_snapshot.sh [optional-topic-name]

# 加载配置
CONFIG_FILE="$(dirname "$0")/../config"
if [[ -f "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
fi

# 默认路径
SESSION_DIR="${SESSION_DIR:-/root/.openclaw/agents/main/sessions}"
VAULT_DIR="${VAULT_DIR:-$HOME/Documents/Obsidian/Clawd Markdowns}"

SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1)

if [[ ! -f "$SESSION_FILE" ]]; then
    echo "No session file found"
    exit 1
fi

TIMESTAMP=$(date +"%Y-%m-%d-%H%M")
TOPIC="${1:-full-conversation}"
SNAPSHOT_FILE="$VAULT_DIR/$TIMESTAMP-$TOPIC.md"

# Estimate tokens
CHARS=$(wc -c < "$SESSION_FILE")
TOKENS=$((CHARS / 4))

cat > "$SNAPSHOT_FILE" <<EOF
# Full Conversation Snapshot - $TOPIC

**Saved**: $(date '+%Y-%m-%d %H:%M %Z')
**Token estimate**: ${TOKENS}k/1,000,000 ($(( (TOKENS * 100) / 1000000 ))%)
**Session file**: $(basename "$SESSION_FILE")

---

EOF

# Parse JSONL to markdown (Obsidian chat format - FIXED)
cat "$SESSION_FILE" | while IFS= read -r line; do
    echo "$line" | jq -r -f /root/clawd/format_message_v2.jq.txt 2>/dev/null
done >> "$SNAPSHOT_FILE"

echo "✅ Saved full snapshot: $SNAPSHOT_FILE"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)