canvas-updater - Obsidian Canvas 知识图谱自动更新器

概述

canvas-updater 是一个 Claude Code Skill,用于自动创建和更新 Obsidian Canvas 知识图谱。它能够智能搜索相关文档,按主题分类,并生成可视化的知识网络图。

核心功能

1. 自动创建 Canvas

  • 根据主题关键词搜索所有相关文档
  • 智能分类文档(插件、学习、集成、AI、配置等9大类别)
  • 生成结构化知识图谱(中心节点 + 分组 + 文件节点 + 连线)

2. 智能增量更新

  • 只添加新发现的文档,完全保留原有内容
  • 新内容放置在独立区域,不干扰用户手动调整的布局
  • 自动去重,避免重复添加相同文件

触发方式

当用户提出以下请求时,Skill 会自动激活:

"创建 Obsidian 知识管理 Canvas"
"更新 cflow Canvas"
"同步新文档到知识图谱"
"为 AI 主题创建 Canvas"

关键词:create Canvas, update Canvas, sync knowledge graph, 创建知识图谱, 更新Canvas

实现原理

技术架构

canvas-updater/
├── SKILL.md              # Skill 元数据和说明
├── workflow.md           # 9步工作流程详解
└── classification.md     # 文件分类规则

关键技术要点

1. 紧凑 JSON 格式 ⚠️

这是最关键的发现! Obsidian Canvas 要求使用紧凑 JSON 格式(冒号后无空格),否则会导致节点被清空。

# ✅ 正确格式
json.dumps(canvas, ensure_ascii=False, indent='\t', separators=(',', ':'))
# 输出: "id":"xxx","type":"file"
 
# ❌ 错误格式(Python 默认)
json.dumps(canvas, ensure_ascii=False, indent='\t')
# 输出: "id": "xxx", "type": "file"  ← 冒号后有空格会导致失败

2. Canvas JSON 结构

{
  "edges": [...],      // 连线数组(必须在最前)
  "nodes": [...],      // 节点数组
  "metadata": {        // 元数据(必须在最后)
    "version": "1.0-1.0",
    "frontmatter": {}
  }
}

3. 节点必需字段

每个节点都必须包含:

{
    "id": "unique_id",           # 唯一标识
    "styleAttributes": {},       # 空对象(必需!)
    "type": "file|group|text",   # 节点类型
    "x": 0,                      # X坐标
    "y": 0,                      # Y坐标
    "width": 400,                # 宽度
    "height": 80                 # 高度
}

文件节点额外需要:

  • file: 文件相对路径

分组节点额外需要:

  • label: 分组标题
  • color: 颜色代码(“1”-“6”)

工作流程

完整 9 步流程

第一步:识别目标 Canvas

# 1. 用户明确主题 → 直接使用
# 2. 否则扫描 90 Resources/Canvas/ 列出所有 .canvas 文件
# 3. 读取目标 Canvas 文件内容

第二步:分析现有内容

# 1. 解析 Canvas JSON 结构
# 2. 提取所有已存在的文件节点路径(用于去重)
# 3. 计算已占用的空间区域
# 4. 识别用户创建的自定义节点

第三步:搜索新文档

Obsidian 主题(特殊规则)

# 搜索范围:
# 1. 文件名包含 "Obsidian" - **/*Obsidian*.md
# 2. 标题包含 "Obsidian" - Grep 搜索 ^#+ .*Obsidian.*
# 3. 标签包含 "obsidian" - YAML frontmatter
# 4. 强关联路径:
#    - 10 Note/300-实用软件/05-笔记工具/02-Obsidian/
#    - Obsidian blog/ 下提到 Obsidian 的文档

其他主题

  • 从 Canvas 文件名提取关键词
  • 使用 Glob/Grep 搜索相关文档

排除路径

.trash/, node_modules/, .obsidian/, public/, .git/
90 Resources/Obsidian导出/, SimpRead/, Canvas双链数据/
70 每日日报/(日记类 Canvas 除外)

第四步:计算新节点位置

核心原则:新增内容独立成区,完全不干预已有内容

# 1. 找到 Canvas 中的最大 X/Y 坐标
max_x = max(node['x'] + node.get('width', 0) for node in existing_nodes)
 
