167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
os_sense.py — 织忆 Soulful OS 无感感知
|
||
============================
|
||
轻量感知牧尘当前在做什么项目,不侵入隐私。
|
||
|
||
用法:
|
||
python3 os_sense.py # 可读摘要
|
||
python3 os_sense.py --json # JSON 输出
|
||
"""
|
||
|
||
import json, os, sys, re
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from collections import Counter
|
||
|
||
HOME = Path.home()
|
||
HERMES = HOME / ".hermes"
|
||
MC = HOME / "mc"
|
||
|
||
STOPWORDS = {"织忆", "小唯", "hermes", "python", "文件", "修改", "更新",
|
||
"了", "的", "在", "是", "我", "你", "他", "她", "它",
|
||
"这个", "那个", "什么", "怎么", "为什么", "和", "与",
|
||
"或", "但", "如果", "因为", "所以", "虽然", "不过",
|
||
"还是", "可以", "会", "能", "有", "没有", "不", "别"}
|
||
|
||
CONFIG_FILES = [
|
||
HERMES / "SOUL.md",
|
||
HERMES / "AGENTS.md",
|
||
HERMES / "config.yaml",
|
||
]
|
||
|
||
|
||
def get_recent_files(n=10):
|
||
"""最近修改的 md 文件"""
|
||
md_files = list((MC).rglob("*.md")) if MC.exists() else []
|
||
md_files = [(f, os.path.getmtime(f)) for f in md_files]
|
||
md_files.sort(key=lambda x: x[1], reverse=True)
|
||
return [{"path": str(f.relative_to(HOME)), "mtime": datetime.fromtimestamp(m).isoformat()}
|
||
for f, m in md_files[:n]]
|
||
|
||
|
||
def get_project_keywords(n=5):
|
||
"""从最近 Wiki 概念文件中提取项目关键词"""
|
||
concepts = MC / "小唯" / "07-Wiki" / "concepts"
|
||
if not concepts.exists():
|
||
return []
|
||
files = list(concepts.glob("*.md"))
|
||
files.sort(key=lambda f: os.path.getmtime(f), reverse=True)
|
||
keywords = []
|
||
for f in files[:n]:
|
||
name = f.stem
|
||
# 去掉日期前缀
|
||
name = re.sub(r'^\d+-', '', name)
|
||
# 提取有意义的词
|
||
for part in re.split(r'[-_]', name):
|
||
if len(part) > 1 and part not in STOPWORDS:
|
||
keywords.append(part)
|
||
return list(set(keywords))[:5]
|
||
|
||
|
||
def get_journal_keywords(n=50):
|
||
"""从 journal 统计高频词"""
|
||
journal_path = HERMES / "daemon" / "journal.jsonl"
|
||
if not journal_path.exists():
|
||
return [], []
|
||
words = []
|
||
try:
|
||
with open(journal_path) as f:
|
||
lines = f.readlines()
|
||
recent = lines[-n:] if len(lines) > n else lines
|
||
for line in recent:
|
||
try:
|
||
entry = json.loads(line)
|
||
text = entry.get("summary", "") + " " + entry.get("action_taken", "")
|
||
# 简单分词(英文)+ 中文词提取(\w 不匹配中文)
|
||
tokens = re.findall(r'[\w]{2,}', text.lower())
|
||
chinese_words = re.findall(r'[\u4e00-\u9fff]{2,}', text)
|
||
tokens = [t for t in tokens if t not in STOPWORDS and len(t) > 1]
|
||
chinese_words = [w for w in chinese_words if w not in STOPWORDS]
|
||
words.extend(tokens + chinese_words)
|
||
except:
|
||
pass
|
||
except:
|
||
pass
|
||
if not words:
|
||
return [], []
|
||
counter = Counter(words)
|
||
top = counter.most_common(10)
|
||
return [w for w, _ in top], top
|
||
|
||
|
||
def check_config_change():
|
||
"""检查是否有重大配置变更"""
|
||
results = []
|
||
for f in CONFIG_FILES:
|
||
if f.exists():
|
||
mtime = os.path.getmtime(f)
|
||
results.append({"file": f.name, "mtime": datetime.fromtimestamp(mtime).isoformat()})
|
||
return results
|
||
|
||
|
||
def generate_summary(projects, keywords, recent_files):
|
||
"""生成自然语言摘要"""
|
||
parts = []
|
||
if keywords:
|
||
parts.append("、".join(keywords[:3]))
|
||
if recent_files:
|
||
latest = recent_files[0]
|
||
# 从路径提取项目名
|
||
path_parts = Path(latest["path"]).parts
|
||
if len(path_parts) >= 2:
|
||
parts.append(f"最近在看 {path_parts[-2]}")
|
||
if not parts:
|
||
return "牧尘最近没有明显的工作模式变化"
|
||
return f"牧尘这周/这段时间在关注:{', '.join(parts)}"
|
||
|
||
|
||
def sense():
|
||
recent_files = get_recent_files(10)
|
||
project_keywords = get_project_keywords(5)
|
||
journal_keywords, journal_freq = get_journal_keywords(50)
|
||
config_files = check_config_change()
|
||
summary = generate_summary(project_keywords, journal_keywords, recent_files)
|
||
|
||
# 判断是否有新方向(config 改变)
|
||
config_change = len(config_files) > 0
|
||
|
||
return {
|
||
"projects": project_keywords,
|
||
"recent_files": recent_files[:5],
|
||
"keywords": journal_keywords[:8],
|
||
"config_change": config_change,
|
||
"config_files": config_files,
|
||
"summary": summary,
|
||
"sensed_at": datetime.now().isoformat(),
|
||
}
|
||
|
||
|
||
def print_human(data):
|
||
print(f"🖥️ OS 感知报告 — {data['sensed_at'][:10]}")
|
||
print()
|
||
if data["recent_files"]:
|
||
print("📁 最近在看的文件:")
|
||
for f in data["recent_files"][:5]:
|
||
print(f" {f['path']}")
|
||
print()
|
||
if data["keywords"]:
|
||
print(f"🔍 工作关键词:{' '.join(data['keywords'][:8])}")
|
||
print()
|
||
if data["projects"]:
|
||
print(f"📂 项目方向:{' '.join(data['projects'])}")
|
||
print()
|
||
if data["config_change"]:
|
||
print("⚙️ 配置有变更:")
|
||
for cf in data["config_files"]:
|
||
print(f" {cf['file']}({cf['mtime'][:10]})")
|
||
print()
|
||
print(f"💭 {data['summary']}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
data = sense()
|
||
if "--json" in sys.argv:
|
||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||
else:
|
||
print_human(data) |