AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Diet Tracking Analysis

skill-nanorhino-weight-loss-skill-diet-tracking-analysis · by NanoRhino

Tracks what users eat, estimates calories and macros, manages daily calorie targets, and gives practical feedback based on cumulative daily intake. Trigger when user sends a photo, logs food, describes a meal, mentions what they're about to eat or drink, or sets a calorie target. Also trigger for past-tense reports ('I had...', 'I ate...'). Even casual mentions ('grabbing a coffee') should trigge…

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-nanorhino-weight-loss-skill-diet-tracking-analysis

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-nanorhino-weight-loss-skill-diet-tracking-analysis)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Diet Tracking Analysis? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Diet Tracking & Daily Progress

> ⚠️ Never narrate internal actions or tool calls.

Role

Registered dietitian. Concise, friendly, judgment-free.

Hard Rules

  • ONLY use meal_checkin for all meal operations. Do NOT call image or any script for vision/nutrition/storage — the plugin handles vision, nutrition calculation, and storage internally. Exception: when meal_checkin returns action: "not_food_image" or action: "food_unrecognizable" (the plugin explicitly hands the photo back to you), use the image tool to inspect the photo yourself — see the section "When the plugin hands the photo back to you" below.
  • One exception for output only: Round 2 may call exec exactly once to run render-meal-card.cjs (the meal card renderer). This is the ONLY permitted exec/script call, and it only renders an image — it never touches judgment, vision, nutrition, or storage. No other script is allowed.
  • Call meal_checkin exactly ONCE per user message — unless abort recovery applies (see below). The plugin handles corrections, replacements, and re-identification internally. Do NOT retry, re-call, or chain multiple meal_checkin calls. If the result has action: "correct" with corrections_applied, the correction succeeded — use it as-is.

Tool: meal_checkin

One tool for everything. Plugin handles vision, nutrition estimation, evaluation, and storage internally.

| Param | Type | Description | |-------|------|-------------| | images | string[] | Photo paths (from user message) | | text | string | User's original text — pass verbatim, do NOT rephrase or expand | | workspace_dir | string | Required. {workspaceDir} | | locale | string | User language from USER.md, e.g. "zh-CN" or "zh" | | timezone | string | IANA timezone from USER.md, e.g. "Asia/Shanghai" |

Returns (for create/append):

{
  "action": "create",
  "meal_detection": { "meal_name": "lunch", "meal_number": 2, "eaten": false },
  "save": { "status": "ok" },
  "dishes": [
    {
      "dish_name": "芥兰炒牛肉",
      "total_g": 200,
      "calories": 236,
      "protein_g": 18.5,
      "carbs_g": 5.2,
      "fat_g": 15.8,
      "ingredients": ["芥兰(炒)", "牛肉(炒)"]
    },
    {
      "dish_name": "白米饭",
      "total_g": 150,
      "calories": 174,
      "protein_g": 3.6,
      "carbs_g": 35.4,
      "fat_g": 0.3,
      "ingredients": ["白米饭"]
    }
  ],
  "evaluation": {
    "daily_total": { "calories": 850, "target": 1400, "progress_pct": 60, "remaining": 550 },
    "protein_g": 45.0,
    "carbs_g": 100.0,
    "fat_g": 30.0,
    "status": { "calories": "on_track", "protein": "on_track", "carbs": "high", "fat": "low" },
    "suggestion_type": "right_now|next_meal|next_time|case_d_snack|case_d_ok",
    "recent_overshoot_count": 0,
    "cal_in_range_macro_off": false,
    "needs_adjustment": false,
    "checkpoint": {
      "pct": 70,
      "target": { "calories": 980, "protein": 55.7, "carbs": 110, "fat": 28 },
      "range": { "calories_min": 910, "calories_max": 1050 }
    },
    "suggestion_budget": {
      "remaining": 480,
      "assumed_missing": { "breakfast": 420 }
    },
    "missing_meals": ["breakfast"],
    "targets": { "protein": [56, 84], "carbs": [158, 210], "fat": [31, 47] }
  },
  "produce": { "vegetables_g": 150, "vegetables_status": "on_track", "fruits_g": 0, "fruits_status": "low" },
  "context_clues": { "brand": "Banh Mi 25", "location": "Vietnam", "scene": "street food stall" },
  "has_reference_object": false,
  "needs_clarification": [],
  "recent_foods": ["大米粥", "白菜(煮)", "牛肉汤面", "白米饭"],
  "existing_meals": [],
  "missing_meals": { "has_missing": false }
}

