Skill: sakurako-irs/clawreceipt (version 1.0.0)

--- name: clawreceipt description: Use this skill to extract receipt information, record expenses, track budgets, and manage financial receipts using the ClawReceipt CLI. --- # ClawReceipt Skill for OpenClaw Use this skill to interface with the ClawReceipt system whenever the user wants to process a receipt, check their monthly spending budget, or export financial data. ## Trigger Conditions Use this skill when the user wants to: - Upload or provide a picture/text of a receipt to be recorded. - Add a new expense, bill, or receipt to the database. - Check their current total spend against their monthly budget. - Update their monthly spending budget. - List recent receipts/expenses. ## Core Workflows ### 1) Process and Add a Receipt When the user provides a receipt image or details, extract the relevant fields (Date, Time, Store, Amount, Category) and use the CLI to save it: 1. Extract details using OCR or LLM vision capabilities. 2. Ensure you have `date` (YYYY-MM-DD), `time` (HH:MM:SS, optional), `store` (string), `amount` (float), and `category` (string, e.g., "อาหาร", "เดินทาง", "Shopping"). 3. Run the CLI command in the ClawReceipt directory: ```bash python run.py add --date "YYYY-MM-DD" --time "HH:MM:SS" --store "<Store>" --amount <Amount> --category "<Category>" ``` 4. Read the output to check if the budget was exceeded and relay that information to the user. ### 2) Check Budget Status If the user asks "How much budget do I have left?" or "What is my total spend?": 1. Run: ```bash python run.py budget ``` 2. Parse the output which includes "Total Spent" and "Target Budget" and accurately report to the user. 3. Alert the user if the status indicates "Exceeded Budget!". ### 3) Set New Budget If the user wants to set a new monthly budget (e.g., "Set my budget to 5000 baht"): 1. Run: ```bash python run.py budget --set <Amount> ``` 2. Confirm to the user that the budget has been updated successfully. ### 4) List All Receipts If the user asks to see history or recently recorded receipts: 1. Run: ```bash python run.py list ``` 2. Summarize the output table for the user. ### 5) Open TUI / Export (Interactive mode only) If the user asks to see a beautiful dashboard or export to CSV/Excel, you can suggest they run: ```bash python run.py tui ``` *(Note: As an agent, do not run the `tui` command directly as it will block the terminal. Instruct the user to run it themselves in a new terminal if they want to interact with the UI or manually export the data).* ## Required Checks Before Execution - Verify `run.py` is present in the ClawReceipt root folder before calling. - Ensure the active Python environment has the required dependencies (`rich`, `pandas`, `openpyxl`, `textual`, etc.) by utilizing `.\venv\Scripts\activate` if available. - Always quote string arguments like `--store "Full Name"` to prevent shell argument splitting. ## Troubleshooting - `UnicodeEncodeError`: Ensure the terminal is using UTF-8 encoding. The `run.py` handles this internally for Windows, but be aware if piping output. - `unrecognized arguments`: Ensure parameters like `--category` are explicitly mapped and don't contain unescaped quotes. ## Completion Checklist - Required fields accurately extracted and fed to the `add` command. - Command executed successfully (exit code 0). - Relevant confirmation (and budget alerts, if any) relayed back to the user clearly.

path: README.md
content: # ClawReceipt

`ClawReceipt` 🧾 is a specialized tool for OpenClaw to automatically capture, process, and manage receipts and financial data using a Retro Neon CLI & TUI.

Primary command: `run.py` (through Python)

## What It Does

- Extracts receipt details (Date, Time, Store, Amount, Category) from OpenClaw via CLI.
- Automatically tracks and monitors spending against a customized monthly budget.
- Triggers `Exceeded Budget!` alerts when total expenses cross the threshold.
- Provides a stunning Terminal UI (TUI) to browse and analyze receipts history.
- Exports receipt data seamlessly to `CSV` or `Excel` formats.
- Integrates flawlessly as an OpenClaw Skill via `SKILL.md`.

## Requirements

- Python `>=3.10`
- Windows (for specific Unicode CLI setup) or Linux/macOS
- OpenClaw CLI (`openclaw`) for triggering the skill

