- docker-compose.yml: hardcoded iCloud Obsidian vault volume → ${OMBRE_HOST_VAULT_DIR:-./buckets}
- write_memory.py / migrate_to_domains.py / reclassify_domains.py / reclassify_api.py:
hardcoded ~/Documents/Obsidian Vault/Ombre Brain → OMBRE_BUCKETS_DIR > load_config() > ./buckets
- write_memory.py: also fix B-04 regression (activation_count: 1 → 0 in frontmatter template)
- reclassify_api.py: model + base_url now read from config (was hardcoded SiliconFlow / DeepSeek-V3)
- tests/dataset.py + test_feel_flow.py: anonymize fixture identifiers (P酱/P0lar1s/北极星 → TestUser/北方)
Project identifiers (git.p0lar1s.uk, p0luz/ombre-brain, P0luz/Ombre-Brain GitHub) intentionally retained as project branding per user decision.
123 lines
3.6 KiB
Python
123 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Ombre Brain 手动记忆写入工具
|
||
用途:在 Copilot 端直接写入记忆文件,绕过 MCP 和 API 调用
|
||
用法:
|
||
python3 write_memory.py --name "记忆名" --content "内容" --domain "情感" --tags "标签1,标签2"
|
||
或交互模式:python3 write_memory.py
|
||
"""
|
||
|
||
import os
|
||
import uuid
|
||
import argparse
|
||
from datetime import datetime
|
||
|
||
|
||
def _resolve_dynamic_dir() -> str:
|
||
"""
|
||
Resolve the `dynamic/` directory under the configured bucket root.
|
||
Priority: $OMBRE_BUCKETS_DIR > config.yaml > built-in default.
|
||
优先级:环境变量 > config.yaml > 内置默认。
|
||
"""
|
||
env_dir = os.environ.get("OMBRE_BUCKETS_DIR", "").strip()
|
||
if env_dir:
|
||
return os.path.join(os.path.expanduser(env_dir), "dynamic")
|
||
try:
|
||
from utils import load_config # local import to avoid hard dep when missing
|
||
cfg = load_config()
|
||
return os.path.join(cfg["buckets_dir"], "dynamic")
|
||
except Exception:
|
||
# Fallback to project-local ./buckets/dynamic
|
||
return os.path.join(
|
||
os.path.dirname(os.path.abspath(__file__)), "buckets", "dynamic"
|
||
)
|
||
|
||
|
||
VAULT_DIR = _resolve_dynamic_dir()
|
||
|
||
|
||
def gen_id():
|
||
return uuid.uuid4().hex[:12]
|
||
|
||
|
||
def write_memory(
|
||
name: str,
|
||
content: str,
|
||
domain: list[str],
|
||
tags: list[str],
|
||
importance: int = 7,
|
||
valence: float = 0.5,
|
||
arousal: float = 0.3,
|
||
):
|
||
mid = gen_id()
|
||
now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||
|
||
# YAML frontmatter
|
||
domain_yaml = "\n".join(f"- {d}" for d in domain)
|
||
tags_yaml = "\n".join(f"- {t}" for t in tags)
|
||
|
||
md = f"""---
|
||
activation_count: 0
|
||
arousal: {arousal}
|
||
created: '{now}'
|
||
domain:
|
||
{domain_yaml}
|
||
id: {mid}
|
||
importance: {importance}
|
||
last_active: '{now}'
|
||
name: {name}
|
||
tags:
|
||
{tags_yaml}
|
||
type: dynamic
|
||
valence: {valence}
|
||
---
|
||
|
||
{content}
|
||
"""
|
||
|
||
path = os.path.join(VAULT_DIR, f"{mid}.md")
|
||
os.makedirs(VAULT_DIR, exist_ok=True)
|
||
with open(path, "w", encoding="utf-8") as f:
|
||
f.write(md)
|
||
|
||
print(f"✓ 已写入: {path}")
|
||
print(f" ID: {mid} | 名称: {name}")
|
||
return mid
|
||
|
||
|
||
def interactive():
|
||
print("=== Ombre Brain 手动写入 ===")
|
||
name = input("记忆名称: ").strip()
|
||
content = input("内容: ").strip()
|
||
domain = [d.strip() for d in input("主题域(逗号分隔): ").split(",") if d.strip()]
|
||
tags = [t.strip() for t in input("标签(逗号分隔): ").split(",") if t.strip()]
|
||
importance = int(input("重要性(1-10, 默认7): ").strip() or "7")
|
||
valence = float(input("效价(0-1, 默认0.5): ").strip() or "0.5")
|
||
arousal = float(input("唤醒(0-1, 默认0.3): ").strip() or "0.3")
|
||
write_memory(name, content, domain, tags, importance, valence, arousal)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser(description="手动写入 Ombre Brain 记忆")
|
||
parser.add_argument("--name", help="记忆名称")
|
||
parser.add_argument("--content", help="记忆内容")
|
||
parser.add_argument("--domain", help="主题域,逗号分隔")
|
||
parser.add_argument("--tags", help="标签,逗号分隔")
|
||
parser.add_argument("--importance", type=int, default=7)
|
||
parser.add_argument("--valence", type=float, default=0.5)
|
||
parser.add_argument("--arousal", type=float, default=0.3)
|
||
args = parser.parse_args()
|
||
|
||
if args.name and args.content and args.domain:
|
||
write_memory(
|
||
name=args.name,
|
||
content=args.content,
|
||
domain=[d.strip() for d in args.domain.split(",")],
|
||
tags=[t.strip() for t in (args.tags or "").split(",") if t.strip()],
|
||
importance=args.importance,
|
||
valence=args.valence,
|
||
arousal=args.arousal,
|
||
)
|
||
else:
|
||
interactive()
|