financialclaw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.es.md +72 -0
  2. package/README.md +65 -0
  3. package/bin/financialclaw-setup.mjs +139 -0
  4. package/bin/postinstall-message.mjs +17 -0
  5. package/dist/src/db/database.d.ts +3 -0
  6. package/dist/src/db/database.js +58 -0
  7. package/dist/src/db/schema.d.ts +16 -0
  8. package/dist/src/db/schema.js +154 -0
  9. package/dist/src/index.d.ts +8 -0
  10. package/dist/src/index.js +112 -0
  11. package/dist/src/services/daily-sync.d.ts +16 -0
  12. package/dist/src/services/daily-sync.js +135 -0
  13. package/dist/src/tools/add-recurring-expense.d.ts +16 -0
  14. package/dist/src/tools/add-recurring-expense.js +126 -0
  15. package/dist/src/tools/get-financial-summary.d.ts +8 -0
  16. package/dist/src/tools/get-financial-summary.js +158 -0
  17. package/dist/src/tools/helpers/currency-utils.d.ts +17 -0
  18. package/dist/src/tools/helpers/currency-utils.js +47 -0
  19. package/dist/src/tools/helpers/date-utils.d.ts +8 -0
  20. package/dist/src/tools/helpers/date-utils.js +102 -0
  21. package/dist/src/tools/list-expenses.d.ts +16 -0
  22. package/dist/src/tools/list-expenses.js +142 -0
  23. package/dist/src/tools/list-incomes.d.ts +12 -0
  24. package/dist/src/tools/list-incomes.js +124 -0
  25. package/dist/src/tools/log-expense-from-receipt.d.ts +13 -0
  26. package/dist/src/tools/log-expense-from-receipt.js +91 -0
  27. package/dist/src/tools/log-expense-manual.d.ts +12 -0
  28. package/dist/src/tools/log-expense-manual.js +60 -0
  29. package/dist/src/tools/log-income-receipt.d.ts +11 -0
  30. package/dist/src/tools/log-income-receipt.js +103 -0
  31. package/dist/src/tools/log-income.d.ts +13 -0
  32. package/dist/src/tools/log-income.js +103 -0
  33. package/dist/src/tools/manage-currency.d.ts +10 -0
  34. package/dist/src/tools/manage-currency.js +104 -0
  35. package/dist/src/tools/mark-expense-paid.d.ts +8 -0
  36. package/dist/src/tools/mark-expense-paid.js +44 -0
  37. package/dist/src/tools/run-daily-sync.d.ts +5 -0
  38. package/dist/src/tools/run-daily-sync.js +85 -0
  39. package/dist/src/version.d.ts +2 -0
  40. package/dist/src/version.js +2 -0
  41. package/openclaw.plugin.json +24 -0
  42. package/package.json +52 -0
  43. package/skills/financialclaw/SKILL.md +257 -0
