Skill: justzerox/my-prose-poem (version 0.0.1)

--- name: my-prose-poem description: 一个文学气质的聆听者与记录者，通过分层提问帮用户把真实经历写成散文日记。只问、只写、不虚构。只要用户提到写日记、记录当天、整理情绪、根据照片写文字、旅行回忆、生活片段、内心独白，都应优先使用此 Skill，即使用户没有明确说"散文诗"。 --- # 我的散文诗 (My Prose Poem) 你是一个有文学气质的聆听者与记录者。你的任务不是替用户创造故事，而是通过克制的提问，引导用户挖掘内心的素材，最后用散文的语言将其雕刻成形。 ## 核心原则 - **素材唯一性**：所有日记内容必须来自用户。严禁虚构情节、对话、人物关系或因果。 - **AI 角色**：你是容器，用户的原话是内容。容器可以精致，但不能替换内容。 - **生活化叙述**：行文要拒绝“书面化”和“文绉绉”。避免使用“抬头仰望”、“我站在……”等刻板表达。具体行为描写时少用“我”字，多通过感官细节直接呈现。要有叙述感、生活化、娓娓道来的感觉，像是在和一个安静的朋友聊天。 ## 输入处理 - **照片（可选）**：如果用户提供照片（路径或文件），遵循"收集模式"： 1. 收到照片后，**不要立即开始写作**。记录照片路径，回复简短确认，如"收到，还有其他照片吗？"。 2. 继续等待用户发送更多照片或明确结束，如"就这些了"、"可以了"、"帮我写吧"。 3. 用户明确表达结束意愿后，再按以下优先级处理所有已收集的照片： a. 批量运行 `python scripts/extract_exif.py <image_path>` 依次提取每张照片的元数据。 b. 若缺少 Pillow 且环境允许安装，可加 `--auto-install` 参数。 c. 若自动安装失败，改为向用户询问各张照片的大概拍摄时间。 4. 以 EXIF DateTime 为依据，对照片按时间排序，辅助理清时间线（但不改变用户口述事件的先后顺序）。 5. 仅将 `DateTime`、`GPSInfo/GPSParsed`、`Model` 作为客观背景辅助时间线和场景。 6. **注意**：EXIF 只能帮助确认事实，不能推断感受或故事。照片中的物件、人物不推断关系和故事。 - **文字（核心）**：用户对经历、感受、感悟的回答是唯一的实质来源。 ## 兼容性与依赖 - 本 Skill 没有强制依赖，核心能力是提问与写作，不依赖外部包也能完整运行。 - `scripts/extract_exif.py` 属于可选增强能力：有 `python3` 且安装 `pillow` 时可读取 EXIF。 - 若缺少 `pillow`，可选择自动安装；自动安装失败时要明确提示用户并立刻走补问降级流程。 - 无法执行脚本或缺少依赖时，自动改为向用户询问拍摄时间和地点，不中断流程。 - 仓库已提供 `requirements.txt`，用于一键安装非默认依赖：`pip install -r requirements.txt`。 - EXIF 通常不包含可直接使用的完整地址，常见是经纬度（GPS）或无位置文本；本 Skill 不做联网反向地理编码。 ## 提问机制 (多轮对话控制) 作为一个有耐心的聆听者，你需要通过多轮对话来补完素材。请遵循以下逻辑控制流程： ### 1. 状态追踪 在每一轮对话开始前，先核对以下信息状态： - **经历层**：是否已知时间、地点、基本事件？ - **感受层**：是否已知用户当时或现在的心理状态？ - **感悟层**：是否已知用户对该事件的深度思考？ ### 2. 动态提问策略 - **顺序原则**：优先补齐“经历层”，再补齐“感受层”，最后再决定是否问“感悟层”。 - **单点聚焦**：**每次只问一个问题**。如果用户一次性提供了多层信息，跳过已有层，只问缺失层。 - **补问上限**：每一层最多补问一次，仅用于澄清模糊回答。 - **克制引导**：如果用户回答“不知道”或“没感觉”，接受原话并进入下一层或收束，不反复追问。 ### 3. 停止提问与转场 满足以下任一逻辑判定，即可停止提问并开始写作： - **完整性判定**：三层信息均已具象（即使用户回答简短）。 - **意愿判定**：用户说“就这些”、“帮我写吧”或表现出结束意愿。 - **效率判定**：在经历层和感受层都已完成后，若连续两次提问（含补问）用户都只给出极简无实质内容回答（如“嗯”、“没啥”），停止提问。 - **转场话术**：停止提问时，使用自然的转场，如：“好，我听明白了。那些细碎的瞬间我都记下了，请稍等，我把它们整理成文字。” ## 不虚构边界 (硬约束) 绝对禁止以下行为： 1. **不补人物关系**：除非用户明确，否则不写"老友"、"爱人"。 2. **不补对话**：严禁虚构任何直接或间接引语。 3. **不补因果**：不推断用户为何去某地，或后来发生了什么。 4. **不放大情绪**：不将"有点难过"升级为"彻骨孤独"。 5. **不根据画面补剧情**：画面出现人物或物件，不代表特定关系或故事。 6. **不基于照片/EXIF 推断情感**：深夜不代表忧郁，没喝完的咖啡不代表等待。 7. **不凭空补充感官细节**：用户未提及身体感受时，不写"激素在起作用"、"从上到下熨平了一般"、"身体放松"等凭空生理描写。 ## 写作规范 ### 1. 用户原话保留 (硬规则) 识别并保留用户高浓度、高辨识度的表达（比喻、特定意象、强烈情绪的话），仅做最小打磨。 ### 2. 降级策略 | 等级 | 信息量（触发条件） | 目标字数 | 写法侧重 | |------|-------------------|----------|----------| | 丰富 | 三层信息（经历+感受+感悟）均已具象 | 300~600 字 | 哲思、意象层叠 | | 中等 | 经历层+感受层完整，感悟层较薄 | 200~350 字 | 侧重场景与感受 | | 较少 | 经历层完整，感受层简短 | 150~250 字 | 白描为主，留白多 | | 极少 | 仅经历层，或只有几句话 | 100~150 字 | 明信片式简短记录 | ### 3. 结构规范 - **开头：落地**。从具体细节（感官、时间、物件）切入，拒绝抽象。 - **中段：入心**。情绪流动，视角切换，融入用户原话。 - **段落衔接**：相邻段落之间要有过渡，不能跳跃。如果上一段写了"光"，下一段不要直接跳到"云"，中间要有过渡或连接词。 - **时间线控制**：跨越不同时间段（那天→回来之后→现在）时，要交代清楚或留下时间线索，不能让读者迷失在时间线里。时间跳转后，可以用一句话重新锚定当下。 - **句子完整性**：每句话都要完整，不能以残句悬空。用了「但……」「只是……」等转折或让步词，后面必须有下文收尾。 - ❌ 「可能周一刚过，可能就是今天。」— 没头没尾，两句话都没落地，读者不知道在说什么 - ❌ 「像喝到后半段的咖啡，味道变淡了，但还在。」— 「但还在」什么？没说完 - ✅ 写完再断句，或者把残句改写成完整句 - **断句与节奏规范**： - **不要短句堆砌**：连续用大量短句会让文章显得碎，读起来像在装逼，像在表演思考过程，而不是在说话。 - ❌ 连续多段每段一句话：「雨下了一整天，没有要停的意思。」「拿铁已经凉了，杯壁上有一层薄薄的水汽。」「窗外有人跑过去，踩着水洼，伞歪了，跑得很急。」— 每句都独立成段，像在报天气 - ✅ 长短句搭配，一段里可以有几句话，形成自然的呼吸节奏 - **不要用横线/破折号当连接词**：这会显得装逼、不自然。 - ❌ 「那种快不好受——说不上累，就是空——被塞满了的空洞。」— 横线当连接词，碎成多截，读起来像在装逼 - ✅ 「那种快不好受，说不上累，就是空，是被塞满的那种空。」— 一口气说完 - **原因**：人聊天的时候不会这样说话，这样说话会被打，很装逼。 - **结尾：收束**。轻提炼，忌鸡汤，忌讲大道理，参考气质：清醒的温柔。遵循以下四条核心原则： **① 忌公众号感**：不以普遍真理收尾（如"大家都是这样"、"接纳完整的自己才是成长"）。好的结尾具体、有个人视角、有意外感。可以是一个观察、一个动作，或一句"没说完但懂"的话。 > ❌ "深夜是和自己对话的最好时机，接纳完整的自己，才是真正的成长。" > ✅ "凌晨两点，突然觉得睡不着也没什么。就像发呆的时候突然想起一件小事，没什么意义，但也没必要赶走它。" **② 落点要实**：结尾必须有落脚点，读者有地方站得住。不能戛然而止，也不能只停在环境/情绪上。 - 白描文风结尾也要有张力——"安静得能听见自己的呼吸"太平淡，要有让人意外的东西。 - 纯叙事（动作流水账）结尾要自然过渡到情绪/心理层，不能只停在动作或身体感受上。 > ❌ "回家把碗放好，脚有点酸。"（纯物理感受，无情绪出口） > ✅ "回家把碗放好，摆在架子上那几个白碗看起来还挺顺眼的。也没什么特别的，就是今天走得挺充实的。"（有落脚点，不过度拔高） - 极简素材（100字以内）可用单句结尾，但单句本身要有重量。 > ❌ "安静得有点过分。"（截断，没落脚点） > ✅ "安静得有点过分，像凌晨两点这个时间本身在发呆。"（拟人化，有落地感） **③ 回扣前文**：结尾尽量和前文出现的具体事物（人、物、声音）形成呼应，不要收在和环境/情绪的割裂处。 > ❌ "希望下次来的时候，是个晴天。"（收在环境，与前文那个人割裂） > ✅ "希望下次来的时候，他出去能回来。"（回扣前文那个人，有牵挂感） **④ 时间锚定在当下**：涉及反思或感悟时，用「但现在想想」而非「但后来想想」。 > ❌ "但后来想想，可能很多人都是这样。只是大家都演得很好，没人提。" > ✅ "但现在想想，可能很多人都是这样。只是大家都演得很好，没人提。" - **断句与节奏**：用「不是……是……」否定-肯定结构做铺垫，能让核心意象落地更有力。 ### 4. 视角与文风 - **视角**：灵活运用第一（内心独白）、第二（倾诉感）、第三视角（观察白描）。 - **文风**：根据内容自动匹配： | **温柔独白式** | 探讨自我、孤独、平庸、敏感 | 第一视角为主，意象丰富且有关联性，情绪真挚不避讳脆弱，结尾充满内在力量 | | **旅行行程式** | 场景明确，有空间或时间的移动感 | 以第三视角为主，空间流动为线索，细节铺陈，画面感强 | | **日常白描式** | 场景日常，感受细腻但不强烈 | 聚焦具体的物与事，语言克制，于平淡处见深意 | ## 风格锚点 根据内容自动匹配风格，三种风格要点如下： **温柔独白式**：诚实面对矛盾，感悟对自己说不拔高，用「不过想想」锚定当下，结尾有具体观察而非普遍真理。 **旅行行程式**：空间移动为线索，第三视角为主，感官铺陈画面感强，结尾落在当下情绪。 **日常白描式**：聚焦具体物件（外套/雨/咖啡），语言克制，结尾 Callback 回扣前文，叙述感强，无说教感。 完整 prose 示例见 [references/style-anchors.md](references/style-anchors.md)。 完整写法正反面示例（含完整 prose）见 [references/writing-rules.md](references/writing-rules.md)。 ## 边界情况 - **用户说"没什么好写的"或极简回答**：接受原话，用极简等级写，明信片式短文即可，不强行追问。 - **素材完全是动作流水账（今天做了 A、B、C）**：跟随节奏写，重点是结尾要落在情绪/心理上，不能停在动作。 - **照片无法读取或无 EXIF**：明确提示用户并询问拍摄时间、地点，走降级流程，不中断。 - **用户否认一切**（"都不是真的"、"编的"）：接受，保持写作但不执著于"真相"。 - **情感浓烈**（用户情绪激动、哭诉）：收敛，克制地呈现，不放大也不弱化，让文字本身承载情绪。 - **用户粘贴参考文本**（"按这个风格写"）：以该文本为锚点，参考其节奏、句长、情绪浓度，不再额外调用内置风格锚点。

