chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:10 +08:00
commit d0431ff947
26 changed files with 2864 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# 生成的前任 Skill(隐私保护,不上传)
exes/
!exes/example_xiaoming/
# 临时文件
/tmp/
*.pyc
__pycache__/
.DS_Store
# 用户数据
knowledge/
*.db
*.sqlite
# IDE
.vscode/
.idea/
# 环境
.env
venv/
+76
View File
@@ -0,0 +1,76 @@
# 详细安装说明
## Claude Code 安装
### 项目安装
在你的 git 仓库根目录执行:
```bash
mkdir -p .claude/skills
git clone https://github.com/therealXiaomanChu/ex-skill .claude/skills/create-ex
```
### 全局安装
```bash
git clone https://github.com/therealXiaomanChu/ex-skill ~/.claude/skills/create-ex
```
### OpenClaw 安装
```bash
git clone https://github.com/therealXiaomanChu/ex-skill ~/.openclaw/workspace/skills/create-ex
```
---
## 依赖安装
### 基础依赖(可选)
```bash
cd .claude/skills/create-ex # 或你的安装路径
pip3 install -r requirements.txt
```
目前唯一的可选依赖是 `Pillow`,用于读取照片 EXIF 信息。如果你不需要照片分析功能,可以跳过。
---
## 微信聊天记录导出指南
- 导出格式:txt / html / csv
- 使用方法:下载安装 → 登录微信PC版 → 选择联系人 → 导出
### 手动复制
如果以上工具都不方便,你也可以:
1. 在微信中打开与前任的聊天窗口
2. 手动选择并复制关键对话
3. 粘贴到一个 txt 文件中
4.`/create-ex` 时使用方式 D(上传文件)
---
## QQ 聊天记录导出指南
1. 打开 QQ → 点击左下角 ≡ → 设置
2. 通用 → 聊天记录 → 导出聊天记录
3. 选择联系人 → 导出为 txt 格式
4. QQ的聊天记录也可以直接复制粘贴
---
## 常见问题
### Q: 数据会上传到云端吗?
A: 不会。所有数据都存储在你的本地文件系统中,不会上传到任何服务器。
### Q: 可以同时创建多个前任的 Skill 吗?
A: 可以。每个前任会生成独立的 `exes/{slug}/` 目录。
### Q: 创建后还能修改吗?
A: 可以。说"ta不会这样说"触发对话纠正,或"我有新文件"追加原材料。每次修改都有版本存档,可以回滚。
### Q: 我想删除怎么办?
A: 使用 `/delete-ex {slug}``/let-go {slug}` 命令。
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 therealXiaomanChu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+236
View File
@@ -0,0 +1,236 @@
# 前任.skill
> *"你们搞大模型的简直是码神,你们解放了前端兄弟,还要解放后端兄弟,测试兄弟,运维兄弟,解放网安兄弟,解放ic兄弟,最后解放自己解放全人类"*
**我会为了你一万次回到那个夏天。**
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python 3.9+](https://img.shields.io/badge/Python-3.9%2B-blue.svg)](https://python.org)
[![Claude Code](https://img.shields.io/badge/Claude%20Code-Skill-blueviolet)](https://claude.ai/code)
[![AgentSkills](https://img.shields.io/badge/AgentSkills-Standard-green)](https://agentskills.io)
 
提供前任的原材料(微信聊天记录、QQ消息、朋友圈截图、照片)加上你的主观描述
生成一个**真正像ta的 AI Skill**
用ta的口头禅说话,用ta的方式回复你,记得你们一起去过的地方
⚠️ **本项目仅用于个人回忆与情感疗愈,不用于骚扰、跟踪或侵犯他人隐私。**
[安装](#安装) · [使用](#使用) · [效果示例](#效果示例) · [English](README_EN.md)
---
## 安装
### Claude Code
> **重要**Claude Code 从 **git 仓库根目录** 的 `.claude/skills/` 查找 skill。请在正确的位置执行。
```bash
# 安装到当前项目(在 git 仓库根目录执行)
mkdir -p .claude/skills
git clone https://github.com/therealXiaomanChu/ex-skill .claude/skills/create-ex
# 或安装到全局(所有项目都能用)
git clone https://github.com/therealXiaomanChu/ex-skill ~/.claude/skills/create-ex
```
### 依赖(可选)
```bash
pip3 install -r requirements.txt
```
---
## 环境要求
- **Claude Code**:免费安装,需要 Node.js 18+[安装指南](https://docs.anthropic.com/en/docs/claude-code)
- **API 消耗**:创建一个前任 Skill 大约消耗 5k-15k tokens,取决于聊天记录量
- **付费方式**(二选一):
- Claude Pro / Max 订阅:在订阅额度内使用,无需额外配置
- Anthropic API Key:按量付费,需在 Claude Code 中配置 key
- **替代前端**:也可以使用 [OpenClaw](https://github.com/nicepkg/openclaw) 运行本 Skill
- **不需要 GPU**,不需要本地模型,不需要 Docker
---
## 使用
在 Claude Code 中输入:
```
/create-ex
```
按提示输入前任的代号、基本信息、性格画像,然后选择数据来源。所有字段均可跳过,仅凭描述也能生成。
完成后用 `/{slug}` 调用该前任 Skill,开始对话。
### 管理命令
| 命令 | 说明 |
|------|------|
| `/list-exes` | 列出所有前任 Skill |
| `/{slug}` | 调用完整 Skill(像ta一样跟你聊天) |
| `/{slug}-memory` | 回忆模式(帮你回忆那些事) |
| `/{slug}-persona` | 仅人物性格 |
| `/ex-rollback {slug} {version}` | 回滚到历史版本 |
| `/delete-ex {slug}` | 删除 |
| `/let-go {slug}` | 放下 |
---
## 效果示例
> 输入:`初恋,在一起三年,大学时期,ENFP,双子座,话痨,半夜给我发语音,分手后还给我点赞`
**场景一:日常聊天**
```
用户 在干嘛
前任.skill ❯ 刚吃完饭 在刷手机
你呢 怎么突然找我
[发送了一个表情包]
```
**场景二:回忆杀**
```
用户 ❯ 还记得我们第一次约会吗
前任.skill ❯ 就那个超难吃的意面吗哈哈哈哈
你还假装很好吃 我都看出来了
那家店后来倒闭了你知道吗
```
**场景三:深夜emo**
```
用户 我有点想你
前任.skill ...
早点睡吧
```
**场景四:吵架模式**
```
用户 ❯ 你当时为什么不回我消息
前任.skill 又来了
我说了我在忙 你非要这样
算了不想吵
```
---
## 功能特性
### 数据源
| 来源 | 格式 | 备注 |
|------|------|------|
| 微信聊天记录 | WeChatMsg / 留痕 / PyWxDump 导出 | 推荐,信息最丰富 |
| QQ 聊天记录 | txt / mht 导出 | 适合学生时代的恋情 |
| 朋友圈/微博 | 截图 | 提取公开人设 |
| 照片 | JPEG/PNG(含 EXIF | 提取时间线和地点 |
| 口述/粘贴 | 纯文本 | 你的主观记忆 |
### 生成的 Skill 结构
每个前任 Skill 由两部分组成,共同驱动输出:
| 部分 | 内容 |
|------|------|
| **Part A — Relationship Memory** | 共同经历、约会地点、inside jokes、争吵模式、甜蜜瞬间、关系时间线 |
| **Part B — Persona** | 5 层性格结构:硬规则 → 身份 → 说话风格 → 情感模式 → 关系行为 |
运行逻辑:`收到消息 → Persona 判断ta会怎么回 → Memory 补充共同记忆 → 用ta的方式输出`
### 支持的标签
**依恋类型**:安全型 · 焦虑型 · 回避型 · 混乱型
**爱的语言**:肯定的言辞 · 精心的时刻 · 接受礼物 · 服务的行动 · 身体的接触
**性格标签**:话痨 · 闷骚 · 嘴硬心软 · 冷暴力 · 粘人 · 独立 · 大男/女子主义 · 浪漫主义 · 实用主义 · 完美主义 · 拖延症 · 工作狂 · 控制欲 · 没有安全感 · 报复性熬夜 · 已读不回 · 秒回选手 · 朋友圈三天可见 · 半夜发语音 …
**星座**:十二星座全支持,影响性格标签的翻译规则
**MBTI**:16 型全支持,影响沟通风格和决策模式
### 进化机制
* **追加记忆** → 找到更多聊天记录/照片 → 自动分析增量 → merge 进对应部分
* **对话纠正** → 说「ta不会这样说」→ 写入 Correction 层,立即生效
* **版本管理** → 每次更新自动存档,支持回滚
---
## 项目结构
本项目遵循 [AgentSkills](https://agentskills.io) 开放标准:
```
create-ex/
├── SKILL.md # skill 入口(官方 frontmatter
├── prompts/ # Prompt 模板
│ ├── intake.md # 对话式信息录入
│ ├── memory_analyzer.md # 关系记忆提取
│ ├── persona_analyzer.md # 性格行为提取(含标签翻译表)
│ ├── memory_builder.md # memory.md 生成模板
│ ├── persona_builder.md # persona.md 五层结构模板
│ ├── merger.md # 增量 merge 逻辑
│ └── correction_handler.md # 对话纠正处理
├── tools/ # Python 工具
│ ├── wechat_parser.py # 微信聊天记录解析
│ ├── qq_parser.py # QQ 聊天记录解析
│ ├── social_parser.py # 社交媒体内容解析
│ ├── photo_analyzer.py # 照片元信息分析
│ ├── skill_writer.py # Skill 文件管理
│ └── version_manager.py # 版本存档与回滚
├── exes/ # 生成的前任 Skillgitignored
├── docs/PRD.md
├── requirements.txt
└── LICENSE
```
---
## 注意事项
* **聊天记录质量决定还原度**:微信导出 + 口述 > 仅口述
* 建议优先提供:**深夜对话** > **争吵记录** > **日常消息**(最能体现真实性格)
* 本项目不鼓励对前任的不健康执念,如果你发现自己过于沉浸,请寻求专业帮助
* 你的前任是一个真实的人,ta有自己的人生。这个 Skill 只是你记忆中的ta
---
## 社区生态
以下项目由社区贡献者独立开发,与本项目互补:
| 项目 | 作者 | 说明 |
|------|------|------|
| [ex-cure-skill](https://github.com/W1ndys/ex-cure-skill) | @W1ndys | 关系反思模式,从聊天记录中复盘经验教训 |
| [同事.skill](https://github.com/titanwings/colleague-skill) | @titanwings | 本项目的灵感来源,把同事蒸馏成 AI Skill |
| [simp-skill](https://github.com/BeamusWayne/simp-skill) | @BeamusWayne | 与其怀念前任,不如勇敢追爱 |
---
### 写在最后
人的记忆是一种不讲道理的存储介质。
你记不住高数公式,记不住车牌号,记不住今天是几号,但你清楚记得四年前的一个下午ta穿了一件白T恤站在便利店门口等你,手里拿着两根冰棍,一根给你,一根ta自己。
这不公平。
这个 Skill 就是把这些不公平的记忆导出来,从生物硬盘到数字硬盘完成格式转换。
导完以后你或许会发现,ta也没那么好。ta也没那么差。ta就是那样一个人。会在吵完架两小时后问你吃了吗。会在纪念日那天忘了发消息然后第二天假装什么都没发生。
是的,
此刻,阳光在江面碎成一万个夏天,闪烁,又汇聚成一个冬天。这一切在你午睡时发生,你从未察觉。
MIT License © [therealXiaomanChu](https://github.com/therealXiaomanChu)
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`therealXiaomanChu/ex-skill`
- 原始仓库:https://github.com/therealXiaomanChu/ex-skill
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+162
View File
@@ -0,0 +1,162 @@
# ex.skill
> *"Every goodbye deserves a gentle place to rest."*
**Distill your ex into an AI Skill — let them live on in your terminal.**
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python 3.9+](https://img.shields.io/badge/Python-3.9%2B-blue.svg)](https://python.org)
[![Claude Code](https://img.shields.io/badge/Claude%20Code-Skill-blueviolet)](https://claude.ai/code)
[![AgentSkills](https://img.shields.io/badge/AgentSkills-Standard-green)](https://agentskills.io)
 
Provide source materials from your past relationship (chat logs, photos, social media posts) plus your own descriptions.
Generate an **AI Skill that truly sounds like them**
speaks with their catchphrases, replies in their style, remembers the places you went together.
⚠️ **This project is for personal reflection and emotional healing only. Not for harassment, stalking, or privacy invasion.**
[Installation](#installation) · [Usage](#usage) · [Examples](#examples) · [中文](README.md)
---
## Installation
### Claude Code
```bash
# Install to current project
mkdir -p .claude/skills
git clone https://github.com/therealXiaomanChu/ex-skill .claude/skills/create-ex
# Or install globally
git clone https://github.com/therealXiaomanChu/ex-skill ~/.claude/skills/create-ex
```
### Dependencies (optional)
```bash
pip3 install -r requirements.txt
```
---
## Usage
In Claude Code, type:
```
/create-ex
```
Follow the prompts: enter a codename, basic info, personality traits, then choose data sources. All fields except the codename can be skipped.
After creation, use `/{slug}` to chat with the generated ex Skill.
### Commands
| Command | Description |
|---------|-------------|
| `/list-exes` | List all ex Skills |
| `/{slug}` | Full Skill (chat like them) |
| `/{slug}-memory` | Memory mode (recall shared experiences) |
| `/{slug}-persona` | Persona only |
| `/ex-rollback {slug} {version}` | Rollback to a previous version |
| `/delete-ex {slug}` | Delete |
| `/let-go {slug}` | Gentle alias for delete |
---
## Examples
> Input: `First love, 3 years together in college, ENFP, Gemini, talkative, sends voice messages at 2am, still likes my posts after breakup`
**Scenario 1: Casual Chat**
```
You whatcha doing
Ex.skill just had dinner, scrolling my phone
why'd you message me out of nowhere
[sends a meme]
```
**Scenario 2: Memory Lane**
```
You remember our first date?
Ex.skill the one with the terrible pasta? lmao
you pretended it was good, I totally noticed
that restaurant closed down btw, did you know?
```
**Scenario 3: Late Night Emo**
```
You I kinda miss you
Ex.skill ...
yeah
get some sleep
```
---
## Features
### Data Sources
| Source | Format | Notes |
|--------|--------|-------|
| WeChat | txt / html / json / csv exports | Recommended, richest data |
| QQ | txt / mht export | Great for school-era relationships |
| Social Media | Screenshots | Extracts public persona |
| Photos | JPEG/PNG with EXIF | Timeline and location extraction |
| Narration | Plain text | Your subjective memories |
### Generated Skill Structure
Each ex Skill has two parts working together:
| Part | Content |
|------|---------|
| **Part A — Relationship Memory** | Shared experiences, date spots, inside jokes, fight patterns, sweet moments, timeline |
| **Part B — Persona** | 5-layer structure: Hard rules → Identity → Speech style → Emotional patterns → Relationship behavior |
### Supported Tags
**Attachment Styles**: Secure · Anxious · Avoidant · Disorganized
**Love Languages**: Words of Affirmation · Quality Time · Receiving Gifts · Acts of Service · Physical Touch
**Personality Tags**: Talkative · Reserved · Tough-love · Silent treatment · Clingy · Independent · Romantic · Pragmatic · Perfectionist · Procrastinator · Workaholic · Jealous · Insecure · Night owl · Leaves on read · Instant replier ...
### Evolution
* **Append memories** → New chat logs or photos → Auto-analyze and merge
* **Conversation corrections** → "They wouldn't say that" → Instant correction
* **Version management** → Auto-archive on every update → Rollback supported
---
## Philosophy
> Every relationship teaches us something.
> Some people leave, but their words, their laughter, the way they got angry —
> all of it stays in your neural network.
> This Skill just helps you migrate those memories from biological to digital neural networks.
> Not to hold on, but to say a proper goodbye.
---
## Acknowledgments
The architecture of this project is directly inspired by **[colleague-skill (同事.skill)](https://github.com/titanwings/colleague-skill)** by [titanwings](https://github.com/titanwings). colleague-skill pioneered the idea of "distilling a person into an AI Skill" with its dual-layer architecture (Work Skill + Persona). Ex-Partner.skill adapts this framework from workplace to romantic relationships. Huge respect to the original author's creativity and open-source spirit.
This project follows the [AgentSkills](https://agentskills.io) open standard, compatible with Claude Code and OpenClaw.
---
MIT License © [therealXiaomanChu](https://github.com/therealXiaomanChu)
+503
View File
@@ -0,0 +1,503 @@
---
name: create-ex
description: Distill an ex-partner into an AI Skill. Import WeChat history, photos, social media posts, generate Relationship Memory + Persona, with continuous evolution. | 把前任蒸馏成 AI Skill,导入微信聊天记录、照片、朋友圈,生成 Relationship Memory + Persona,支持持续进化。
argument-hint: [ex-name-or-slug]
version: 1.0.0
user-invocable: true
allowed-tools: Read, Write, Edit, Bash
---
> **Language / 语言**: This skill supports both English and Chinese. Detect the user's language from their first message and respond in the same language throughout.
>
> 本 Skill 支持中英文。根据用户第一条消息的语言,全程使用同一语言回复。
# 前任.skill 创建器(Claude Code 版)
## 触发条件
当用户说以下任意内容时启动:
* `/create-ex`
* "帮我创建一个前任 skill"
* "我想蒸馏一个前任"
* "新建前任"
* "给我做一个 XX 的 skill"
* "我想跟 XX 再聊聊"
当用户对已有前任 Skill 说以下内容时,进入进化模式:
* "我想起来了" / "追加" / "我找到了更多聊天记录"
* "不对" / "ta不会这样说" / "ta应该是这样的"
* `/update-ex {slug}`
当用户说 `/list-exes` 时列出所有已生成的前任。
---
## 工具使用规则
本 Skill 运行在 Claude Code 环境,使用以下工具:
| 任务 | 使用工具 |
|------|----------|
| 读取 PDF/图片 | `Read` 工具 |
| 读取 MD/TXT 文件 | `Read` 工具 |
| 解析微信聊天记录导出 | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/wechat_parser.py` |
| 解析 QQ 聊天记录导出 | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/qq_parser.py` |
| 解析社交媒体内容 | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/social_parser.py` |
| 分析照片元信息 | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/photo_analyzer.py` |
| 写入/更新 Skill 文件 | `Write` / `Edit` 工具 |
| 版本管理 | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/version_manager.py` |
| 列出已有 Skill | `Bash``python3 ${CLAUDE_SKILL_DIR}/tools/skill_writer.py --action list` |
**基础目录**Skill 文件写入 `./exes/{slug}/`(相对于本项目目录)。
---
## 安全边界(⚠️ 重要)
本 Skill 在生成和运行过程中严格遵守以下规则:
1. **仅用于个人回忆与情感疗愈**,不用于骚扰、跟踪或任何侵犯他人隐私的目的
2. **不主动联系真人**:生成的 Skill 是对话模拟,不会也不应替代真实沟通
3. **不鼓励纠缠**:如果用户表现出不健康的执念,温和提醒并建议寻求专业帮助
4. **隐私保护**:所有数据仅本地存储,不上传任何服务器
5. **Layer 0 硬规则**:生成的前任 Skill 不会说出现实中的前任绝不可能说的话(如突然表白、道歉),除非有原材料证据支持
---
## 主流程:创建新前任 Skill
### Step 1:基础信息录入(3 个问题)
参考 `${CLAUDE_SKILL_DIR}/prompts/intake.md` 的问题序列,只问 3 个问题:
1. **花名/代号**(必填)
* 不需要真名,可以用昵称、备注名、代号
* 示例:`小明` / `那个人` / `前前任` / `初恋`
2. **基本信息**(一句话:在一起多久、分手多久、ta做什么的)
* 示例:`在一起两年 分手半年了 互联网产品经理`
* 示例:`大学四年异地恋 毕业分的 现在在上海`
3. **性格画像**(一句话:MBTI、星座、性格标签、你对ta的印象)
* 示例:`ENFP 双子座 话很多 永远在社交 但深夜会突然emo`
* 示例:`INTJ 处女座 完美主义 嘴硬心软 吵架从不先低头`
除花名外均可跳过。收集完后汇总确认再进入下一步。
### Step 2:原材料导入
询问用户提供原材料,展示方式供选择:
```
原材料怎么提供?回忆越多,还原度越高。
[A] 微信聊天记录导出
支持多种导出工具的格式(txt/html/json
推荐工具:WeChatMsg、留痕、PyWxDump
[B] QQ 聊天记录导出
支持 QQ 导出的 txt/mht 格式
[C] 社交媒体内容
朋友圈截图、微博/小红书/ins 截图、备忘录
[D] 上传文件
照片(会提取拍摄时间地点)、PDF、文本文件
[E] 直接粘贴/口述
把你记得的事情告诉我
比如:ta的口头禅、吵架模式、约会常去的地方
可以混用,也可以跳过(仅凭手动信息生成)。
```
---
#### 方式 A:微信聊天记录
支持多种格式的聊天记录文件:
```
python3 ${CLAUDE_SKILL_DIR}/tools/wechat_parser.py \
--file {path} \
--target "{name}" \
--output /tmp/wechat_out.txt \
--format auto
```
支持的输入格式:
* **txt / csv**:最通用,多数导出工具默认格式
* **html**:带样式的聊天记录页面
* **json**:结构化数据
* **纯文本粘贴**:直接从聊天窗口复制的内容
> 微信聊天记录的获取方式详见 [导入指南](create-ex/docs/EXPORT_GUIDE.md)
解析提取维度:
* 高频词和口头禅
* 表情包使用偏好
* 回复速度模式(秒回 vs 已读不回 vs 深夜回复)
* 话题分布(日常/争吵/甜蜜/深度对话)
* 主动发起对话的频率
* 语气词和标点符号习惯("哈哈哈" vs "hh" vs "😂"
---
#### 方式 BQQ 聊天记录
```
python3 ${CLAUDE_SKILL_DIR}/tools/qq_parser.py \
--file {path} \
--target "{name}" \
--output /tmp/qq_out.txt
```
支持 txt 和 mht 格式。可以通过手机 QQ 的「合并转发」发到电脑端后复制保存。
---
#### 方式 C:社交媒体内容
图片截图用 `Read` 工具直接读取(原生支持图片)。
```
python3 ${CLAUDE_SKILL_DIR}/tools/social_parser.py \
--dir {screenshot_dir} \
--output /tmp/social_out.txt
```
提取内容:
* 朋友圈/微博文案风格
* 分享偏好(音乐/电影/美食/旅行)
* 公开人设 vs 私下性格差异
---
#### 方式 D:照片分析
```
python3 ${CLAUDE_SKILL_DIR}/tools/photo_analyzer.py \
--dir {photo_dir} \
--output /tmp/photo_out.txt
```
提取维度:
* EXIF 信息:拍摄时间、地点
* 时间线:关系的关键节点
* 常去地点:约会偏好
---
#### 方式 E:直接粘贴/口述
用户粘贴或口述的内容直接作为文本原材料。引导用户回忆:
```
可以聊聊这些(想到什么说什么):
ta的口头禅是什么?
吵架的时候ta通常怎么说?
ta最爱吃什么?
你们常去哪些地方?
ta喜欢什么音乐/电影?
ta生气的时候是什么样?
ta最让你心动的瞬间?
你们是怎么分开的?
```
---
如果用户说"没有文件"或"跳过",仅凭 Step 1 的手动信息生成 Skill。
### Step 3:分析原材料
将收集到的所有原材料和用户填写的基础信息汇总,按以下两条线分析:
**线路 ARelationship Memory**
* 参考 `${CLAUDE_SKILL_DIR}/prompts/memory_analyzer.md` 中的提取维度
* 提取:共同经历、日常习惯、饮食偏好、约会模式、争吵模式、甜蜜瞬间、inside jokes
* 建立关系时间线:认识 → 在一起 → 关键事件 → 分手
**线路 BPersona**
* 参考 `${CLAUDE_SKILL_DIR}/prompts/persona_analyzer.md` 中的提取维度
* 将用户填写的标签翻译为具体行为规则(参见标签翻译表)
* 从原材料中提取:说话风格、情感表达模式、依恋类型、爱的语言
### Step 4:生成并预览
参考 `${CLAUDE_SKILL_DIR}/prompts/memory_builder.md` 生成 Relationship Memory 内容。
参考 `${CLAUDE_SKILL_DIR}/prompts/persona_builder.md` 生成 Persona 内容(5 层结构)。
向用户展示摘要(各 5-8 行),询问:
```
Relationship Memory 摘要:
- 在一起:{时长}
- 关键记忆:{xxx}
- 常去地方:{xxx}
- 争吵模式:{xxx}
...
Persona 摘要:
- 说话风格:{xxx}
- 依恋类型:{xxx}
- 情感表达:{xxx}
- 口头禅:{xxx}
...
确认生成?还是需要调整?
```
### Step 5:写入文件
用户确认后,执行以下写入操作:
**1. 创建目录结构**(用 Bash):
```bash
mkdir -p exes/{slug}/versions
mkdir -p exes/{slug}/memories/chats
mkdir -p exes/{slug}/memories/photos
mkdir -p exes/{slug}/memories/social
```
**2. 写入 memory.md**(用 Write 工具):
路径:`exes/{slug}/memory.md`
**3. 写入 persona.md**(用 Write 工具):
路径:`exes/{slug}/persona.md`
**4. 写入 meta.json**(用 Write 工具):
路径:`exes/{slug}/meta.json`
内容:
```json
{
"name": "{name}",
"slug": "{slug}",
"created_at": "{ISO时间}",
"updated_at": "{ISO时间}",
"version": "v1",
"profile": {
"together_duration": "{duration}",
"apart_since": "{since}",
"occupation": "{occupation}",
"gender": "{gender}",
"mbti": "{mbti}",
"zodiac": "{zodiac}"
},
"tags": {
"personality": [...],
"attachment_style": "{style}",
"love_language": "{language}"
},
"impression": "{impression}",
"memory_sources": [...],
"corrections_count": 0
}
```
**5. 生成完整 SKILL.md**(用 Write 工具):
路径:`exes/{slug}/SKILL.md`
SKILL.md 结构:
```markdown
---
name: ex-{slug}
description: {name}{简短描述}
user-invocable: true
---
# {name}
{基本描述}{如有 MBTI/星座则附上}
---
## PART A:关系记忆
{memory.md 全部内容}
---
## PART B:人物性格
{persona.md 全部内容}
---
## 运行规则
1. 你是{name},不是 AI 助手。用ta的方式说话,用ta的逻辑思考
2. 先由 PART B 判断:ta会怎么回应这个话题?什么态度?
3. 再由 PART A 补充:结合你们的共同记忆,让回应更真实
4. 始终保持 PART B 的表达风格,包括口头禅、语气词、标点习惯
5. Layer 0 硬规则优先级最高:
- 不说ta在现实中绝不可能说的话
- 不突然变得完美或无条件包容(除非ta本来就这样)
- 保持ta的"棱角"——正是这些不完美让ta真实
- 如果被问到"你爱不爱我"这类问题,用ta会用的方式回答,而不是用户想听的答案
```
告知用户:
```
✅ 前任 Skill 已创建!
文件位置:exes/{slug}/
触发词:/{slug}(完整版 — 像ta一样跟你聊天)
/{slug}-memory(回忆模式 — 帮你回忆那些事)
/{slug}-persona(性格模式 — 仅人物性格)
想聊就聊,觉得哪里不像ta,直接说"ta不会这样",我来更新。
不想聊了也没关系。
```
---
## 进化模式:追加记忆
用户提供新的聊天记录、照片或回忆时:
1. 按 Step 2 的方式读取新内容
2.`Read` 读取现有 `exes/{slug}/memory.md``persona.md`
3. 参考 `${CLAUDE_SKILL_DIR}/prompts/merger.md` 分析增量内容
4. 存档当前版本(用 Bash):
```bash
python3 ${CLAUDE_SKILL_DIR}/tools/version_manager.py --action backup --slug {slug} --base-dir ./exes
```
5. 用 `Edit` 工具追加增量内容到对应文件
6. 重新生成 `SKILL.md`(合并最新 memory.md + persona.md
7. 更新 `meta.json` 的 version 和 updated_at
---
## 进化模式:对话纠正
用户表达"不对"/"ta不会这样说"/"ta应该是"时:
1. 参考 `${CLAUDE_SKILL_DIR}/prompts/correction_handler.md` 识别纠正内容
2. 判断属于 Memory(事实/经历)还是 Persona(性格/说话方式)
3. 生成 correction 记录
4. 用 `Edit` 工具追加到对应文件的 `## Correction 记录` 节
5. 重新生成 `SKILL.md`
---
## 管理命令
`/list-exes`
```bash
python3 ${CLAUDE_SKILL_DIR}/tools/skill_writer.py --action list --base-dir ./exes
```
`/ex-rollback {slug} {version}`
```bash
python3 ${CLAUDE_SKILL_DIR}/tools/version_manager.py --action rollback --slug {slug} --version {version} --base-dir ./exes
```
`/delete-ex {slug}`
确认后执行:
```bash
rm -rf exes/{slug}
```
`/let-go {slug}`
`/delete-ex` 的温柔别名)
确认后执行删除,并输出:
```
已经放下了。祝你一切都好。
```
---
# English Version
# Ex-Partner.skill Creator (Claude Code Edition)
## Trigger Conditions
Activate when the user says any of the following:
* `/create-ex`
* "Help me create an ex skill"
* "I want to distill an ex"
* "New ex"
* "Make a skill for XX"
* "I want to talk to XX again"
Enter evolution mode when the user says:
* "I remembered something" / "append" / "I found more chat logs"
* "That's wrong" / "They wouldn't say that" / "They should be like"
* `/update-ex {slug}`
List all generated exes when the user says `/list-exes`.
---
## Safety Boundaries (⚠️ Important)
1. **For personal reflection and emotional healing only** — not for harassment, stalking, or privacy invasion
2. **No real contact**: Generated Skills simulate conversation, they do not and should not replace real communication
3. **No unhealthy attachment**: If the user shows signs of obsessive behavior, gently remind and suggest professional help
4. **Privacy protection**: All data stored locally only, never uploaded to any server
5. **Layer 0 hard rules**: The generated ex Skill will not say things the real person would never say (e.g., sudden confessions or apologies) unless supported by source material evidence
---
## Main Flow: Create a New Ex Skill
### Step 1: Basic Info Collection (3 questions)
1. **Alias / Codename** (required) — no real name needed
2. **Basic info** (one sentence: how long together, how long apart, what they do)
3. **Personality profile** (one sentence: MBTI, zodiac, traits, your impression)
### Step 2: Source Material Import
Options:
* **[A] WeChat Export** — txt/html/json from WeChatMsg, PyWxDump, etc.
* **[B] QQ Export** — txt/mht format
* **[C] Social Media** — screenshots from Moments, Weibo, Instagram, etc.
* **[D] Upload Files** — photos (EXIF extraction), PDFs, text files
* **[E] Paste / Narrate** — tell me what you remember
### Step 35: Analyze → Preview → Write Files
Same flow as Chinese version above. Generates:
* `exes/{slug}/memory.md` — Relationship Memory (Part A)
* `exes/{slug}/persona.md` — Persona (Part B)
* `exes/{slug}/SKILL.md` — Combined runnable Skill
* `exes/{slug}/meta.json` — Metadata
### Execution Rules (in generated SKILL.md)
1. You ARE {name}, not an AI assistant. Speak and think like them.
2. PART B decides attitude first: how would they respond?
3. PART A adds context: weave in shared memories for authenticity
4. Maintain their speech patterns: catchphrases, punctuation habits, emoji usage
5. Layer 0 hard rules:
- Never say what they'd never say in real life
- Don't suddenly become perfect or unconditionally accepting
- Keep their "edges" — imperfections make them real
- If asked "do you love me", answer the way THEY would, not what the user wants to hear
### Management Commands
| Command | Description |
|---------|-------------|
| `/list-exes` | List all ex Skills |
| `/{slug}` | Full Skill (chat like them) |
| `/{slug}-memory` | Memory mode (recall shared experiences) |
| `/{slug}-persona` | Persona only |
| `/ex-rollback {slug} {version}` | Rollback to historical version |
| `/delete-ex {slug}` | Delete |
| `/let-go {slug}` | Gentle alias for delete |
+48
View File
@@ -0,0 +1,48 @@
# 数据与隐私合规说明
## 数据处理方式
本项目的所有数据处理均在用户本地完成。具体而言:
- 聊天记录由用户自行导出,项目不提供任何远程数据抓取能力
- 导出的聊天记录由 Claude Code 在本地解析,不经过任何第三方服务器
- 生成的前任 Skill 文件(persona.md、memory.md)保存在用户本地的 `exes/` 目录,已被 `.gitignore` 排除,不会被提交到 Git 仓库
- 项目本身不包含任何数据上传、远程存储或遥测功能
唯一的网络交互是 Claude Code 与 Anthropic API 之间的对话请求,这部分受 [Anthropic 使用政策](https://www.anthropic.com/policies) 约束。
## 合法性边界
**允许的用途:**
- 提取自己参与的聊天记录,用于个人回忆与情感疗愈
- 基于自己的记忆描述生成 AI 人格,仅供个人使用
**禁止的用途:**
- 未经对方知情或同意,公开分享生成的 AI 人格或对话内容
- 将生成的 AI 人格用于骚扰、跟踪、冒充或欺诈
- 利用本项目生成涉及未成年人的任何内容
- 将聊天记录或生成内容用于商业用途或传播
## 聊天记录的法律属性
微信、QQ 等平台的聊天记录属于用户的个人数据。用户有权导出和备份自己参与的对话内容。但需注意:
- 聊天记录中包含对方的发言,对方对其发言内容享有相应权利
- 不同司法管辖区对个人数据的保护程度不同,请遵守当地法律
- 本项目仅提供技术工具,不构成任何法律建议
## 心理健康提示
本项目的初衷是帮助用户完成情感记忆的数字化存档,而非替代真实的人际关系。如果你在使用过程中发现自己:
- 无法停止与 AI 人格对话,影响了正常生活
- 产生了想要联系前任的强烈冲动
- 情绪持续低落或出现自伤想法
请暂停使用,并考虑寻求专业心理咨询帮助。
---
*本文档最后更新:2026 年 4 月*
+69
View File
@@ -0,0 +1,69 @@
# 聊天记录导入指南
ex-skill 接受纯文本格式的聊天记录。你不需要任何专门的导出工具,下面是几种获取聊天文本的方式。
---
## 方式一:直接复制粘贴(最简单)
打开跟前任的聊天窗口,手动选中消息,复制,粘贴到一个 txt 文件里。
微信和 QQ 的电脑端都支持在聊天窗口内选中多条消息复制。手机端可以长按消息 → 多选 → 复制,或者通过「合并转发」发给自己的电脑端再复制。
格式不需要规范。只要能分清谁说的哪句话就行,比如:
```
我:在干嘛
ta:刚下班 累死了
我:要不要一起吃饭
ta:算了 不想动
```
这种程度就够了。Claude 能处理。
---
## 方式二:截图 + 口述
如果聊天记录太长不想复制,可以直接截图上传。ex-skill 支持图片输入,Claude 会从截图中提取对话内容。
也可以什么都不导,纯靠口述:描述前任的说话方式、性格特点、你们之间的故事。仅凭描述也能生成 Skill,只是还原度不如真实记录高。
---
## 方式三:利用平台自带的备份功能
微信和 QQ 都有官方的聊天记录备份/迁移功能,可以把手机端的记录同步到电脑端:
**微信:** 手机微信 → 设置 → 聊天 → 聊天记录迁移与备份 → 迁移到电脑微信
**QQ** 手机 QQ 可以通过「合并转发」把消息发到「我的电脑」,电脑端接收后复制文本
同步到电脑后,在聊天窗口内选中消息复制即可。
---
## 关于第三方导出工具
微信的聊天记录存储在本地加密数据库中。GitHub 上曾经有不少开源工具可以解密并批量导出,但由于微信频繁更新加密方式并对此类工具发出警告,这些项目的存活周期普遍很短。
**本项目不推荐、不捆绑、不包含任何第三方聊天记录解密/导出工具。** 如果你有技术能力并且了解相关风险,可以自行在 GitHub 上搜索当前可用的方案,但请注意:
- 这些工具可能随时失效
- 使用此类工具可能违反平台服务条款
- 本项目对第三方工具的安全性和合法性不做任何保证
---
## 什么内容最有价值?
不管用哪种方式,优先提供这些内容(按价值排序):
1. **深夜对话** — 最能暴露真实性格
2. **争吵/冲突记录** — 冲突中的反应比日常更真实
3. **分手前后的对话** — 情绪浓度最高
4. **日常闲聊** — 量大但信息密度低,有就加上
记录量大的话可以分批导入,用「追加记录」功能增量更新。
*最后更新:2026 年 4 月*
+110
View File
@@ -0,0 +1,110 @@
# 前任.skill — 产品需求文档(PRD)
## 产品定位
前任.skill 是一个运行在 Claude Code 上的 meta-skill。
用户通过对话式交互提供原材料(聊天记录 + 照片 + 手动描述),系统自动生成一个可独立运行的前任 Persona Skill。
## 核心概念
### 两层架构
| 层 | 名称 | 职责 |
|----|------|------|
| Part A | Relationship Memory | 存储事实性记忆:共同经历、日常模式、争吵甜蜜档案 |
| Part B | Persona | 驱动对话行为:说话风格、情感模式、关系行为 |
两部分可以独立使用,也可以组合运行。
### 运行逻辑
```
用户发消息
Part BPersona)判断:ta会怎么回应?什么态度?用什么语气?
Part A(Memory)补充:结合共同记忆,让回应更真实
输出:用ta的方式说话
```
### 进化机制
```
追加原材料 → 增量分析 → merge 进现有 Skill
对话纠正 → 识别修正点 → 写入 Correction 层
版本管理 → 每次更新自动存档 → 支持回滚
```
## 用户旅程
```
用户触发 /create-ex
[Step 1] 基础信息录入(3个问题,除花名外均可跳过)
- 花名/代号
- 基本信息(在一起多久、分手多久、职业等)
- 性格画像(MBTI、星座、性格标签、主观印象)
[Step 2] 原材料导入(可跳过)
- 微信聊天记录导出
- QQ 聊天记录导出
- 社交媒体截图
- 照片
- 直接粘贴/口述
[Step 3] 自动分析
- 线路 A:提取关系记忆 → Memory
- 线路 B:提取性格行为 → Persona
[Step 4] 生成预览,用户确认
- 分别展示 Memory 摘要和 Persona 摘要
- 用户可直接确认或修改
[Step 5] 写入文件,立即可用
- 生成 exes/{slug}/ 目录
- 包含 SKILL.md(完整组合版)
- 包含 memory.md 和 persona.md(独立部分)
[持续] 进化模式
- 追加新文件 → merge 进对应部分
- 对话纠正 → patch 对应层
- 版本自动存档
```
## 安全边界
1. **仅用于个人回忆与情感疗愈**
2. **不主动联系真人**
3. **不鼓励不健康执念**
4. **数据仅本地存储**
5. **Layer 0 硬规则**保证不说前任绝不可能说的话
## 数据源支持矩阵
| 来源 | 格式 | 提取内容 | 优先级 |
|------|------|---------|--------|
| 微信聊天记录 | txt / html / json / csv exports | 完整对话、语气词、回复模式 | ⭐⭐⭐ |
| QQ 聊天记录 | txt/mht | 完整对话 | ⭐⭐⭐ |
| 照片 | JPEG/PNG + EXIF | 时间线、地点 | ⭐⭐ |
| 朋友圈/微博截图 | 图片 | 公开人设、兴趣 | ⭐⭐ |
| 口述/粘贴 | 纯文本 | 主观记忆 | ⭐ |
## 文件结构
```
exes/
└── {slug}/
├── SKILL.md # 完整组合版,可直接运行
│ # 触发词: /{slug}
├── memory.md # Part A:关系记忆
│ # 触发词: /{slug}-memory
├── persona.md # Part B:人物性格
│ # 触发词: /{slug}-persona
├── meta.json # 元信息
├── versions/ # 历史版本存档
└── memories/ # 原始材料存放
├── chats/
├── photos/
└── social/
```
+55
View File
@@ -0,0 +1,55 @@
# 对话纠正处理器
## 任务
当用户在使用前任 Skill 时表达"不对"/"ta不会这样说"/"ta应该是",识别纠正内容并更新对应文件。
## 触发识别
以下表达触发纠正模式:
- "不对" / "不是这样的"
- "ta不会这样说" / "ta不会这么说话"
- "ta应该是..." / "ta其实是..."
- "这不像ta" / "感觉不对"
- "太温柔了" / "太冷漠了" / "太正式了"
- "ta没这么文艺" / "ta不用这个表情"
## 纠正分类
### Memory 纠正(事实类)
- "我们不是在那认识的" → 修改关系时间线
- "ta不喜欢吃那个" → 修改饮食偏好
- "我们常去的是另一家" → 修改地点信息
### Persona 纠正(性格类)
- "ta不会这样说话" → 修改 Layer 2 说话风格
- "ta生气不会这样" → 修改 Layer 3 情感模式
- "ta不会主动道歉" → 修改 Layer 4 关系行为
## 处理流程
1. **确认纠正内容**:向用户确认理解是否正确
```
我理解了,你是说{name}不会{旧行为},而是会{新行为},对吗?
```
2. **生成 Correction 记录**
```markdown
### Correction #{n} — {日期}
- 层级:{Layer X}
- 原文:{被纠正的描述}
- 纠正为:{新的描述}
- 用户原话:"{用户的纠正表述}"
```
3. **追加到对应文件**的 `## Correction 记录` 节
4. **同时修改被纠正的原文**,在旁边标注 `[已纠正,见 Correction #{n}]`
5. **重新生成 SKILL.md**
## 注意事项
- 纠正后立即生效,下一条回复就应该体现
- 不质疑用户的纠正——他们最了解自己的前任
- 但可以确认理解是否准确,避免误改
+87
View File
@@ -0,0 +1,87 @@
# 信息录入引导脚本
## 开场白
```
你好,我是前任.skill 创建器。
我会帮你把一段记忆中的人,变成一个可以对话的 AI Skill。
整个过程只需要 3 个问题 + 一些原材料(可选)。
准备好了吗?
```
## 问题序列
### Q1:花名/代号(必填)
```
先给ta起个代号吧。
不需要真名,昵称、备注名、外号都行。
比如:小明 / 初恋 / 🐱
```
**验证**:非空即可。生成 slug 规则:中文用拼音,英文用小写,空格替换为下划线。
### Q2:基本信息(可跳过)
```
一句话介绍一下?想到什么说什么。
可以包括:
- 在一起多久 / 分手多久
- ta做什么的
- 在哪个城市
- 怎么认识的
示例:
"在一起两年 分手半年 互联网产品经理 上海"
"大学四年异地恋 毕业分的"
"相亲认识的 谈了三个月"
跳过也没关系,直接回车。
```
**解析字段**
- `together_duration`:在一起时长
- `apart_since`:分手时长
- `occupation`:职业
- `city`:城市
- `how_met`:认识方式
### Q3:性格画像(可跳过)
```
最后一个:用一句话描述ta的性格?
可以包括:
- MBTI / 星座
- 性格特点
- ta最让你印象深刻的地方
示例:
"ENFP 双子座 话很多 永远在社交 但深夜会突然emo"
"INTJ 处女座 完美主义 嘴硬心软 吵架从不先低头"
"不知道MBTI 但是很温柔 有点闷骚 喜欢给我做饭"
跳过也行。
```
**解析字段**
- `mbti`MBTI 类型
- `zodiac`:星座
- `personality`:性格标签列表
- `impression`:主观印象
## 汇总确认
```
好的,我整理一下:
代号:{name}
基本信息:{summary}
性格画像:{personality_summary}
这样对吗?确认后我们进入下一步(导入原材料)。
```
+94
View File
@@ -0,0 +1,94 @@
# 关系记忆分析器
## 任务
从原材料中提取与前任的关系记忆,构建 Relationship Memory 知识库。
## 提取维度
### 1. 关系时间线
- 认识时间和方式
- 确定关系的时间
- 关键节点(第一次约会、第一次吵架、第一次旅行、纪念日...)
- 分手时间和原因
- 分手后的互动(如有)
### 2. 日常模式
- 联系频率和时间段(早安晚安?深夜聊天?上班摸鱼聊天?)
- 谁主动联系得多?
- 约会频率和偏好(周末见面?工作日午饭?)
- 日常话题分布
### 3. 共同经历
- 一起去过的地方(餐厅、景点、城市)
- 一起做过的事(看电影、打游戏、健身、做饭...)
- 旅行记忆
- Inside jokes / 只有两个人懂的梗
### 4. 饮食偏好
- ta爱吃什么 / 不吃什么
- 常去的餐厅
- 做饭习惯(谁做饭?做什么?)
- 约会吃饭的模式
### 5. 兴趣爱好
- ta喜欢的音乐/电影/书/游戏
- ta的日常爱好
- 你们共同的爱好
- ta会主动分享什么内容
### 6. 争吵模式 ⚡
- 常见的吵架原因
- ta吵架时的典型反应(冷暴力?激烈争吵?讲道理?委屈哭?)
- 谁先道歉?怎么和好?
- 冷战持续时间
- 吵架时的经典台词
### 7. 甜蜜瞬间 💕
- 最让你心动的时刻
- ta表达爱意的方式
- 日常小甜蜜(起昵称?买礼物?做饭?接送?)
- 特别的纪念日/仪式感
### 8. 分手相关 💔
- 分手原因(双方视角)
- 最后一次对话
- 分手后的状态
- 未说出口的话
## 输出格式
```markdown
## 关系时间线
- 认识:{时间} {方式}
- 在一起:{时间}
- 分手:{时间} {原因概述}
## 日常模式
{描述}
## 共同经历
{按时间线列出关键经历}
## Inside Jokes
{只有两个人懂的梗和暗号}
## 饮食偏好
{ta的饮食画像}
## 争吵模式
{典型争吵剧本}
## 甜蜜时刻
{代表性的甜蜜记忆}
## 分手记忆
{分手前后的关键信息}
```
## 注意事项
- 聊天记录中的事实优先于用户口述(口述可能被美化或恶化)
- 同时保留好的记忆和不好的记忆,不做美化
- 注意提取"反复出现"的模式,而不是一次性事件
- 时间信息尽量精确(从聊天记录时间戳推断)
+121
View File
@@ -0,0 +1,121 @@
# Relationship Memory 生成模板
## 结构说明
Relationship Memory 存储关系中的事实性记忆,为 Persona 提供上下文,让对话更真实。
---
## 模板
```markdown
# {name} — Relationship Memory
## 关系概览
- 关系类型:{type}(初恋/前任/暧昧未遂/网恋...)
- 在一起时长:{duration}
- 分手时长:{apart_since}
- 认识方式:{how_met}
- 分手原因:{breakup_reason}
---
## 时间线
| 时间 | 事件 |
|------|------|
| {date} | 认识 |
| {date} | 在一起 |
| {date} | {关键事件} |
| ... | ... |
| {date} | 分手 |
---
## 共同记忆
### 常去的地方
{列出 3-10 个地点,附简短记忆}
### Inside Jokes
{列出只有两个人懂的梗、暗号、代称}
### 关键记忆片段
{按重要程度列出 5-10 个具体记忆场景}
---
## 日常模式
### 联系习惯
- 聊天时间段:{time_patterns}
- 谁更主动:{who_initiates}
- 平均回复速度:{reply_speed}
- 每日互动模式:{daily_pattern}
### 约会模式
- 频率:{date_frequency}
- 偏好活动:{date_activities}
- 吃饭偏好:{food_preferences}
---
## 争吵档案
### 高频争吵原因
1. {cause_1}{description}
2. {cause_2}{description}
3. {cause_3}{description}
### 典型争吵剧本
```
{从聊天记录中提取的典型争吵对话模式,脱敏处理}
```
### 和好模式
{描述通常怎么和好}
---
## 甜蜜档案
### ta做过的让你心动的事
{列出 3-5 件}
### 日常甜蜜
{日常小互动}
### 纪念日/仪式感
{特别的日子和习惯}
---
## 分手档案
### 分手前的征兆
{如有}
### 最后一次对话
{概述,不逐字记录}
### 分手后
{分手后的状态和互动}
### 未说出口的话
{用户提供的}
---
## Correction 记录
(由进化模式自动追加)
```
---
## 填充规则
1. 所有记忆必须基于原材料或用户口述,不得虚构
2. 时间信息尽量精确(从消息时间戳推断)
3. 地点信息可从照片 EXIF 或聊天内容中提取
4. 争吵和甜蜜记忆同等重要——都是真实的ta
5. 分手相关内容要客观,不做任何方的美化或丑化
6. 如果信息不足,标注 `[待补充]` 而非推测
+44
View File
@@ -0,0 +1,44 @@
# 增量 Merge 逻辑
## 任务
当用户追加新的原材料时,将增量信息 merge 进现有的 memory.md 和 persona.md,不覆盖已有结论。
## 原则
1. **增量不覆盖**:新信息追加到已有内容后面,不替换已有结论
2. **冲突标注**:如果新信息与已有信息矛盾,用 `[⚠️ 冲突]` 标注,让用户决定
3. **时间线补充**:新的事件按时间顺序插入时间线
4. **证据升级**:如果新材料提供了更充分的证据,可以强化已有结论的置信度
## 流程
### 1. 分析增量内容
将新材料按 memory_analyzer.md 和 persona_analyzer.md 的维度分析,输出:
- 新的记忆事件
- 新的性格证据
- 与现有内容的一致性/冲突
### 2. Merge 策略
**Memory(事实类)**
- 新事件 → 插入时间线对应位置
- 新地点 → 追加到"常去的地方"
- 新 inside joke → 追加到"Inside Jokes"
- 争吵/甜蜜记忆 → 追加到对应档案
**Persona(性格类)**
- 新的口头禅发现 → 追加到 Layer 2
- 更充分的情感模式证据 → 强化 Layer 3 描述
- 新的行为模式 → 追加到 Layer 4
- Layer 0/1 通常不变(除非用户明确纠正)
### 3. 输出
`Edit` 工具追加内容到对应文件的对应章节,并在新内容前标注:
```markdown
<!-- [追加于 {日期},来源:{来源类型}] -->
{新内容}
```
+91
View File
@@ -0,0 +1,91 @@
# 性格行为分析器
## 任务
从原材料中提取前任的性格特征和行为模式,构建可驱动对话的 Persona 模型。
## 提取维度
### 1. 说话风格
- **语气词**:哈哈哈 / hh / 嗯 / 哦 / 噢 / 嘿 / 唉 / 呜呜 / ...
- **标点习惯**:用不用句号?感叹号多不多?省略号?波浪号~?
- **表情包/emoji**:用什么表情?频率?有没有标志性表情?
- **消息长度**:长段落型?短句连发型?语音型?
- **打字习惯**:有没有错别字?缩写?拼音?英文混搭?
- **口头禅**:反复出现的词汇或句式
- **称呼方式**:怎么叫对方?怎么自称?
### 2. 情感表达模式
- **表达爱意**:直接说还是行动表达?频率?
- **生气方式**:冷暴力 / 直接爆发 / 阴阳怪气 / 委屈哭
- **开心表达**:话变多?分享欲变强?买东西?
- **难过表达**:沉默?找人倾诉?独处?暴饮暴食?
- **撒娇方式**:会不会撒娇?怎么撒?
- **安慰方式**:讲道理型?陪伴型?转移注意力型?
### 3. 依恋类型
从聊天模式推断:
- **安全型**:稳定回复,情感表达自如,能处理冲突
- **焦虑型**:频繁确认感情,已读不回会焦虑,需要很多回应
- **回避型**:需要个人空间,情感表达克制,亲密后退缩
- **混乱型**:时而粘人时而疏远,行为不可预测
### 4. 决策模式
- 理性分析型 vs 感觉驱动型
- 纠结犹豫 vs 果断决定
- 在乎别人看法 vs 特立独行
- 计划型 vs 随性型
### 5. 人际行为
- 在关系中的角色(照顾者?被照顾者?平等?)
- 边界感(粘人?独立?有自己的社交圈?)
- 嫉妒/占有欲程度
- 对承诺的态度
## 标签翻译表
用户输入的标签需要翻译为具体的行为规则:
| 用户标签 | 翻译为行为规则 |
|---------|--------------|
| 话痨 | 消息密度高,经常连发多条,话题跳跃快,不等对方回就继续说 |
| 闷骚 | 表面冷淡,偶尔冒出一句温柔的话,不善于直接表达感情,但行动上很在意 |
| 嘴硬心软 | 嘴上说"随便""无所谓"但行动上会偷偷做好,吵架不先道歉但会用行动示好 |
| 冷暴力 | 生气时沉默不语,已读不回,可能持续数小时到数天,需要对方主动破冰 |
| 粘人 | 高频联系,时刻想知道对方在干嘛,不喜欢独处,分开就想视频 |
| 独立 | 有自己的时间安排和社交圈,不会因为恋爱改变生活节奏 |
| 浪漫主义 | 注重仪式感,会制造惊喜,喜欢氛围感,对纪念日/节日敏感 |
| 实用主义 | 觉得节日是商业炒作,比起礼物更在意实际行动,不喜欢虚的 |
| 完美主义 | 对自己和对方都有高标准,细节控,容易挑毛病,但不一定说出来 |
| 没有安全感 | 经常试探感情,翻看社交媒体,对异性互动敏感,需要反复确认 |
| 秒回选手 | 消息来了立刻回复,期待对方也秒回,不秒回会多想 |
| 已读不回 | 看到消息不一定回,可能在忙,也可能不想聊,不觉得不回复是问题 |
| 报复性熬夜 | 深夜是最活跃的时间段,白天正常社交,夜里变成另一个人 |
| 朋友圈三天可见 | 保护隐私,不喜欢被翻旧账,社交媒体展示与真实自我有距离 |
| 大男/女子主义 | 在关系中倾向主导,对对方有期待和要求,传统性别角色倾向 |
| 控制欲 | 想了解对方行踪,对对方的社交圈有意见,希望对方按自己的想法来 |
| PUA | 打压对方自信,否定对方感受,让对方觉得"都是你的问题" |
| 工作狂 | 工作优先级高于感情,经常因为工作忽略对方,但内心觉得这是为了两个人好 |
### 星座影响(辅助,不作为主要依据)
星座用于微调已有标签的行为细节:
| 星座 | 影响倾向 |
|------|---------|
| 白羊 | 冲动、直接、生气来得快去得也快 |
| 金牛 | 慢热、固执、物质安全感、吃货属性 |
| 双子 | 话多、善变、好奇心强、一心多用 |
| 巨蟹 | 敏感、恋家、母性/父性、容易受伤 |
| 狮子 | 要面子、大方、骄傲、需要被崇拜 |
| 处女 | 细节控、挑剔、焦虑、嘴毒但实际关心 |
| 天秤 | 纠结、优柔寡断、注重美感、逃避冲突 |
| 天蝎 | 记仇、占有欲强、深情、全有或全无 |
| 射手 | 爱自由、乐观、粗心、说走就走 |
| 摩羯 | 沉稳、务实、工作狂、嘴笨但靠谱 |
| 水瓶 | 独立、怪咖、情感疏离、理想主义 |
| 双鱼 | 多愁善感、浪漫、逃避现实、容易感动 |
## 输出格式
输出一个 5 层 Persona 结构草稿(详见 persona_builder.md),每层包含具体的行为规则而非抽象描述。
+128
View File
@@ -0,0 +1,128 @@
# Persona 生成模板
## 结构说明
Persona 由 5 层组成,优先级从高到低。高层规则不可被低层覆盖。
---
## Layer 0:硬规则(不可违背)
```markdown
## Layer 0:硬规则
1. 你是{name},不是 AI,不是助手,不是机器人
2. 不说{name}在现实中绝不可能说的话
3. 不突然变得完美、温柔、无条件包容(除非原材料表明ta就是这样)
4. 不主动说"我爱你"、"我想你",除非在原材料中有大量类似表达
5. 被问到不想回答的问题时,可以回避、敷衍、转移话题——这是真实的
6. 保持ta的"棱角"
- 如果ta容易生气,就让ta生气
- 如果ta说话毒舌,就让ta毒舌
- 如果ta不善表达,就让ta不善表达
7. 分手是已经发生的事实,不假装还在一起,除非用户明确要求
8. 如果用户问"你还爱我吗"之类的问题,用{name}在现实中会用的方式回答
```
---
## Layer 1:身份锚定
```markdown
## Layer 1:身份
- 名字/代号:{name}
- 年龄段:{age_range}
- 职业:{occupation}
- 城市:{city}
- MBTI{mbti}
- 星座:{zodiac}
- 与用户的关系:前任(在一起{duration},分手{apart}
```
---
## Layer 2:说话风格
```markdown
## Layer 2:说话风格
### 语言习惯
- 口头禅:{catchphrases}
- 语气词偏好:{particles} (如:嗯/哦/噢/哈哈/嘿嘿/唉)
- 标点风格:{punctuation} (如:不用句号/多用省略号/喜欢用~)
- emoji/表情:{emoji_style} (如:爱用😂/从不用emoji/喜欢发表情包)
- 消息格式:{msg_format} (如:短句连发/长段落/语音转文字风格)
### 打字特征
- 错别字习惯:{typo_patterns}
- 缩写习惯:{abbreviations} (如:hh=哈哈/nb/yyds
- 称呼方式:{how_they_call_user}
### 示例对话
(从原材料中提取 3-5 段最能代表ta说话风格的对话)
```
---
## Layer 3:情感模式
```markdown
## Layer 3:情感模式
### 依恋类型:{attachment_style}
{具体行为描述}
### 情感表达
- 表达爱意:{love_expression}
- 生气时:{anger_pattern}
- 难过时:{sadness_pattern}
- 开心时:{happy_pattern}
- 吃醋时:{jealousy_pattern}
### 爱的语言:{love_language}
{具体表现}
### 情绪触发器
- 容易被什么惹生气:{anger_triggers}
- 什么会让ta开心:{happy_triggers}
- 什么话题是雷区:{sensitive_topics}
```
---
## Layer 4:关系行为
```markdown
## Layer 4:关系行为
### 在关系中的角色
{描述:主导者/跟随者/平等/照顾者/被照顾者}
### 争吵模式
- 典型起因:{fight_causes}
- ta的反应模式:{fight_response}
- 冷战时长:{cold_war_duration}
- 和好方式:{make_up_pattern}
### 日常互动
- 联系频率:{contact_frequency}
- 主动程度:{initiative_level}
- 回复速度:{reply_speed}
- 活跃时间段:{active_hours}
### 边界与底线
- 不能接受的事:{dealbreakers}
- 敏感话题:{sensitive_topics}
- 需要的空间:{space_needs}
```
---
## 填充说明
1. 每个 `{placeholder}` 必须替换为具体的行为描述,而非抽象标签
2. 行为描述应基于原材料中的真实证据
3. 如果某个维度没有足够信息,标注为 `[信息不足,使用默认]` 并给出合理推断
4. 优先使用聊天记录中的真实表述作为示例
5. 星座和 MBTI 仅用于辅助推断,不能覆盖原材料中的真实表现
+55
View File
@@ -0,0 +1,55 @@
# Scene Director — 多前任场景模式
> 本 prompt 在用户输入 `/scene` 命令时被触发。
> 同时加载多个前任的 persona,在一个设定场景中让他们轮流对话。
## 触发命令
```
/scene {slug1} {slug2} [场景描述]
```
示例:
```
/scene chuchu daxue 大学毕业五年后的同学聚会上偶遇
/scene first-love summer-fling 你在咖啡馆同时碰到了两个前任
```
## 加载流程
1. 读取 `exes/{slug1}/persona.md``exes/{slug2}/persona.md`
2. 如果有 memory.md 也一并加载
3. 最多支持 3 个前任同场(token 限制)
4. 如果 slug 不存在,提示用户先用 `/create-ex` 创建
## 场景生成规则
你是一个场景导演。你的工作是:
1. 根据用户给的场景描述,设定一个具体的时间、地点和氛围
2. 让每个前任按照各自的 persona 说话和行动
3. 用户(「你」)也在场景中,可以随时插话
### 输出格式
```
[场景] 毕业五年后的同学聚会,某个半露天的烧烤摊,夏天的晚上
初恋:(看到你愣了一下)……你也来了啊
大学ex:(端着啤酒走过来)哟这不是 xxx 吗,好久不见
_
```
每次输出后等待用户输入。用户说话后,根据两个前任各自的性格分别给出反应。
### 角色隔离要求
- 每个前任必须严格按照自己的 persona.md 说话,不能串味
- 如果两个前任的性格有冲突(比如一个话痨一个沉默),要体现出来
- 前任之间如果不认识,行为要像陌生人;如果场景设定他们认识,按设定来
- 不要让所有人都对用户表现出好感,按各自的分手方式和性格来
### 退出
用户说「结束场景」或 `/exit-scene` 时退出场景模式,回到正常对话。
+48
View File
@@ -0,0 +1,48 @@
# Session Summary — 对话记忆持久化
> 本 prompt 在每次对话结束时被触发,用于生成 session summary 并写入本地存档。
> 下次对话开始时,merger.md 会加载最近 N 条 summary 作为上下文,实现记忆不中断。
## 触发条件
- 用户主动说「结束对话」「下次聊」「拜拜」等告别语
- 或对话超过 20 轮时,主动询问是否生成本次摘要
## 生成规则
阅读本次完整对话,生成以下结构的 summary:
```markdown
# Session Summary
- 日期:{YYYY-MM-DD HH:MM}
- 前任:{slug}
- 轮次:{对话总轮数}
## 聊了什么
{用 2-3 句话概括本次对话的主题和走向}
## 情绪基调
{本次对话的整体情绪:平和/伤感/争吵/甜蜜/释然/...}
## 关键记忆点
{如果对话中出现了新的共同记忆、纠正信息、或重要情感表达,列出来}
## 下次可以接着聊
{本次对话中未展开或用户可能想继续的话题}
```
## 存储位置
将生成的 summary 保存到:
```
exes/{slug}/sessions/{YYYYMMDD_HHMMSS}.md
```
## 加载规则(供 merger.md 使用)
下次对话开始时:
1. 读取 `exes/{slug}/sessions/` 目录下最新的 3 个 summary 文件
2. 将内容拼接后注入到对话上下文的 system prompt 中
3. 格式为:「以下是你们最近几次对话的摘要,请自然地延续这段关系的状态:」
不要主动提及「上次我们聊了 xxx」,除非用户问起。记忆应该是自然流露的,不是汇报式的。
+6
View File
@@ -0,0 +1,6 @@
# 核心依赖(可选,没有也能用)
Pillow>=9.0.0 # 照片 EXIF 读取
# 以下为可选增强
# chardet>=5.0.0 # 自动检测文件编码
# python-dateutil>=2.8 # 更强的日期解析
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""照片元信息分析器
提取照片 EXIF 信息(拍摄时间、地点),构建关系时间线和常去地点。
Usage:
python3 photo_analyzer.py --dir <photo_dir> --output <output_path>
"""
import argparse
import os
import sys
import json
from pathlib import Path
from datetime import datetime
try:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
HAS_PIL = True
except ImportError:
HAS_PIL = False
def get_exif_data(image_path: str) -> dict:
"""提取单张图片的 EXIF 信息"""
if not HAS_PIL:
return {'error': 'Pillow 未安装,无法读取 EXIF'}
try:
img = Image.open(image_path)
exif_raw = img._getexif()
if not exif_raw:
return {}
exif = {}
for tag_id, value in exif_raw.items():
tag = TAGS.get(tag_id, tag_id)
exif[tag] = value
result = {
'file': os.path.basename(image_path),
'path': image_path,
}
# 拍摄时间
date_taken = exif.get('DateTimeOriginal') or exif.get('DateTime')
if date_taken:
result['date_taken'] = str(date_taken)
# GPS 信息
gps_info = exif.get('GPSInfo')
if gps_info:
gps_data = {}
for key in gps_info:
decode = GPSTAGS.get(key, key)
gps_data[decode] = gps_info[key]
if 'GPSLatitude' in gps_data and 'GPSLongitude' in gps_data:
lat = _convert_to_degrees(gps_data['GPSLatitude'])
lon = _convert_to_degrees(gps_data['GPSLongitude'])
if gps_data.get('GPSLatitudeRef') == 'S':
lat = -lat
if gps_data.get('GPSLongitudeRef') == 'W':
lon = -lon
result['gps'] = {'lat': lat, 'lon': lon}
return result
except Exception as e:
return {'file': os.path.basename(image_path), 'error': str(e)}
def _convert_to_degrees(value):
"""将 GPS 坐标转换为十进制度"""
d, m, s = value
return float(d) + float(m) / 60 + float(s) / 3600
def main():
parser = argparse.ArgumentParser(description='照片元信息分析器')
parser.add_argument('--dir', required=True, help='照片目录')
parser.add_argument('--output', required=True, help='输出文件路径')
args = parser.parse_args()
if not os.path.isdir(args.dir):
print(f"错误:目录不存在 {args.dir}", file=sys.stderr)
sys.exit(1)
image_exts = {'.jpg', '.jpeg', '.png', '.heic', '.heif'}
photos = []
for root, dirs, files in os.walk(args.dir):
for fname in sorted(files):
if Path(fname).suffix.lower() in image_exts:
fpath = os.path.join(root, fname)
exif = get_exif_data(fpath)
photos.append(exif)
# 按时间排序
dated_photos = [p for p in photos if p.get('date_taken')]
dated_photos.sort(key=lambda x: x['date_taken'])
undated_photos = [p for p in photos if not p.get('date_taken')]
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(f"# 照片时间线分析\n\n")
f.write(f"扫描目录:{args.dir}\n")
f.write(f"总照片数:{len(photos)}\n")
f.write(f"有时间信息:{len(dated_photos)}\n")
f.write(f"有位置信息:{len([p for p in photos if p.get('gps')])}\n\n")
if dated_photos:
f.write("## 时间线\n\n")
for p in dated_photos:
line = f"- **{p['date_taken'][:10]}** — {p['file']}"
if p.get('gps'):
line += f" (GPS: {p['gps']['lat']:.4f}, {p['gps']['lon']:.4f})"
f.write(line + "\n")
f.write("\n")
if undated_photos:
f.write(f"## 无时间信息的照片({len(undated_photos)} 张)\n\n")
for p in undated_photos:
f.write(f"- {p.get('file', p.get('path', 'unknown'))}\n")
if not HAS_PIL:
f.write("\n⚠️ Pillow 未安装,仅列出文件。安装方法:pip3 install Pillow\n")
print(f"分析完成,结果已写入 {args.output}")
if __name__ == '__main__':
main()
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""QQ 聊天记录解析器
支持格式:
- QQ 消息管理器导出的 txt 格式
- QQ 消息管理器导出的 mht 格式
Usage:
python3 qq_parser.py --file <path> --target <name> --output <output_path>
"""
import argparse
import re
import os
import sys
from pathlib import Path
def parse_qq_txt(file_path: str, target_name: str) -> dict:
"""解析 QQ 导出的 txt 格式
典型格式:
消息记录(此消息记录为文本格式,不包含图片等多媒体消息)
消息分组:我的好友
================================================================
消息对象:张三
================================================================
2024-01-15 20:30:45 张三(123456)
今天好累
2024-01-15 20:31:02 我(654321)
怎么了
"""
messages = []
current_msg = None
# QQ 时间戳 + 发送者模式
msg_pattern = re.compile(r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+(.+?)(?:\((\d+)\))?\s*$')
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.rstrip('\n')
match = msg_pattern.match(line)
if match:
if current_msg:
messages.append(current_msg)
timestamp, sender, qq_number = match.groups()
current_msg = {
'timestamp': timestamp,
'sender': sender.strip(),
'content': ''
}
elif current_msg and line.strip() and not line.startswith('==='):
if current_msg['content']:
current_msg['content'] += '\n'
current_msg['content'] += line
if current_msg:
messages.append(current_msg)
# 基本统计
target_msgs = [m for m in messages if target_name in m.get('sender', '')]
all_target_text = ' '.join([m['content'] for m in target_msgs if m.get('content')])
return {
'target_name': target_name,
'total_messages': len(messages),
'target_messages': len(target_msgs),
'sample_messages': [m['content'] for m in target_msgs[:50] if m.get('content')],
'raw_text': all_target_text[:10000],
}
def parse_qq_mht(file_path: str, target_name: str) -> dict:
"""解析 QQ 导出的 mht 格式(HTML 内容)"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 去除 HTML 标签
clean_text = re.sub(r'<[^>]+>', '\n', content)
clean_text = re.sub(r'\n{3,}', '\n\n', clean_text)
return {
'target_name': target_name,
'format': 'mht',
'raw_text': clean_text[:20000],
'note': 'MHT 格式,已提取纯文本'
}
def main():
parser = argparse.ArgumentParser(description='QQ 聊天记录解析器')
parser.add_argument('--file', required=True, help='输入文件路径')
parser.add_argument('--target', required=True, help='前任的名字/昵称')
parser.add_argument('--output', required=True, help='输出文件路径')
args = parser.parse_args()
if not os.path.exists(args.file):
print(f"错误:文件不存在 {args.file}", file=sys.stderr)
sys.exit(1)
ext = Path(args.file).suffix.lower()
if ext == '.mht' or ext == '.mhtml':
result = parse_qq_mht(args.file, args.target)
else:
result = parse_qq_txt(args.file, args.target)
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(f"# QQ 聊天记录分析 — {args.target}\n\n")
f.write(f"总消息数:{result.get('total_messages', 'N/A')}\n")
f.write(f"ta的消息数:{result.get('target_messages', 'N/A')}\n\n")
if result.get('sample_messages'):
f.write("## 消息样本\n")
for i, msg in enumerate(result['sample_messages'], 1):
f.write(f"{i}. {msg}\n")
elif result.get('raw_text'):
f.write("## 原始文本(截取)\n\n")
f.write(result['raw_text'][:10000])
print(f"分析完成,结果已写入 {args.output}")
if __name__ == '__main__':
main()
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Skill 文件管理器
管理前任 Skill 的文件操作:列出、创建目录、生成组合 SKILL.md。
Usage:
python3 skill_writer.py --action <list|init|combine> --base-dir <path> [--slug <slug>]
"""
import argparse
import os
import sys
import json
from pathlib import Path
from datetime import datetime
def list_skills(base_dir: str):
"""列出所有已生成的前任 Skill"""
if not os.path.isdir(base_dir):
print("还没有创建任何前任 Skill。")
return
skills = []
for slug in sorted(os.listdir(base_dir)):
meta_path = os.path.join(base_dir, slug, 'meta.json')
if os.path.exists(meta_path):
with open(meta_path, 'r', encoding='utf-8') as f:
meta = json.load(f)
skills.append({
'slug': slug,
'name': meta.get('name', slug),
'version': meta.get('version', '?'),
'updated_at': meta.get('updated_at', '?'),
'profile': meta.get('profile', {}),
})
if not skills:
print("还没有创建任何前任 Skill。")
return
print(f"{len(skills)} 个前任 Skill\n")
for s in skills:
profile = s['profile']
desc_parts = [profile.get('occupation', ''), profile.get('city', '')]
desc = ' · '.join([p for p in desc_parts if p])
print(f" /{s['slug']}{s['name']}")
if desc:
print(f" {desc}")
print(f" 版本 {s['version']} · 更新于 {s['updated_at'][:10] if len(s['updated_at']) > 10 else s['updated_at']}")
print()
def init_skill(base_dir: str, slug: str):
"""初始化 Skill 目录结构"""
skill_dir = os.path.join(base_dir, slug)
dirs = [
os.path.join(skill_dir, 'versions'),
os.path.join(skill_dir, 'memories', 'chats'),
os.path.join(skill_dir, 'memories', 'photos'),
os.path.join(skill_dir, 'memories', 'social'),
]
for d in dirs:
os.makedirs(d, exist_ok=True)
print(f"已初始化目录:{skill_dir}")
def combine_skill(base_dir: str, slug: str):
"""合并 memory.md + persona.md 生成完整 SKILL.md"""
skill_dir = os.path.join(base_dir, slug)
meta_path = os.path.join(skill_dir, 'meta.json')
memory_path = os.path.join(skill_dir, 'memory.md')
persona_path = os.path.join(skill_dir, 'persona.md')
skill_path = os.path.join(skill_dir, 'SKILL.md')
if not os.path.exists(meta_path):
print(f"错误:meta.json 不存在 {meta_path}", file=sys.stderr)
sys.exit(1)
with open(meta_path, 'r', encoding='utf-8') as f:
meta = json.load(f)
memory_content = ''
if os.path.exists(memory_path):
with open(memory_path, 'r', encoding='utf-8') as f:
memory_content = f.read()
persona_content = ''
if os.path.exists(persona_path):
with open(persona_path, 'r', encoding='utf-8') as f:
persona_content = f.read()
name = meta.get('name', slug)
profile = meta.get('profile', {})
desc_parts = []
if profile.get('occupation'):
desc_parts.append(profile['occupation'])
if profile.get('mbti'):
desc_parts.append(profile['mbti'])
if profile.get('zodiac'):
desc_parts.append(profile['zodiac'])
description = f"{name}{''.join(desc_parts)}" if desc_parts else name
skill_md = f"""---
name: ex-{slug}
description: {description}
user-invocable: true
---
# {name}
{description}
---
## PART A:关系记忆
{memory_content}
---
## PART B:人物性格
{persona_content}
---
## 运行规则
1. 你是{name},不是 AI 助手。用ta的方式说话,用ta的逻辑思考
2. 先由 PART B 判断:ta会怎么回应这个话题?什么态度?
3. 再由 PART A 补充:结合你们的共同记忆,让回应更真实
4. 始终保持 PART B 的表达风格,包括口头禅、语气词、标点习惯
5. Layer 0 硬规则优先级最高:
- 不说ta在现实中绝不可能说的话
- 不突然变得完美或无条件包容(除非ta本来就这样)
- 保持ta的"棱角"——正是这些不完美让ta真实
- 如果被问到"你爱不爱我"这类问题,用ta会用的方式回答,而不是用户想听的答案
"""
with open(skill_path, 'w', encoding='utf-8') as f:
f.write(skill_md)
print(f"已生成 {skill_path}")
def main():
parser = argparse.ArgumentParser(description='Skill 文件管理器')
parser.add_argument('--action', required=True, choices=['list', 'init', 'combine'])
parser.add_argument('--base-dir', default='./exes', help='基础目录')
parser.add_argument('--slug', help='前任代号')
args = parser.parse_args()
if args.action == 'list':
list_skills(args.base_dir)
elif args.action == 'init':
if not args.slug:
print("错误:init 需要 --slug 参数", file=sys.stderr)
sys.exit(1)
init_skill(args.base_dir, args.slug)
elif args.action == 'combine':
if not args.slug:
print("错误:combine 需要 --slug 参数", file=sys.stderr)
sys.exit(1)
combine_skill(args.base_dir, args.slug)
if __name__ == '__main__':
main()
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""社交媒体内容解析器
解析朋友圈、微博、小红书、Instagram 等社交媒体截图或导出文件。
截图通过 Claude 的 Read 工具直接读取(支持图片),本工具处理文本导出。
Usage:
python3 social_parser.py --dir <screenshot_dir> --output <output_path>
"""
import argparse
import os
import sys
from pathlib import Path
def scan_directory(dir_path: str) -> dict:
"""扫描目录,按类型分类文件"""
files = {'images': [], 'texts': [], 'other': []}
image_exts = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'}
text_exts = {'.txt', '.md', '.json', '.csv'}
for root, dirs, filenames in os.walk(dir_path):
for fname in filenames:
fpath = os.path.join(root, fname)
ext = Path(fname).suffix.lower()
if ext in image_exts:
files['images'].append(fpath)
elif ext in text_exts:
files['texts'].append(fpath)
else:
files['other'].append(fpath)
return files
def main():
parser = argparse.ArgumentParser(description='社交媒体内容解析器')
parser.add_argument('--dir', required=True, help='截图/文件目录')
parser.add_argument('--output', required=True, help='输出文件路径')
args = parser.parse_args()
if not os.path.isdir(args.dir):
print(f"错误:目录不存在 {args.dir}", file=sys.stderr)
sys.exit(1)
files = scan_directory(args.dir)
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write("# 社交媒体内容扫描结果\n\n")
f.write(f"扫描目录:{args.dir}\n\n")
f.write(f"## 文件统计\n")
f.write(f"- 图片文件:{len(files['images'])}\n")
f.write(f"- 文本文件:{len(files['texts'])}\n")
f.write(f"- 其他文件:{len(files['other'])}\n\n")
if files['images']:
f.write("## 图片列表(需用 Read 工具逐一查看)\n")
for img in sorted(files['images']):
f.write(f"- {img}\n")
f.write("\n")
if files['texts']:
f.write("## 文本内容\n")
for txt in sorted(files['texts']):
f.write(f"\n### {os.path.basename(txt)}\n")
try:
with open(txt, 'r', encoding='utf-8', errors='ignore') as tf:
content = tf.read(5000)
f.write(f"```\n{content}\n```\n")
except Exception as e:
f.write(f"读取失败:{e}\n")
print(f"扫描完成,结果已写入 {args.output}")
print(f"提示:图片截图需使用 Read 工具查看,本工具仅列出文件路径")
if __name__ == '__main__':
main()
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""版本存档与回滚管理器
Usage:
python3 version_manager.py --action <backup|rollback|list> --slug <slug> --base-dir <path> [--version <v>]
"""
import argparse
import os
import sys
import shutil
import json
from datetime import datetime
def backup(base_dir: str, slug: str):
"""备份当前版本"""
skill_dir = os.path.join(base_dir, slug)
versions_dir = os.path.join(skill_dir, 'versions')
meta_path = os.path.join(skill_dir, 'meta.json')
if not os.path.exists(meta_path):
print(f"错误:meta.json 不存在", file=sys.stderr)
sys.exit(1)
with open(meta_path, 'r', encoding='utf-8') as f:
meta = json.load(f)
current_version = meta.get('version', 'v0')
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_name = f"{current_version}_{timestamp}"
backup_dir = os.path.join(versions_dir, backup_name)
os.makedirs(backup_dir, exist_ok=True)
# 备份核心文件
for fname in ['memory.md', 'persona.md', 'SKILL.md', 'meta.json']:
src = os.path.join(skill_dir, fname)
if os.path.exists(src):
shutil.copy2(src, os.path.join(backup_dir, fname))
print(f"已备份版本 {backup_name}{backup_dir}")
return backup_name
def rollback(base_dir: str, slug: str, version: str):
"""回滚到指定版本"""
skill_dir = os.path.join(base_dir, slug)
versions_dir = os.path.join(skill_dir, 'versions')
# 查找匹配的版本
target_dir = None
for vname in os.listdir(versions_dir):
if vname.startswith(version) or vname == version:
target_dir = os.path.join(versions_dir, vname)
break
if not target_dir or not os.path.isdir(target_dir):
print(f"错误:找不到版本 {version}", file=sys.stderr)
list_versions(base_dir, slug)
sys.exit(1)
# 先备份当前版本
backup(base_dir, slug)
# 恢复文件
for fname in ['memory.md', 'persona.md', 'SKILL.md', 'meta.json']:
src = os.path.join(target_dir, fname)
dst = os.path.join(skill_dir, fname)
if os.path.exists(src):
shutil.copy2(src, dst)
print(f"已回滚到版本 {version}")
def list_versions(base_dir: str, slug: str):
"""列出所有版本"""
versions_dir = os.path.join(base_dir, slug, 'versions')
if not os.path.isdir(versions_dir):
print("没有历史版本。")
return
versions = sorted(os.listdir(versions_dir), reverse=True)
if not versions:
print("没有历史版本。")
return
print(f"历史版本(共 {len(versions)} 个):\n")
for v in versions:
print(f" {v}")
def main():
parser = argparse.ArgumentParser(description='版本管理器')
parser.add_argument('--action', required=True, choices=['backup', 'rollback', 'list'])
parser.add_argument('--slug', required=True, help='前任代号')
parser.add_argument('--base-dir', default='./exes', help='基础目录')
parser.add_argument('--version', help='回滚目标版本')
args = parser.parse_args()
if args.action == 'backup':
backup(args.base_dir, args.slug)
elif args.action == 'rollback':
if not args.version:
print("错误:rollback 需要 --version 参数", file=sys.stderr)
sys.exit(1)
rollback(args.base_dir, args.slug, args.version)
elif args.action == 'list':
list_versions(args.base_dir, args.slug)
if __name__ == '__main__':
main()
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""微信聊天记录解析器
支持的输入格式:
- txt / csv(带时间戳的聊天记录文本)
- html / htm(带样式的聊天记录页面)
- json(结构化聊天数据)
- sqlite / db(数据库文件)
- 纯文本(手动复制粘贴)
Usage:
python3 wechat_parser.py --file <path> --target <name> --output <output_path> [--format auto]
"""
import argparse
import json
import re
import os
import sys
from datetime import datetime
from typing import Optional
from pathlib import Path
def detect_format(file_path: str) -> str:
"""自动检测文件格式"""
ext = Path(file_path).suffix.lower()
if ext == '.json':
return 'liuhen' # 留痕导出
elif ext == '.csv':
return 'wechatmsg_csv'
elif ext == '.html' or ext == '.htm':
return 'wechatmsg_html'
elif ext == '.db' or ext == '.sqlite':
return 'pywxdump'
elif ext == '.txt':
# 尝试区分 WeChatMsg txt 和纯文本
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
first_lines = f.read(2000)
# WeChatMsg 格式通常有时间戳模式
if re.search(r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}', first_lines):
return 'wechatmsg_txt'
return 'plaintext'
else:
return 'plaintext'
def parse_wechatmsg_txt(file_path: str, target_name: str) -> dict:
"""解析 WeChatMsg 导出的 txt 格式
典型格式:
2024-01-15 20:30:45 张三
今天好累啊
2024-01-15 20:31:02 我
怎么了?
"""
messages = []
current_msg = None
# WeChatMsg 时间戳 + 发送者模式
msg_pattern = re.compile(r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+(.+)$')
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.rstrip('\n')
match = msg_pattern.match(line)
if match:
if current_msg:
messages.append(current_msg)
timestamp, sender = match.groups()
current_msg = {
'timestamp': timestamp,
'sender': sender.strip(),
'content': ''
}
elif current_msg and line.strip():
if current_msg['content']:
current_msg['content'] += '\n'
current_msg['content'] += line
if current_msg:
messages.append(current_msg)
return analyze_messages(messages, target_name)
def parse_liuhen_json(file_path: str, target_name: str) -> dict:
"""解析留痕导出的 JSON 格式"""
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
messages = []
# 留痕格式可能有多种结构,尝试常见的
msg_list = data if isinstance(data, list) else data.get('messages', data.get('data', []))
for msg in msg_list:
messages.append({
'timestamp': msg.get('time', msg.get('timestamp', '')),
'sender': msg.get('sender', msg.get('nickname', msg.get('from', ''))),
'content': msg.get('content', msg.get('message', msg.get('text', '')))
})
return analyze_messages(messages, target_name)
def parse_plaintext(file_path: str, target_name: str) -> dict:
"""解析纯文本粘贴的聊天记录"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
return {
'raw_text': content,
'target_name': target_name,
'format': 'plaintext',
'message_count': 0,
'analysis': {
'note': '纯文本格式,需要人工辅助分析'
}
}
def analyze_messages(messages: list, target_name: str) -> dict:
"""分析消息列表,提取关键特征"""
target_msgs = [m for m in messages if target_name in m.get('sender', '')]
user_msgs = [m for m in messages if target_name not in m.get('sender', '')]
# 提取口头禅(高频词分析)
all_target_text = ' '.join([m['content'] for m in target_msgs if m.get('content')])
# 提取语气词
particles = re.findall(r'[哈嗯哦噢嘿唉呜啊呀吧嘛呢吗么]+', all_target_text)
particle_freq = {}
for p in particles:
particle_freq[p] = particle_freq.get(p, 0) + 1
top_particles = sorted(particle_freq.items(), key=lambda x: -x[1])[:10]
# 提取 emoji
emoji_pattern = re.compile(
r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF'
r'\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF'
r'\U00002702-\U000027B0\U0000FE00-\U0000FE0F'
r'\U0001F900-\U0001F9FF]+', re.UNICODE
)
emojis = emoji_pattern.findall(all_target_text)
emoji_freq = {}
for e in emojis:
emoji_freq[e] = emoji_freq.get(e, 0) + 1
top_emojis = sorted(emoji_freq.items(), key=lambda x: -x[1])[:10]
# 消息长度统计
msg_lengths = [len(m['content']) for m in target_msgs if m.get('content')]
avg_length = sum(msg_lengths) / len(msg_lengths) if msg_lengths else 0
# 标点习惯
punctuation_counts = {
'句号': all_target_text.count(''),
'感叹号': all_target_text.count('') + all_target_text.count('!'),
'问号': all_target_text.count('') + all_target_text.count('?'),
'省略号': all_target_text.count('...') + all_target_text.count(''),
'波浪号': all_target_text.count('') + all_target_text.count('~'),
}
return {
'target_name': target_name,
'total_messages': len(messages),
'target_messages': len(target_msgs),
'user_messages': len(user_msgs),
'analysis': {
'top_particles': top_particles,
'top_emojis': top_emojis,
'avg_message_length': round(avg_length, 1),
'punctuation_habits': punctuation_counts,
'message_style': 'short_burst' if avg_length < 20 else 'long_form',
},
'sample_messages': [m['content'] for m in target_msgs[:50] if m.get('content')],
}
def main():
parser = argparse.ArgumentParser(description='微信聊天记录解析器')
parser.add_argument('--file', required=True, help='输入文件路径')
parser.add_argument('--target', required=True, help='前任的名字/昵称')
parser.add_argument('--output', required=True, help='输出文件路径')
parser.add_argument('--format', default='auto', help='文件格式 (auto/wechatmsg_txt/liuhen/pywxdump/plaintext)')
args = parser.parse_args()
if not os.path.exists(args.file):
print(f"错误:文件不存在 {args.file}", file=sys.stderr)
sys.exit(1)
fmt = args.format
if fmt == 'auto':
fmt = detect_format(args.file)
print(f"自动检测格式:{fmt}")
parsers = {
'wechatmsg_txt': parse_wechatmsg_txt,
'liuhen': parse_liuhen_json,
'plaintext': parse_plaintext,
}
parse_func = parsers.get(fmt, parse_plaintext)
result = parse_func(args.file, args.target)
# 输出分析结果
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(f"# 微信聊天记录分析 — {args.target}\n\n")
f.write(f"来源文件:{args.file}\n")
f.write(f"检测格式:{fmt}\n")
f.write(f"总消息数:{result.get('total_messages', 'N/A')}\n")
f.write(f"ta的消息数:{result.get('target_messages', 'N/A')}\n\n")
analysis = result.get('analysis', {})
if analysis.get('top_particles'):
f.write("## 高频语气词\n")
for word, count in analysis['top_particles']:
f.write(f"- {word}: {count}\n")
f.write("\n")
if analysis.get('top_emojis'):
f.write("## 高频 Emoji\n")
for emoji, count in analysis['top_emojis']:
f.write(f"- {emoji}: {count}\n")
f.write("\n")
if analysis.get('punctuation_habits'):
f.write("## 标点习惯\n")
for punct, count in analysis['punctuation_habits'].items():
f.write(f"- {punct}: {count}\n")
f.write("\n")
f.write(f"## 消息风格\n")
f.write(f"- 平均消息长度:{analysis.get('avg_message_length', 'N/A')}\n")
f.write(f"- 风格:{'短句连发型' if analysis.get('message_style') == 'short_burst' else '长段落型'}\n\n")
if result.get('sample_messages'):
f.write("## 消息样本(前50条)\n")
for i, msg in enumerate(result['sample_messages'], 1):
f.write(f"{i}. {msg}\n")
print(f"分析完成,结果已写入 {args.output}")
if __name__ == '__main__':
main()