Skill: glorysunshine/expense-tracker-daily (version 1.0.2)

--- name: expense-tracker-daily description: | 智能记账助手。当用户提到记账、花钱、支出、消费、花费、记录开销、查账、统计支出、账单等意图时触发。 支持自然语言输入（"午饭花了35"、"打车25块"、"昨天买衣服200"），自动分类，多维度统计分析。 关键词：记账, 花钱, 支出, 消费, 花费, 账单, 查账, 统计支出, 记录开销, 钱花哪了, 月度统计. --- # 智能记账 ## 核心脚本 所有数据操作通过 `scripts/expense_tracker.py` 完成： ``` SKILL_DIR = {SKILL_DIR} PYTHON = python SCRIPT = "{SKILL_DIR}/scripts/expense_tracker.py" ``` ### 命令列表 | 命令 | 用途 | 示例 | |------|------|------| | `add` | 添加记录 | `--amount 35 --category 餐饮 --desc "午饭" --date 2026-04-09` | | `list` | 列出记录 | `--limit 20 --category 餐饮 --from 2026-04-01 --to 2026-04-30` | | `delete` | 删除记录 | `--id 3` | | `stats` | 统计分析 | `--period month --date 2026-04-09` | | `categories` | 查看分类 | （无参数） | | `summary` | 全局总览 | `--top 5` | ### 执行模板 ```bash python "{SKILL_DIR}/scripts/expense_tracker.py" <command> [options] ``` ## 处理流程 ### 1. 记账 从用户自然语言中提取：金额、分类、描述、日期。 **金额提取规则**（按优先级）： - `¥35` / `￥35` → 35 - `35元` / `35块` / `35块钱` → 35 - `花了35` / `花了35元` → 35 - `35.5元` → 35.5 - 纯数字 `35` → 35 **日期提取**： - "今天" → 当天 - "昨天" → 前一天 - "前天" → 前两天 - "上周X" / "上个月" → 对应日期 - 无日期关键词 → 默认今天 **分类判断**： 优先看用户是否明确说了分类（"餐饮花了35"），没有则根据描述中的关键词自动匹配。参考 `references/categories.md` 中的完整关键词表。无法匹配时归入"其他"。 **执行示例**： ```bash python "{SKILL_DIR}/scripts/expense_tracker.py" add --amount 35 --category 餐饮 --desc "午饭" --date 2026-04-09 ``` ### 2. 查询记录 ```bash # 最近20条 python "{SKILL_DIR}/scripts/expense_tracker.py" list --limit 20 # 按分类 + 日期范围 python "{SKILL_DIR}/scripts/expense_tracker.py" list --category 餐饮 --from 2026-04-01 --to 2026-04-30 ``` ### 3. 统计分析 ```bash # 本月统计 python "{SKILL_DIR}/scripts/expense_tracker.py" stats --period month # 本周统计 python "{SKILL_DIR}/scripts/expense_tracker.py" stats --period week # 某天统计 python "{SKILL_DIR}/scripts/expense_tracker.py" stats --period day --date 2026-04-09 ``` stats 返回 `by_category`（按分类金额）和 `daily`（按日期金额），用来生成人类可读的报告。 ### 4. 删除记录 先 list 找到要删除的记录 id，确认后执行： ```bash python "{SKILL_DIR}/scripts/expense_tracker.py" delete --id 3 ``` ### 5. 全局总览 ```bash python "{SKILL_DIR}/scripts/expense_tracker.py" summary --top 5 ``` ## 输出格式 记账成功后回复格式： ``` ✅ 已记录：餐饮 ¥35.00 — 午饭 (2026-04-09) ``` 查询/统计回复要求简洁，用列表而非表格（适配移动端）： ``` 📊 本月支出统计 💰 总计：¥1,280.50（23笔） 📂 分类排行： 餐饮 ¥580.00 (45.3%) 交通 ¥320.00 (25.0%) 购物 ¥200.00 (15.6%) 其他 ¥180.50 (14.1%) ``` ## 数据存储 数据存储在 `~/.qclaw/workspace/expense-tracker-data/expenses.json`，纯本地，不联网。

path: README.md
content: # expense-tracker-daily

> OpenClaw 智能记账 Skill — 用自然语言记账，AI 自动分类，多维度统计分析。

一句话记账，不用打开任何 App。对 AI 说"午饭花了35"，自动记录、自动归类。

## 特性