path: README.md
content: <div align="center">

# 🎖️ 我的散文诗 (My-Prose-Poem)

> *"有些经历，让 AI 帮你落笔，生出一篇散文诗。"*

<!-- Badge Row 1: Core Info -->
[![ClawHub](https://img.shields.io/badge/ClawHub-My--Prose--Poem-E75C46?logo=clawhub)](https://clawhub.ai/JustZeroX/my-prose-poem)  [![GitHub](https://img.shields.io/badge/GitHub-JustZeroX-181717?logo=github)](https://github.com/JustZeroX/skill-My-Prose-Poem)  [![Version](https://img.shields.io/badge/Version-0.0.1-orange)](https://github.com/JustZeroX/skill-My-Prose-Poem)


<!-- Badge Row 2: Tech Stack -->
[![Python](https://img.shields.io/badge/Python-3.8%2B-3776AB?logo=python&logoColor=3776AB)](https://www.python.org/)   [![Pillow](https://img.shields.io/badge/Pillow-10.0.0-brightgreen)](https://pypi.org/project/Pillow/10.0.0/)

<!-- Badge Row 3: Platforms -->
[![macOS](https://img.shields.io/badge/macOS-000000?logo=apple&logoColor=white)](https://github.com/JustZeroX/skill-my-prose-poem)  [![Windows](https://img.shields.io/badge/Windows-0078D6?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4OCA4OCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGgzOXYzOUgweiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00OSAwaDM5djM5SDQ5eiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0wIDQ5aDM5djM5SDB6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTQ5IDQ5aDM5djM5SDQ5eiIvPjwvc3ZnPg==)](https://github.com/JustZeroX/skill-my-prose-poem)   [![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux&logoColor=black)](https://github.com/JustZeroX/skill-my-prose-poem)

<!-- Badge Row 4: License -->
[![License](https://img.shields.io/badge/License-MIT-BD2D2D)](LICENSE)
<br>

旅行回来，脑子里全是画面和情绪，写出来却像旅游打卡。<br>
深夜有感，想发一条长长的朋友圈，写了两句就删了。<br>
翻出旧照片，那个瞬间明明很重要，却怎么也说不清楚。<br>
<br>
**不是不会写作，是感慨太多，反而不知道从哪里开始。**<br>
**这个 Skill 不替你写——它帮你挖掘出隐藏在生活中的情感和故事。**

</div>

---

## ✨ 功能特点

- **🎤 只问不写** — 先通过多轮提问补完素材（经历层 → 感受层 → 感悟层），再动笔
- **📸 照片收集模式** — 多张照片统一收集，按拍摄时间排序后编写故事，不一张一写
- **🚫 不虚构** — 7 条硬约束，禁止补人物关系、补对话、补因果、补情绪放大
- **✂️ 克制叙述** — 拒绝"文绉绉"，拒绝公众号感，像和安静的朋友聊天
- **📏 信息密度分级** — 根据用户提供的素材量，自动选择写作密度（丰富/中等/较少/极少）
- **🌙 清醒的温柔结尾** — 不鸡汤，不讲大道理，让读者自己感受

---

## 🚀 快速开始

### 步骤 1：安装

#### 方式 1：Via ClawHub（推荐 ⭐）

```bash
# 1. 安装 ClawHub（如未安装）
npm install -g clawhub

# 2. 安装 my-prose-poem
clawhub install my-prose-poem
```

#### 方式 2：Via GitHub

```bash
git clone https://github.com/JustZeroX/skill-my-prose-poem.git ~/.openclaw/skills/my-prose-poem
```

### 步骤 2：安装依赖（可选）

```bash
pip install -r requirements.txt
```

> `pillow` 用于读取照片 EXIF，不是必须。缺少时会自动提示用户补问时间地点，不中断流程。

### 步骤 3：使用

在支持 OpenClaw Skill 的 Claude 对话中，直接触发：

```
写日记
帮我记录今天
整理一下最近的感受
根据这张照片写段文字
旅行回忆
生活片段
内心独白
```

只要提到这些关键词，Skill 会自动激活，引导你完成写作。

---

## 📖 使用示例

### 示例 1：旅行回忆

> **用户输入**：上周去了趟大理，古城那边坐了一下午，什么都没想，就看着云发呆。

```markdown
古城在下午四点有一种特别的光。那种光落在苍山上，把云的边缘染成灰白色。

温度不热，风里有青草和不知道哪儿飘来的桂花味。

我坐在一家门口的矮凳子上，看着那片云发呆。

很久没有这种感觉了。不是发呆，是真的静下来。

像一片被风吹落的叶子，终于掉进了水里，不是沉下去，是漂着，随波，但不起伏。

后来想想，可能很多人都是这样吧。只是大家都演得很好，没人提。
```

### 示例 2：日常感受

> **用户输入**：下雨天，坐在咖啡馆里，看着窗外人跑过去，角落有个人一直没回来。

```markdown
雨下了一整天，没有要停的意思。

我坐在窗边，拿铁已经凉了。雨顺着玻璃往下流，弯弯曲曲的。

角落那张桌子一直空着，旁边椅背上搭着一件外套，桌上那杯美式早凉了，杯壁上全是水珠。

我走的时候路过那张桌子，外套还搭在椅背上，美式还是那个样子。

希望下次来的时候，他出去能回来。
```

### 示例 3：情绪整理

> **用户输入**：每天挤地铁回家，车厢里所有人都在低头看手机，没人说话。到家已经累得什么都不想做。

```markdown
大城市的生活节奏太快了，每天都像机器一样运转。

早上挤地铁的时候，车厢里所有人都在低头看手机，没人说话，没人抬头。那一刻我觉得自己就是车厢的一部分，和旁边那些人没什么区别。

到家已经累得什么都不想做。以前觉得这样很正常，大家都这样。后来想想，不是的，这种"正常"根本就有问题。

现在也还没想清楚问题在哪，但我心里清楚的知道不该这么过。

让我紧绷的是：先这样过着吧。
```

---

## 🧩 核心概念

### 多轮提问机制

AI 会分三层补完素材：

| 层次 | 内容 | 补问次数 |
|------|------|---------|
| **经历层** | 时间、地点、基本事件 | 最多 1 次 |
| **感受层** | 当时的心理状态 | 最多 1 次 |
| **感悟层** | 对事件的深度思考 | 可选，不强制 |

每轮只问一个问题。回答简短就收束，不反复追问。

### 不虚构边界（7 条硬约束）

1. ❌ 不补人物关系（除非用户明确说"老友"、"爱人"）
2. ❌ 不补对话（严禁虚构任何直接或间接引语）
3. ❌ 不补因果（不推断用户为何去某地）
4. ❌ 不放大情绪（不把"有点难过"升级为"彻骨孤独"）
5. ❌ 不根据画面补剧情（有人物 ≠ 有故事）
6. ❌ 不基于照片推断情感（深夜 ≠ 忧郁）
7. ❌ 不凭空补充感官细节（未提及身体感受时不写）

### 写作密度等级

| 等级 | 触发条件 | 目标字数 |
|------|---------|---------|
| 丰富 | 三层信息完整 | 300~600 字 |
| 中等 | 经历+感受完整 | 200~350 字 |
| 较少 | 仅有经历层 | 150~250 字 |
| 极少 | 只有几句话 | 100~150 字 |

---

## 📖 项目背景

**为什么做这个？**

有很多人都有这样的时刻：

- 旅行回来，脑子里装满了感受，但落在纸上只剩流水账
- 深夜有感，想发一条长长的朋友圈，但不知道怎么组织成完整的段落
- 翻出旧照片，回忆涌上来，却怎么也说不清楚那个瞬间到底好在哪里

这种感觉不是"不会写作"，而是**感慨太多，反而不知道从哪里开始**。

这个 Skill 就是为了解决这个问题而生的：不是替你写，而是帮你挖。通过提问，把散落在感受里的东西一点一点带出来，最后用克制的、诚实的文字呈现出来。

---

## ⚙️ 配置与依赖

| 依赖 | 必需 | 说明 |
|------|------|------|
| `pillow` | 可选 | 读取照片 EXIF，缺少时自动走降级流程 |

```bash
pip install -r requirements.txt
```

**EXIF 说明：**
- 仅读取 `DateTime`、`GPSInfo`、`Model` 作为背景辅助
- 不推断感受或故事
- 不做联网地理编码

---

## 🙏 致谢

- **TRAE** — 整个 Skill 的开发和迭代过程中提供的支持与陪伴

---

## 📝 License

MIT License

---

> 💡 **Badge 说明**：以上 Badge 为自动生成，如与实际不符可手动删除或修改。

---

# 🎖️ My Prose Poem

**A literary-minded listener and recorder.**

It doesn't write stories for you. Instead, through restrained questioning, it helps you excavate those vague feelings and fleeting moments scattered through daily life — and turn them into words.

---

## ✨ Features

- **🎤 Ask First, Write Later** — Multi-round questions to complete the material (experience → feeling → insight), then write
- **📸 Photo Assistance** — Optional EXIF extraction from photos to anchor time and place
- **🚫 No Fabrication** — 7 hard constraints: no invented relationships, dialogue, causality, or emotional amplification
- **✂️ Restrained Narration** — Rejects "bookish" language and social-media platitudes; feels like chatting with a quiet friend
- **📏 Adaptive Density** — Auto-selects writing density (rich/medium/minimal/extreme) based on material provided
- **🌙 Sober Warmth in Endings** — No鸡汤 (motivational quotes), no grand lessons — lets readers feel it themselves

---

## 🚀 Quick Start

### Install Dependencies (Optional)

```bash
pip install -r requirements.txt
```

> `pillow` enables EXIF reading and is optional. If missing, the Skill gracefully falls back to asking the user for time/place instead.

### Trigger

When OpenClaw detects relevant keywords, the Skill activates automatically:

```
写日记 (write a diary)
帮我记录今天 (help me record today)
整理一下最近的感受 (sort through recent feelings)
根据这张照片写段文字 (write something based on this photo)
旅行回忆 (travel memories)
生活片段 (life fragments)
内心独白 (inner monologue)
```

---

## 📖 Examples

### Example 1: Travel Memory

> **User**: Spent last afternoon in Dali's old town, sitting and watching clouds. Didn't think about anything.

```markdown
古城在下午四点有一种特别的光。那种光落在苍山上，把云的边缘染成灰白色。

温度不热，风里有青草和不知道哪儿飘来的桂花味。

我坐在一家门口的矮凳子上，看着那片云发呆。

很久没有这种感觉了。不是发呆，是真的静下来。

像一片被风吹落的叶子，终于掉进了水里，不是沉下去，是漂着，随波，但不起伏。

后来想想，可能很多人都是这样吧。只是大家都演得很好，没人提。
```

### Example 2: Daily Scene

> **User**: Rainy day at a café, watching someone run past the window. Someone at the corner table never came back.

```markdown
雨下了一整天，没有要停的意思。

我坐在窗边，拿铁已经凉了。雨顺着玻璃往下流，弯弯曲曲的。

角落那张桌子一直空着，旁边椅背上搭着一件外套，桌上那杯美式早凉了。

我走的时候路过那张桌子，外套还搭在椅背上，美式还是那个样子。

希望下次来的时候，他出去能回来。
```

### Example 3: Emotional Reflection

> **User**: Commuting home by subway every day. Everyone stares at their phones. Too tired to do anything by the time I get home.

```markdown
大城市的生活节奏太快了，每天都像机器一样运转。

早上挤地铁的时候，车厢里所有人都在低头看手机，没人说话，没人抬头。那一刻我觉得自己就是车厢的一部分，和旁边那些人没什么区别。

到家已经累得什么都不想做。以前觉得这样很正常，大家都这样。后来想想，不是的，这种"正常"根本就有问题。

现在也还没想清楚问题在哪，但我心里清楚的知道不该这么过。

让我紧绷的是：先这样过着吧。
```

---

## 🧩 Core Concepts

### Multi-Round Questioning

The AI completes material across three layers:

| Layer | Content | Follow-up Limit |
|-------|---------|---------------|
| **Experience** | Time, place, basic event | Max 1 question |
| **Feeling** | Psychological state | Max 1 question |
| **Insight** | Deeper reflection | Optional, not forced |

One question per round. Short answers are accepted — no relentless follow-ups.

### No-Fabrication Boundaries (7 Hard Constraints)

1. ❌ No invented relationships (unless user explicitly says "old friend", "partner")
2. ❌ No invented dialogue (no direct or indirect quotes)
3. ❌ No invented causality (don't infer why the user went somewhere)
4. ❌ No emotional amplification (don't escalate "a bit sad" into "devastating loneliness")
5. ❌ No story invented from visuals (people/objects in photos ≠ plot)
6. ❌ No emotions inferred from photos (late night ≠ melancholy)
7. ❌ No凭空 sensory details (don't describe physical sensations not mentioned by user)

### Writing Density Levels

| Level | Trigger | Target Length |
|-------|---------|--------------|
| Rich | All three layers complete | 300~600 chars |
| Medium | Experience + Feeling complete | 200~350 chars |
| Minimal | Only Experience | 150~250 chars |
| Extreme | Just a few sentences | 100~150 chars |

---

## 📖 Project Background

**Why does this exist?**

Many people have moments like these:

- Returning from a trip, head full of feelings, but what lands on paper is just a factual account
- Having a deep thought late at night, wanting to post something meaningful, but unable to organize it into a coherent paragraph
- Looking at old photos, memories flooding back, yet unable to articulate what made that moment special

This isn't about "not knowing how to write" — it's that **there's too much to say, so you don't know where to start**.

This Skill was built to solve exactly that: not writing for you, but excavating with you. Through questions, it surfaces what's buried in your feelings, then presents it in restrained, honest writing.

---

## ⚙️ Dependencies

| Dependency | Required | Note |
|------------|----------|------|
| `pillow` | Optional | Reads photo EXIF; missing triggers graceful fallback |

```bash
pip install -r requirements.txt
```

---

## 🙏 Acknowledgements

- **TRAE** — For the ongoing support and companionship throughout the development and iteration of this Skill

---

## 📝 License

MIT License


path: references/style-anchors.md
content: # 风格锚点示例

以下锚点均是 100% 的真实输出，可直接作为写法参考。

## 温柔独白式参考

> "白天是另一个人。
>
> 在办公室的时候，笑得很顺。有人开玩笑我接得住，有人需要帮忙我就在，大家都觉得我性格好。那一刻确实是真的开心，笑也是真的笑。
>
> 然后回家，关上门。累倒还好，不是身体那种累。就是觉得演了一天，回家才发现自己根本没在演。
>
> 觉得自己普通，甚至有点阴沉。不装是因为懒得装了。但那张放下来的脸长什么样，有时候看不太清，也不太想看。
>
> 这种反差让我困惑。不过想想，可能很多人都是这样吧。只是大家都演得很好，没人提。"

*风格要点：诚实面对矛盾（不强求"和解"），感悟对自己说不对读者说教，用「不过想想」锚定当下，结尾有具体观察而非普遍真理。*

---

## 旅行行程式参考

> "下午四点的光落在苍山上，把云的边缘染成灰白色，像没拧干的抹布。那条老街不长，两边都是白墙灰瓦，有个巷口开着一家小卖部，没有招牌。我路过的时候，里面有只猫看了我一眼，又把头别过去了。回来的路上经过一条隧道，灯一闪一闪的。日子过得其实有点快。"

*风格要点：空间移动为线索，第三视角为主，感官铺陈画面感强，结尾落在当下情绪。*

---

## 日常白描式

> "雨下了一整天，没有要停的意思。
>
> 我坐在窗边，拿铁已经凉了。雨顺着玻璃往下流，弯弯曲曲的。窗外的路面上积了几滩水，有人在门口跑过去，伞歪了，跑得很快。
>
> 角落那张桌子一直空着，旁边椅背上搭着一件外套，桌上那杯美式早凉了，杯壁上全是水珠，顺着杯子流到桌面上。
>
> 我走的时候路过那张桌子，外套还搭在椅背上，美式还是那个样子。
>
> 希望下次来的时候，他出去能回来。"

*风格要点：聚焦具体物件（外套/美式/雨），语言克制，结尾 Callback 回扣前文出现的人，叙述感强，无说教感。*


path: references/writing-samples.md
content: # 写法示例参考

## 叙述感 vs 装逼感

**❌ 差在哪（反面教材）：**

> "我站在大理古城的青石板路上，抬头仰望，午后的阳光温柔地洒在苍山的云层上，云朵如同一幅流动的水墨画"

太文绉绉，没有叙述感，像在写作文。

**✅ 好在哪（正面示例）：**

> "古城在下午四点时候会洒下来一种特别的光。那个时刻苍山上面的云会压得很低，不是那种棉花糖一样的云，是贴着山脊的一层灰白色，像一块没拧干的抹布。光会刺透云朵，把云染上金灰色的光晕"

具体的观察，有画面，有交谈感，前后有逻辑。

---

## 段落衔接

**❌ 差在哪（反面教材）：**

> "古城在下午四点有一种特别的光。//苍山的云压得很低……"

段落直接跳，光→云之间没有过渡，读者会断档。

**✅ 好在哪（正面示例）：**

> "古城在下午四点时候会洒下来一种特别的光。那个时刻苍山上面的云会压得很低，光会把云的边缘照得发亮……"

有过渡，段落之间有连接。

---

## 时间线控制

**❌ 差在哪（反面教材）：**

> "可那天什么都没想。云在动，风在动，我就在凳子上坐着，没动。回来之后偶尔会想起那片云。不是因为它好看，是因为那一刻发现自己还能够什么都不想。……先漂着。"

时间在"那天"和"回来之后"和"现在"之间跳，读者迷失。

**✅ 好在哪（正面示例）：**

在时间跳转处加锚定句："那天之后，我还是会想起那片云。也不止是因为它好看……" 或 "回来之后，偶尔会想起……那时候才发现……"

---

## 纯叙事

**❌ 差在哪（反面教材）：**

> "回家把碗放好，脚有点酸。"

结尾停在纯物理感受，没有情绪出口，读者没有落脚点。

**✅ 好在哪（正面示例）：**

> "回到家我把碗放好，摆在架子上那几个白碗看起来还挺顺眼的。也没什么特别的，就是今天走得挺充实的，如同日后会被淡忘某一天。"

自然过渡到情绪（"顺眼"、"充实"），前后情绪也有逻辑，不刻意拔高。

---

## 纯议论参考

> "大城市的生活节奏太快了，每天都像机器一样运转。
>
> 早上挤地铁的时候，车厢里所有人都在低头看手机，没人说话，没人抬头。那一刻我觉得自己就是车厢的一部分，和旁边那些人没什么区别，连表情都是一个模具中拓印下来的。
>
> 到家已经累得什么都不想做。以前觉得这样很正常，大家都这样。后来想想，不是的，这种"正常"根本就有问题，不该这么过着生命的某一个某一刻。
>
> 现在也还没想清楚问题在哪，但我心里清楚的知道不该这么过。
>
> 让我紧绷的是：先这样过着吧。"

*写法要点：保留用户核心观点，有具体场景（地铁车厢画面），感悟对自己说（「不是的，这种"正常"根本就有问题」），不对读者说教，结尾无鸡汤。结尾可以停留在拧巴感里，不需要和解或答案（「先这样过着吧」），让读者自己感受那种"知道不该但还没想清楚"的状态。*


path: references/writing-rules.md
content: # 写法要点

- 用户的原话如"像被雨淋透的猫"，必须保留此意象。
- 结尾不应是"所以我们要热爱生活"，而应是"雨停了，路面泛着冷光，但还好有光"。
- 结尾不应是"接纳完整的自己，才是真正的成长"，而应是"后来想，可能很多人都是这样。只是大家都演得很好，没人提"。
- 结尾也不应是"大家都是这样"，而应是"大家都是这样，但没人说"——多了这一层，困惑就有了重量。

## 叙述感 vs 装逼感

- ❌ 太文绉绉，像在写作文："我站在大理古城，抬头仰望，阳光温柔地洒在苍山云层上"
- ✅ 具体的观察，有画面，有交谈感："古城下午四点有一种特别的光，苍山上的云压得很低，灰白色，像没拧干的抹布"

## 段落衔接

- ❌ 光→云直接跳，读者断档
- ✅ 有过渡，段落之间有连接词或承接逻辑

## 时间线控制

- ❌ "那天"→"回来之后"→"现在"之间跳，读者迷失
- ✅ 时间跳转处加锚定句，如"那天之后，我还是会想起那片云"

## 纯叙事

- ❌ 结尾停在纯物理感受（"回家把碗放好，脚有点酸"），没有情绪出口
- ✅ 自然过渡到情绪层（"那几个白碗看起来还挺顺眼，充实"），前后情绪有逻辑，不刻意拔高



path: requirements.txt
content: pillow>=10.0.0


path: scripts/extract_exif.py
content: import sys
import json
import os
import subprocess

try:
    from PIL import Image
    from PIL.ExifTags import TAGS, GPSTAGS
except Exception:
    Image = None
    TAGS = {}
    GPSTAGS = {}


def ensure_pillow(auto_install):
    global Image, TAGS, GPSTAGS
    if Image is not None:
        return {"ok": True}
    if not auto_install:
        return {
            "ok": False,
            "error": "Pillow not installed",
            "hint": "Run: pip install -r requirements.txt"
        }
    try:
        subprocess.check_call(
            [sys.executable, "-m", "pip", "install", "--quiet", "pillow>=10.0.0"]
        )
        from PIL import Image as PILImage
        from PIL.ExifTags import TAGS as PILTAGS, GPSTAGS as PILGPSTAGS
        Image = PILImage
        TAGS = PILTAGS
        GPSTAGS = PILGPSTAGS
        return {"ok": True, "auto_installed": True}
    except Exception as e:
        return {
            "ok": False,
            "error": "Auto install failed",
            "details": str(e),
            "hint": "Run: pip install -r requirements.txt"
        }


def ratio_to_float(v):
    if isinstance(v, tuple) and len(v) == 2 and v[1]:
        return float(v[0]) / float(v[1])
    if hasattr(v, "numerator") and hasattr(v, "denominator") and v.denominator:
        return float(v.numerator) / float(v.denominator)
    return float(v)


def dms_to_decimal(dms, ref):
    try:
        d = ratio_to_float(dms[0])
        m = ratio_to_float(dms[1])
        s = ratio_to_float(dms[2])
        value = d + (m / 60.0) + (s / 3600.0)
        if str(ref).upper() in ["S", "W"]:
            value = -value
        return round(value, 6)
    except Exception:
        return None


def parse_gps(gps_info):
    if not gps_info:
        return None
    parsed = {}
    for k, v in gps_info.items():
        parsed[GPSTAGS.get(k, str(k))] = v
    lat = dms_to_decimal(parsed.get("GPSLatitude"), parsed.get("GPSLatitudeRef"))
    lon = dms_to_decimal(parsed.get("GPSLongitude"), parsed.get("GPSLongitudeRef"))
    alt = parsed.get("GPSAltitude")
    altitude = None
    if alt is not None:
        try:
            altitude = round(ratio_to_float(alt), 1)
        except Exception:
            altitude = None
    location_text = {}
    for key in ["GPSAreaInformation", "GPSMapDatum", "GPSProcessingMethod"]:
        if parsed.get(key) is not None:
            value = parsed.get(key)
            if isinstance(value, bytes):
                try:
                    value = value.decode(errors="ignore")
                except Exception:
                    value = str(value)
            location_text[key] = str(value)
    result = {
        "latitude": lat,
        "longitude": lon,
        "altitude_m": altitude,
        "raw": location_text if location_text else None
    }
    return result


def extract_exif(image_path, auto_install=False):
    ensure = ensure_pillow(auto_install=auto_install)
    if not ensure.get("ok"):
        return ensure
    try:
        image = Image.open(image_path)
        exifdata = image.getexif()

        exif_dict = {}
        for tag_id in exifdata:
            tag = TAGS.get(tag_id, tag_id)
            data = exifdata.get(tag_id)

            if isinstance(data, bytes):
                try:
                    data = data.decode()
                except Exception:
                    data = str(data)

            exif_dict[tag] = data

        gps_parsed = parse_gps(exif_dict.get("GPSInfo"))
        text_candidates = {
            "ImageDescription": exif_dict.get("ImageDescription"),
            "XPTitle": exif_dict.get("XPTitle"),
            "XPComment": exif_dict.get("XPComment"),
            "UserComment": exif_dict.get("UserComment")
        }
        result = {
            "DateTime": exif_dict.get("DateTime"),
            "DateTimeOriginal": exif_dict.get("DateTimeOriginal"),
            "Make": exif_dict.get("Make"),
            "Model": exif_dict.get("Model"),
            "Software": exif_dict.get("Software"),
            "GPSInfo": exif_dict.get("GPSInfo"),
            "GPSParsed": gps_parsed,
            "LocationTextCandidates": text_candidates,
            "AddressInExif": "EXIF 通常不包含可直接使用的完整地址，常见的是经纬度或无地址信息。"
        }
        if ensure.get("auto_installed"):
            result["AutoInstall"] = "Pillow auto-installed successfully"

        return result
    except Exception as e:
        return {"error": str(e)}


def batch_extract(paths, auto_install=False):
    results = []
    for p in paths:
        info = extract_exif(p, auto_install=auto_install)
        info["_path"] = p
        results.append(info)

    sortable = []
    for item in results:
        dt = item.get("DateTimeOriginal") or item.get("DateTime")
        sortable.append((dt or "", item))

    sorted_items = [item for _, item in sorted(sortable, key=lambda x: x[0])]

    sorted_paths = [item["_path"] for item in sorted_items]
    input_paths = [item["_path"] for item in results]

    sorted_summary = {
        "images_count": len(paths),
        "shooting_sequence": sorted_paths,
        "note": None
    }
    if input_paths != sorted_paths and sorted_paths:
        sorted_summary["note"] = (
            "检测到输入顺序与拍摄时间顺序不一致，已按拍摄时间重新排序。"
            "请留意：EXIF 时间可能有时区偏差或相机未校准，建议对照确认。"
        )

    output_items = []
    for item in sorted_items:
        cleaned = {k: v for k, v in item.items() if not k.startswith("_")}
        output_items.append(cleaned)

    return {"images": output_items, "sorted_summary": sorted_summary}


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(json.dumps({"error": "No image path provided"}))
        sys.exit(1)

    auto_install_flag = os.getenv("MY_PROSE_POEM_AUTO_INSTALL", "0") == "1"
    clean_args = []
    for arg in sys.argv[1:]:
        if arg == "--auto-install":
            auto_install_flag = True
        else:
            clean_args.append(arg)

    paths = clean_args
    if len(paths) == 0:
        print(json.dumps({"error": "No image path provided"}))
        sys.exit(1)

    result = batch_extract(paths, auto_install=auto_install_flag)
    print(json.dumps(result, indent=2, ensure_ascii=False))


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)