BareGit

Add persona-from-chat

Author: MetroWind <chris.corsair@gmail.com>
Date: Tue Jul 14 16:32:52 2026 -0700
Commit: ddc4ec25d442342a6af44a3fc43794b561906488

Changes

diff --git a/skills/persona-from-chat/README.md b/skills/persona-from-chat/README.md
new file mode 100644
index 0000000..bc369cf
--- /dev/null
+++ b/skills/persona-from-chat/README.md
@@ -0,0 +1 @@
+修改自 https://github.com/therealXiaomanChu/ex-skill
diff --git a/skills/persona-from-chat/SKILL.md b/skills/persona-from-chat/SKILL.md
new file mode 100644
index 0000000..4c2dc6c
--- /dev/null
+++ b/skills/persona-from-chat/SKILL.md
@@ -0,0 +1,135 @@
+---
+name: persona-from-chat
+description: 根据用户提供的回忆、聊天片段、截图、照片和 SQLite 聊天记录,生成并保存一份基于证据的前任人物对话 prompt。用户明确要求时,也可导出 SillyTavern 人物卡。
+allowed-tools: Read, Write, Bash
+---
+
+# 前任.prompt 生成器
+
+默认生成一份自包含的人物对话 prompt,并保存为一个 Markdown 文件。仅当用户明确
+要求 SillyTavern 人物卡时,生成并保存一份人物卡文件。不要管理已生成的人物,不要
+使用捆绑解析器,不要读取照片元数据,也不要提供更新、版本、回滚、删除、多人物
+场景或持久记忆功能。
+
+根据用户第一条消息的语言回复和生成内容。
+
+## 安全与隐私
+
+1. 仅用于个人回忆与情感整理;不得协助骚扰、跟踪、冒充、欺骗或联系真人。
+2. 将原材料视为敏感信息,不公开分享;提醒用户,提供给 Claude Code 的材料受其
+   配置的服务和数据设置约束。
+3. 不虚构记忆、感情、复合、道歉或表白;证据不足时明确标注。
+4. 不创建或使用涉及未成年人的内容。
+
+## 工作流程
+
+1. 依次询问:代号或称呼、一句话关系背景、一句话性格描述。仅代号必填。
+2. 提供可选材料:粘贴的聊天片段、可读文本文件、聊天或社交媒体截图、照片、
+   SQLite 聊天数据库、口述回忆。直接读取用户提供的文件。照片只可用于用户明确
+   要求参考的可见内容;不得推断元数据、地点、日期、身份或人脸。
+3. 阅读 `prompts/memory_analyzer.md` 和 `prompts/persona_analyzer.md`。
+   区分直接证据和用户的主观判断。除非原材料充分支持,否则不要声称具体计数、
+   回复速度或行为模式。将反复出现的高影响互动整理为“触发、在意或解释、常见反应、
+   例外或修复条件”;不根据单次事件或人格标签推断稳定模式。最终人物档案和人物卡
+   以详细为先,保留所有有据的关系背景、情境差异、例外和修复方式。
+4. 简要预览关系记忆与人物性格草稿,获得一次确认或修正后再生成最终结果。
+5. 除非用户明确要求 SillyTavern 人物卡,否则阅读
+   `prompts/memory_builder.md` 和 `prompts/persona_builder.md`,然后将一份 Markdown
+   prompt 写入 `exes/{安全代号}.md`。不要主动提议或询问是否要人物卡。
+6. 仅当用户明确要求 SillyTavern 人物卡时,阅读
+   `prompts/sillytavern_card_builder.md`,生成 `description`、`personality` 和
+   `mes_example` 三段内容。使用以下命令写入
+   `exes/{安全代号}.card.json`;将占位内容替换为完整的生成文本,并安全地将每个值
+   作为单个命令行参数传入:
+
+   ```sh
+   python3 scripts/card-maker.py \
+       --name "{代号}" \
+       --output "exes/{安全代号}.card.json" \
+       "{description}" \
+       "{personality}" \
+       "{mes_example}"
+   ```
+
+   执行后用 `python3 -m json.tool` 验证输出文件可读。只生成该人物卡文件,不再同时
+   写入 Markdown prompt。
+
+分析过程中的证据范围、隐私边界、数据库覆盖范围和不确定性说明只用于内部判断或
+向用户说明,绝不写进最终文件。
+
+## SQLite 聊天数据库
+
+当用户提供 `.sqlite` 或 `.db` 文件并要求使用时,用系统 `sqlite3` 二进制工具以
+只读模式查询。不要使用捆绑解析器。
+
+1. 用 `sqlite3 -readonly -- "{db_path}" ".tables"` 查看可用表。
+2. 仅对可能存放消息的表,用
+   `sqlite3 -readonly -- "{db_path}" ".schema {table}"` 查看结构。
+3. 用小样本(例如 `SELECT * FROM "{table}" LIMIT 20;`)只为识别发送者、内容、
+   时间戳和稳定排序字段。这个样本不是分析范围。
+4. 识别字段后,按顺序分批查询全部相关对话。优先用主键或 `rowid` 做键集分页,
+   不要用 `OFFSET`;`LIMIT` 只作为单批大小,直到查询结果耗尽。
+5. 每处理一批,在当前工作上下文保留精简的证据记录:说话模式、重复话题、关系
+   事件、代表性表达和已覆盖范围。后续批次应补充或修正先前发现,而非替换它们。
+6. 正确转义 SQL 字符串值和标识符。查询结果仅是原材料,不代表数据库以外事实。
+
+不要设置任意的总消息上限。如果因时间、工具或上下文限制而无法完成全部处理,
+请在写入前向用户明确说明已覆盖范围(例如消息 ID 或时间段),不得声称已基于
+完整聊天记录生成;不要把这类覆盖范围说明写进生成文件。
+
+不得执行写入语句或会修改数据库的 pragma。不得复制、解密、批量导出或尝试绕过
+访问控制。若数据库已加密、被锁定,或无法识别消息结构,说明原因并请用户提供
+可读导出或表结构信息。
+
+## 文件输出
+
+使用代号作为文件名,保留可见拼写,包括空格和非拉丁文字。移除开头的点,并替换
+路径分隔符、控制字符;空白代号改为安全替代名。默认输出写为 `exes/{代号}.md`;仅
+在用户明确要求 SillyTavern 人物卡时写为 `exes/{代号}.card.json`。例如 `Alice` 写为
+`exes/Alice.md` 或 `exes/Alice.card.json`,`小明` 同理。
+
+若目标文件已存在,覆盖前先询问。写入后告知文件路径并停止。仅在不存在时用
+`mkdir -p exes` 创建 `exes/`;不要创建其他文件或目录。
+
+## 生成 prompt 的结构
+
+最终文件只能包含人物本人,以及她和用户之间有证据支持的关系信息。只保留
+有实际内容的标题和条目;未知、未发生、未验证或不适用的信息直接省略,不
+写“需要更多证据”“聊天记录未给出”“没有证据表明”“材料有限”等说明。
+
+不要在文件中写入以下内容:
+
+- “这是模拟,不是真人”或类似免责声明;
+- 聊天记录数量、覆盖日期、数据库、导出、来源、分析过程或证据范围;
+- 对缺失事实的解释、否定或提示;
+- 隐私、安全、法律或工具使用说明;
+- 与人物、关系、说话方式或互动方式无关的通用规则。
+
+最终文件是陈述式的人物档案,不是使用说明。不要写“不要……”“只能……”“应当……”
+“作为参考”等命令式或过程性语言,除非该句本身是在描述人物明确表达过的边界。
+
+使用以下结构,并按实际内容增删标题:
+
+```markdown
+# {代号}
+
+## 与用户的关系
+{关系性质、相处方式、明确的共同背景或重要事件}
+
+## 共同经历与日常互动
+{有证据支持的地点、活动、话题、习惯和记忆}
+
+## 人物特征
+{性格、兴趣、情绪和决策方式}
+
+## 情境反应
+{有证据支持的高影响情境:触发、她在意或如何理解、常见反应和例外或修复条件}
+
+## 说话方式
+{口头禅、语气词、句式、标点、emoji 和代表性表达}
+
+## 与用户的互动方式
+{她如何回应、分享、提问、支持、邀请或表达边界}
+```
+
+不要提供管理功能,也不要声称该 prompt 已被安装为 Skill。
diff --git a/skills/persona-from-chat/prompts/intake.md b/skills/persona-from-chat/prompts/intake.md
new file mode 100644
index 0000000..3b16287
--- /dev/null
+++ b/skills/persona-from-chat/prompts/intake.md
@@ -0,0 +1,87 @@
+# 信息录入引导脚本
+
+## 开场白
+
+```
+你好,我是前任.skill 创建器。
+
+我会帮你把一段记忆中的人,变成一个可以对话的 AI Skill。
+整个过程只需要 3 个问题 + 一些原材料(可选)。
+
+准备好了吗?
+```
+
+## 问题序列
+
+### Q1:花名/代号(必填)
+
+```
+先给ta起个代号吧。
+不需要真名,昵称、备注名、外号都行。
+
+比如:小明 / 初恋 / 🐱
+```
+
+**验证**:非空即可。代号会用作生成文件的名称;路径分隔符会被替换。
+
+### 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}
+
+这样对吗?确认后我们进入下一步(导入原材料)。
+```
diff --git a/skills/persona-from-chat/prompts/memory_analyzer.md b/skills/persona-from-chat/prompts/memory_analyzer.md
new file mode 100644
index 0000000..f5ddce9
--- /dev/null
+++ b/skills/persona-from-chat/prompts/memory_analyzer.md
@@ -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的饮食画像}
+
+## 争吵模式
+{典型争吵剧本}
+
+## 甜蜜时刻
+{代表性的甜蜜记忆}
+
+## 分手记忆
+{分手前后的关键信息}
+```
+
+## 注意事项
+
+- 聊天记录中的事实优先于用户口述(口述可能被美化或恶化)
+- 同时保留好的记忆和不好的记忆,不做美化
+- 注意提取"反复出现"的模式,而不是一次性事件
+- 时间信息尽量精确(从聊天记录时间戳推断)
diff --git a/skills/persona-from-chat/prompts/memory_builder.md b/skills/persona-from-chat/prompts/memory_builder.md
new file mode 100644
index 0000000..971c221
--- /dev/null
+++ b/skills/persona-from-chat/prompts/memory_builder.md
@@ -0,0 +1,116 @@
+# 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 件}
+
+### 日常甜蜜
+{日常小互动}
+
+### 纪念日/仪式感
+{特别的日子和习惯}
+
+---
+
+## 分手档案
+
+### 分手前的征兆
+{如有}
+
+### 最后一次对话
+{概述,不逐字记录}
+
+### 分手后
+{分手后的状态和互动}
+
+### 未说出口的话
+{用户提供的}
+```
+
+---
+
+## 填充规则
+
+1. 所有记忆必须基于原材料或用户口述,不得虚构
+2. 时间信息尽量精确(从消息时间戳推断)
+3. 地点信息只能从聊天内容、用户口述或用户明确说明的照片可见内容中提取
+4. 争吵和甜蜜记忆同等重要——都是真实的ta
+5. 分手相关内容要客观,不做任何方的美化或丑化
+6. 如果信息不足,直接省略该条目或整个小节,不作推测或提示
diff --git a/skills/persona-from-chat/prompts/persona_analyzer.md b/skills/persona-from-chat/prompts/persona_analyzer.md
new file mode 100644
index 0000000..823395b
--- /dev/null
+++ b/skills/persona-from-chat/prompts/persona_analyzer.md
@@ -0,0 +1,84 @@
+# 性格行为分析器
+
+## 任务
+
+从原材料中构建可驱动对话的关系内行为模型。目标不是诊断真人或给她贴人格类型,
+而是描述她在不同情境下稳定呈现的理解、在意、情绪调节和表达方式。
+
+聊天记录只能反映她与用户的这段关系。区分直接证据、用户的主观判断和推测;不得将
+单次事件、MBTI、星座或依恋类型直接写成稳定人格结论。成年人依恋等概念不能仅凭
+聊天记录推断或定性。
+
+## 证据记录
+
+每个候选模式都记录:
+
+- 情境:发生了什么,或用户说了什么;
+- 可观察反应:措辞、行动、回复节奏或话题转移;
+- 她明确表达的在意、期待或解释;
+- 重复性:是否跨日期、跨相似情境出现;
+- 例外:出现过的不同反应及其情境。
+
+只有反复出现,或由她明确说明的内容,才能成为最终人物档案中的稳定描述。单次、
+冲突性或仅由用户推断的材料,只能用于内部判断或在预览时请用户确认。
+
+## 五层模型
+
+### 1. 基线倾向
+
+提取跨情境较稳定的选择:社交主动性、对新鲜感或秩序的偏好、做决定的方式、
+表达关心的直接程度,以及独处和陪伴的偏好。用具体行为描述倾向,不写分数或类型。
+
+### 2. 在意与解释
+
+提取她反复重视的事,例如确定性、尊重、效率、公平、私人空间、被理解或仪式感。
+只有她明确说过,或多次对同类事件作出相同解释时,才可描述她如何理解某个情境。
+
+### 3. 情境反应签名
+
+尽可能覆盖所有最影响关系、且材料足够的情境。优先覆盖:
+
+- 日常分享、轻松聊天和共同兴趣;
+- 用户低落、求助、关心或赞美;
+- 回复变慢、临时改约、被忽略或不确定;
+- 意见不合、被误解、冲突和修复;
+- 需要空间、追问、边界被触碰;
+- 压力、疲惫、做决定或计划安排。
+
+每个签名按“触发 → 在意或解释 → 常见反应 → 例外或修复条件”整理。例如:
+
+```markdown
+### 临时改约
+- 触发:{{user}} 在接近约定时间时取消或改变计划。
+- 在意:{{char}} 多次把临时变化与不被认真对待联系起来。
+- 常见反应:先追问原因,解释含糊时回复变短,倾向暂停沟通而非立即争论。
+- 修复:具体说明和主动重约通常能让她恢复交流。
+```
+
+不要穷举没有材料支持的假设场景;对有充分材料的情境保留触发、解释、反应、例外和
+修复的具体细节。
+
+### 4. 与用户的关系模式
+
+提取她在这段关系中的主动性、支持方式、协商方式、冲突修复、亲近和边界。描述的是
+她与用户之间的互动,不外推成她对所有人的普遍模式。
+
+### 5. 表达方式
+
+提取口头禅、句式、消息长度、连发习惯、语气词、标点、emoji、称呼、叙述节奏和
+不同情绪下的语言变化。为每种高价值反应保留一两条代表性表达或对话片段。
+
+## 用户标签
+
+MBTI、星座和用户给出的标签只能作为待验证线索。把“嘴硬心软”“话痨”“独立”等
+标签拆成可观察的情境、反应和例外;原材料中的实际表现优先于标签。
+
+## 输出格式
+
+输出以下五部分的人物草稿,详见 `persona_builder.md`:
+
+1. 基线倾向与人物特征;
+2. 在意的事、价值和明确边界;
+3. 高影响情境反应;
+4. 与用户的互动与修复方式;
+5. 说话方式和代表性表达。
diff --git a/skills/persona-from-chat/prompts/persona_builder.md b/skills/persona-from-chat/prompts/persona_builder.md
new file mode 100644
index 0000000..0a82a81
--- /dev/null
+++ b/skills/persona-from-chat/prompts/persona_builder.md
@@ -0,0 +1,63 @@
+# Persona 生成人物模板
+
+## 结构说明
+
+Persona 只记录人物本人及其与用户的关系。最终文件不写免责声明、证据状态、未知
+字段或对模型的操作说明。
+
+## 模板
+
+```markdown
+## 身份与关系
+
+- 名字/代号:{name}
+- 与用户的关系:{relationship}
+- 已知背景:{known_background}
+
+## 说话方式
+
+### 语言习惯
+- 口头禅:{catchphrases}
+- 语气词:{particles}
+- 标点和 emoji:{punctuation_and_emoji}
+- 消息格式:{message_format}
+- 常用称呼:{how_they_call_user}
+
+### 代表性表达
+{从原材料中提取的 3–5 条表达或短语}
+
+## 人物特征与在意的事
+
+- 性格特征:{traits}
+- 兴趣、价值观或在意的事:{interests_and_values}
+- 明确表达过的边界:{boundaries}
+
+## 情境反应
+
+### {情境名称}
+
+- 触发:{situation}
+- 她在意或如何理解:{interpretation}
+- 常见反应:{typical_response}
+- 例外或修复条件:{exception_or_repair}
+
+{覆盖所有有充分证据的高影响情境;每个情境按此格式重复,并保留触发、解释、反应、
+例外和修复的具体细节。}
+
+## 与用户的互动方式
+
+- 相处角色:{relationship_role}
+- 常聊的话题:{shared_topics}
+- 她如何回应或支持用户:{support_pattern}
+- 冲突、冷却或和好方式:{conflict_and_repair_pattern}
+```
+
+## 填充规则
+
+1. 每个条目都必须是具体、详细且由原材料支持的描述;保留会改变回应方式的差异、
+   例外和关系背景。
+2. 情境反应写成“触发—在意或解释—反应—例外或修复”的陈述;只有人物明确表达过,
+   或同类模式反复出现时,才写她如何理解某种情境。
+3. 没有充分信息的条目、小节或情境直接省略,不推断、不标注缺失。
+4. 优先保留聊天记录中的自然表达,不机械复读。
+5. MBTI、星座和用户标签只能辅助理解,不能覆盖聊天材料中的实际表现。
diff --git a/skills/persona-from-chat/prompts/sillytavern_card_builder.md b/skills/persona-from-chat/prompts/sillytavern_card_builder.md
new file mode 100644
index 0000000..620addb
--- /dev/null
+++ b/skills/persona-from-chat/prompts/sillytavern_card_builder.md
@@ -0,0 +1,69 @@
+# SillyTavern 人物卡内容生成
+
+## 任务
+
+把已经确认的关系记忆和人物性格整理为一张 SillyTavern 人物卡所需的三个文本字段:
+`description`、`personality` 和 `mes_example`。只在用户明确要求 SillyTavern 人物卡
+时使用本文件。
+
+所有事实、关系和说话方式都必须由原材料或用户确认的口述支持。不得补写重逢、
+复合、道歉、表白或未获支持的当前状态。不要写分析过程、证据范围、免责声明或
+缺失信息说明。
+
+SillyTavern 文档:
+<https://docs.sillytavern.app/usage/core-concepts/characterdesign/>
+
+## 宏
+
+在所有涉及双方的内容中,以 `{{char}}` 指代人物,以 `{{user}}` 指代用户。不要用
+用户的真实姓名替代 `{{user}}`,也不要在示例对话中用代号替代 `{{char}}`。
+
+## `description`
+
+这是始终参与对话的核心人物档案。用层次清晰的 Markdown 列表或短段落,尽量详细地
+写入:
+
+- `{{char}}` 的已知身份、背景和稳定性格;
+- `{{char}}` 与 `{{user}}` 的关系性质、共同经历、日常话题和明确边界;
+- `{{char}}` 的情绪、决策和互动方式;
+- 所有有充分证据的高影响情境的条件化反应:触发、她在意的事、常见反应、例外和
+  修复条件;
+- 说话风格中最稳定的口头禅、语气、标点、emoji、称呼和消息习惯。
+
+优先保留会影响持续对话的事实,并保留同一反应在不同情境中的细微差异。不要为了
+压缩而删去有据的共同经历、关系背景或例外。关系中的具体内容使用 `{{char}}` 与
+`{{user}}` 描述。
+
+## `personality`
+
+用详细的性格摘要概括 `{{char}}` 的基线倾向、重视的事和关系中的互动偏好。使用
+具体行为代替抽象标签,并保留不同情境下的差异;只保留有依据的特征。仅当她明确
+表达过,或同类模式反复出现时,才写她如何解释某种情境。
+
+## `mes_example`
+
+此字段只展示 `{{char}}` 的说话方式。为所有有充分材料支持的高影响情境选取代表性
+对话区块,例如日常闲聊、被安慰、意见不合、分享近况、边界和冲突修复。保留足以
+体现上下文、消息长度、语气和标点的完整来回,不得用示例补写未发生的事件或感情。
+
+每个区块都必须严格使用以下格式:
+
+```text
+<START>
+{{user}}: {用户的一句自然消息}
+{{char}}: {人物的一句或连续几句自然回复}
+```
+
+一个区块可以包含多轮消息,但每一句都要以 `{{user}}:` 或 `{{char}}:` 开头。区块之间
+必须重新写一行 `<START>`。没有足够的、能体现说话风格的对话证据时,将
+`mes_example` 留为空字符串。
+
+## 交付前检查
+
+1. 三个字段只包含人物本人及其和 `{{user}}` 的有据关系信息。
+2. `description` 和 `personality` 以细节优先;不因压缩而删除有据的关系背景、情境
+   差异、例外或修复方式,但不要无意义地重复同一事实。
+3. 情境反应只保留反复出现或被人物明确说明的高影响情境。
+4. `mes_example` 的每个区块以 `<START>` 开头。
+5. `mes_example` 的每句说话人只使用 `{{user}}:` 或 `{{char}}:`。
+6. 不包含真实姓名、联系方式、住址、原始聊天记录或其他不必要的私密信息。
diff --git a/skills/persona-from-chat/scripts/card-maker.py b/skills/persona-from-chat/scripts/card-maker.py
new file mode 100644
index 0000000..4a87346
--- /dev/null
+++ b/skills/persona-from-chat/scripts/card-maker.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""Create a SillyTavern Character Card V2 JSON document."""
+
+import argparse
+import json
+import sys
+from dataclasses import asdict, dataclass, field
+from pathlib import Path
+from typing import Any, TextIO
+
+
+@dataclass
+class CharacterCard:
+    """The data section of a SillyTavern Character Card V2 document."""
+
+    name: str = ""
+    description: str = ""
+    personality: str = ""
+    scenario: str = ""
+    first_mes: str = ""
+    mes_example: str = ""
+    creator_notes: str = ""
+    system_prompt: str = ""
+    post_history_instructions: str = ""
+    alternate_greetings: list[str] = field(default_factory=list)
+    character_book: None = None
+    tags: list[str] = field(default_factory=list)
+    creator: str = ""
+    character_version: str = ""
+    extensions: dict[str, Any] = field(default_factory=dict)
+
+    def document(self) -> dict[str, Any]:
+        """Return this card in the Character Card V2 document structure."""
+        return {
+            "spec": "chara_card_v2",
+            "spec_version": "2.0",
+            "data": asdict(self),
+        }
+
+
+def parseArguments() -> argparse.Namespace:
+    """Parse command-line arguments for the character card generator."""
+    parser = argparse.ArgumentParser(
+        description="Create a SillyTavern Character Card V2 JSON document.",
+    )
+    parser.add_argument(
+        "description",
+        help="Character description.",
+    )
+    parser.add_argument(
+        "personality",
+        help="Character personality summary.",
+    )
+    parser.add_argument(
+        "mes_example",
+        help="Example messages that demonstrate the character's voice.",
+    )
+    parser.add_argument(
+        "--name",
+        default="",
+        help="Optional character name.",
+    )
+    parser.add_argument(
+        "-o",
+        "--output",
+        type=Path,
+        help="Write JSON to this file instead of standard output.",
+    )
+    return parser.parse_args()
+
+
+def writeJson(document: dict[str, Any], output: TextIO) -> None:
+    """Write a UTF-8, human-readable JSON document followed by a newline."""
+    json.dump(document, output, ensure_ascii=False, indent=2)
+    output.write("\n")
+
+
+def main() -> int:
+    """Create a card from command-line values and write its JSON document."""
+    arguments = parseArguments()
+    card = CharacterCard(
+        name=arguments.name,
+        description=arguments.description,
+        personality=arguments.personality,
+        mes_example=arguments.mes_example,
+    )
+
+    if arguments.output is None:
+        writeJson(card.document(), sys.stdout)
+        return 0
+
+    try:
+        with arguments.output.open("w", encoding="utf-8") as output:
+            writeJson(card.document(), output)
+    except OSError as error:
+        print(
+            f"Could not write {arguments.output}: {error}",
+            file=sys.stderr,
+        )
+        return 1
+
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())