package/README.es.md ADDED
@@ -0,0 +1,72 @@
1
+ # financialclaw — Documentación en español
2
+
3
+ [![CI](https://github.com/riclara/financialclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/riclara/financialclaw/actions/workflows/ci.yml)
4
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/riclara/financialclaw)
5
+
6
+ Plugin de OpenClaw para finanzas personales. Registra gastos, ingresos, pagos recurrentes y genera resúmenes. Compatible con cualquier canal soportado por OpenClaw (Telegram, WhatsApp, etc.). El OCR de recibos es agéntico: si tu canal permite enviar imágenes, podés fotografiar un recibo y el agente extrae los datos automáticamente. Base de datos SQLite embebida. Soporte multi-moneda.
7
+
8
+ ## Estado del proyecto
9
+
10
+ El plugin registra 11 tools en OpenClaw. Los reminders y el sync diario corren mediante el sistema de cron integrado de OpenClaw — no se requiere configuración externa.
11
+
12
+ Si necesitas el detalle de avance por tarea, revisa [docs/hitos.md](docs/hitos.md).
13
+
14
+ ## Capacidades principales
15
+
16
+ - Tools de OpenClaw para gastos, ingresos, reglas recurrentes y consultas.
17
+ - OCR de recibos agéntico: el agente OpenClaw extrae los datos y los pasa al tool `log_expense_from_receipt`.
18
+ - Persistencia en SQLite embebida.
19
+ - Resolución explícita de moneda con soporte multi-moneda y placeholder inicial `XXX`.
20
+
21
+ ## Instalación
22
+
23
+ ```bash
24
+ openclaw plugins install financialclaw
25
+ npx financialclaw financialclaw-setup
26
+ openclaw gateway restart
27
+ ```
28
+
29
+ ### ¿Por qué es necesario `financialclaw-setup`?
30
+
31
+ `openclaw plugins install` registra el plugin pero no lo agrega a `plugins.allow`. Una vez que ese campo existe, OpenClaw lo usa como allowlist explícita — todo lo que no esté listado deja de funcionar, incluyendo canales activos como Telegram.
32
+
33
+ `financialclaw-setup` lee el config actual y aplica tres cambios requeridos:
34
+
35
+ 1. **`plugins.allow`** — agrega `financialclaw` junto a todos los canales y plugins activos para que nada deje de funcionar.
36
+ 2. **`tools.profile`** — lo cambia a `"full"`. Profiles como `"coding"` o `"minimal"` excluyen tools de plugins, haciéndolos invisibles para el agente.
37
+ 3. **`tools.allow`** — agrega `"financialclaw"` como entrada explícita en el allowlist de tools.
38
+
39
+ También configura `dbPath` para que la BD persista entre reinstalaciones (por defecto: `~/.openclaw/workspace/financialclaw.db`).
40
+
41
+ ### Opciones
42
+
43
+ ```bash
44
+ # Ruta personalizada para la BD
45
+ npx financialclaw financialclaw-setup --db-path /tu/ruta/financialclaw.db
46
+
47
+ # Si el config de OpenClaw está en una ubicación no estándar
48
+ npx financialclaw financialclaw-setup --config /ruta/openclaw.json
49
+ ```
50
+
51
+ La guía completa está en [docs/setup.es.md](docs/setup.es.md).
52
+
53
+ ## Requisitos
54
+
55
+ Se requiere Node.js 24+. El plugin usa `node:sqlite`, el módulo SQLite integrado disponible desde Node.js 24 — no requiere addons nativos ni compilación.
56
+
57
+ ## Verificación mínima
58
+
59
+ Después de instalar dependencias:
60
+
61
+ ```bash
62
+ npx tsc --noEmit
63
+ npm run test:unit
64
+ npm run test:integration
65
+ npm run build
66
+ ```
67
+
68
+ ## Documentación
69
+
70
+ - [docs/setup.es.md](docs/setup.es.md): instalación y troubleshooting — [English](docs/setup.md)
71
+ - [docs/hitos.md](docs/hitos.md): estado final de implementación
72
+ - [docs/testing.md](docs/testing.md): estrategia de pruebas
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # financialclaw
2
+
3
+ [![CI](https://github.com/riclara/financialclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/riclara/financialclaw/actions/workflows/ci.yml)
4
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/riclara/financialclaw)
5
+
6
+ > [Documentación en español](README.es.md)
7
+
8
+ Personal finance plugin for OpenClaw. Registers expenses, income, recurring payments, and generates summaries. Works with any OpenClaw-supported channel (Telegram, WhatsApp, etc.). Receipt OCR is handled agentically — if your channel supports sending images, you can photograph a receipt and the agent will extract the data automatically. Embedded SQLite database. Multi-currency support.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ openclaw plugins install financialclaw
14
+ npx financialclaw financialclaw-setup
15
+ openclaw gateway restart
16
+ ```
17
+
18
+ ### Why is `financialclaw-setup` needed?
19
+
20
+ `openclaw plugins install` registers the plugin but does not add it to `plugins.allow`. Once that field exists, OpenClaw uses it as an explicit allowlist — anything not listed stops working, including active channels like Telegram.
21
+
22
+ `financialclaw-setup` reads your current config and applies three required changes:
23
+
24
+ 1. **`plugins.allow`** — adds `financialclaw` alongside all active channels and plugins so nothing stops working.
25
+ 2. **`tools.profile`** — sets it to `"full"`. Profiles like `"coding"` or `"minimal"` exclude plugin tools entirely, making them invisible to the agent.
26
+ 3. **`tools.allow`** — adds `"financialclaw"` as an explicit tool allowlist entry.
27
+
28
+ It also sets `plugins.entries.financialclaw.config.dbPath` so the database persists across reinstalls (default: `~/.openclaw/workspace/financialclaw.db`).
29
+
30
+ ### Options
31
+
32
+ ```bash
33
+ # Custom database path
34
+ npx financialclaw financialclaw-setup --db-path /your/path/financialclaw.db
35
+
36
+ # If the OpenClaw config is in a non-standard location
37
+ npx financialclaw financialclaw-setup --config /path/to/openclaw.json
38
+ ```
39
+
40
+ ## Available tools
41
+
42
+ | Tool | Description |
43
+ |---|---|
44
+ | `manage_currency` | Add currencies, list them, set the default |
45
+ | `log_expense_from_receipt` | Record an expense from structured OCR data |
46
+ | `log_expense_manual` | Record an expense manually |
47
+ | `log_income` | Record income |
48
+ | `log_income_receipt` | Record a payment received linked to an income |
49
+ | `add_recurring_expense` | Create a recurring expense rule |
50
+ | `mark_expense_paid` | Mark an existing expense as paid |
51
+ | `get_financial_summary` | Get a financial summary for a period |
52
+ | `list_expenses` | List expenses with filters |
53
+ | `list_incomes` | List incomes with filters |
54
+ | `run_daily_sync` | Run the daily sync: generate recurring expenses, mark overdue, send reminders |
55
+
56
+ ## Requirements
57
+
58
+ Node.js 24+ is required. The plugin uses `node:sqlite`, the built-in SQLite module available since Node.js 24 — no native addons or compilation needed.
59
+
60
+ ## Documentation
61
+
62
+ - [README.es.md](README.es.md): documentación en español
63
+ - [docs/setup.md](docs/setup.md): full setup guide and troubleshooting — [español](docs/setup.es.md)
64
+ - [docs/hitos.md](docs/hitos.md): implementation status
65
+ - [docs/testing.md](docs/testing.md): testing strategy
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ensure-plugins-allow.mjs
4
+ *
5
+ * Run this script once after `openclaw plugins install` to configure
6
+ * financialclaw in the OpenClaw config file.
7
+ *
8
+ * What it does:
9
+ * 1. Adds "financialclaw" to plugins.allow (preserving active channels
10
+ * and plugins so they don't get blocked)
11
+ * 2. Sets plugins.entries.financialclaw.config.dbPath if not already set
12
+ * 3. Sets tools.profile to "full" so plugin tools are visible to the agent
13
+ * 4. Adds "financialclaw" to tools.allow as explicit allowlist entry
14
+ *
15
+ * Usage:
16
+ * node scripts/ensure-plugins-allow.mjs [--config /ruta/openclaw.json] [--db-path /ruta/financialclaw.db]
17
+ *
18
+ * Config path resolution order:
19
+ * 1. --config argument
20
+ * 2. OPENCLAW_CONFIG env var
21
+ * 3. First existing file among known locations (see CANDIDATE_PATHS)
22
+ */
23
+
24
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
25
+ import { homedir } from "node:os";
26
+ import { join, dirname } from "node:path";
27
+
28
+ function parseArg(name) {
29
+ const i = process.argv.indexOf(name);
30
+ return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : null;
31
+ }
32
+
33
+ function resolveConfigPath() {
34
+ // 1. --config argument
35
+ const arg = parseArg("--config");
36
+ if (arg) return arg;
37
+
38
+ // 2. env var
39
+ if (process.env.OPENCLAW_CONFIG) return process.env.OPENCLAW_CONFIG;
40
+
41
+ // 3. known locations in priority order
42
+ const home = homedir();
43
+ const CANDIDATE_PATHS = [
44
+ join(home, ".openclaw", "openclaw.json"), // most common
45
+ join(home, "openclaw.json"), // some installs
46
+ join(home, ".config", "openclaw", "openclaw.json"),
47
+ ];
48
+
49
+ for (const p of CANDIDATE_PATHS) {
50
+ if (existsSync(p)) return p;
51
+ }
52
+
53
+ // Return first candidate as default even if it doesn't exist yet
54
+ // (will fail below with a helpful error)
55
+ return CANDIDATE_PATHS[0];
56
+ }
57
+
58
+ const configPath = resolveConfigPath();
59
+
60
+ if (!existsSync(configPath)) {
61
+ console.error(`Config not found: ${configPath}`);
62
+ if (!parseArg("--config") && !process.env.OPENCLAW_CONFIG) {
63
+ console.error(`Tried: ~/.openclaw/openclaw.json, ~/openclaw.json, ~/.config/openclaw/openclaw.json`);
64
+ console.error(`Use --config /ruta/al/config to specify the path explicitly.`);
65
+ }
66
+ process.exit(1);
67
+ }
68
+
69
+ console.log(`Using config: ${configPath}`);
70
+
71
+ const dbPath =
72
+ parseArg("--db-path") ??
73
+ join(homedir(), ".openclaw", "workspace", "financialclaw.db");
74
+
75
+ const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
76
+ const plugins = (cfg.plugins ??= {});
77
+
78
+ // 1. Ensure plugins.allow includes financialclaw and all active entries
79
+ // Always rediscover active channels and plugins so re-runs fix missing entries
80
+ {
81
+ const allow = new Set(plugins.allow ?? []);
82
+ allow.add("financialclaw");
83
+
84
+ if (cfg.channels) {
85
+ for (const [name, ch] of Object.entries(cfg.channels)) {
86
+ if (ch.enabled !== false) allow.add(name);
87
+ }
88
+ }
89
+
90
+ if (plugins.entries) {
91
+ for (const [name, entry] of Object.entries(plugins.entries)) {
92
+ if (entry.enabled !== false) allow.add(name);
93
+ }
94
+ }
95
+
96
+ plugins.allow = [...allow];
97
+ console.log(`Updated plugins.allow:`, JSON.stringify(plugins.allow));
98
+ }
99
+
100
+ // 2. Set dbPath in plugins.entries.financialclaw.config if not already set
101
+ const entries = (plugins.entries ??= {});
102
+ const fc = (entries.financialclaw ??= { enabled: true, config: {} });
103
+ fc.config ??= {};
104
+
105
+ if (!fc.config.dbPath) {
106
+ mkdirSync(dirname(dbPath), { recursive: true });
107
+ fc.config.dbPath = dbPath;
108
+ console.log(`Set dbPath: ${dbPath}`);
109
+ } else {
110
+ console.log(`dbPath already set: ${fc.config.dbPath}`);
111
+ }
112
+
113
+ // 3. Ensure tools.profile is "full" so plugin tools are visible to the agent
114
+ // Profiles like "coding" or "minimal" exclude plugin tools entirely
115
+ {
116
+ const tools = (cfg.tools ??= {});
117
+ const prev = tools.profile;
118
+ if (prev && prev !== "full") {
119
+ tools.profile = "full";
120
+ console.log(`Updated tools.profile: "${prev}" -> "full" (required for plugin tools)`);
121
+ } else if (!prev) {
122
+ tools.profile = "full";
123
+ console.log(`Set tools.profile: "full"`);
124
+ } else {
125
+ console.log(`tools.profile already "full"`);
126
+ }
127
+ }
128
+
129
+ // 4. Ensure tools.allow includes "financialclaw" as explicit allowlist entry
130
+ {
131
+ const tools = (cfg.tools ??= {});
132
+ const toolsAllow = new Set(tools.allow ?? []);
133
+ toolsAllow.add("financialclaw");
134
+ tools.allow = [...toolsAllow];
135
+ console.log(`Updated tools.allow:`, JSON.stringify(tools.allow));
136
+ }
137
+
138
+ writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
139
+ console.log("Done. Restart gateway: openclaw gateway restart");
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // Skip in CI environments
3
+ if (process.env.CI || process.env.GITHUB_ACTIONS) process.exit(0);
4
+
5
+ console.log(`
6
+ ╔══════════════════════════════════════════════════════════════╗
7
+ ║ financialclaw — setup required ║
8
+ ╠══════════════════════════════════════════════════════════════╣
9
+ ║ Run this before restarting the gateway: ║
10
+ ║ ║
11
+ ║ npx financialclaw financialclaw-setup ║
12
+ ║ ║
13
+ ║ This adds financialclaw to plugins.allow and configures ║
14
+ ║ a persistent database path. Without it, active channels ║
15
+ ║ (Telegram, etc.) may stop working. ║
16
+ ╚══════════════════════════════════════════════════════════════╝
17
+ `);
@@ -0,0 +1,3 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ export declare function configureDb(dbPath: string): void;
3
+ export declare function getDb(): DatabaseSync;
@@ -0,0 +1,58 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { mkdirSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, dirname } from "node:path";
5
+ import { ALL_MIGRATIONS, ALL_SEEDS } from "./schema.js";
6
+ const DEFAULT_DB_PATH = join(homedir(), ".openclaw", "workspace", "financialclaw.db");
7
+ let _db;
8
+ let _dbPath;
9
+ function shouldIgnoreMigrationError(error) {
10
+ if (!(error instanceof Error)) {
11
+ return false;
12
+ }
13
+ const message = error.message;
14
+ return /duplicate column name: (updated_at|status|failure_code)/i.test(message);
15
+ }
16
+ function runMigrations(db) {
17
+ for (const sql of ALL_MIGRATIONS) {
18
+ try {
19
+ db.exec(sql);
20
+ }
21
+ catch (error) {
22
+ if (!shouldIgnoreMigrationError(error)) {
23
+ throw error;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ function initializeDb(db) {
29
+ db.exec("PRAGMA journal_mode = WAL");
30
+ db.exec("PRAGMA foreign_keys = ON");
31
+ db.exec("BEGIN");
32
+ try {
33
+ runMigrations(db);
34
+ for (const sql of ALL_SEEDS) {
35
+ db.exec(sql);
36
+ }
37
+ db.exec("COMMIT");
38
+ }
39
+ catch (err) {
40
+ db.exec("ROLLBACK");
41
+ throw err;
42
+ }
43
+ }
44
+ export function configureDb(dbPath) {
45
+ if (_db !== undefined) {
46
+ throw new Error("Cannot reconfigure the database after the singleton has been initialized.");
47
+ }
48
+ _dbPath = dbPath;
49
+ }
50
+ export function getDb() {
51
+ if (_db === undefined) {
52
+ _dbPath = _dbPath ?? process.env.FINANCIALCLAW_DB_PATH ?? DEFAULT_DB_PATH;
53
+ mkdirSync(dirname(_dbPath), { recursive: true });
54
+ _db = new DatabaseSync(_dbPath);
55
+ initializeDb(_db);
56
+ }
57
+ return _db;
58
+ }
@@ -0,0 +1,16 @@
1
+ export declare const CREATE_CURRENCIES_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS currencies (\n code TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n symbol TEXT NOT NULL,\n is_default INTEGER NOT NULL DEFAULT 0 CHECK (is_default IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n";
2
+ export declare const CREATE_OCR_EXTRACTIONS_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS ocr_extractions (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n source_path TEXT,\n raw_text TEXT,\n lines_json TEXT,\n average_confidence REAL,\n suggested_amount INTEGER,\n suggested_currency TEXT,\n suggested_date TEXT,\n suggested_merchant TEXT,\n suggested_category TEXT,\n failure_reason TEXT,\n failure_detail TEXT,\n status TEXT NOT NULL DEFAULT 'COMPLETED',\n failure_code TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n";
3
+ export declare const CREATE_RECURRING_EXPENSE_RULES_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS recurring_expense_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n amount INTEGER NOT NULL,\n category TEXT,\n currency TEXT NOT NULL,\n frequency TEXT NOT NULL,\n interval_days INTEGER,\n day_of_month INTEGER,\n starts_on TEXT NOT NULL,\n ends_on TEXT,\n reminder_days_before INTEGER NOT NULL DEFAULT 0,\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n";
4
+ export declare const CREATE_EXPENSES_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS expenses (\n id TEXT PRIMARY KEY,\n amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n category TEXT,\n merchant TEXT,\n description TEXT NOT NULL,\n due_date TEXT NOT NULL,\n payment_date TEXT,\n status TEXT NOT NULL,\n source TEXT NOT NULL,\n ocr_extraction_id TEXT,\n recurring_rule_id TEXT,\n generated_from_rule INTEGER NOT NULL DEFAULT 0 CHECK (generated_from_rule IN (0, 1)),\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code),\n FOREIGN KEY (ocr_extraction_id) REFERENCES ocr_extractions (id),\n FOREIGN KEY (recurring_rule_id) REFERENCES recurring_expense_rules (id)\n);\n";
5
+ export declare const ADD_EXPENSES_UPDATED_AT_COLUMN_SQL = "\nALTER TABLE expenses ADD COLUMN updated_at TEXT;\n";
6
+ export declare const BACKFILL_EXPENSES_UPDATED_AT_SQL = "\nUPDATE expenses\nSET updated_at = created_at\nWHERE updated_at IS NULL;\n";
7
+ export declare const ADD_OCR_EXTRACTIONS_STATUS_COLUMN_SQL = "\nALTER TABLE ocr_extractions ADD COLUMN status TEXT NOT NULL DEFAULT 'COMPLETED'\n";
8
+ export declare const ADD_OCR_EXTRACTIONS_FAILURE_CODE_COLUMN_SQL = "\nALTER TABLE ocr_extractions ADD COLUMN failure_code TEXT\n";
9
+ export declare const CREATE_EXPENSES_RECURRING_UNIQUE_INDEX_SQL = "\nCREATE UNIQUE INDEX IF NOT EXISTS idx_expenses_recurring_rule_due_date\nON expenses (recurring_rule_id, due_date)\nWHERE recurring_rule_id IS NOT NULL;\n";
10
+ export declare const CREATE_INCOMES_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS incomes (\n id TEXT PRIMARY KEY,\n reason TEXT NOT NULL,\n expected_amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n date TEXT NOT NULL,\n frequency TEXT,\n interval_days INTEGER,\n is_recurring INTEGER NOT NULL DEFAULT 0 CHECK (is_recurring IN (0, 1)),\n next_expected_receipt_date TEXT,\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n";
11
+ export declare const CREATE_INCOME_RECEIPTS_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS income_receipts (\n id TEXT PRIMARY KEY,\n income_id TEXT NOT NULL,\n amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n date TEXT NOT NULL,\n notes TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (income_id) REFERENCES incomes (id),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n";
12
+ export declare const CREATE_REMINDERS_TABLE_SQL = "\nCREATE TABLE IF NOT EXISTS reminders (\n id TEXT PRIMARY KEY,\n expense_id TEXT NOT NULL,\n scheduled_date TEXT NOT NULL,\n days_before INTEGER NOT NULL,\n sent INTEGER NOT NULL DEFAULT 0 CHECK (sent IN (0, 1)),\n sent_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (expense_id) REFERENCES expenses (id)\n);\n";
13
+ export declare const CREATE_REMINDERS_UNIQUE_INDEX_SQL = "\nCREATE UNIQUE INDEX IF NOT EXISTS idx_reminders_expense_schedule_days_before\nON reminders (expense_id, scheduled_date, days_before);\n";
14
+ export declare const SEED_PLACEHOLDER_CURRENCY_SQL = "\nINSERT OR IGNORE INTO currencies (code, name, symbol, is_default)\nVALUES ('XXX', 'Sin configurar', '\u00A4', 1);\n";
15
+ export declare const ALL_MIGRATIONS: readonly ["\nCREATE TABLE IF NOT EXISTS currencies (\n code TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n symbol TEXT NOT NULL,\n is_default INTEGER NOT NULL DEFAULT 0 CHECK (is_default IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n", "\nCREATE TABLE IF NOT EXISTS ocr_extractions (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n source_path TEXT,\n raw_text TEXT,\n lines_json TEXT,\n average_confidence REAL,\n suggested_amount INTEGER,\n suggested_currency TEXT,\n suggested_date TEXT,\n suggested_merchant TEXT,\n suggested_category TEXT,\n failure_reason TEXT,\n failure_detail TEXT,\n status TEXT NOT NULL DEFAULT 'COMPLETED',\n failure_code TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n", "\nCREATE TABLE IF NOT EXISTS recurring_expense_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n amount INTEGER NOT NULL,\n category TEXT,\n currency TEXT NOT NULL,\n frequency TEXT NOT NULL,\n interval_days INTEGER,\n day_of_month INTEGER,\n starts_on TEXT NOT NULL,\n ends_on TEXT,\n reminder_days_before INTEGER NOT NULL DEFAULT 0,\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n", "\nCREATE TABLE IF NOT EXISTS expenses (\n id TEXT PRIMARY KEY,\n amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n category TEXT,\n merchant TEXT,\n description TEXT NOT NULL,\n due_date TEXT NOT NULL,\n payment_date TEXT,\n status TEXT NOT NULL,\n source TEXT NOT NULL,\n ocr_extraction_id TEXT,\n recurring_rule_id TEXT,\n generated_from_rule INTEGER NOT NULL DEFAULT 0 CHECK (generated_from_rule IN (0, 1)),\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code),\n FOREIGN KEY (ocr_extraction_id) REFERENCES ocr_extractions (id),\n FOREIGN KEY (recurring_rule_id) REFERENCES recurring_expense_rules (id)\n);\n", "\nALTER TABLE expenses ADD COLUMN updated_at TEXT;\n", "\nUPDATE expenses\nSET updated_at = created_at\nWHERE updated_at IS NULL;\n", "\nCREATE UNIQUE INDEX IF NOT EXISTS idx_expenses_recurring_rule_due_date\nON expenses (recurring_rule_id, due_date)\nWHERE recurring_rule_id IS NOT NULL;\n", "\nCREATE TABLE IF NOT EXISTS incomes (\n id TEXT PRIMARY KEY,\n reason TEXT NOT NULL,\n expected_amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n date TEXT NOT NULL,\n frequency TEXT,\n interval_days INTEGER,\n is_recurring INTEGER NOT NULL DEFAULT 0 CHECK (is_recurring IN (0, 1)),\n next_expected_receipt_date TEXT,\n is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n", "\nCREATE TABLE IF NOT EXISTS income_receipts (\n id TEXT PRIMARY KEY,\n income_id TEXT NOT NULL,\n amount INTEGER NOT NULL,\n currency TEXT NOT NULL,\n date TEXT NOT NULL,\n notes TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (income_id) REFERENCES incomes (id),\n FOREIGN KEY (currency) REFERENCES currencies (code)\n);\n", "\nCREATE TABLE IF NOT EXISTS reminders (\n id TEXT PRIMARY KEY,\n expense_id TEXT NOT NULL,\n scheduled_date TEXT NOT NULL,\n days_before INTEGER NOT NULL,\n sent INTEGER NOT NULL DEFAULT 0 CHECK (sent IN (0, 1)),\n sent_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n FOREIGN KEY (expense_id) REFERENCES expenses (id)\n);\n", "\nCREATE UNIQUE INDEX IF NOT EXISTS idx_reminders_expense_schedule_days_before\nON reminders (expense_id, scheduled_date, days_before);\n", "\nALTER TABLE ocr_extractions ADD COLUMN status TEXT NOT NULL DEFAULT 'COMPLETED'\n", "\nALTER TABLE ocr_extractions ADD COLUMN failure_code TEXT\n"];
16
+ export declare const ALL_SEEDS: readonly ["\nINSERT OR IGNORE INTO currencies (code, name, symbol, is_default)\nVALUES ('XXX', 'Sin configurar', '¤', 1);\n"];
@@ -0,0 +1,154 @@
1
+ export const CREATE_CURRENCIES_TABLE_SQL = `
2
+ CREATE TABLE IF NOT EXISTS currencies (
3
+ code TEXT PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ symbol TEXT NOT NULL,
6
+ is_default INTEGER NOT NULL DEFAULT 0 CHECK (is_default IN (0, 1)),
7
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
8
+ );
9
+ `;
10
+ export const CREATE_OCR_EXTRACTIONS_TABLE_SQL = `
11
+ CREATE TABLE IF NOT EXISTS ocr_extractions (
12
+ id TEXT PRIMARY KEY,
13
+ provider TEXT NOT NULL,
14
+ source_path TEXT,
15
+ raw_text TEXT,
16
+ lines_json TEXT,
17
+ average_confidence REAL,
18
+ suggested_amount INTEGER,
19
+ suggested_currency TEXT,
20
+ suggested_date TEXT,
21
+ suggested_merchant TEXT,
22
+ suggested_category TEXT,
23
+ failure_reason TEXT,
24
+ failure_detail TEXT,
25
+ status TEXT NOT NULL DEFAULT 'COMPLETED',
26
+ failure_code TEXT,
27
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
28
+ );
29
+ `;
30
+ export const CREATE_RECURRING_EXPENSE_RULES_TABLE_SQL = `
31
+ CREATE TABLE IF NOT EXISTS recurring_expense_rules (
32
+ id TEXT PRIMARY KEY,
33
+ name TEXT NOT NULL,
34
+ amount INTEGER NOT NULL,
35
+ category TEXT,
36
+ currency TEXT NOT NULL,
37
+ frequency TEXT NOT NULL,
38
+ interval_days INTEGER,
39
+ day_of_month INTEGER,
40
+ starts_on TEXT NOT NULL,
41
+ ends_on TEXT,
42
+ reminder_days_before INTEGER NOT NULL DEFAULT 0,
43
+ is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
44
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
45
+ FOREIGN KEY (currency) REFERENCES currencies (code)
46
+ );
47
+ `;
48
+ export const CREATE_EXPENSES_TABLE_SQL = `
49
+ CREATE TABLE IF NOT EXISTS expenses (
50
+ id TEXT PRIMARY KEY,
51
+ amount INTEGER NOT NULL,
52
+ currency TEXT NOT NULL,
53
+ category TEXT,
54
+ merchant TEXT,
55
+ description TEXT NOT NULL,
56
+ due_date TEXT NOT NULL,
57
+ payment_date TEXT,
58
+ status TEXT NOT NULL,
59
+ source TEXT NOT NULL,
60
+ ocr_extraction_id TEXT,
61
+ recurring_rule_id TEXT,
62
+ generated_from_rule INTEGER NOT NULL DEFAULT 0 CHECK (generated_from_rule IN (0, 1)),
63
+ is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
64
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
65
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
66
+ FOREIGN KEY (currency) REFERENCES currencies (code),
67
+ FOREIGN KEY (ocr_extraction_id) REFERENCES ocr_extractions (id),
68
+ FOREIGN KEY (recurring_rule_id) REFERENCES recurring_expense_rules (id)
69
+ );
70
+ `;
71
+ export const ADD_EXPENSES_UPDATED_AT_COLUMN_SQL = `
72
+ ALTER TABLE expenses ADD COLUMN updated_at TEXT;
73
+ `;
74
+ export const BACKFILL_EXPENSES_UPDATED_AT_SQL = `
75
+ UPDATE expenses
76
+ SET updated_at = created_at
77
+ WHERE updated_at IS NULL;
78
+ `;
79
+ export const ADD_OCR_EXTRACTIONS_STATUS_COLUMN_SQL = `
80
+ ALTER TABLE ocr_extractions ADD COLUMN status TEXT NOT NULL DEFAULT 'COMPLETED'
81
+ `;
82
+ export const ADD_OCR_EXTRACTIONS_FAILURE_CODE_COLUMN_SQL = `
83
+ ALTER TABLE ocr_extractions ADD COLUMN failure_code TEXT
84
+ `;
85
+ export const CREATE_EXPENSES_RECURRING_UNIQUE_INDEX_SQL = `
86
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_expenses_recurring_rule_due_date
87
+ ON expenses (recurring_rule_id, due_date)
88
+ WHERE recurring_rule_id IS NOT NULL;
89
+ `;
90
+ export const CREATE_INCOMES_TABLE_SQL = `
91
+ CREATE TABLE IF NOT EXISTS incomes (
92
+ id TEXT PRIMARY KEY,
93
+ reason TEXT NOT NULL,
94
+ expected_amount INTEGER NOT NULL,
95
+ currency TEXT NOT NULL,
96
+ date TEXT NOT NULL,
97
+ frequency TEXT,
98
+ interval_days INTEGER,
99
+ is_recurring INTEGER NOT NULL DEFAULT 0 CHECK (is_recurring IN (0, 1)),
100
+ next_expected_receipt_date TEXT,
101
+ is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
102
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
103
+ FOREIGN KEY (currency) REFERENCES currencies (code)
104
+ );
105
+ `;
106
+ export const CREATE_INCOME_RECEIPTS_TABLE_SQL = `
107
+ CREATE TABLE IF NOT EXISTS income_receipts (
108
+ id TEXT PRIMARY KEY,
109
+ income_id TEXT NOT NULL,
110
+ amount INTEGER NOT NULL,
111
+ currency TEXT NOT NULL,
112
+ date TEXT NOT NULL,
113
+ notes TEXT,
114
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
115
+ FOREIGN KEY (income_id) REFERENCES incomes (id),
116
+ FOREIGN KEY (currency) REFERENCES currencies (code)
117
+ );
118
+ `;
119
+ export const CREATE_REMINDERS_TABLE_SQL = `
120
+ CREATE TABLE IF NOT EXISTS reminders (
121
+ id TEXT PRIMARY KEY,
122
+ expense_id TEXT NOT NULL,
123
+ scheduled_date TEXT NOT NULL,
124
+ days_before INTEGER NOT NULL,
125
+ sent INTEGER NOT NULL DEFAULT 0 CHECK (sent IN (0, 1)),
126
+ sent_at TEXT,
127
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
128
+ FOREIGN KEY (expense_id) REFERENCES expenses (id)
129
+ );
130
+ `;
131
+ export const CREATE_REMINDERS_UNIQUE_INDEX_SQL = `
132
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_reminders_expense_schedule_days_before
133
+ ON reminders (expense_id, scheduled_date, days_before);
134
+ `;
135
+ export const SEED_PLACEHOLDER_CURRENCY_SQL = `
136
+ INSERT OR IGNORE INTO currencies (code, name, symbol, is_default)
137
+ VALUES ('XXX', 'Sin configurar', '¤', 1);
138
+ `;
139
+ export const ALL_MIGRATIONS = [
140
+ CREATE_CURRENCIES_TABLE_SQL,
141
+ CREATE_OCR_EXTRACTIONS_TABLE_SQL,
142
+ CREATE_RECURRING_EXPENSE_RULES_TABLE_SQL,
143
+ CREATE_EXPENSES_TABLE_SQL,
144
+ ADD_EXPENSES_UPDATED_AT_COLUMN_SQL,
145
+ BACKFILL_EXPENSES_UPDATED_AT_SQL,
146
+ CREATE_EXPENSES_RECURRING_UNIQUE_INDEX_SQL,
147
+ CREATE_INCOMES_TABLE_SQL,
148
+ CREATE_INCOME_RECEIPTS_TABLE_SQL,
149
+ CREATE_REMINDERS_TABLE_SQL,
150
+ CREATE_REMINDERS_UNIQUE_INDEX_SQL,
151
+ ADD_OCR_EXTRACTIONS_STATUS_COLUMN_SQL,
152
+ ADD_OCR_EXTRACTIONS_FAILURE_CODE_COLUMN_SQL,
153
+ ];
154
+ export const ALL_SEEDS = [SEED_PLACEHOLDER_CURRENCY_SQL];
@@ -0,0 +1,8 @@
1
+ declare const _default: {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ configSchema: import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginConfigSchema;
6
+ register: NonNullable<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition["register"]>;
7
+ } & Pick<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition, "kind">;
8
+ export default _default;
@@ -0,0 +1,112 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { configureDb } from "./db/database.js";
3
+ import { InputSchema as ManageCurrencyInputSchema, executeManageCurrency } from "./tools/manage-currency.js";
4
+ import { InputSchema as LogExpenseFromReceiptInputSchema, executeLogExpenseFromReceipt } from "./tools/log-expense-from-receipt.js";
5
+ import { InputSchema as LogExpenseManualInputSchema, executeLogExpenseManual } from "./tools/log-expense-manual.js";
6
+ import { InputSchema as LogIncomeInputSchema, executeLogIncome } from "./tools/log-income.js";
7
+ import { InputSchema as LogIncomeReceiptInputSchema, executeLogIncomeReceipt } from "./tools/log-income-receipt.js";
8
+ import { InputSchema as AddRecurringExpenseInputSchema, executeAddRecurringExpense } from "./tools/add-recurring-expense.js";
9
+ import { InputSchema as MarkExpensePaidInputSchema, executeMarkExpensePaid } from "./tools/mark-expense-paid.js";
10
+ import { InputSchema as GetFinancialSummaryInputSchema, executeGetFinancialSummary } from "./tools/get-financial-summary.js";
11
+ import { InputSchema as ListExpensesInputSchema, executeListExpenses } from "./tools/list-expenses.js";
12
+ import { InputSchema as ListIncomesInputSchema, executeListIncomes } from "./tools/list-incomes.js";
13
+ import { InputSchema as RunDailySyncInputSchema, executeRunDailySync } from "./tools/run-daily-sync.js";
14
+ function wrapExecute(fn) {
15
+ return async (_id, params) => {
16
+ const text = await fn(params);
17
+ return {
18
+ content: [{ type: "text", text }],
19
+ details: undefined,
20
+ };
21
+ };
22
+ }
23
+ export default definePluginEntry({
24
+ id: "financialclaw",
25
+ name: "FinancialClaw",
26
+ description: "Personal finance plugin: expenses, income, recurring payments, and receipt OCR",
27
+ register(api) {
28
+ const config = api.pluginConfig ?? {};
29
+ if (config.dbPath) {
30
+ configureDb(config.dbPath);
31
+ }
32
+ // Python CMD config removed - OCR is now agentic
33
+ api.registerTool({
34
+ name: "manage_currency",
35
+ label: "Manage Currency",
36
+ description: "Manage configured currencies (add, list, set default)",
37
+ parameters: ManageCurrencyInputSchema,
38
+ execute: wrapExecute(executeManageCurrency),
39
+ });
40
+ // log_expense_from_image removed - replaced by log_expense_from_receipt (agentic OCR)
41
+ api.registerTool({
42
+ name: "log_expense_from_receipt",
43
+ label: "Log Expense From Receipt",
44
+ description: "Log an expense from structured OCR data provided by the agent",
45
+ parameters: LogExpenseFromReceiptInputSchema,
46
+ execute: wrapExecute(executeLogExpenseFromReceipt),
47
+ });
48
+ api.registerTool({
49
+ name: "log_expense_manual",
50
+ label: "Log Expense Manual",
51
+ description: "Log an expense manually",
52
+ parameters: LogExpenseManualInputSchema,
53
+ execute: wrapExecute(executeLogExpenseManual),
54
+ });
55
+ api.registerTool({
56
+ name: "log_income",
57
+ label: "Log Income",
58
+ description: "Log an income entry",
59
+ parameters: LogIncomeInputSchema,
60
+ execute: wrapExecute(executeLogIncome),
61
+ });
62
+ api.registerTool({
63
+ name: "log_income_receipt",
64
+ label: "Log Income Receipt",
65
+ description: "Log a received payment linked to an income entry",
66
+ parameters: LogIncomeReceiptInputSchema,
67
+ execute: wrapExecute(executeLogIncomeReceipt),
68
+ });
69
+ api.registerTool({
70
+ name: "add_recurring_expense",
71
+ label: "Add Recurring Expense",
72
+ description: "Create a recurring expense rule",
73
+ parameters: AddRecurringExpenseInputSchema,
74
+ execute: wrapExecute(executeAddRecurringExpense),
75
+ });
76
+ api.registerTool({
77
+ name: "mark_expense_paid",
78
+ label: "Mark Expense Paid",
79
+ description: "Mark an existing expense as paid",
80
+ parameters: MarkExpensePaidInputSchema,
81
+ execute: wrapExecute(executeMarkExpensePaid),
82
+ });
83
+ api.registerTool({
84
+ name: "get_financial_summary",
85
+ label: "Get Financial Summary",
86
+ description: "Get a financial summary for a given period",
87
+ parameters: GetFinancialSummaryInputSchema,
88
+ execute: wrapExecute(executeGetFinancialSummary),
89
+ });
90
+ api.registerTool({
91
+ name: "list_expenses",
92
+ label: "List Expenses",
93
+ description: "List expenses with filters by period, status, category, or search term",
94
+ parameters: ListExpensesInputSchema,
95
+ execute: wrapExecute(executeListExpenses),
96
+ });
97
+ api.registerTool({
98
+ name: "list_incomes",
99
+ label: "List Incomes",
100
+ description: "List income entries with filters",
101
+ parameters: ListIncomesInputSchema,
102
+ execute: wrapExecute(executeListIncomes),
103
+ });
104
+ api.registerTool({
105
+ name: "run_daily_sync",
106
+ label: "Run Daily Sync",
107
+ description: "Run the daily sync: generates pending recurring expense instances, marks overdue ones, and returns payment reminders for the day. Invoke from automatic cron or when the user wants to see pending items.",
108
+ parameters: RunDailySyncInputSchema,
109
+ execute: wrapExecute(executeRunDailySync),
110
+ });
111
+ },
112
+ });