Shopping List
Manage the household shopping list. Add items with quantities, check them off,
organize by category. Data lives in skills/shopping-list/data/.
For full command reference and output formats, read skills/shopping-list/references/commands.md.
Before Every Command
Run these checks before every shopping list operation, in order:
- 1. If
skills/shopping-list/data/ directory does not exist, create it. - If
data/active.json does not exist, create it with:
{
"items": [],
"categories": ["Produce", "Dairy", "Meat", "Pantry", "Frozen", "Beverages", "Household", "Personal"],
"lastModified": "<current ISO timestamp>"
}
- 3. If
data/active.json exists but fails to parse as valid JSON, rename it to data/active.json.corrupt and create a fresh default file. Tell the user: "Shopping list data was corrupted. Saved backup as active.json.corrupt and started a fresh list." - If
data/config.json does not exist, create it with: INLINECODE7 - If
config.json has "user": null, ask the user: "What's your name? I'll use it to track who added each item." Store their answer (lowercased) in config.json before proceeding with the original command. - Run the archive process (see Archive section below).
All file paths in this document are relative to skills/shopping-list/ unless stated otherwise.
Data Files
data/active.json
The live shopping list. Contains all items that have not yet been archived.
CODEBLOCK1
Field rules:
- -
id -- Generate via uuidgen in bash. If that command is unavailable, construct an ID from the current ISO timestamp concatenated with 4 random hex characters (e.g. 2026-02-24T10:00:00Z-a3f1). - INLINECODE15 -- The item name as the user provided it, with leading/trailing whitespace trimmed. Preserve original casing for display purposes.
- INLINECODE16 -- Always computed as
name.toLowerCase().trim(). Recompute on every add or edit. This field is used for all matching and deduplication logic. - INLINECODE18 -- Optional. Defaults to
null when the user does not specify a quantity. When present, must be a number greater than 0. Fractional values are fine (e.g. 0.5 for half a pound). - INLINECODE20 -- Optional free text (e.g. "gallons", "lbs", "bunch", "bag"). Defaults to
null when not specified. - INLINECODE22 -- One of the values from the
categories array, or "Uncategorized". - INLINECODE24 -- Boolean. Starts as
false. Set to true when the user checks off the item. - INLINECODE27 -- ISO timestamp when the item was checked off.
null when not checked off. - INLINECODE29 -- Lowercase string from
config.json user field (e.g. "aj" or "shal"). - INLINECODE31 -- ISO timestamp when the item was first added.
- INLINECODE32 -- Optional free text for special instructions ("the organic one", "Costco size"). Defaults to
null. - INLINECODE34 (top-level array) -- The master list of known categories. Starts with 8 presets. Custom categories are appended when created.
- INLINECODE35 -- ISO timestamp. Updated on every write to this file.
data/config.json
Stores session-persistent user configuration.
CODEBLOCK2
- -
user -- Set on first interaction, persists across sessions. Lowercase string. - INLINECODE37 -- Reserved for Phase 2 restock suggestions. Ignore for now; preserve the field when writing.
data/history-YYYY-MM.json
Monthly archive of purchased items. One file per calendar month, created on demand.
CODEBLOCK3
The archivedDate field is added to each item when it moves from active to history. All original fields from the active item are preserved. A new file is created automatically when the first item is archived in a month that does not yet have a history file.
Core Operations
Adding Items
Parse the user's natural language into name, quantity, and unit for each item.
Category inference: Silently infer the category from the item name. Common mappings: milk/cheese/yogurt/butter go to Dairy, chicken/beef/fish to Meat, bananas/lettuce/onions to Produce, rice/pasta/flour to Pantry, dish soap/paper towels to Household, shampoo/toothpaste to Personal, beer/wine/juice to Beverages, frozen pizza/ice cream to Frozen. If the mapping is not obvious, assign "Uncategorized". Never prompt the user for a category. If the user wants to change it, they can say "move X to Y category".
Multi-item input: The user may add several items at once: "add eggs, bread, and 2 gallons milk" should produce 3 separate items. Parse commas and "and" as item separators. When the parsing is ambiguous (compound item names like "hot dog buns" near separators), ask the user rather than guessing wrong.
Duplicate handling: Before creating a new item, check if an item with the same normalizedName already exists in the active list.
- - If it exists and is not checked off, update the existing item's quantity (add to it if the user gives a new quantity, or leave unchanged if not). Do not create a duplicate.
- If it exists and is checked off, uncheck it (set
checkedOff: false, checkedOffDate: null) and update the quantity if specified.
New categories: If the user explicitly specifies a category that is not in the categories array, add it to the array and assign the item to it.
Set addedBy from config.json user value. Set addedDate to the current ISO timestamp. Set checkedOff to false and checkedOffDate to null. Update lastModified after writing.
Checking Off Items
Match items by normalizedName -- case-insensitive, partial match is acceptable (e.g. "milk" matches "whole milk" if it is the only milk item).
- - One match: Set
checkedOff to true and checkedOffDate to the current ISO timestamp. Confirm to the user. - Multiple matches: List the matching items and ask the user which one to check off.
- No match: Show the current list so the user can identify the correct item.
Checked-off items remain in active.json and display with strikethrough styling until the archive process moves them to history (after 24 hours).
Removing Items
Permanently delete an item from the active list. No archive record, no history entry. This exists for "I added this by mistake" corrections.
Match by normalizedName using the same matching rules as checking off. Confirm removal to the user.
Editing Items
Match the target item by normalizedName, then update the specified fields. Supported editable fields: name, quantity, unit, category, notes.
If the name changes, recompute normalizedName. If the category changes to one not in the categories array, add it. Update lastModified after writing.
Changing User
When the user says "switch to Shal", "I'm AJ", or similar, update the user field in config.json. All subsequent item additions will use the new user value for addedBy.
Archive Process
This process runs automatically at the start of every shopping list command, before handling the user's request.
- 1. Read
data/active.json. - Find all items where
checkedOff is true AND checkedOffDate is more than 24 hours ago. - If no items qualify, skip the rest of this process.
- Determine the current month string (e.g. "2026-02"). Read
data/history-2026-02.json. If it does not exist, create it with { "month": "2026-02", "archivedItems": [] }. - Append each qualifying item to the history file's
archivedItems array, adding an archivedDate field set to the current ISO timestamp. - Write the history file.
- Remove the archived items from the
items array in data/active.json. - Write the active file.
Write order matters: Always write the history file before the active file. If the process is interrupted between the two writes, the worst case is a harmless duplicate in history. Reversing the order risks data loss -- items removed from active but never written to history.
The shopping clear command triggers this same process immediately for all checked items, bypassing the 24-hour wait.
Displaying the List
When showing the shopping list, sort categories in this fixed order:
- 1. Preset categories in their defined order: Produce, Dairy, Meat, Pantry, Frozen, Beverages, Household, Personal
- Custom categories alphabetically after all presets
- "Uncategorized" always appears last
Skip any category that has zero items (considering only unchecked items for this purpose). Within each category, sort items alphabetically by normalizedName. Do not rely on the stored array order in JSON.
Checked-off items appear with strikethrough styling within their category, visually distinct from unchecked items. They remain visible until archived.
History Queries
When the user asks about past purchases ("what did we buy last month", "purchase history", "what did we get in January"), read the relevant data/history-YYYY-MM.json file(s). Present results grouped by date (most recent first) with item names and quantities. If no history file exists for the requested period, tell the user no purchase history was found for that month.
Note: history reflects the archive date, not the purchase date. An item checked off on Feb 28 but archived on March 1 appears in March's history.
购物清单
管理家庭购物清单。添加带数量的物品,勾选完成,按类别整理。数据存储在 skills/shopping-list/data/ 目录下。
完整命令参考和输出格式,请阅读 skills/shopping-list/references/commands.md。
每次执行命令前
每次执行购物清单操作前,请按顺序执行以下检查:
- 1. 如果 skills/shopping-list/data/ 目录不存在,则创建它。
- 如果 data/active.json 不存在,则用以下内容创建它:
json
{
items: [],
categories: [生鲜蔬果, 乳制品, 肉类, 干货, 冷冻食品, 饮品, 家居用品, 个人护理],
lastModified: <当前ISO时间戳>
}
- 3. 如果 data/active.json 存在但无法解析为有效的JSON,则将其重命名为 data/active.json.corrupt 并创建一个新的默认文件。告知用户:购物清单数据已损坏。已备份为 active.json.corrupt 并重新创建了清单。
- 如果 data/config.json 不存在,则用以下内容创建它:{ user: null, snoozes: {} }
- 如果 config.json 中的 user: null,则询问用户:你叫什么名字?我将用它来追踪谁添加了每个物品。 在继续执行原始命令之前,将用户的回答(小写)存储在 config.json 中。
- 执行归档流程(参见下面的归档部分)。
除非另有说明,本文档中的所有文件路径均相对于 skills/shopping-list/。
数据文件
data/active.json
当前购物清单。包含所有尚未归档的物品。
json
{
items: [
{
id: F47AC10B-58CC-4372-A567-0E02B2C3D479,
name: 全脂牛奶,
normalizedName: 全脂牛奶,
quantity: 2,
unit: 加仑,
category: 乳制品,
checkedOff: false,
checkedOffDate: null,
addedBy: aj,
addedDate: 2026-02-24T10:00:00Z,
notes: null
}
],
categories: [生鲜蔬果, 乳制品, 肉类, 干货, 冷冻食品, 饮品, 家居用品, 个人护理],
lastModified: 2026-02-24T10:00:00Z
}
字段规则:
- - id -- 在bash中使用 uuidgen 生成。如果该命令不可用,则从当前ISO时间戳连接4个随机十六进制字符构建ID(例如 2026-02-24T10:00:00Z-a3f1)。
- name -- 用户提供的物品名称,去除首尾空格。保留原始大小写用于显示。
- normalizedName -- 始终计算为 name.toLowerCase().trim()。每次添加或编辑时重新计算。此字段用于所有匹配和去重逻辑。
- quantity -- 可选。当用户未指定数量时默认为 null。存在时,必须为大于0的数字。支持小数值(例如0.5表示半磅)。
- unit -- 可选的自由文本(例如加仑、磅、把、袋)。未指定时默认为 null。
- category -- categories 数组中的值之一,或未分类。
- checkedOff -- 布尔值。初始为 false。当用户勾选物品时设为 true。
- checkedOffDate -- 物品被勾选时的ISO时间戳。未勾选时为 null。
- addedBy -- 来自 config.json 用户字段的小写字符串(例如aj或shal)。
- addedDate -- 物品首次添加时的ISO时间戳。
- notes -- 可选的自由文本,用于特殊说明(有机的、Costco大包装)。默认为 null。
- categories(顶层数组)-- 已知类别的总列表。以8个预设类别开始。创建自定义类别时追加。
- lastModified -- ISO时间戳。每次写入此文件时更新。
data/config.json
存储会话持久的用户配置。
json
{ user: aj, snoozes: {} }
- - user -- 首次交互时设置,跨会话持久化。小写字符串。
- snoozes -- 保留用于第二阶段补货建议。暂时忽略;写入时保留该字段。
data/history-YYYY-MM.json
已购买物品的月度归档。每个日历月一个文件,按需创建。
json
{
month: 2026-02,
archivedItems: [
{ ...与active item相同字段..., archivedDate: 2026-02-25T08:00:00Z }
]
}
当物品从活跃清单移至历史记录时,每个物品会添加 archivedDate 字段。保留活跃物品的所有原始字段。当第一个物品在尚无历史文件的月份被归档时,会自动创建新文件。
核心操作
添加物品
将用户的自然语言解析为每个物品的名称、数量和单位。
类别推断: 根据物品名称静默推断类别。常见映射:牛奶/奶酪/酸奶/黄油归入乳制品,鸡肉/牛肉/鱼肉归入肉类,香蕉/生菜/洋葱归入生鲜蔬果,大米/意面/面粉归入干货,洗洁精/纸巾归入家居用品,洗发水/牙膏归入个人护理,啤酒/葡萄酒/果汁归入饮品,冷冻披萨/冰淇淋归入冷冻食品。如果映射不明显,则分配未分类。切勿提示用户选择类别。如果用户想更改,可以说将X移到Y类别。
多物品输入: 用户可以一次添加多个物品:添加鸡蛋、面包和2加仑牛奶应生成3个单独的物品。将逗号和和解析为物品分隔符。当解析存在歧义时(如分隔符附近的复合物品名称热狗面包),询问用户而不是错误猜测。
重复处理: 在创建新物品之前,检查活跃清单中是否已存在具有相同 normalizedName 的物品。
- - 如果存在且未勾选,则更新现有物品的数量(如果用户提供了新数量则累加,否则保持不变)。不要创建重复项。
- 如果存在且已勾选,则取消勾选(设置 checkedOff: false、checkedOffDate: null)并在指定时更新数量。
新类别: 如果用户明确指定了不在 categories 数组中的类别,则将其添加到数组中并将物品分配给它。
从 config.json 用户值设置 addedBy。将 addedDate 设置为当前ISO时间戳。将 checkedOff 设置为 false,checkedOffDate 设置为 null。写入后更新 lastModified。
勾选物品
通过 normalizedName 匹配物品 -- 不区分大小写,允许部分匹配(例如,如果牛奶是唯一的牛奶物品,则匹配全脂牛奶)。
- - 一个匹配: 将 checkedOff 设置为 true,checkedOffDate 设置为当前ISO时间戳。向用户确认。
- 多个匹配: 列出匹配的物品并询问用户要勾选哪一个。
- 无匹配: 显示当前清单,以便用户识别正确的物品。
已勾选的物品保留在 active.json 中,并以删除线样式显示,直到归档流程将其移至历史记录(24小时后)。
删除物品
从活跃清单中永久删除物品。无归档记录,无历史条目。用于我误添加了这个的更正。
使用与勾选相同的匹配规则通过 normalizedName 进行匹配。向用户确认删除。
编辑物品
通过 normalizedName 匹配目标物品,然后更新指定字段。支持的可编辑字段:名称、数量、单位、类别、备注。
如果名称更改,重新计算 normalizedName。如果类别更改为不在 categories 数组中的类别,则添加它。写入后更新 lastModified。
切换用户
当用户说切换到Shal、我是AJ或类似内容时,更新 config.json 中的 user 字段。所有后续物品添加将使用新的用户值作为 addedBy。
归档流程
此流程在每个购物清单命令开始时自动运行,在处理用户请求之前执行。
- 1. 读取 data/active.json。
- 查找所有 checkedOff 为 true 且 checkedOffDate 超过24小时前的物品。
- 如果没有符合条件的物品,则跳过此流程的其余部分。
- 确定当前月份字符串(例如2026-02)。读取 data/history-2026-02.json。如果不存在,