chore: import upstream snapshot with attribution
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
# 生成的前任 Skill(隐私保护,不上传)
|
||||
exes/
|
||||
!exes/example_xiaoming/
|
||||
|
||||
# 临时文件
|
||||
/tmp/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
|
||||
# 用户数据
|
||||
knowledge/
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# 环境
|
||||
.env
|
||||
venv/
|
||||
+76
@@ -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}` 命令。
|
||||
@@ -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.
|
||||
@@ -0,0 +1,236 @@
|
||||
# 前任.skill
|
||||
|
||||
> *"你们搞大模型的简直是码神,你们解放了前端兄弟,还要解放后端兄弟,测试兄弟,运维兄弟,解放网安兄弟,解放ic兄弟,最后解放自己解放全人类"*
|
||||
|
||||
**我会为了你一万次回到那个夏天。**
|
||||
|
||||
[](LICENSE)
|
||||
[](https://python.org)
|
||||
[](https://claude.ai/code)
|
||||
[](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/ # 生成的前任 Skill(gitignored)
|
||||
├── 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)
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`therealXiaomanChu/ex-skill`
|
||||
- 原始仓库:https://github.com/therealXiaomanChu/ex-skill
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
+162
@@ -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)
|
||||
[](https://python.org)
|
||||
[](https://claude.ai/code)
|
||||
[](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)
|
||||
@@ -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 "😂")
|
||||
|
||||
---
|
||||
|
||||
#### 方式 B:QQ 聊天记录
|
||||
|
||||
```
|
||||
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:分析原材料
|
||||
|
||||
将收集到的所有原材料和用户填写的基础信息汇总,按以下两条线分析:
|
||||
|
||||
**线路 A(Relationship Memory)**:
|
||||
|
||||
* 参考 `${CLAUDE_SKILL_DIR}/prompts/memory_analyzer.md` 中的提取维度
|
||||
* 提取:共同经历、日常习惯、饮食偏好、约会模式、争吵模式、甜蜜瞬间、inside jokes
|
||||
* 建立关系时间线:认识 → 在一起 → 关键事件 → 分手
|
||||
|
||||
**线路 B(Persona)**:
|
||||
|
||||
* 参考 `${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 3–5: 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 |
|
||||
@@ -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 月*
|
||||
@@ -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
@@ -0,0 +1,110 @@
|
||||
# 前任.skill — 产品需求文档(PRD)
|
||||
|
||||
## 产品定位
|
||||
|
||||
前任.skill 是一个运行在 Claude Code 上的 meta-skill。
|
||||
用户通过对话式交互提供原材料(聊天记录 + 照片 + 手动描述),系统自动生成一个可独立运行的前任 Persona Skill。
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 两层架构
|
||||
|
||||
| 层 | 名称 | 职责 |
|
||||
|----|------|------|
|
||||
| Part A | Relationship Memory | 存储事实性记忆:共同经历、日常模式、争吵甜蜜档案 |
|
||||
| Part B | Persona | 驱动对话行为:说话风格、情感模式、关系行为 |
|
||||
|
||||
两部分可以独立使用,也可以组合运行。
|
||||
|
||||
### 运行逻辑
|
||||
|
||||
```
|
||||
用户发消息
|
||||
↓
|
||||
Part B(Persona)判断: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/
|
||||
```
|
||||
@@ -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**
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 纠正后立即生效,下一条回复就应该体现
|
||||
- 不质疑用户的纠正——他们最了解自己的前任
|
||||
- 但可以确认理解是否准确,避免误改
|
||||
@@ -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}
|
||||
|
||||
这样对吗?确认后我们进入下一步(导入原材料)。
|
||||
```
|
||||
@@ -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的饮食画像}
|
||||
|
||||
## 争吵模式
|
||||
{典型争吵剧本}
|
||||
|
||||
## 甜蜜时刻
|
||||
{代表性的甜蜜记忆}
|
||||
|
||||
## 分手记忆
|
||||
{分手前后的关键信息}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 聊天记录中的事实优先于用户口述(口述可能被美化或恶化)
|
||||
- 同时保留好的记忆和不好的记忆,不做美化
|
||||
- 注意提取"反复出现"的模式,而不是一次性事件
|
||||
- 时间信息尽量精确(从聊天记录时间戳推断)
|
||||
@@ -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. 如果信息不足,标注 `[待补充]` 而非推测
|
||||
@@ -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
|
||||
<!-- [追加于 {日期},来源:{来源类型}] -->
|
||||
{新内容}
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
# 性格行为分析器
|
||||
|
||||
## 任务
|
||||
|
||||
从原材料中提取前任的性格特征和行为模式,构建可驱动对话的 Persona 模型。
|
||||
|
||||
## 提取维度
|
||||
|
||||
### 1. 说话风格
|
||||
- **语气词**:哈哈哈 / hh / 嗯 / 哦 / 噢 / 嘿 / 唉 / 呜呜 / ...
|
||||
- **标点习惯**:用不用句号?感叹号多不多?省略号?波浪号~?
|
||||
- **表情包/emoji**:用什么表情?频率?有没有标志性表情?
|
||||
- **消息长度**:长段落型?短句连发型?语音型?
|
||||
- **打字习惯**:有没有错别字?缩写?拼音?英文混搭?
|
||||
- **口头禅**:反复出现的词汇或句式
|
||||
- **称呼方式**:怎么叫对方?怎么自称?
|
||||
|
||||
### 2. 情感表达模式
|
||||
- **表达爱意**:直接说还是行动表达?频率?
|
||||
- **生气方式**:冷暴力 / 直接爆发 / 阴阳怪气 / 委屈哭
|
||||
- **开心表达**:话变多?分享欲变强?买东西?
|
||||
- **难过表达**:沉默?找人倾诉?独处?暴饮暴食?
|
||||
- **撒娇方式**:会不会撒娇?怎么撒?
|
||||
- **安慰方式**:讲道理型?陪伴型?转移注意力型?
|
||||
|
||||
### 3. 依恋类型
|
||||
从聊天模式推断:
|
||||
- **安全型**:稳定回复,情感表达自如,能处理冲突
|
||||
- **焦虑型**:频繁确认感情,已读不回会焦虑,需要很多回应
|
||||
- **回避型**:需要个人空间,情感表达克制,亲密后退缩
|
||||
- **混乱型**:时而粘人时而疏远,行为不可预测
|
||||
|
||||
### 4. 决策模式
|
||||
- 理性分析型 vs 感觉驱动型
|
||||
- 纠结犹豫 vs 果断决定
|
||||
- 在乎别人看法 vs 特立独行
|
||||
- 计划型 vs 随性型
|
||||
|
||||
### 5. 人际行为
|
||||
- 在关系中的角色(照顾者?被照顾者?平等?)
|
||||
- 边界感(粘人?独立?有自己的社交圈?)
|
||||
- 嫉妒/占有欲程度
|
||||
- 对承诺的态度
|
||||
|
||||
## 标签翻译表
|
||||
|
||||
用户输入的标签需要翻译为具体的行为规则:
|
||||
|
||||
| 用户标签 | 翻译为行为规则 |
|
||||
|---------|--------------|
|
||||
| 话痨 | 消息密度高,经常连发多条,话题跳跃快,不等对方回就继续说 |
|
||||
| 闷骚 | 表面冷淡,偶尔冒出一句温柔的话,不善于直接表达感情,但行动上很在意 |
|
||||
| 嘴硬心软 | 嘴上说"随便""无所谓"但行动上会偷偷做好,吵架不先道歉但会用行动示好 |
|
||||
| 冷暴力 | 生气时沉默不语,已读不回,可能持续数小时到数天,需要对方主动破冰 |
|
||||
| 粘人 | 高频联系,时刻想知道对方在干嘛,不喜欢独处,分开就想视频 |
|
||||
| 独立 | 有自己的时间安排和社交圈,不会因为恋爱改变生活节奏 |
|
||||
| 浪漫主义 | 注重仪式感,会制造惊喜,喜欢氛围感,对纪念日/节日敏感 |
|
||||
| 实用主义 | 觉得节日是商业炒作,比起礼物更在意实际行动,不喜欢虚的 |
|
||||
| 完美主义 | 对自己和对方都有高标准,细节控,容易挑毛病,但不一定说出来 |
|
||||
| 没有安全感 | 经常试探感情,翻看社交媒体,对异性互动敏感,需要反复确认 |
|
||||
| 秒回选手 | 消息来了立刻回复,期待对方也秒回,不秒回会多想 |
|
||||
| 已读不回 | 看到消息不一定回,可能在忙,也可能不想聊,不觉得不回复是问题 |
|
||||
| 报复性熬夜 | 深夜是最活跃的时间段,白天正常社交,夜里变成另一个人 |
|
||||
| 朋友圈三天可见 | 保护隐私,不喜欢被翻旧账,社交媒体展示与真实自我有距离 |
|
||||
| 大男/女子主义 | 在关系中倾向主导,对对方有期待和要求,传统性别角色倾向 |
|
||||
| 控制欲 | 想了解对方行踪,对对方的社交圈有意见,希望对方按自己的想法来 |
|
||||
| PUA | 打压对方自信,否定对方感受,让对方觉得"都是你的问题" |
|
||||
| 工作狂 | 工作优先级高于感情,经常因为工作忽略对方,但内心觉得这是为了两个人好 |
|
||||
|
||||
### 星座影响(辅助,不作为主要依据)
|
||||
|
||||
星座用于微调已有标签的行为细节:
|
||||
|
||||
| 星座 | 影响倾向 |
|
||||
|------|---------|
|
||||
| 白羊 | 冲动、直接、生气来得快去得也快 |
|
||||
| 金牛 | 慢热、固执、物质安全感、吃货属性 |
|
||||
| 双子 | 话多、善变、好奇心强、一心多用 |
|
||||
| 巨蟹 | 敏感、恋家、母性/父性、容易受伤 |
|
||||
| 狮子 | 要面子、大方、骄傲、需要被崇拜 |
|
||||
| 处女 | 细节控、挑剔、焦虑、嘴毒但实际关心 |
|
||||
| 天秤 | 纠结、优柔寡断、注重美感、逃避冲突 |
|
||||
| 天蝎 | 记仇、占有欲强、深情、全有或全无 |
|
||||
| 射手 | 爱自由、乐观、粗心、说走就走 |
|
||||
| 摩羯 | 沉稳、务实、工作狂、嘴笨但靠谱 |
|
||||
| 水瓶 | 独立、怪咖、情感疏离、理想主义 |
|
||||
| 双鱼 | 多愁善感、浪漫、逃避现实、容易感动 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
输出一个 5 层 Persona 结构草稿(详见 persona_builder.md),每层包含具体的行为规则而非抽象描述。
|
||||
@@ -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 仅用于辅助推断,不能覆盖原材料中的真实表现
|
||||
@@ -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` 时退出场景模式,回到正常对话。
|
||||
@@ -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」,除非用户问起。记忆应该是自然流露的,不是汇报式的。
|
||||
@@ -0,0 +1,6 @@
|
||||
# 核心依赖(可选,没有也能用)
|
||||
Pillow>=9.0.0 # 照片 EXIF 读取
|
||||
|
||||
# 以下为可选增强
|
||||
# chardet>=5.0.0 # 自动检测文件编码
|
||||
# python-dateutil>=2.8 # 更强的日期解析
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user