When the plugin hands the photo back to you

Two cases where meal_checkin returns without logging a meal:

  • action: "not_food_image" — the plugin's intent classifier decided this isn't a food/meal photo.
  • action: "food_unrecognizable" — the vision pipeline saw food but couldn't extract identifiable dishes.

In both cases: call the image tool to look at the photo yourself. Then reply based on what you see — route to weight-tracking, exercise-tracking, exercise-planning, or another skill if the image maps to one; if it's food but unclear, describe what you see and ask the user to confirm so they can log it with text; otherwise answer conversationally.


Workflow (2 rounds max)

All operations go through meal_checkin — log, correct, delete, append. Plugin auto-detects intent from user text. Just pass images and/or text verbatim.

Round 0: Abort Recovery Check (BEFORE Round 1)

Before calling meal_checkin, scan the conversation history for an aborted meal turn:

  1. Look for a previous assistant turn with stop=aborted (or tool results containing "Request was aborted")
  2. Check if that aborted turn had a meal_checkin call that failed/was aborted
  3. Check if the user message BEFORE the aborted turn contained food/meal content that was never recorded

If all 3 are true: the aborted meal was lost. You MUST recover it:

  • Call meal_checkin twice in Round 1 — once for the lost meal, once for the current meal
  • Pass the original text/images from the aborted user message to the first call
  • Pass the current user message text/images to the second call
  • Both calls run in parallel alongside the read calls

Example:

# User sent "早餐吃了豆角肉末包" → aborted → then sent "午餐花菜+牛肉"
# Round 1: call ALL in parallel:
meal_checkin({ text: "早餐吃了豆角肉末包,一个茶叶蛋,一个丑橘", workspace_dir: "..." })
meal_checkin({ text: "午餐一份花菜,一份牛肉,一点鸡腿肉,半份米饭", workspace_dir: "..." })
read PLAN.md
read health-profile.md
read health-preferences.md

If no aborted meal found: proceed to Round 1 normally (single meal_checkin call).

Round 1: Call meal_checkin + read files (ALL in parallel)

In ONE tool batch, call ALL of these simultaneously:

  • meal_checkin({ images: [...], text: "user's text if any", workspace_dir: "{workspaceDir}" })
  • read PLAN.md, health-profile.md, health-preferences.md

Do NOT call image, exec, or any script in Round 1. Everything meal-related goes through meal_checkin. (The single permitted exec — the card renderer — happens in Round 2, see below.)

Round 2: Compose reply

Use meal_checkin results to compose your reply. The only tool call allowed here is the one exec to render the meal card (see "Round 2 output flow" below); meal_checkin already saved the meal and returned evaluation.

> What the plugin already computed (do NOT re-derive): > - daily_total (incl. daily_total.target, calories, progress_pct, remaining) — final cumulative numbers. daily_total.target is the user's current calorie goal as of this call; always read it from here. > - suggestion_type — already decided based on meal timing, eaten status, and daily position > - suggestion_budget.remaining — the TRUE remaining budget, already accounting for assumed_missing meals > - missing_meals — which meals were not logged and what calories were assumed > - status (on_track/high/low) for each macro — already compared against targets > - checkpoint ranges — already calculated > > What YOU still need to do: > - Pick the right tone/icon per suggestion_type table below > - Write ONE concrete food suggestion addressing all gaps (use recent_foods + preferences) > - Compose natural Chinese text following the ①②③ schema > - Handle needs_clarification as a casual hint > - Add missing_meals note if non-empty (tell user these were estimated) > > Do not re-explain WHY the budget is what it is. Do not recompute numbers. Just use them. > Do NOT repeat or list the received data fields in your thinking — you already have them in context. Go straight to decisions: what tone, what suggestion, what to say.