## Install

Clone the repository and prepare your virtual environment:

```bash
git clone https://github.com/OpenKrab/ClawReceipt.git
cd ClawReceipt
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt
```

## Quick Start

Initialize your budget (e.g. 5,000 Baht):

```bash
python run.py budget --set 5000
```

Add a new receipt (typically triggered via OpenClaw):

```bash
python run.py add --date "2026-02-27" --time "15:30:00" --store "Seven Eleven" --amount 120.50 --category "อาหาร"
```

Launch the interactive Terminal Dashboard:

```bash
python run.py tui
```

## Main Commands

```bash
# Add a new receipt record
python run.py add --date <YYYY-MM-DD> --time <HH:MM:SS> --store <Name> --amount <Amount> --category <Category>

# List all receipts in the terminal archive
python run.py list

# Open interactive TUI Dashboard
python run.py tui

# Set overall monthly budget
python run.py budget --set <Amount>

# Check current spent vs budget status
python run.py budget

# Silent budget alert check (exit 1 if exceeded, 0 if within)
python run.py alert
```

## Paths Used by Default

- SQLite Database path: `data/receipts.db`
- Source Code: `src/`

## For OpenClaw Agents

- Check `SKILL.md` for proper instructions on integrating standard CLI triggers into OpenClaw's context.
- Avoid running the `tui` command as an automated agent, as it blocks the terminal input indefinitely. Use `add`, `budget`, or `list` for CI/CD or Agentic behavior.

## Development

```bash
pip install -r requirements.txt
# Test the database
python src/db.py
# Test the components
python run.py list
```

## License

MIT


path: requirements.txt
content: textual
rich
pandas
openpyxl
pydantic
pillow
python-dotenv


path: run.py
content: import sys
import os

# Add src folder to the path to prioritize our local modules
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src'))

from cli import main

if __name__ == "__main__":
    main()


path: src/cli.py
content: import argparse
import sys
import os

if sys.stdout.encoding != 'utf-8':
    sys.stdout.reconfigure(encoding='utf-8')

from rich.console import Console
from rich.table import Table

from db import init_db, add_receipt, get_budget, set_budget, get_total_spent, get_receipts
from styling import print_banner, print_section, print_success, print_error, print_warning, print_info, print_key_value, COLORS

