用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/cajias/claude-skills --skill obsidian-file-recovery-extraction命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | obsidian-file-recovery-extraction |
| description | Extract Obsidian File Recovery snapshots from Chrome IndexedDB using CCL Chromium Reader |
| tags | ["obsidian","data-recovery","leveldb","electron","forensics"] |
When Obsidian vault files are corrupted or lost, the File Recovery core plugin maintains historical snapshots in Chrome/Electron IndexedDB format.
Standard tools cannot read Obsidian's backup data:
~/Library/Application Support/obsidian/IndexedDB/app_obsidian.md_0.indexeddb.leveldb/level, classic-level, Python plyvel all failstrings extraction produces mangled textUse the forensics tool that properly deserializes Chrome IndexedDB:
# Install
git clone https://github.com/cclgroupltd/ccl_chromium_reader /tmp/ccl_chromium_reader
# CRITICAL: Close Obsidian first (database locks!)
# Copy DB to clean location
cp -r "~/Library/Application Support/obsidian/IndexedDB/app_obsidian.md_0.indexeddb.leveldb" /tmp/obsidian-db-clean/
import sys
sys.path.insert(0, '/tmp/ccl_chromium_reader')
from ccl_chromium_reader import ccl_chromium_indexeddb
LEVELDB_PATH = "/tmp/obsidian-db-clean/app_obsidian.md_0.indexeddb.leveldb"
BLOB_PATH = "~/Library/Application Support/obsidian/IndexedDB/app_obsidian.md_0.indexeddb.blob"
wrapper = ccl_chromium_indexeddb.WrappedIndexDB(LEVELDB_PATH, BLOB_PATH)
# Find backup database (name contains "backup")
for db_id in wrapper.database_ids:
if 'backup' in db_id.name:
backup_db = wrapper[db_id.dbid_no]
break
# Access "backups" object store
backups_store = backup_db["backups"]
# Each record has: path, ts (timestamp), data (content)
for record in backups_store.iterate_records():
val = record.value
path = val.get('path', '')
ts = val.get('ts', 0)
data = val.get('data', '')
File Recovery keeps multiple snapshots. If a file was emptied/corrupted later, the latest backup has empty data. Always keep the longest content version:
best_backups = {} # path -> {data, len}
for record in backups_store.iterate_records():
path = val.get('path', '')
data = val.get('data', '')
data_len = len(str(data)) if data else 0
# Keep LONGEST, not latest
if path not in best_backups or data_len > best_backups[path]['len']:
best_backups[path] = {'data': data, 'len': data_len}
| Component | Path |
|---|---|
| LevelDB | ~/Library/Application Support/obsidian/IndexedDB/app_obsidian.md_0.indexeddb.leveldb/ |
| Blob storage | ~/Library/Application Support/obsidian/IndexedDB/app_obsidian.md_0.indexeddb.blob |
| Database name | Contains "backup" (e.g., d4fd49d66ec66ec4-backup) |
| Object store | backups |
| Record fields | path, ts, data |
Chrome IndexedDB uses V8's internal serialization:
The strings command extracts readable portions but mangles:
import os
VAULT_PATH = "/path/to/obsidian/vault"
for rel_path, info in best_backups.items():
data = info.get('data', '')
if len(str(data)) < 50:
continue # Skip empty
full_path = os.path.join(VAULT_PATH, rel_path)
# Only restore if file needs it
if os.path.exists(full_path):
with open(full_path) as f:
current = f.read()
if len(current) > 100 and 'needs-regeneration' not in current:
continue # Already good
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w') as f:
f.write(str(data))