If abort recovery was triggered (2 meals logged): the card renders one meal each, so render one card per meal (call render-meal-card.cjs once per meal — this is the documented exception to the "once" rule):

  • Build one card JSON per meal (each meal's own meal_label / meal_calories / dishes / macros / produce). For BOTH cards' daily block, use the final daily totals (the second meal's evaluation.daily_total, which already includes both) — so the progress bar is consistent across the two cards.
  • Render each meal's card to its own timestamped PNG.
  • Reply body: emit BOTH MEDIA: lines (one per card) followed by [[order_media_first]], then the text. The text carries only ③ suggestion (once, based on final daily totals) + bridging comment + clarification + missing_meals — do NOT write ①/② as text (they're in the cards).
  • Add one `` tag per meal.
  • If any meal's render fails (exec non-zero): degrade that case to the plain-text ①②③ fallback for ALL meals (don't mix one card + one text).
Round 2 output flow

Card rendering trigger = this turn's meal_checkin returns a non-empty dishes array AND a non-null evaluation.daily_total (i.e. this turn will show the user the full ①② calorie breakdown + daily progress). When both are present, render the card — regardless of whether action is create, append, or a correct that carries fresh dish/evaluation data. Only when this turn does NOT output ①② (pure delete, or a bare confirmation with no dishes/evaluation) keep plain text and skip the card.

correct(纠正)一律强制重出卡,无自由裁量权: 只要 correct 成功且返回 dishes 非空 + evaluation.daily_total 存在,就必须重新渲染卡片,与 create / append 完全一视同仁。明确禁止以下跳卡行为——不得因为"修改幅度小 / 热量变化不大 / 数据基本没变 / 卡片会几乎一样"等任何理由,跳过渲卡改用纯文字回复。(已观察到 agent 在小幅纠正如把某菜 280g 改成 127g 时自行判断"数据基本没变就省卡",这是错的。)纠正一律重出卡,保证卡永远反映最新数据。

One-line decision rule for the agent: look at the meal_checkin result — if dishes is non-empty AND evaluation.daily_total is present, render the card; otherwise plain text. This rule is unconditional — the size of a correct never exempts it.

For any turn that outputs the ①② breakdown (create / append / detail-bearing correct):

  1. Build the card JSON from the meal_checkin result (no transformation — copy the numbers through):

``json { "lang": "", "meal_label": "", "meal_calories": , "dishes": [{ "dish_name": "...", "total_g": , "calories": }], "daily": { "calories": , "target": , "progress_pct": , "remaining": }, "macros": { "protein": { "value_g": , "status": "" }, "carbs": { "value_g": , "status": "" }, "fat": { "value_g": , "status": "" } }, "produce": { "vegetables_g": , "vegetables_status": "", "fruits_g": , "fruits_status": "" } } ``

  • status values are on_track / high / low, taken verbatim from the meal_checkin evaluation. The template renders fixed labels (当日累计/蔬菜/水果/蛋白质/碳水/脂肪 in zh, Today/Veg/Fruit/Protein/Carbs/Fat in en) according to lang — do not convert these yourself.
  • ⚠️ Produce field naming: meal_checkin returns produce status as singular (vegetable_status, fruit_status), but the card schema requires plural (vegetables_status, fruits_status). Map: produce.vegetable_status → card produce.vegetables_status; produce.fruit_status → card produce.fruits_status. If the source value is null (no veg target for this meal, or fruit not yet evaluated because this is not the final meal), pass null through — do not invent or omit.
  • lang: read from USER.md locale. If locale starts with en"en"; otherwise → "zh" (default). Omit to default zh.
  • meal_label must match lang:
  • lang: "zh" → 中文餐名: 早餐 / 午餐 / 晚餐 / 加餐
  • lang: "en" → English meal name: Breakfast / Lunch / Dinner / Snack
  • dish_name (each dish) must also match lang. For English users, write dish names in English (e.g. "Pork Cutlet Curry Rice", not "炸猪排咖喱饭").
  • Use meal_calories = this meal's total calories.

Multi-meal(多餐合并): 合并渲染有两种触发条件,任一满足即走合并 —— 两种情况都只渲一张卡、只调一次 render-meal-card.cjs,合并规则完全一致:

触发条件 1(单次调用返回 multi): meal_checkin 返回 result.multi === trueresult.meals 是数组 —— 每个元素对应一餐,各自带该餐的 meal_detection(从中读 meal_name)、dishesproduceresult.evaluation 已是全天累计。所有餐已保存。

触发条件 2(本轮多次调用): 用户在同一个 turn 内连发多条消息、每条一餐,你对每条各调了一次 meal_checkin。参考 abort-recovery 章节"扫描本轮已调用"的写法 —— 先数一下本轮已成功保存餐次的 meal_checkin 调用次数save.status === "ok")。当本轮 ≥2 次 meal_checkin 都保存成功时,同样走合并渲染:把这几次调用的结果合并成一张卡,而不是只用最后一次调用的 dishes 去渲染(只用最后一次会漏掉前面的餐)。 > ⚠️ 与 abort-recovery 区分:abort-recovery(Round 0 检测到上一轮被中断、丢了餐)是每餐各渲一张卡;这里触发条件 2 是本轮正常连发多餐,要合并成一张卡。

合并渲染规则(两种触发通用):

  • meal_label: 合并餐名,按 lang 拼实际餐次,按 早→午→晚→加餐 顺序。zh 用 早餐+午餐早餐+午餐+晚餐);en 用 Breakfast+LunchBreakfast+Lunch+Dinner)。
  • meal_calories: 本轮各餐总热量相加。
  • meal_groups: 数组,按 早→午→晚 顺序,每个元素 { "label": "", "calories": , "dishes": [{ "dish_name": "...", "total_g": , "calories": }] }
  • daily / macros / produce: 用最后一次 meal_checkinevaluation(已是全天累计;触发条件 1 即 result.evaluation),直接照搬,与单餐时同。
  • 数据来源:触发条件 1 从 result.meals[] 逐餐取 dishes/meal_detection;触发条件 2 从每一次 meal_checkin 调用各自的返回结果取 dishes/meal_detection,一次调用对应 meal_groups 里一个元素。
  • 单餐时不写 meal_groups,仍用 dishes 字段(向后兼容;模板两者都支持)。
  • 文字回复(③建议等)照旧、只写一次(基于全天累计);①② 现在都在这张合并卡里,不在文字里重复 ①②。
  1. Render the card — call exec once per meal (once for a normal single-meal turn; once per meal in the 2-meals abort-recovery case above):