def main():
    init_db()
    parser = argparse.ArgumentParser(description="ClawReceipt 🧾 - ตัวช่วยจัดการใบเสร็จ (รับข้อมูลจาก OpenClaw)")
    
    subparsers = parser.add_subparsers(dest="command", help="คำสั่งที่ใช้งานได้")

    # Command: add
    parser_add = subparsers.add_parser("add", help="เพิ่มใบเสร็จใหม่ (สำหรับ OpenClaw 调用)")
    parser_add.add_argument("--date", required=True, help="วันที่ตัวอย่างเช่น 2026-02-27")
    parser_add.add_argument("--time", required=False, default="", help="เวลาตัวอย่างเช่น 15:30:00")
    parser_add.add_argument("--store", required=True, help="ชื่อร้านค้า")
    parser_add.add_argument("--amount", required=True, type=float, help="ราคารวม")
    parser_add.add_argument("--category", required=True, help="หมวดหมู่เช่น อาหาร, เดินทาง")

    # Command: tui
    parser_tui = subparsers.add_parser("tui", help="เปิดดูข้อมูลทั้งหมดผ่านหน้าจอ TUI แบบสวยงาม")

    # Command: budget
    parser_budget = subparsers.add_parser("budget", help="จัดการหรือตรวจสอบ Budget")
    parser_budget.add_argument("--set", type=float, help="ตั้งค่า Budget รายเดือน")

    # Command: alert
    parser_alert = subparsers.add_parser("alert", help="ตรวจสอบว่ายอดตอนนี้เกิน Budget หรือยัง (ใช้สำหรับเช็คสถานะเงียบๆ)")

    # Command: list
    parser_list = subparsers.add_parser("list", help="แสดงรายการใบเสร็จทั้งหมดในรูปแบบตาราง CLI")

    args = parser.parse_args()
    console = Console()

    if args.command == "add":
        print_banner("บันทึกใบเสร็จใหม่เข้าสู่ระบบ")
        add_receipt(args.date, args.time, args.store, args.amount, args.category)
        
        print_section("Processing Receipt")
        print_success("Saved perfectly! 🚀")
        print_key_value("Store", args.store)
        print_key_value("Amount", f"{args.amount:,.2f} ฿")
        print_key_value("Category", args.category)
        
        # ตรวจสอบ Budget ในตัว
        budget = get_budget()
        spent = get_total_spent()
        if budget > 0 and spent > budget:
            console.print()
            print_error(f"ALERT! Spent ({spent:,.2f} ฿) exceeds Monthly Budget ({budget:,.2f} ฿) 📉")

    elif args.command == "tui":
        # Import ตรงนี้เพื่อไม่ให้หน่วงเวลาตอนรัน CLI ปกติ
        from tui import ClawReceiptTUI
        app = ClawReceiptTUI()
        app.run()

    elif args.command == "budget":
        print_banner("Budget Management")
        if args.set is not None:
            set_budget(args.set)
            print_section("Update Success")
            print_success(f"New monthly budget is set to [bold {COLORS['warning']}]{args.set:,.2f} ฿[/]!")
        else:
            budget = get_budget()
            spent = get_total_spent()
            print_section("Status")
            print_key_value("Total Spent", f"[bold {COLORS['warning']}]{spent:,.2f} ฿[/]")
            print_key_value("Target Budget", f"[bold cyan]{budget:,.2f} ฿[/]")
            
            if budget > 0 and spent > budget:
                print_error("Exceeded Budget! Watch out!")
            else:
                print_success("Still within budget margin. Great job!")

    elif args.command == "alert":
        budget = get_budget()
        spent = get_total_spent()
        if budget > 0 and spent > budget:
            console.print(f"[bold red]🚨 Exceeded Budget! Spent: {spent}[/bold red]")
            sys.exit(1)
        else:
            console.print(f"[green]✅ Within Budget[/green]")
            sys.exit(0)
            
    elif args.command == "list":
        print_banner("All Recorded Receipts")
        df = get_receipts()
        if df.empty:
            print_info("No receipts found in database yet.")
            return
            
        table = Table(title="🧾 Receipt Archive (ClawReceipt)", 
                      title_style=f"bold {COLORS['primary']}",
                      header_style=f"bold {COLORS['info']}",
                      border_style=COLORS['gray'])
        table.add_column("ID", style=COLORS['gray'])
        table.add_column("Date", style="cyan")
        table.add_column("Time", style="blue")
        table.add_column("Store", style="magenta")
        table.add_column("Amount", justify="right", style=COLORS['warning'])
        table.add_column("Category", style=COLORS['success'])
        
        for index, row in df.iterrows():
            table.add_row(
                str(row['id']), 
                row['date'], 
                row['time'],
                row['store'], 
                f"{row['amount']:,.2f}", 
                row['category']
            )
            
        console.print(table)

    else:
        parser.print_help()

if __name__ == "__main__":
    main()


path: src/db.py
content: import sqlite3
import pandas as pd
from datetime import datetime
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join(BASE_DIR, "data")
os.makedirs(DATA_DIR, exist_ok=True)
DB_FILE = os.path.join(DATA_DIR, "receipts.db")