- 🗣️ **自然语言输入** — "午饭35"、"打车25块"、"昨天买衣服200" 都行
- 🏷️ **智能分类** — 9 大分类，100+ 关键词自动匹配
- 📊 **多维度统计** — 按日/周/月统计，按分类排行，每日趋势
- 🗑️ **完整 CRUD** — 添加、查询、删除，数据可控
- 💾 **纯本地存储** — 数据存本地 JSON，不联网、不传云端
- 📦 **零依赖** — 纯 Python 标准库，不需要安装任何第三方包

## 快速开始

### 前置要求

- [OpenClaw](https://github.com/nicepkg/openclaw) 已安装并运行
- Python 3.7+

### 安装

将整个 `expense-tracker-daily` 目录复制到 OpenClaw skills 目录：

```bash
cp -r expense-tracker-daily ~/.qclaw/skills/expense-tracker-daily
```

Windows:

```powershell
Copy-Item -Recurse expense-tracker-daily $env:USERPROFILE\.qclaw\skills\expense-tracker-daily
```

重启 OpenClaw Gateway 即生效。

### 使用

安装后直接用自然语言跟 AI 对话即可：

| 你说的 | AI 做的事 |
|--------|----------|
| 午饭花了35 | 记录：餐饮 ¥35.00 — 午饭 |
| 打车去公司25块 | 记录：交通 ¥25.00 — 打车去公司 |
| 昨天买衣服200 | 记录：购物 ¥200.00 — 买衣服（日期=昨天） |
| 本月花了多少 | 按月统计，分类排行 + 每日趋势 |
| 最近有什么餐饮支出 | 查询餐饮分类的记录列表 |
| 删掉刚才那笔 | 通过 id 删除指定记录 |
| 查一下所有分类 | 列出支持的 9 大分类 |

## 目录结构

```
expense-tracker-daily/
├── README.md                  ← 你正在看的文件
├── SKILL.md                   ← OpenClaw Skill 配置（AI 指令）
├── scripts/
│   └── expense_tracker.py     ← 核心引擎（数据存储 + 统计计算）
└── references/
    └── categories.md          ← 分类关键词映射表
```

### 文件说明

| 文件 | 作用 |
|------|------|
| `SKILL.md` | OpenClaw Skill 的入口文件，定义元数据、触发条件、处理流程。AI 读这个文件来知道怎么用这个 Skill |
| `scripts/expense_tracker.py` | Python CLI 工具，负责所有数据操作。AI 通过调用这个脚本来读写数据 |
| `references/categories.md` | 完整的分类关键词映射表，AI 用它来判断一句话属于哪个分类 |

## 数据存储

所有记账数据存储在本地：

```
~/.qclaw/workspace/expense-tracker-daily-data/
├── expenses.json    ← 所有支出记录
└── config.json      ← 用户配置（扩展分类等）
```

数据文件和 Skill 目录隔离——卸载 Skill 不会丢数据，重装 Skill 直接接着用。

### 数据格式（expenses.json）

```json
[
  {
    "id": 1,
    "amount": 35.0,
    "category": "餐饮",
    "description": "午饭",
    "date": "2026-04-09",
    "timestamp": 1775725459669
  }
]
```

## 支持的分类

| 分类 | 关键词示例 |
|------|-----------|
| 🍜 餐饮 | 午饭、外卖、火锅、奶茶、咖啡、烧烤... |
| 🚌 交通 | 打车、地铁、高铁、油费、停车... |
| 🛒 购物 | 超市、淘宝、买衣服、日用品、数码... |
| 🎮 娱乐 | 电影、游戏、演唱会、旅游、会员... |
| 🏠 住房 | 房租、水电、燃气、物业、宽带... |
| 🏥 医疗 | 看病、药、体检、挂号、保险... |
| 📚 教育 | 学费、培训、网课、教材、考试... |
| 🎁 人情 | 红包、份子、礼物、随礼、过节... |
| 📦 其他 | 无法匹配以上分类的支出 |

完整关键词表见 [`references/categories.md`](references/categories.md)。

## CLI 命令参考

`expense_tracker.py` 也支持直接命令行调用：

```bash
# 添加记录
python scripts/expense_tracker.py add --amount 35 --category 餐饮 --desc "午饭" --date 2026-04-09

# 查询记录
python scripts/expense_tracker.py list --limit 20
python scripts/expense_tracker.py list --category 餐饮 --from 2026-04-01 --to 2026-04-30

# 统计分析
python scripts/expense_tracker.py stats --period month
python scripts/expense_tracker.py stats --period week
python scripts/expense_tracker.py stats --period day --date 2026-04-09

# 全局总览
python scripts/expense_tracker.py summary --top 5

# 查看分类
python scripts/expense_tracker.py categories

# 删除记录
python scripts/expense_tracker.py delete --id 3
```

## 工作原理

```
用户: "午饭花了35块"
        │
        ▼
   ┌─────────────┐
   │  AI 解析层   │  ← SKILL.md 定义的规则
   │ 提取金额/    │     金额: 35
   │ 分类/日期    │     分类: 餐饮 (关键词匹配)
   └──────┬──────┘     日期: 今天
          │
          ▼
   ┌─────────────┐
   │ Python 引擎  │  ← expense_tracker.py
   │ 写入 JSON    │     纯本地，零依赖
   └──────┬──────┘
          │
          ▼
   ✅ 已记录：餐饮 ¥35.00 — 午饭 (2026-04-09)
```

AI 负责「理解人话」，Python 脚本负责「存数据」，各司其职。

## Roadmap

- [ ] CSV 导出（方便导入 Excel）
- [ ] 预算设置 + 超支提醒
- [ ] 多币种支持
- [ ] 云端同步（IMA / 腾讯文档 / Notion）
- [ ] 图表生成（支出趋势图）

## License

MIT


path: references/categories.md
content: # 分类关键词映射表

完整的关键词→分类映射，用于从用户描述中自动识别分类。

## 餐饮

午饭、晚饭、早饭、早餐、午餐、晚餐、宵夜、外卖、吃饭、餐厅、食堂、饭、面、粉、火锅、烧烤、烤肉、奶茶、咖啡、饮品、饮料、零食、小吃、水果、蛋糕、甜品、冰激凌、聚餐、请客、宴、酒、啤酒、菜、便当、盒饭、麻辣烫、冒菜、饺子、包子、馒头、煎饼、粥、拉面、寿司、披萨、汉堡、炸鸡、麻辣香锅、串串、螺蛳粉、酸菜鱼、烤鱼、日料、西餐、快餐、便当、下午茶

## 交通

打车、滴滴、出租、地铁、公交、高铁、火车、飞机、机票、车票、油费、加油、停车、过路费、高速、骑手、顺风车、摩的、单车、共享、地铁卡、一卡通、船票、签证（含交通场景）

## 购物

超市、淘宝、京东、拼多多、网购、购物、买、商城、衣服、裤子、鞋、包、化妆品、护肤品、日用品、家电、数码、手机、电脑、平板、耳机、充电器、家具、家居、厨具、文具、书籍、杂货

## 娱乐

电影、游戏、KTV、唱歌、酒吧、演出、演唱会、音乐节、门票、景点、旅游、旅行、会员、视频、音乐、直播、打赏、剧本杀、密室逃脱、桌游、网吧

## 住房

房租、租金、水电、电费、水费、燃气、物业、网费、宽带、维修、装修、家具安装、暖气费

## 医疗

看病、医院、药、挂号、体检、牙、眼科、保险、门诊、住院、手术、保健、中医、理疗

## 教育

学费、课程、培训、书、教材、考试、报名费、网课、知识付费、订阅、补习、兴趣班

## 人情

红包、礼物、份子、随礼、生日、结婚、满月、过节、春节、中秋、月饼、探望、丧事

## 其他

无法匹配以上任何分类的支出。


path: scripts/expense_tracker.py
content: #!/usr/bin/env python3
"""
expense_tracker.py — 智能记账数据引擎
用法:
  python expense_tracker.py add --amount 35 --category 餐饮 --desc "午饭" [--date 2026-04-09]
  python expense_tracker.py list [--limit 20] [--category 餐饮] [--from 2026-04-01] [--to 2026-04-30]
  python expense_tracker.py delete --id <id>
  python expense_tracker.py stats [--period month|week|day] [--date 2026-04-09]
  python expense_tracker.py categories
  python expense_tracker.py summary [--top 5]
"""

import argparse
import io
import json
import sys
import os
from datetime import datetime, timedelta
from pathlib import Path

# Fix Windows stdout encoding
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")

DATA_DIR = Path.home() / ".qclaw" / "workspace" / "expense-tracker-data"
DATA_FILE = DATA_DIR / "expenses.json"
CONFIG_FILE = DATA_DIR / "config.json"

# ── 分类关键词映射 ──────────────────────────────────────────
CATEGORY_KEYWORDS = {
    "餐饮": [
        "午饭", "晚饭", "早饭", "早餐", "午餐", "晚餐", "宵夜", "外卖",
        "吃饭", "餐厅", "食堂", "饭", "面", "粉", "火锅", "烧烤", "烤肉",
        "奶茶", "咖啡", "饮品", "饮料", "零食", "小吃", "水果", "蛋糕",
        "甜品", "冰激凌", "聚餐", "请客", "宴", "酒", "啤酒", "菜",
        "便当", "盒饭", "麻辣烫", "冒菜", "饺子", "包子", "馒头",
        "煎饼", "粥", "拉面", "寿司", "披萨", "汉堡", "炸鸡", "麻辣香锅",
    ],
    "交通": [
        "打车", "滴滴", "出租", "地铁", "公交", "高铁", "火车", "飞机",
        "机票", "车票", "油费", "加油", "停车", "过路费", "高速", "骑手",
        "顺风车", "摩的", "单车", "共享", "地铁卡", "一卡通",
    ],
    "购物": [
        "超市", "淘宝", "京东", "拼多多", "网购", "购物", "买", "商城",
        "衣服", "裤子", "鞋", "包", "化妆品", "护肤品", "日用品",
        "家电", "数码", "手机", "电脑", "平板", "耳机", "充电器",
        "家具", "家居", "厨具", "文具", "书籍",
    ],
    "娱乐": [
        "电影", "游戏", "KTV", "唱歌", "酒吧", "演出", "演唱会", "音乐节",
        "门票", "景点", "旅游", "旅行", "门票", "会员", "视频", "音乐",
        "网费", "话费", "充值", "VIP", "直播", "打赏",
    ],
    "住房": [
        "房租", "租金", "水电", "电费", "水费", "燃气", "物业", "网费",
        "宽带", "维修", "装修", "家具", "家电安装",
    ],
    "医疗": [
        "看病", "医院", "药", "挂号", "体检", "牙", "眼科", "保险",
        "门诊", "住院", "手术", "保健",
    ],
    "教育": [
        "学费", "课程", "培训", "书", "教材", "考试", "报名费",
        "网课", "知识付费", "订阅",
    ],
    "人情": [
        "红包", "礼物", "份子", "随礼", "生日", "结婚", "满月",
        "过节", "春节", "中秋", "月饼",
    ],
}

# ── 数据操作 ────────────────────────────────────────────────

def _ensure_dir():
    DATA_DIR.mkdir(parents=True, exist_ok=True)

def _load_data():
    _ensure_dir()
    if DATA_FILE.exists():
        try:
            return json.loads(DATA_FILE.read_text("utf-8"))
        except (json.JSONDecodeError, IOError):
            return []
    return []

def _save_data(expenses):
    _ensure_dir()
    DATA_FILE.write_text(json.dumps(expenses, ensure_ascii=False, indent=2), "utf-8")

def _next_id():
    expenses = _load_data()
    if not expenses:
        return 1
    return max(e.get("id", 0) for e in expenses) + 1

# ── 命令实现 ────────────────────────────────────────────────

def cmd_add(args):
    expense = {
        "id": _next_id(),
        "amount": round(float(args.amount), 2),
        "category": args.category or "其他",
        "description": args.desc or "",
        "date": args.date or datetime.now().strftime("%Y-%m-%d"),
        "timestamp": int(datetime.now().timestamp() * 1000),
    }
    expenses = _load_data()
    expenses.append(expense)
    _save_data(expenses)
    _out({
        "status": "ok",
        "expense": expense,
        "message": f"已记录: {expense['category']} ¥{expense['amount']} — {expense['description'] or '无备注'} ({expense['date']})"
    })

def cmd_list(args):
    expenses = _load_data()
    # 过滤
    if args.category:
        expenses = [e for e in expenses if e.get("category") == args.category]
    if args.from_date:
        expenses = [e for e in expenses if e.get("date", "") >= args.from_date]
    if args.to_date:
        expenses = [e for e in expenses if e.get("date", "") <= args.to_date]
    # 按日期倒序
    expenses.sort(key=lambda e: (e.get("date", ""), e.get("id", 0)), reverse=True)
    # 限制
    limit = args.limit or len(expenses)
    shown = expenses[:limit]
    _out({
        "status": "ok",
        "total": len(expenses),
        "shown": len(shown),
        "expenses": shown
    })

def cmd_delete(args):
    expenses = _load_data()
    target_id = int(args.id)
    before = len(expenses)
    expenses = [e for e in expenses if e.get("id") != target_id]
    after = len(expenses)
    if before == after:
        _out({"status": "error", "message": f"未找到 id={target_id} 的记录"})
    else:
        _save_data(expenses)
        _out({"status": "ok", "message": f"已删除 id={target_id} 的记录"})

def cmd_stats(args):
    expenses = _load_data()
    period = args.period or "month"
    ref_date = args.date or datetime.now().strftime("%Y-%m-%d")
    ref = datetime.strptime(ref_date, "%Y-%m-%d")

    # 时间范围
    if period == "day":
        start = ref
        end = ref
    elif period == "week":
        start = ref - timedelta(days=ref.weekday())
        end = start + timedelta(days=6)
    else:  # month
        start = ref.replace(day=1)
        next_month = ref.replace(day=28) + timedelta(days=4)
        end = next_month.replace(day=1) - timedelta(days=1)

    start_str = start.strftime("%Y-%m-%d")
    end_str = end.strftime("%Y-%m-%d")
    filtered = [e for e in expenses if start_str <= e.get("date", "") <= end_str]

    total = sum(e.get("amount", 0) for e in filtered)
    by_category = {}
    daily = {}
    for e in filtered:
        cat = e.get("category", "其他")
        by_category[cat] = by_category.get(cat, 0) + e.get("amount", 0)
        d = e.get("date", "")
        daily[d] = daily.get(d, 0) + e.get("amount", 0)

    # 排序
    by_category = dict(sorted(by_category.items(), key=lambda x: x[1], reverse=True))

    _out({
        "status": "ok",
        "period": period,
        "period_label": {"day": "日", "week": "周", "month": "月"}[period],
        "date": ref_date,
        "range": {"start": start_str, "end": end_str},
        "total": round(total, 2),
        "count": len(filtered),
        "by_category": {k: round(v, 2) for k, v in by_category.items()},
        "daily": {k: round(v, 2) for k, v in sorted(daily.items())},
    })

def cmd_categories(_args):
    _out({
        "status": "ok",
        "categories": list(CATEGORY_KEYWORDS.keys()) + ["其他"],
        "keywords_map": {k: v[:10] for k, v in CATEGORY_KEYWORDS.items()},  # 只返回前10个关键词做预览
    })

def cmd_summary(args):
    expenses = _load_data()
    top = args.top or 5
    # 总计
    total = sum(e.get("amount", 0) for e in expenses)
    count = len(expenses)
    # 按类别
    by_category = {}
    for e in expenses:
        cat = e.get("category", "其他")
        by_category[cat] = by_category.get(cat, 0) + e.get("amount", 0)
    by_category = dict(sorted(by_category.items(), key=lambda x: x[1], reverse=True)[:top])
    # 最近5笔
    recent = sorted(expenses, key=lambda e: e.get("timestamp", 0), reverse=True)[:5]

    _out({
        "status": "ok",
        "total_expenses": round(total, 2),
        "total_records": count,
        "top_categories": {k: round(v, 2) for k, v in by_category.items()},
        "recent": recent,
    })

# ── 工具 ────────────────────────────────────────────────────

def _out(data):
    print(json.dumps(data, ensure_ascii=False, indent=2))

# ── CLI ─────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="expense-tracker CLI")
    sub = parser.add_subparsers(dest="command")

    # add
    p_add = sub.add_parser("add", help="添加记录")
    p_add.add_argument("--amount", "-a", required=True, help="金额")
    p_add.add_argument("--category", "-c", help="分类")
    p_add.add_argument("--desc", "-d", default="", help="描述")
    p_add.add_argument("--date", help="日期 YYYY-MM-DD")

    # list
    p_list = sub.add_parser("list", help="列出记录")
    p_list.add_argument("--limit", "-n", type=int, help="数量限制")
    p_list.add_argument("--category", "-c", help="按分类过滤")
    p_list.add_argument("--from", dest="from_date", help="起始日期")
    p_list.add_argument("--to", dest="to_date", help="结束日期")

    # delete
    p_del = sub.add_parser("delete", help="删除记录")
    p_del.add_argument("--id", required=True, help="记录ID")

    # stats
    p_stats = sub.add_parser("stats", help="统计分析")
    p_stats.add_argument("--period", "-p", choices=["day", "week", "month"], default="month")
    p_stats.add_argument("--date", help="参考日期 YYYY-MM-DD")

    # categories
    sub.add_parser("categories", help="查看分类")

    # summary
    p_sum = sub.add_parser("summary", help="总览")
    p_sum.add_argument("--top", "-t", type=int, default=5, help="Top N 分类")

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        sys.exit(1)

    cmds = {
        "add": cmd_add,
        "list": cmd_list,
        "delete": cmd_delete,
        "stats": cmd_stats,
        "categories": cmd_categories,
        "summary": cmd_summary,
    }
    cmds[args.command](args)

if __name__ == "__main__":
    main()


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