#!/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)