def init_db():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS receipts (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    date TEXT,
                    time TEXT,
                    store TEXT,
                    amount REAL,
                    category TEXT
                 )''')
    try:
        c.execute("ALTER TABLE receipts ADD COLUMN time TEXT")
    except sqlite3.OperationalError:
        pass
    c.execute('''CREATE TABLE IF NOT EXISTS config (
                    key TEXT PRIMARY KEY,
                    value TEXT
                 )''')
    # Default budget if not set
    c.execute("INSERT OR IGNORE INTO config (key, value) VALUES ('budget', '0.0')")
    conn.commit()
    conn.close()

def add_receipt(date, time, store, amount, category):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("INSERT INTO receipts (date, time, store, amount, category) VALUES (?, ?, ?, ?, ?)",
              (date, time, store, amount, category))
    conn.commit()
    conn.close()

def get_receipts():
    conn = sqlite3.connect(DB_FILE)
    df = pd.read_sql_query("SELECT id, date, COALESCE(time, '') as time, store, amount, category FROM receipts ORDER BY id DESC", conn)
    conn.close()
    return df

def get_budget():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT value FROM config WHERE key='budget'")
    row = c.fetchone()
    conn.close()
    return float(row[0]) if row else 0.0

def set_budget(amount):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("INSERT OR REPLACE INTO config (key, value) VALUES ('budget', ?)", (str(amount),))
    conn.commit()
    conn.close()

def get_total_spent():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT SUM(amount) FROM receipts")
    row = c.fetchone()
    conn.close()
    return float(row[0] or 0.0)

def export_to_csv(filename):
    df = get_receipts()
    df.to_csv(filename, index=False)

def export_to_excel(filename):
    df = get_receipts()
    df.to_excel(filename, index=False)


path: src/styling.py
content: import sys
from rich.console import Console
from rich.text import Text
from rich.panel import Panel

console = Console()

# -----------------
# 🎨 COLOR PALETTE
# -----------------
COLORS = {
    "primary": "#ff9500",      # Orange
    "secondary": "#ff6b35",    # Deep orange
    "accent": "#ff9f1c",       # Bright orange
    "warning": "#ffd93d",      # Yellow
    "error": "#ff6b6b",        # Red
    "success": "#00ff9f",      # Neon green
    "info": "#6c5ce7",         # Purple
    "gray": "#666666",
}

# -----------------
# 🎨 ASCII LOGO
# -----------------
ASCII_LOGO = """
  ██████╗██╗      █████╗ ██╗    ██╗██████╗ ███████╗ ██████╗███████╗██╗██████╗ ████████╗███████╗
 ██╔════╝██║     ██╔══██╗██║    ██║██╔══██╗██╔════╝██╔════╝██╔════╝██║██╔══██╗╚══██╔══╝██╔════╝
 ██║     ██║     ███████║██║ █╗ ██║██████╔╝█████╗  ██║     █████╗  ██║██████╔╝   ██║   ███████╗
 ██║     ██║     ██╔══██║██║███╗██║██╔══██╗██╔══╝  ██║     ██╔══╝  ██║██╔═══╝    ██║   ╚════██║
 ╚██████╗███████╗██║  ██║╚███╔███╔╝██║  ██║███████╗╚██████╗███████╗██║██║        ██║   ███████║
  ╚═════╝╚══════╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝╚═╝        ╚═╝   ╚══════╝
"""

# -----------------
# 🚀 UTILS
# -----------------
def generate_gradient_text(text: str, color1: str = COLORS["primary"], color2: str = COLORS["secondary"]) -> Text:
    """Creates a gradient effect across a string of text using Rich."""
    rich_text = Text()
    # Extremely basic mapping: left half color1, right half color2.
    # Rich doesn't have built-in true character-by-character hex interpolation yet,
    # so we approximate it (or just use two main colors).
    length = len(text)
    mid = length // 2
    rich_text.append(text[:mid], style=f"bold {color1}")
    rich_text.append(text[mid:], style=f"bold {color2}")
    return rich_text

def print_banner(subtitle: str = "ClawReceipt - Receipt Manager for OpenClaw"):
    """Prints the awesome retro gradient banner."""
    title = generate_gradient_text(ASCII_LOGO, COLORS["primary"], COLORS["secondary"])
    console.print(title)
    
    line = "─" * 60
    console.print(generate_gradient_text(line, COLORS["primary"], COLORS["secondary"]))
    console.print(f"  [bold {COLORS['secondary']}]🦞[/] [bold {COLORS['primary']}]{subtitle}[/]")
    console.print(generate_gradient_text(line, COLORS["primary"], COLORS["secondary"]))
    console.print()

def print_section(title: str):
    """Prints a styled retro section header."""
    console.print()
    border_color = COLORS["secondary"]
    console.print(f"[{border_color}]┌{'─' * 58}┐[/]")
    console.print(f"[{border_color}]│[/] [bold cyan]{title.ljust(56)}[/] [{border_color}]│[/]")
    console.print(f"[{border_color}]└{'─' * 58}┘[/]")

def print_success(message: str):
    console.print(f"  [bold {COLORS['success']}]✓[/] {message}")

def print_error(message: str):
    console.print(f"  [bold {COLORS['error']}]✗[/] {message}")

def print_warning(message: str):
    console.print(f"  [bold {COLORS['warning']}]⚠[/] {message}")

def print_info(message: str):
    console.print(f"  [bold {COLORS['info']}]ℹ[/] {message}")

def print_key_value(key: str, value: str, indent: int = 2):
    spaces = " " * indent
    console.print(f"{spaces}[cyan]●[/] [{COLORS['gray']}]{key}:[/] [white]{value}[/]")


path: src/tui.py
content: from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, DataTable, Static, Button
from textual.containers import Horizontal
from db import get_receipts, get_budget, get_total_spent, init_db, export_to_csv, export_to_excel

class StatsWidget(Static):
    def update_stats(self):
        budget = get_budget()
        spent = get_total_spent()
        if budget == 0:
            status = "[b green]✅ ยังไม่มีการตั้ง Budget[/b green]"
        elif spent <= budget:
            status = f"[b green]✅ ภายใน Budget[/b green]" 
        else:
            status = f"[b red]🚨 เกิน Budget แล้ว![/b red]"
        self.update(f"💰 การใช้จ่ายรวม: {spent:.2f} ฿\n🎯 งบประมาณ (Budget): {budget:.2f} ฿\n{status}")

class ClawReceiptTUI(App):
    CSS = """
    Screen {
        layout: vertical;
    }
    #stats {
        padding: 1;
        background: $boost;
        margin: 1;
        border: solid $accent;
    }
    DataTable {
        height: 1fr;
        margin: 1;
    }
    #actions {
        height: auto;
        padding: 1;
        layout: horizontal;
        align: center middle;
    }
    Button {
        margin: 0 1;
    }
    """

    BINDINGS = [
        ("q", "quit", "Quit"),
        ("r", "refresh", "Refresh Data"),
        ("c", "export_csv", "Export CSV"),
        ("e", "export_excel", "Export Excel"),
    ]

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)
        self.stats = StatsWidget(id="stats")
        yield self.stats
        with Horizontal(id="actions"):
            yield Button("Export CSV", id="btn_csv", variant="primary")
            yield Button("Export Excel", id="btn_excel", variant="success")
            yield Button("Refresh", id="btn_refresh", variant="warning")
            yield Button("Quit", id="btn_quit", variant="error")
        self.table = DataTable()
        yield self.table
        yield Footer()

    def on_mount(self) -> None:
        init_db()
        self.title = "🧾 ClawReceipt - The Ultimate Receipt System"
        self.table.add_columns("ID", "Date", "Time", "Store", "Amount (฿)", "Category")
        self.table.cursor_type = "row"
        self.populate_data()
    
    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn_refresh":
            self.action_refresh()
        elif event.button.id == "btn_csv":
            self.action_export_csv()
        elif event.button.id == "btn_excel":
            self.action_export_excel()
        elif event.button.id == "btn_quit":
            self.exit()

    def action_refresh(self) -> None:
        self.populate_data()
        self.notify("ข้อมูลถูกรีเฟรชแล้ว!")

    def action_export_csv(self) -> None:
        export_to_csv("receipts_export.csv")
        self.notify("ส่งออกไฟล์ CSV สำเร็จ: receipts_export.csv 📄")

    def action_export_excel(self) -> None:
        export_to_excel("receipts_export.xlsx")
        self.notify("ส่งออกไฟล์ Excel สำเร็จ: receipts_export.xlsx 📊")

    def populate_data(self) -> None:
        self.stats.update_stats()
        self.table.clear()
        
        df = get_receipts()
        
        rows = []
        for index, row in df.iterrows():
            rows.append((
                str(row['id']), 
                row['date'], 
                row['time'],
                row['store'], 
                f"{row['amount']:.2f}", 
                row['category']
            ))
            
        self.table.add_rows(rows)

if __name__ == "__main__":
    app = ClawReceiptTUI()
    app.run()


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