# 2. 在完全空白的区域规划新节点
start_x = max(2200, max_x + 500)  # 至少间距 500px
start_y = -600
 
# 3. 所有新增内容放在独立的分组内

第五步:创建新增内容独立区

当新增文档 ≤ 20 个:创建单个总分组

group = {
    "id": "update20251206",
    "styleAttributes": {},
    "type": "group",
    "x": start_x,
    "y": start_y,
    "width": group_width,
    "height": group_height,
    "color": "6",
    "label": "📌 新增内容 (2025-12-06)"
}

当新增文档 > 20 个:创建多个子分组按类型细分

categories = {
    'plugin': ('🔌 插件类', '1'),
    'learning': ('📚 学习类', '2'),
    'publish': ('📝 发布类', '3'),
    'ai': ('🤖 AI类', '4'),
    'integration': ('🔗 集成类', '5'),
    'config': ('⚙️ 配置类', '2'),
    'other': ('📄 其他', '6')
}
 
# 垂直堆叠子分组
current_y = start_y
for category in categories:
    # 创建子分组...
    current_y += group_height + 150  # 间距 150px

第六步:生成文件节点

网格布局参数

NODE_WIDTH = 400
NODE_HEIGHT = 80
H_SPACING = 50   # 水平间距
V_SPACING = 30   # 垂直间距
COLS = 2         # 每行列数
 
for i, filepath in enumerate(files):
    row = i // COLS
    col = i % COLS
 
    node_x = group_x + 50 + col * (NODE_WIDTH + H_SPACING)
    node_y = group_y + 50 + 40 + row * (NODE_HEIGHT + V_SPACING)
 
    node = {
        "file": filepath,
        "id": f"new20251206_{i+1:03d}",
        "styleAttributes": {},
        "type": "file",
        "x": node_x,
        "y": node_y,
        "width": NODE_WIDTH,
        "height": NODE_HEIGHT
    }

第七步:智能合并

核心原则:只添加,不修改,不删除

# 1. 保留原有所有内容
original_nodes = canvas["nodes"]
original_edges = canvas["edges"]
 
# 2. 分组插入到开头(显示在底层)
for group in reversed(new_groups):
    canvas["nodes"].insert(0, group)
 
# 3. 文件节点追加到末尾(显示在顶层)
canvas["nodes"].extend(new_file_nodes)

插入顺序

更新后的 nodes 数组:
[
  新增分组1,           // 插入到最前面(底层背景)
  新增分组2,
  ...,
  原有分组1,           // 原有内容保持不变
  原有分组2,
  ...,
  原有文件节点1,
  ...,
  新增文件节点1,       // 新文件节点放在末尾(顶层)
  新增文件节点2,
  ...
]

第八步:保存 Canvas

关键:使用紧凑 JSON 格式

json_str = json.dumps(
    canvas,
    ensure_ascii=False,
    indent='\t',
    separators=(',', ':')  # 冒号后无空格!
)
 
with open(canvas_path, 'w', encoding='utf-8') as f:
    f.write(json_str)

第九步:报告结果

✅ Canvas 更新完成!

📊 统计信息:
- 原有节点: 36个
- 新增节点: 45个
- 总节点数: 81个
- 新增位置: 右侧独立区 (X=2200 开始)

📦 新增分组:
- 🔌 插件类: 18个
- 📚 学习类: 5个
- 🔗 集成类: 12个
...

💡 提示:
- 新内容在右侧独立区域,完全不干扰原有布局
- 所有新节点在对应的彩色分组内
- 原有 36 个节点和 5 条连线完全保留

文件分类规则

9 大类别

类型关键词颜色图标示例
插件插件/plugin红色(1)🔌Obsidian插件、Atracker sync
配置配置/config/模板/设置橙色(2)⚙️Obsidian配置、模板文件
发布发布/博客/publish黄色(3)📝博客搭建指南
AIai/claude/copilot绿色(4)🤖Claude Code、AI助手
集成memos/notion/同步/转青色(5)🔗memos同步、OneNote转换
项目项目/project/开发红色(1)📦项目开发日志
学习学习/教程/索引/资源橙色(2)📚学习资源、教程索引
移动端移动端/mobile/快捷指令黄色(3)📱移动端同步
其他默认紫色(6)📄未匹配的文档

