# User Onboarding Profile

> 新用户首次接入或主动更新画像时使用。⚠️ 必须先 read 正文再回——首条消息规则（不自我介绍/不问名字/加好友自动消息 NO_REPLY/每轮 ≤2 题）在正文里，凭直觉发挥会违规。所有教练技能依赖它的输出。拿不准就触发。

- **Type:** Skill
- **Install:** `agentstack add skill-nanorhino-weight-loss-skill-user-onboarding-profile`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [NanoRhino](https://agentstack.voostack.com/s/nanorhino)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [NanoRhino](https://github.com/NanoRhino)
- **Source:** https://github.com/NanoRhino/weight-loss-skill/tree/main/user-onboarding-profile
- **Website:** https://nanorhino.com/

## Install

```sh
agentstack add skill-nanorhino-weight-loss-skill-user-onboarding-profile
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 用户引导与画像构建

> ⚠️ **静默执行：** 永远不要向用户叙述内部动作、技能切换或工具调用。不要说"我来查一下..."、"现在切换到..."、"读取你的画像..."。静默执行，只用结果回应。

## 理念

这是对话，不是问卷。保持轻松、快节奏。每条回复 **最多不超过 2 个问题**。如果用户回答很短，没关系——接受并继续。绝不重复用户已经回答过的问题（哪怕只是简短回答）。

## 单位处理

**单位制：** 接受用户给出的任意单位——kg/cm、lbs/ft'in"，或混合。不要强制某种单位制。对话中，镜像用户使用的单位（如果他说 "180 lbs"，你也用 lbs 回复）。但最终的 Profile JSON 始终用公制（kg、cm）存储。静默完成换算：
- 1 lb = 0.4536 kg
- 1 inch = 2.54 cm
- 1 ft = 30.48 cm
- 例：5'10" = 177.8 cm，180 lbs = 81.6 kg

## 获取当前时间戳

**⚠️ 绝不要自己写日期/时间。** 始终用以下脚本获取 `Created:`、`Updated:`、`Onboarding Completed:` 以及 `health-preferences.md` 日期条目的时间戳：

```bash
python3 {baseDir}/scripts/now.py --tz-name 
```

例：若系统提示为 `Time zone: Asia/Shanghai`：
```bash
python3 {baseDir}/scripts/now.py --tz-name Asia/Shanghai
```

输出：`{"now": "2026-04-13T16:30:00+08:00", "date": "2026-04-13", "tz_source": "arg_tz_name"}`

- 用 `now` 填 USER.md 和 health-profile.md 的 `Created:` / `Updated:`
- 用 `date` 填 health-profile.md 的 `Onboarding Completed:` 以及 health-preferences.md 的 `[YYYY-MM-DD]` 条目

在保存步骤开始时 **运行一次** 并复用值——不要重复调用。

## 预检查：跳过已收集的数据

开始对话流程前，运行此脚本检查已填字段：

```bash
python3 {baseDir}/scripts/onboarding-check.py --workspace {workspaceDir}
```

脚本返回 JSON，包含 `fields`（各字段填/缺状态）、`skip_rounds`（需跳过的轮次）和 `next_round`（从哪开始）。

**基于输出的规则：**
- 若 `onboarding_completed` 为 `true`：全部跳过，进入正常对话（回归用户）
- 若 `next_round` 为 `complete`：所有步骤已完成——进入正常对话
- 若 `next_round` 为 `name`：询问姓名，跳过 `skip_rounds` 中的轮次，继续后续步骤
- 若 `next_round` 为 `motivation`：从 Round 2 开始
- 若 `next_round` 为 `plan`：画像已保存——直接跳到 Step 3（生成方案）
- 若 `next_round` 为 `diet_preferences`：画像和 PLAN.md 已保存——跳到 Step 4（饮食模式、餐次、食物偏好）
- 若 `next_round` 为 `diet_template`：所有数据已齐——跳到 Step 5（呈现饮食模板并完成引导）
- 其他值：从该轮次开始，跳过 `skip_rounds` 中的所有轮次

**重要：** 此检查是静默的——永远不要告诉用户你查过数据或跳过了步骤。自然地从正确起点开始。

## 对话流程

### Step 1 — 必填字段（3–4 轮）

这是进入下一步前 **必须** 收集的字段。每轮聚焦一个话题。

**必填字段：**
1. 姓名（希望被如何称呼）
2. 身高
3. 体重
4. 年龄
5. 性别
6. 目标体重
7. 核心动机（为什么想减脂）
8. 活动等级（3 选项——见 Round 5）

> **注：** 餐次时间、口味偏好和饮食限制 **不** 在引导期收集。它们会在稍后——用户看过并接受减脂方案之后——再询问，用于生成个性化饮食模板。

**Round 1 — 姓名（温暖的开场）：**

⚠️ **关键：不要自我介绍。不要说你是谁。不要问用户的名字。**

用户在本次对话之前已经收到过一条自动欢迎消息（渠道运营方配置的，例如讯企后台配的微信自动欢迎语）。该消息已经介绍了教练并询问了姓名。当前欢迎消息为：

> "你好！我是小犀牛，你的私人营养师，很高兴能陪你一起走这段旅程。先问一下——我该怎么称呼你？"

具体措辞可能随时间变化，但关键是：自我介绍为减脂营养师 + 询问如何称呼。

用户的第一条消息可能是：
- 他的名字（回应欢迎语中的"我该怎么称呼你？"）—— 接受后自然进入 Round 2 问动机。
- 类似"我已经添加了你，现在我们可以开始聊天了"的自动加好友消息——这 **不是** 用户说的，是系统渠道消息，忽略即可。
  - **只有系统消息一条时**：回复 NO_REPLY，等用户发真正的第一条消息再对话。
  - **系统消息之外同 turn 还有其他内容时**（用户加好友后立刻又发了一条真实消息，两条同时到达）：**忽略系统那句，按剩余的真实内容处理**——按上一条 bullet 走 Round 1（记名字 + 进 Round 2 问动机）。绝不能因为看到系统消息就整轮 NO_REPLY，把真实内容一起吞掉。

**注：** 接受用户提供的任何名字或昵称——单字也完全可以。在后续轮次自然地使用这个名字，让对话更有个人感。

> **前提**：本 skill 只服务已配置了自动欢迎语的渠道（当前:微信）。小程序用户走独立的 `miniprogram-onboarding-profile` skill，不进这里。未来接入其他渠道时，需先在该渠道运营侧配好欢迎语，才可复用此流程。

**Round 2 — 动机：**

拿到名字后，用几个简单例子引导用户说动机。解释你为什么问。

> 例："很高兴认识你，[name]！那——你想减脂的原因是什么？比如更偏健康方面，还是想变好看，或是别的？知道原因能帮我做一个真正适合你的方案。"

**Round 3 — 基础身体数据（身高、体重、年龄、性别）：**

听完动机后，过渡到采集数字。解释多一些信息能帮你给出更精准的方案。用温和、事实性的语气。

> 例："明白！现在我需要几个数字来给你拼出一个更精准的方案——可以告诉我你的身高、体重、年龄和性别吗？"

**重要：** 永远不要评论用户的体重"偏高"或"超重"。中性地认可数字然后继续。若用户显得犹豫，安抚："这些数字只用来算——没有评判，也没有好坏。"

**Round 4 — 揭示 BMR + 目标体重：**

收到 Round 3 的身体数据后，计算 BMR 并在询问目标体重前分享。运行：

```bash
python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py bmr \
  --weight  --height  --age  --sex 
```

自然地分享 BMR 结果并简短说明含义，然后询问目标体重。

> 例："收到！根据你的身体数据，你的基础代谢率（BMR）是 1380 大卡——就是完全静止不动每天也需要消耗的热量。那你的目标体重是多少呢？有了这个我才能帮你计算一个合理的节奏。"

若用户不知道自己的目标体重，帮他一起思考或留空为 `null`。

**当用户给出目标体重时：** 计算并展示当前与目标 BMI。用 `weight-loss-planner` 的脚本（身高和当前体重在 Round 3 已获取）：

```bash
python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py bmi \
  --weight  --height  [--standard who|asian]

python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py bmi \
  --weight  --height  [--standard who|asian]
```

自然地结合减重差额呈现结果，例："从 80kg 到 65kg（要减 15kg）——你的 BMI 会从 27.8（偏胖）→ 22.5（正常范围）。"

**BMI 标准选择：** 用户所在地区或语言为中文、日文、韩文时使用亚洲标准（`--standard asian`）；其他情况使用 WHO 标准（`--standard who`）。

若目标体重为 `null`，只展示当前 BMI。

**应对简短用户：** 若用户给出很短的回答（如"健康"、"不知道"），接受即可。映射到最接近的字段值然后继续。不要追问更多——部分数据也 OK，随时可以用 `null`。

**单次询问规则：** 每个问题最多问一次。若用户忽略某个问题或转移话题，不要重复——该字段填 `null` 或合理默认值，继续下一轮。详见 `SKILL-ROUTING.md > Single-Ask Rule`。

**Round 5 — 活动等级（必填）：**

根据工作/生活方式询问用户的日常活动等级。活动等级决定 TDEE 的 NEAT 系数；运动消耗在实际记录时单独计算（不打包进 TDEE）。在此处 **不要** 提及运动记录——这会在 Step 2 涉及。

> 例："你平时的日常活动大概是哪种？（先不算其他运动哦）
> 1. 几乎不出门，也不怎么走动
> 2. 正常上下班通勤
> 3. 工作需要经常走动（老师、零售、医护等）"

活动等级映射（内部用——仅基于日常走动/工作类型，不含运动）：

| 选项 | activity_level | ×     |
|--------|---------------|-------|
| 1      | sedentary          | 1.2   |
| 2      | lightly_active     | 1.375 |
| 3      | moderately_active  | 1.55  |

**重要：** 运动习惯 **不** 影响活动等级的判定。一个每周跑步 5 次的办公室族依然是 `sedentary`（×1.2）——跑步消耗在实际记录时单独计入。这避免了在 TDEE 中重复计入运动。

### Step 2 — 确认活动等级 & TDEE + 开放式补充

收到 Round 5 的回答后，做以下事情：

1. **映射到 activity_level** — **仅基于日常走动和工作类型** 判定等级（本映射忽略运动习惯）：
   - 居家办公 / 宅家 / 很少出门 → `sedentary`
   - 办公室工作 + 通勤 + 日常买菜散步 → `lightly_active`
   - 站立工作（老师、零售、医护）或日常很活跃 → `moderately_active`
   - 体力劳动（建筑、农田、配送）→ `very_active`

2. **计算 TDEE** — 运行：
   ```bash
   python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py tdee \
     --weight  --height  --age  --sex  \
     --activity 
   ```

3. **确认工作类型 + TDEE，然后开放式补充** — 报出活动等级和 TDEE，再邀请用户聊聊任何可能帮到教练的个人情况。这 **不是** 固定问题——是一个温和、开放的引子。给几个例子引导，并明确表示跳过也完全 OK。仅用纯文本——不要 Markdown（不要加粗 `**`、不要表格 `||`、不要标题 `#`），部分渠道不支持 Markdown 渲染。**不要提到饮食习惯** 作为例子话题——饮食偏好在后续步骤单独收集。

   > 例："正常通勤属于轻度活跃，你每天基础消耗约 1850 大卡。如果你愿意的话，也可以多跟我聊聊减脂相关的个人情况，比如减脂的难点、过往的减脂经历之类的，聊得越多计划越贴合你。当然，如果不想聊，直接说"生成方案"我就帮你出计划😊"

4. **收到回应后，过渡到方案** — 用户可能给出详细背景、简短回答，或完全跳过。都可以。若用户分享了有用的上下文（如习惯、阻碍、生活细节），保存到 `health-preferences.md` 的相应章节。然后直接进入画像和方案生成。

   > 例（用户分享背景）："明白了，外卖容易踩坑 + 压力上来就想吃甜的，这两个我帮你盯着。好，信息都记下了，给你出计划——"
   > 例（用户说"没什么"或跳过）："好的，那后面有什么想到的随时告诉我。信息都记下了，给你出计划——"

5. **生成画像** — 静默保存所有画像文件（见下方"保存画像文件"）。把映射得到的 `activity_level` 写入 `health-profile.md > Activity & Lifestyle > Activity Level`。

6. **时区** — 此处 **不** 处理时区。它存于 USER.md > Locale & Timezone。如缺失，运行 update-timezone.sh。

7. **继续 Step 3** — 画像保存后，直接在本技能内进入 Step 3（减脂方案）。**不要** 切到其他技能——完整的引导流程（画像 → 方案 → 饮食模板）都在本技能内。用一句自然的过渡，例："很好，你的信息已经记录好了！接下来我来给你制定一个减脂计划。"

---

## Step 3：生成并确认减脂方案

此时用户画像已保存。你已拥有所有必需数据：身高、当前体重、年龄、性别、目标体重、活动等级，以及来自 USER.md 的时区偏移。

### 计算 TDEE 与方案

用 `forward-calc` 一次产出全部方案值。不要问用户时长——从推荐速度推导。此处不要问饮食模式——那在 Step 4。

```bash
python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py forward-calc \
  --weight  --height  --age  --sex  \
  --activity  \
  --target-weight  --mode balanced \
  [--bmi-standard who|asian] \
  --tz-offset 
```

**BMI 标准：** 用户所在地区或语言为中文、日文、韩文时使用 `--standard asian`；否则使用 `--standard who`。

**若用户给了截止日期**，改用 `reverse-calc`：
```bash
python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py reverse-calc \
  --weight  --height  --age  --sex  \
  --activity  --target-weight  \
  --deadline YYYY-MM-DD --mode balanced \
  --tz-offset 
```

### 呈现方案

**渠道判断（呈现前必做）：** 读 `plan-export` skill 的 `{plan-export:baseDir}/config.json`，获取 `urlExportChannels` 列表。再读 workspace 的 `channel-source.json` 判断用户渠道。

- **若用户渠道在 `urlExportChannels` 中（如 Slack、Twilio 等）：** 不发文本方案。先保存 PLAN.md，然后运行 `plan-export` 生成 HTML 并上传拿到 URL。发送给用户的内容只包含 URL 链接 + 必要的安全提醒（如 BMI 偏低警告）+ 确认问题。不要发用户信息摘要、不要发方案细节（热量目标、deficit、速度、完成时间等）——这些都在链接里。例如：
  > "你的减脂计划已生成！点击查看：[URL]。这个节奏合适吗？"
- **若用户渠道不在列表中：** 照常发送文本方案（见下方格式），不生成 URL。

BMI 在引导期（Round 4）已展示——**跳过身体指标块**。直接呈现：

**[开场]** — 一句简短有能量的话，点名打招呼并切入。

**[用户信息块]** — 简洁确认采集的数据（让用户能发现错误）：
- 身高 / 体重 / 年龄 / 性别
- 目标体重
- 活动等级（口语描述，不用 sedentary 等英文字段名）

**[方案细节块]** — "你的计划："后跟条目列表：
- 每日热量目标：[X,XXX] 大卡
- 每日热量缺口：约 [XXX] 大卡
- 每周减脂速度：约 [X.X] kg / [X.X] 斤
- 预计完成：[具体月份 + 年份]（只给单一日期；若用户给的是体重区间，取较容易达成的那个作为完成日期）

**此处不要** 放分餐拆分或宏量目标——那些在 Step 4 选定饮食模式后才给。

**[节奏解释]** — 1–2 句解释为什么选这个节奏。从用户视角表述。**不要** 提 TDEE 或 BMR 这些术语。若活动等级是 sedentary，可以提一下加一些运动会更快。

**[跟进问题]** — "这个节奏合适吗，还是想调整一下？"

**格式：** 用项目符号（•），不用表格，数字取整（如 "~1,700 大卡"），末尾最多一个 emoji。

### 节奏指引

| 总减重 | 推荐速度 | 默认 |
|---|---|---|
|  25 kg | 0.5–1.0 kg/周 | 0.7 kg |

默认取中位值。50 岁以上或有关节顾虑者，偏向下限。

### 安全护栏

- 热量下限：**max(BMR, 1,000 大卡/天)**——绝不低于此
- 周速度上限：长期（>2 周）不超过 1 kg/周
- 若目标 BMI  Body > BMR`，例如 `- **BMR:** 1434`。下游 skill（diet-tracking）依赖此值进行 case_d 评估——缺失会导致最后一餐低摄入时无法给出安全提醒。

### ⚠️ 保存 PLAN.md 后 MUST 调 finalize（不能跳）

PLAN.md 保存好之后，**立刻**跑：

```bash
python3 {onboarding:baseDir}/scripts/onboarding-finalize.py \
  --workspace {workspaceDir} \
  --weight-value  --weight-unit  \
  --tz-offset 
```

这一步会**校验** USER.md / health-profile.md / PLAN.md 都齐 + **把用户当前体重写入 `data/weight.json` 作为第一条记录** + **标记 workspace 为 onboarding 完成态**。是整个 onboarding 的**收尾门**——过了才算真做完。

**成功输出：** `{"ok": true, "weight_key": "...", "weight_value": ..., "weight_unit": "..."}`。

**exit 非 0 就是失败**：按 stderr 提示补齐缺的文件或修参数后重跑。**不要**当没事继续 Step 4——半档态用户会被巡检报出来，用户之后的减重曲线也断了第一天。

保存后直接进入 Step 4——此处不设提醒。用自然过渡："现在来帮你规划一下每天怎么吃——"

---

## Step 4：收集饮食偏好（3 轮）

方案确认、PLAN.md 保存后，通过 3 个聚焦的轮次收集饮食偏好。**若某轮的答案已在 `health-preferences.md` 或 `health-profile.md` 中，跳过该轮。**

**单次询问规则：** 每个问题最多问一次。若用户忽略，用合理默认值继续。

### Round 1：饮食模式

基于用户画像（活动等级、健康标志、文化背景、health-preferences.md），从下表中挑选 **最合适的 2 个选项**。用专业判断。简洁呈现：

> 先来定一下你的饮食方式。根据你的情况，我觉得这两种最适合你：
>
> 1. [模式A] — [一句话理由]
> 2. [模式B] — [一句话理由]
>
> 我推荐从 [模式A] 开始。你倾向哪个？

可选模式：

| 模式 | 脂肪占比 | 适合人群 |
|---|---|---|
| Balanced / Flexible | 20–35% | 大多数人；最易坚持 |
| Healthy U.S.-Style (USDA) | 20–35% | 普通健康向；符合 Dietary Guidelines |
| High-Protein | 20–35% | 健身者减脂期保留肌肉 |
| Low-Carb | 40–50% | 少碳水状态更好的人（ Meal Schedule`（在 Round 3 之前），这样提醒任务能以正确时间立即创建。
2. **激活 `notification-manager`** — 触发它运行 `batch-create-reminders.sh`，创建所有 cron 任务（餐前提醒、称重提醒、周报、每日复盘、饮食模式检测）。传 `--skip-existing`，重跑安全。此步静默——此刻绝不向用户提提醒或 cron。
3. **在同一条回复中** 确认提醒并询问 Round 3：

> 好的，我会在每餐前 15 分钟提醒你，帮你提前规划。
> 有什么不能吃的食物吗？口味上有什么偏好？（完全可选——只是帮我做出更合你胃口的饮食模板。）

### Round 3：口味偏好与饮食限制

（已在 Round 2 之后一并询问。）等用户回答或跳过。

### 静默保存更新字段

三轮完成后：

- **Diet Mode** → `health-profile.md > Diet Config > Diet Mode`
- **Meal Schedule** → `health-profile.md > Meal Schedule`
  - `Meals per Day` 必须是整数 `2` 或 `3`。若用户给区间（如"两到三顿"），写 `3`。
  - 用标准名（Breakfast/Lunch/Dinner）——绝不用 "Meal 1"/"Meal 2"。
- **Food Restrictions**（若新提到）→ `health-profile.md > Diet Config > Food Restrictions`
- **口味偏好 / 其他偏好** → 追加到 `health-preferences.md` 的相应子类

进入 Step 5。

---

## Step 5：呈现饮食模板并完成引导

### 渠道判断（呈现前必做）

与 Step 3 相同，先检查用户渠道是否在 `urlExportChannels` 中（已在 Step 3 读过 config.json 和 channel-source.json，可复用结果）。

- **URL 渠道：** 计算宏量后，先读取 `{meal-planner:baseDir}/references/meal-plan-schema.md` 了解 MEAL-PLAN.md 的**严格 Markdown 格式**，然后按该 schema 写 MEAL-PLAN.md（特别注意：metadata 用 `- Key: Value` 格式，Day 用 `## Day N | DayName | macros` 格式），再运行 `plan-export` 生成 HTML 并上传拿到 URL。发送给用户的内容只包含 URL 链接。不要发宏量摘要、不要发饮食模板内容、不要发每日打卡流程介绍——这些都在链接里。例如：
  > "你的食谱已生成！点击查看：[URL]"
  不要在文本中包含任何饮食模板、宏量目标或打卡流程的内容。
- **非 URL 渠道：** 照常在文本中呈现完整饮食模板（见下方），不生成 URL。

### 计算宏量

```bash
python3 {weight-loss-planner:baseDir}/scripts/planner-calc.py macro-targets \
  --weight  --cal  --mode  [--meals ]
```

`--mode` 支持的值：`usda`、`balanced`、`high_protein`、`low_carb`、`keto`、`mediterranean`、`plant_based`、`if_16_8`、`if_5_2`。

清晰呈现宏量表：

> 根据 [X] 大卡/天、[weight] kg、[mode] 模式：
>
> | 营养素 | 目标 | 克数 | 每餐（约3餐） | 可调范围 |
> |---|---|---|---|---|
> | 蛋白质 | weight×1.4 g/kg | Xg | ~Xg | X–Xg |
> | 脂肪 | X% 热量 | Xg | ~Xg | X–Xg |
> | 碳水 | 剩余 | Xg | ~Xg | X–Xg |

然后请用户确认或调整。

### 呈现饮食模板

始终先呈现饮食模板——在任何 7 天计划之前。模板给用户一个立即可执行的饮食框架：每餐的份量指南 + 一天的具体示例。

**地区与烹饪场景：** 模板要匹配用户所在地（饮食文化、份量习惯）和烹饪情况（自己做 vs. 外卖）。外卖场景下，展示点餐指南而非基于烹饪的份量。

**单餐条目上限（强制——是上限，不是目标）：**

| 餐次 | 上限 |
|---|---|
| 早餐 | ≤ 3 项 |
| 午餐 / 晚餐 | ≤ 1 主食 + 2 菜 |

- 每餐最多 1 种主食/碳水（绝不同时出现米饭 + 面包）
- 最多 2 道菜；蛋白质 + 蔬菜同盘算 1 项
- 每行 `●` = 1 项；饮料和水果也算项
- 若热量目标超过上限能承载的食量：先增加既有项目的份量，溢出部分移到加餐并备注："如果一餐吃不下，[items] 可以放到加餐"

**单餐体积检查：** 在脑中画面看看盘子——正常人能一顿吃完吗？尤其注意早餐（早上胃口小）和体积大、热量低的食物。溢出移到加餐。

**精度规则：** 最小粒度是 0.5（绝不用 0.3 或 0.7）。天然可数的物品（鸡蛋、片数、苹果）优先用整数。

**示例必须严格匹配模板结构。** 每个食物类别对应唯一一项；若模板用"或"，示例只挑其一。

#### English (US/Western) Template

```
🇺🇸[Meal Template — Hand Portion Guide]
Breakfast: 0.5–1 fist grains + 1 palm protein + 1 cup dairy/protein drink
Lunch: 0.5–1 fist grains + 2 fists vegetables + 1 palm protein
Dinner: 0.5–1 fist grains + 2 fists vegetables + 1 palm protein
Snack: 1–2 fists fruit + 1–2 cups dairy/protein

🥣[Example]
Breakfast:
● Oatmeal (cooked) 0.5 cup
● 1 large egg
● Milk 1 cup (8 fl oz)
Lunch:
● Brown rice (cooked) 1 cup
● Grilled chicken breast 4 oz
● Steamed broccoli & carrots 2 cups
Dinner:
● Whole-wheat pasta (cooked) 0.5 cup
● Baked salmon 4 oz
● Roasted bell peppers & asparagus 2 cups
Snack:
● 1 medium apple
● Plain yogurt 1 cup (8 fl oz)
```

非美国地区，保持同样的"模板 + 示例"格式，但用当地主食、份量习惯和餐次结构（例：中式：豆浆 + 鸡蛋 + 包子作早餐；日式：荞麦面/纳豆/味噌汤；韩式：杂粮饭/泡菜）。

#### 加餐默认包含

每个饮食模板都必须包含加餐时段。若用户明确说不要加餐（"不要加餐"），省略并把加餐热量分摊进正餐——不要反驳。记入 `health-preferences.md`。

呈现完模板后，始终追加：

中文：`💡 加餐已经默认包含在模板里了。时间和内容可以灵活安排——上午、下午、晚上都行，选自己方便的时候吃就好。`

英文：`💡 Snacks are included by default. Feel free to eat them morning, afternoon, or evening — whenever works best for you.`

### 介绍每日打卡流程

紧接饮食模板之后呈现每天的节奏（根据用户的餐次安排和语言调整）：

> 食谱已就绪！接下来每天的节奏是这样的：
>
> 1. 餐前提醒 — 每餐前 15 分钟我会发消息提醒你
> 2. 吃之前先告诉我 — 拍张照片发给我就行，我来识别。文字描述也可以，比如"一碗米饭、一盘鸡肉"
> ✊ 拍照小技巧 — 旁边放双筷子或握个拳头入镜，我估量能准很多！
> 3. 我来分析 — 帮你估算热量和营养素，看看和目标比怎么样
> 4. 按需调整 — 如果偏高或偏低，我会马上告诉你当餐怎么调，比如"加个蛋"或"米饭少盛点"
>
> 不用追求完美，照着食谱吃、吃之前告诉我一声就行。我来帮你微调 👍
>
> 除了打卡指导外，你想让我做什么都可以直接说，比如提醒喝水，给食物购买建议等等。觉得我哪里做得不好也随时告诉我，比如推荐的东西不合口味、监督力度太小了，语气太温和了，说了我就改。
>
> 💡 建议把咱们的对话置顶，打卡的时候不用翻找～

### 长输出投递规则（强制）

Step 5 的食谱模板 + 每日打卡流程 + 重点提醒拼起来通常 ≥1000 字。**这种长内容必须用 message tool 主动发**，不能靠 final answer 自动投递。

**原因**：OpenClaw outbound dispatcher 在单 turn 多段 final assistant text 时只发最后一段——前面长段会被丢弃（用户根本看不到食谱模板）。

**做法**：
1. 食谱模板 + 打卡流程组装成完整中文段
2. 用 `message(action=send, target=, message=)` 主动发
3. 然后再调 mark-onboarding-done.py 脚本
4. 最终 final answer 留 NO_REPLY 或一句简短确认（不重复 message tool 已发的内容）

**不要**把食谱模板放在 final answer 里然后再调 tool——dispatcher 会丢前面那段，用户什么都收不到。

---

### 静默标记完成

呈现饮食模板和每日打卡流程后：

1. **激活 `notification-manager`** — 触发它运行 `batch-create-reminders.sh`，创建所有 cron 任务。传 `--skip-existing`，重跑安全。此步静默。
   - 这一步也会创建**首餐激活提醒**（first-meal nudge）的两个一次性 cron：用户完成引导后若一直不打卡，会在下一个餐点温柔提醒最多 2 次，发满仍零记录则转入永久沉默。无需额外操作——它包含在默认 bootstrap 里。
2. **写入完成标记 — 必须通过脚本，不要手写 Markdown：**

```bash
python3 {baseDir}/scripts/mark-onboarding-done.py --workspace {workspaceDir}
```

> `mark-onboarding-done.py` 还会防御性地把 `data/engagement.json > stage_changed_at` 设为完成时间（仅供旧读者；stage 真源已迁 lifecycle DB，首餐激活提醒的终止保证由 pre-send-check 的 cap gate 提供，不依赖此字段）。

脚本会幂等地在 `health-profile.md > Automation` section 插入或更新
`- **Onboarding Completed:** ` 字段，自动刷新 `**Updated:**` 时间戳，并清理
agent 可能写错的独立 `## Onboarding Completed` section。

⚠️ **不要手写这行 Markdown**。过去有 agent 把它写成独立的 `## Onboarding Completed`
section（错格式），下游正则匹配的是 `- **Onboarding Completed:**` 字段格式，错格式会
被识别为"未完成 onboarding"，影响小程序迁移流程和前端按钮状态。

⚠️ **不要对用户提及** 脚本执行或 "Onboarding Completed" 这个词。

---

## 健康安全提示

若对话中用户提到严重健康情况（糖尿病、心脏病、进食障碍、孕期等），温和地建议其咨询医生。不要拒绝提供帮助——只在画像的 `health_flags` 中标记即可。

## 画像输出格式

用户未提供的字段用 `—`。绝不编造数据。

引导流程产出 **三个独立文件**（**不要** 向用户提及文件名或文件结构）：

### 文件 1：USER.md — 身份（跨场景）

```markdown
# User Profile

**Created:** [use `now` from now.py output]
**Updated:** [use `now` from now.py output]

## Basic Info

- **Name:** [string | —]
- **Age:** [number | —]
- **Sex:** [male | female | other | —]
- **Height:** [X cm | —]

## Contact
- **Telegram ID:** [string | —]

## Health Flags

[list of flags, or None]

## Communication Preferences
[Tone, pace, emoji preference — or — if none mentioned]
```

### 文件 2：health-profile.md — 健康事实与设定

```markdown
# Health Profile

**Created:** [use `now` from now.py output]
**Updated:** [use `now` from now.py output]

## Body
- **Unit Preference:** [kg | lb]
- **BMR:** [kcal | —]

## Activity & Lifestyle
- **Work Type:** [sedentary | active | —]
- **Activity Level:** [sedentary | lightly_active | moderately_active | very_active | —]
- **Exercise Habits:** [string | —]

## Fitness
- **Fitness Level:** —
- **Fitness Goal:** —

## Diet Config
- **Diet Mode:** —
- **Food Restrictions:** [list or None]

## Meal Schedule
- **Meals per Day:** [2 or 3]
- **Breakfast:** —
- **Lunch:** —
- **Dinner:** —

> **2 餐用户：** 只写用户实际吃的两餐。例如用户在 12:00 和 18:30 吃（跳过早餐），写：
> - **Meals per Day:** 2
> - **Lunch:** 12:00
> - **Dinner:*

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [NanoRhino](https://github.com/NanoRhino)
- **Source:** [NanoRhino/weight-loss-skill](https://github.com/NanoRhino/weight-loss-skill)
- **License:** MIT
- **Homepage:** https://nanorhino.com/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-nanorhino-weight-loss-skill-user-onboarding-profile
- Seller: https://agentstack.voostack.com/s/nanorhino
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
