基于 Karpathy LLM Wiki 范式构建的个人知识库操作 skill。
Wiki 存放于本地 ~/wiki/,内容来自三个来源:IMA 笔记、印象笔记、本地文件。
text
~/wiki/
├── raw/ ← 用户放置待处理文件(PDF/PPT/Word)
├── schema.md ← 分类和格式规则
├── index.md ← 总目录(自动维护)
├── log.md ← 已处理记录(去重依据)
└── pages/ ← Wiki 知识页面
└── [主题].md
每次操作前,先加载凭证:
bash
# IMA 凭证
IMA_CLIENT_ID="$(cat ~/.config/ima/client_id 2>/dev/null)"
IMA_API_KEY="$(cat ~/.config/ima/api_key 2>/dev/null)"
if [ -z "$IMA_CLIENT_ID" ] || [ -z "$IMA_API_KEY" ]; then
echo "缺少 IMA 凭证,请检查 ~/.config/ima/"
exit 1
fi
# Evernote 凭证
if [ -z "$EVERNOTE_TOKEN" ]; then
echo "缺少 EVERNOTE_TOKEN,请配置环境变量"
exit 1
fi
EVERNOTE_HOST="${EVERNOTE_HOST:-app.yinxiang.com}"
用户意图操作读取章节处理 IMA / 腾讯笔记新内容Ingest — IMA## Ingest:IMA 笔记处理印象笔记指定笔记Ingest — Evernote## Ingest:印象笔记回写/更新已有印象笔记(插内容到顶部等)Evernote 回写### 回写印象笔记处理 raw/ 里的文件Ingest — 本地文件## Ingest:本地文件处理所有新内容三路并行 Ingest以上三个章节查询 wiki 内容Query## Query整理/检查 wikiLint## Lint可视化 wiki 图谱 / "看看知识库长什么样" / "生成关系图"Visualize## Visualize生成 Demo 定制版本Demo 生成## Demo 生成
读取 IMA 中的笔记,对比 log.md 中已处理记录,Ingest 新增或更新过的笔记。
bash
curl -s --max-time 15 \
-X POST "https://ima.qq.com/openapi/note/v1/list_note_by_folder_id" \
-H "ima-openapi-clientid: $IMA_CLIENT_ID" \
-H "ima-openapi-apikey: $IMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cursor": "", "limit": 50}'
从返回结果 data.note_book_list[].basic_info.basic_info 取:
docid:笔记 IDtitle:标题modify_time:最后修改时间(毫秒时间戳,字符串格式)读取 ~/wiki/log.md 中"IMA 笔记"表格,跳过已记录的 doc_id。
如果 modify_time 比 log 中记录的更新,视为有更新,重新 Ingest。
bash
curl -s --max-time 15 \
-X POST "https://ima.qq.com/openapi/note/v1/get_doc_content" \
-H "ima-openapi-clientid: $IMA_CLIENT_ID" \
-H "ima-openapi-apikey: $IMA_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"doc_id\": \"$DOC_ID\", \"target_content_format\": 0}"
返回 data.content 为纯文本正文。
读取 ~/wiki/schema.md 了解规则,然后:
~/wiki/pages/[主题].md(格式见 ## Wiki 页面格式)~/wiki/index.md~/wiki/log.md 的"IMA 笔记"表格追加一行:text
| {doc_id} | {标题} | {modify_time} | {今日日期} |
内容特征建议分类处理方式销售演示脚本、Demo 流程Demo Script创建带替换占位符的模板页产品/技术知识对应产品分类创建知识页行业分析、市场资讯对应行业分类创建知识页个人笔记/随想自动聚类按内容决定
读取用户指定的印象笔记笔记,Ingest 进 Wiki。
注意:印象笔记有 220+ 笔记本,不做全量扫描。用户明确指定笔记标题或笔记本时才处理。
重要:evernote2 需要
python3.12(不兼容 Python 3.14+,因distutils已移除)。 所有印象笔记相关脚本必须用python3.12执行。 依赖已安装:python3.12 -m pip install evernote2 oauth2 setuptools --break-system-packagesevernote2 库需 patch:/opt/homebrew/lib/python3.12/site-packages/evernote2/api/client.py第145行getargspec→getfullargspec(已完成)
python3
import os, re
from evernote2.api.client import EvernoteClient
import evernote2.edam.notestore.ttypes as NoteStoreTypes
token = os.environ.get('EVERNOTE_TOKEN')
host = os.environ.get('EVERNOTE_HOST', 'app.yinxiang.com')
client = EvernoteClient(token=token, service_host=host)
note_store = client.get_note_store()
def enml_to_text(enml):
text = re.sub(r'<[^>]+>', '\n', enml)
return re.sub(r'\n+', '\n', text).strip()
python
f = NoteStoreTypes.NoteFilter()
f.words = 'intitle:"笔记标题"'
spec = NoteStoreTypes.NotesMetadataResultSpec(includeTitle=True, includeUpdated=True)
result = note_store.findNotesMetadata(token, f, 0, 10, spec)
for note in result.notes:
content = note_store.getNoteContent(token, note.guid)
text = enml_to_text(content)
# → 进入 Ingest 分析流程
对比 ~/wiki/log.md 中"印象笔记"表格,跳过 guid 已记录且 updated 未变化的笔记。
处理完成后在 log.md 中追加:
text
| {guid} | {标题} | {updated_ms} | {今日日期} |
印象笔记开发者 Token 有效期约 2 周。若遇到 EDAMUserException errorCode=9(AUTH_EXPIRED),提示用户去 https://app.yinxiang.com/api/DeveloperToken.action 重新生成,并更新 ~/.zshrc 中的 EVERNOTE_TOKEN。
🔴 安全:token 若被贴进对话(HAI 环境会进内网日志),提醒用户下次直接在 Terminal
export或写~/.zshrc。刷新后主动帮用户把新 token 覆盖进~/.zshrc(Edit 替换旧行),省得每次过期重贴。
当用户要求"把内容整理进某篇印象笔记 / 插到笔记顶部"时,用 updateNote 回写。 三个必踩的坑:
ENML 是严格 XML, & 必须转义成 &
EDAMUserException errorCode=11( reference to entity must end with ';')< → <、 > → >; " 在正文里 OK 不用转content.replace('&','&')(但注意别把已有的 &/ <br /> 二次转义——若手写 div 就手动只转正文里的裸 &)note.resources = None 再 updateNote
getNote 带回的 resources(附件)在 update 时可能触发校验问题;纯文本笔记回写前置 note.resources = None 最稳顶部插入 pattern(保留原有内容,新块插最前) python
marker = '<en-note>'
idx = content.find(marker) + len(marker)
new_content = content[:idx] + new_block + content[idx:]
note.content = new_content
note.resources = None
ns.updateNote(token, note)
<div>...</div> 逐行,空行用 <div><br /></div><div><br /></div>完整回写脚本:
python3
import os
from evernote2.api.client import EvernoteClient
token = os.environ['EVERNOTE_TOKEN']
client = EvernoteClient(token=token, service_host='app.yinxiang.com')
ns = client.get_note_store()
note = ns.getNote(token, GUID, True, False, False, False)
new_block = '<div>标题</div><div>正文里的 & 已转义</div><div><br /></div>'
idx = note.content.find('<en-note>') + len('<en-note>')
note.content = note.content[:idx] + new_block + note.content[idx:]
note.resources = None
ns.updateNote(token, note)
bash
# 用 Edit 工具替换 ~/.zshrc 里的旧 EVERNOTE_TOKEN 行,不要 append 造成重复
grep -n "EVERNOTE_TOKEN" ~/.zshrc # 先定位旧行
处理用户放入 ~/wiki/raw/ 的文件,提取文字内容后 Ingest 进 Wiki。
bash
ls -la ~/wiki/raw/
对比 ~/wiki/log.md 中"本地文件"表格,找出未处理的文件。
根据扩展名选择提取方式:
python
import os
from pathlib import Path
file_path = os.path.expanduser("~/wiki/raw/文件名")
ext = Path(file_path).suffix.lower()
if ext == '.pptx':
from pptx import Presentation
prs = Presentation(file_path)
slides = []
for i, slide in enumerate(prs.slides):
texts = [s.text.strip() for s in slide.shapes if hasattr(s, "text") and s.text.strip()]
if texts:
slides.append(f"Slide {i+1}: {' | '.join(texts)}")
content = '\n'.join(slides)
elif ext == '.pdf':
import subprocess
result = subprocess.run(['pdftotext', file_path, '-'], capture_output=True, text=True)
content = result.stdout
elif ext in ['.docx']:
from docx import Document
doc = Document(file_path)
content = '\n'.join(p.text for p in doc.paragraphs if p.text.strip())
elif ext in ['.md', '.txt']:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
依赖检查:
python-pptx: pip3 install python-pptx(已安装 1.0.2)pdftotext:macOS 系统自带python-docx: pip3 install python-docx同 IMA Ingest 步骤 4,分析内容后写入 pages/,更新 index.md。
处理完成后在 log.md 追加:
text
| {文件名} | {mtime} | {今日日期} |
从 Wiki 中检索信息,综合多个 page 回答用户问题。
~/wiki/index.md 了解现有分类和页面列表~/wiki/pages/[主题].md 文件pages/ 目录,不要重复 Ingest 已处理过的内容定期检查 Wiki 质量,发现问题并修复。
关联主题 中链接它last_updated 未跟进关联主题 中链接列出发现的问题,询问用户是否逐项修复。修复后更新相关 page 和 index.md。
把 ~/wiki/pages/ 转成 单文件交互式知识图谱 HTML(cytoscape 力导向图 + 节点详情面板),数据不出本地。这是 personal-wiki 的 Obsidian 平替——不用装 Obsidian、不改 wiki 结构。
bash
# 需要能 import yaml 的 python(3.11/3.12/3 均可,仅依赖 pyyaml)
python3 ~/.claude/skills/personal-wiki/scripts/viz/visualize.py
open ~/wiki/wiki-graph.html
参数(可选): visualize.py [WIKI_PAGES_DIR] [OUT_HTML] [BUNDLE_NAME]
默认 ~/wiki/pages → ~/wiki/wiki-graph.html,名称 "My Wiki"。
输出示例: [ok] 65 concepts / 371 edges / 1356 KB -> ...
github.com/GoogleCloudPlatform/knowledge-catalog)。LICENSE 在 scripts/viz/OKF-LICENSE.md。google-adk / google-cloud-bigquery(几百 MB,只给它的 enrich agent 用)。[[双链]] 补丁(必须):OKF 原版只认 [text](path.md) 标准链接,我的 wiki 用 [[双链]]。 generator.py 已 patch:两遍扫描先建 alias_map(文件名 stem + frontmatter title + 首个 H1 → concept_id), _norm_alias() 做模糊匹配(lower + 空格↔下划线 + 破折号归一),支持 [[name]] 和 [[name|alias]] 两种写法。没这个补丁边会几乎全丢。生成后看孤立节点(图上没有任何连线的点)= Lint 检查项 1 的可视化版。跑一遍图谱能直观发现"孤立页面 / 稀疏聚类",比逐页读 关联主题 快。
基于 ~/wiki/pages/Demo_Script_*.md 中的模板,替换客户和行业信息,生成定制版 Demo 脚本。
文件行业适用场景Demo_Script_ECP_HK_零售.md零售/高端零售SAP EC Payroll HK,MPF/ORSO/PCC/回溯计算
每个 ~/wiki/pages/[主题].md 的标准格式:
markdown
---
category: [分类名]
tags: [标签1, 标签2]
sources:
- type: evernote | ima_note | local_file
id: [guid / doc_id / 文件名]
title: [原始标题]
last_updated: YYYY-MM-DD
---
# [主题名]
## 核心摘要
(3-5 句话)
## 详细内容
(要点、关键概念、数据、背景)
## 关联主题
- [[相关主题1]]
- [[相关主题2]]
## 来源记录
- [原始标题](来源类型) — YYYY-MM-DD
Demo Script 页面 在标准格式基础上,顶部额外包含:
markdown
## 印象笔记
| guid | 标题 | updated (ms) | ingest 时间 |
|------|------|-------------|------------|
| abc123 | 笔记标题 | 1743868000000 | 2026-04-06 |
## IMA 笔记
| doc_id | 标题 | modify_time (ms) | ingest 时间 |
|--------|------|-----------------|------------|
| 7379871906939637 | SAP主权云 | 1759497600000 | 2026-04-05 |
## 本地文件
| 文件名 | mtime | ingest 时间 |
|--------|-------|------------|
| report.pptx | 2026-04-03T11:13:00 | 2026-04-05 |