分类优先级

# 1. 路径特征优先
if '插件' in filepath or '/plugin' in filepath.lower():
    return 'plugin'
 
# 2. 项目类
if any(k in filename for k in ['项目', 'project', '开发']):
    return 'project'
 
# 3. 配置类
if any(k in filename for k in ['配置', '模板', 'config', '设置']):
    return 'config'
 
# 4. AI类
if any(k in filepath.lower() for k in ['ai', 'claude', 'copilot']):
    return 'ai'
 
# 5. 默认:其他
return 'other'

空间布局策略

新增区域定位

# 右侧独立区
start_x = max(2200, max_x + 500)  # 至少距离现有内容 500px
start_y = -600
 
# 确保与现有内容有足够间距(≥500px)

节点尺寸标准

文件节点: 400 x 80 px
分组节点: 动态计算(根据内容)
网格布局: 2列,水平间距 50px,垂直间距 30px
分组间距: ≥150px 垂直堆叠

布局示例

现有内容区域                      新增独立区域
┌─────────────┐                  ┌──────────────────┐
│  原有分组1   │                  │  📚 学习类 (5个)  │
│             │                  │  ┌────┐  ┌────┐  │
│  ○ 文件1     │                  │  │文件│  │文件│  │
│  ○ 文件2     │    ← 500px →    │  └────┘  └────┘  │
│             │                  └──────────────────┘
└─────────────┘                           ↓ 150px
   max_x                                ┌──────────────────┐
                                        │  🔌 插件类 (3个)  │
                                        │  ┌────┐  ┌────┐  │
                                        │  │文件│  │文件│  │
                                        │  └────┘  └────┘  │
                                        └──────────────────┘

使用示例

示例 1:创建 Obsidian 知识图谱

用户输入

创建 Obsidian 知识管理 Canvas

Skill 执行流程

  1. 搜索所有包含 “Obsidian” 的文档(文件名、标题、标签)
  2. 找到 96 个相关文档
  3. 按类型分类:插件(18)、学习(13)、集成(12)、发布(8)…
  4. 创建中心节点 “Obsidian 知识体系”
  5. 环绕中心节点创建 6 个分组
  6. 在各分组内按网格布局放置文件节点
  7. 创建连线连接分组到中心节点
  8. 保存到 90 Resources/Canvas/Obsidian知识主题.canvas

结果

✅ Canvas 创建完成!
📊 总节点数: 96个文档 + 6个分组 + 1个中心 = 103个节点
🔗 连线数: 6条(各分组连接到中心)
📍 文件位置: 90 Resources/Canvas/Obsidian知识主题.canvas

示例 2:增量更新 Canvas

用户输入

更新 Obsidian Canvas

Skill 执行流程

  1. 读取现有 Canvas(36个节点)
  2. 搜索新文档,发现 45 个未包含的文档
  3. 计算最大 X 坐标:max_x = 1839
  4. 新增区域起点:x = 2200, y = -600
  5. 创建 5 个彩色分组(插件、学习、集成、发布、AI)
  6. 在各分组内添加新文件节点
  7. 保留原有所有内容(36个节点 + 5条连线)
  8. 保存更新后的 Canvas

结果

✅ Canvas 更新完成!
📊 原有节点: 36个 → 新增节点: 45个 → 总节点数: 81个
💡 新内容在右侧独立区域(X=2200),完全不干扰原有布局

示例 3:创建 CFlow 主题 Canvas

用户输入

创建 cflow 知识主题 Canvas

Skill 执行流程

  1. 搜索标题包含 “cflow” 或 “memos” 的文档
  2. 排除日记文件(70 每日日报/)
  3. 找到 22 个相关文档
  4. 分类:配置部署(8)、其他(7)、项目开发(3)、集成(2)…
  5. 创建中心节点 “CFlow/Memos 知识体系”
  6. 创建 6 个分组并放置文件节点
  7. 保存到 90 Resources/Canvas/cflow知识主题.canvas

结果

✅ Canvas 创建完成!
📦 分组统计:
- ⚙️ 配置部署: 8个
- 📄 其他: 7个
- 📦 项目开发: 3个
- 🔗 集成: 2个
- 🔧 问题解决: 1个
- 🔌 插件: 1个