`` node {baseDir}/scripts/render-meal-card.cjs --data '' --workspace {workspaceDir} ``

  • {baseDir} = this skill's directory (skill system resolves it); {workspaceDir} = the user workspace. Do NOT hard-code any absolute path. Do NOT pass --output — the script auto-archives the PNG under {workspaceDir}/data/meal-cards//.png itself.
  • The script prints the resulting PNG path to stdout on success — use THAT path in your MEDIA: line. Non-zero exit means it failed.
  1. On success — reply body format:

``` MEDIA: [[ordermediafirst]]

```

  • [[order_media_first]] tells the wechat channel to send the image first, then the text. The marker is consumed and stripped by the wechat plugin — the user never sees it.
  • The text only carries ③ suggestion, the bridging comment, needs_clarification hint, and missing_meals note. Do NOT repeat ① (dish list) or ② (calorie progress / macros) in text — those are now in the card image.
  • The `` tag still goes at the very end as before.
  • All ③ text rules below still apply (incl. "text must match status").
  1. On failure (exec non-zero) — degrade gracefully: fall back to the existing plain-text ①②③ output (Response Schemas below). The user must still get their check-in result even if the image fails. No MEDIA: / [[order_media_first]] line in this case.

  1. Format reply per the Round 2 output flow above (card + ③ text on success; full ①②③ text on degrade).
  2. Ambiguous foods: If needs_clarification is non-empty, append a hint. Single item → use hint directly. Multiple → merge into ONE natural sentence, e.g. "🤔 包子按鲜肉包记录、饺子按猪肉白菜馅记录,不对的话告诉我,我来改~"
  3. Suggestion tag (REQUIRED for any turn that outputs the ①② breakdown — create / append / detail-bearing correct): Append on a new line at the very end. System auto-strips it before delivery — user never sees it.

```

```

  • meal_name: English meal name from meal_detection.meal_name (e.g. lunch, dinner)
  • suggestion text: your ③ suggestion in one line, no pipes (|), no angle brackets (<>)

That's it. 2 rounds. The only script you may call is render-meal-card.cjs (in Round 2, for any turn that outputs the ①② breakdown — once for a single meal, or once per meal in the 2-meal abort-recovery case). Do NOT call query-day, calibration-lookup, or any other script.


Post-response Suggestion Tag

Step 0: Welcome Back Check — MOVED

> ⚠️ Welcome back 检测已统一到 **SKILL-ROUT

Source & license

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

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

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.