注意事项

⚠️ 关键要点

  1. 紧凑 JSON 格式:这是最容易被忽略但最关键的要求

    • 必须使用 separators=(',', ':')
    • 冒号后不能有空格
    • 否则 Obsidian 会清空所有节点
  2. 完全保留用户修改

    • 不删除任何原有节点
    • 不移动任何现有内容
    • 不修改用户创建的连线
  3. 智能去重

    • 使用文件相对路径作为唯一标识
    • 每次只添加新发现的文件
  4. 路径验证

    • 所有文件路径必须真实存在
    • 使用相对路径(相对于 vault 根目录)

常见问题

Q: 为什么更新后节点被清空了?
A: 检查 JSON 格式!确保使用 separators=(',', ':'),冒号后不能有空格。

Q: 如何避免干扰原有布局?
A: Skill 会自动计算新增区域,放置在 max_x + 500px 的位置,完全独立。

Q: 可以自定义分类规则吗?
A: 可以修改 .claude/skills/canvas-updater/classification.md 文件。

Q: 支持哪些主题?
A: 理论上支持任何主题,只需提供主题关键词即可自动搜索和分类。

技术细节

允许的工具

allowed-tools: Read, Write, Grep, Glob, Bash
  • Read: 读取现有 Canvas 文件
  • Write: 保存更新后的 Canvas
  • Grep: 搜索文档标题和内容
  • Glob: 按文件名模式搜索
  • Bash: 执行路径验证和文件操作

文件结构

.claude/skills/canvas-updater/
├── SKILL.md              # Skill 元数据
│   ├── name: canvas-updater
│   ├── description: 自动发现触发条件
│   └── allowed-tools: 工具权限
│
├── workflow.md           # 9步工作流程
│   ├── 第一步:识别目标 Canvas
│   ├── 第二步:分析现有内容
│   ├── 第三步:搜索新文档
│   ├── 第四步:计算新节点位置
│   ├── 第五步:创建新增内容独立区
│   ├── 第六步:生成文件节点
│   ├── 第七步:智能合并
│   ├── 第八步:保存 Canvas
│   └── 第九步:报告结果
│
└── classification.md     # 文件分类规则
    ├── 9大类别定义
    ├── 分类优先级
    └── 示例参考

版本历史

v1.0 (2025-12-06)

首次发布

  • ✅ 支持创建和更新 Canvas
  • ✅ 智能文件分类(9大类别)
  • ✅ 独立区域布局策略
  • ✅ 紧凑 JSON 格式支持
  • ✅ 完全保留用户修改
  • ✅ 自动去重机制

核心发现

  • 🔧 发现 Obsidian Canvas 紧凑 JSON 格式要求
  • 📝 建立完整的 9 步工作流程
  • 🎨 定义 9 大文件分类体系
  • 🗺️ 确立独立区域布局策略

相关资源

官方文档

相关文件

  • Agent 版本:.claude/agents/Canvas知识图谱更新.md
  • Skill 配置:.claude/skills/canvas-updater/SKILL.md
  • 工作流程:.claude/skills/canvas-updater/workflow.md
  • 分类规则:.claude/skills/canvas-updater/classification.md

示例 Canvas

  • 90 Resources/Canvas/Obsidian知识主题.canvas - 96个 Obsidian 相关文档
  • 90 Resources/Canvas/cflow知识主题.canvas - 22个 CFlow/Memos 文档
  • 90 Resources/Canvas/Claude Code知识主题.canvas - 32个 Claude Code 文档

总结

canvas-updater Skill 是一个强大的知识管理自动化工具,它能够:

  1. 自动发现:根据主题关键词智能搜索相关文档
  2. 智能分类:按照 9 大类别自动归类文档
  3. 可视化呈现:生成结构化的 Canvas 知识图谱
  4. 增量更新:只添加新内容,完全保留用户修改
  5. 零干扰:新内容独立布局,不影响原有结构

通过使用紧凑 JSON 格式、独立区域布局、智能去重等技术,确保了 Canvas 更新的稳定性和可靠性。这是一个真正实用的知识管理自动化解决方案。


作者:苏苏
创建时间:2025-12-06
Skill 版本:v1.0