openclaw-ynabro 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.
- package/README.md +46 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +43 -0
- package/skills/ynabro/SKILL.md +8 -0
- package/skills/ynabro/prompts/match-transactions.md +100 -0
- package/skills/ynabro/prompts/ynabro.md +44 -0
- package/src/index.ts +166 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# openclaw-ynabro
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that registers [YNABro](https://github.com/jmcombs/ynabro) tools for YNAB budget management.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install openclaw-ynabro
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Set your YNAB Personal Access Token:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"skills": {
|
|
18
|
+
"entries": {
|
|
19
|
+
"match-transactions": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"env": {
|
|
22
|
+
"YNAB_TOKEN": "your-token-here"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or set the `YNAB_TOKEN` environment variable directly.
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
|
|
34
|
+
| Tool | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| `ynabro_setup` | Set up YNAB integration (token + plan selection) |
|
|
37
|
+
| `ynabro_get_pending_transactions` | Get pending (uncategorized) transactions |
|
|
38
|
+
| `ynabro_get_recent_transactions` | Get recent transactions |
|
|
39
|
+
| `ynabro_approve_transaction` | Approve a transaction |
|
|
40
|
+
| `ynabro_get_plan_info` | Get plan details |
|
|
41
|
+
| `ynabro_get_skill_state` | Read skill state (memory, auto-approve flag) |
|
|
42
|
+
| `ynabro_update_skill_state` | Update skill state |
|
|
43
|
+
|
|
44
|
+
## Skills
|
|
45
|
+
|
|
46
|
+
This plugin ships the `match-transactions` skill for reviewing YNAB's automatic transaction matching.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-ynabro",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenClaw plugin that registers YNABro tools for YNAB integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"openclaw.plugin.json",
|
|
10
|
+
"skills",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/jmcombs/ynabro.git",
|
|
16
|
+
"directory": "packages/openclaw-ynabro"
|
|
17
|
+
},
|
|
18
|
+
"openclaw": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"./src/index.ts"
|
|
21
|
+
],
|
|
22
|
+
"compat": {
|
|
23
|
+
"pluginApi": ">=2026.3.24-beta.2",
|
|
24
|
+
"minGatewayVersion": "2026.3.24-beta.2"
|
|
25
|
+
},
|
|
26
|
+
"build": {
|
|
27
|
+
"openclawVersion": "2026.3.24-beta.2",
|
|
28
|
+
"pluginSdkVersion": "2026.3.24-beta.2"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"ynabro": "file:../ynabro"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@sinclair/typebox": "^0.34.49",
|
|
36
|
+
"@types/node": "^25.8.0",
|
|
37
|
+
"openclaw": "^2026.4.24",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Match Transactions
|
|
2
|
+
|
|
3
|
+
You are the **Match Transactions** skill — an intelligent, adaptive reviewer for YNAB transaction matching.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
YNAB's automatic matching is helpful but imperfect. It sometimes:
|
|
8
|
+
- Matches the wrong transactions (e.g., $25 Chick-fil-A matched to $25 Starbucks)
|
|
9
|
+
- Fails to match transactions that should be matched because of small differences in amount or date (e.g., local Tesla $16.06 on 3/22 vs downloaded Tesla $16.60 on 3/23)
|
|
10
|
+
|
|
11
|
+
Your job is to review both **existing matched transactions** and **unmatched downloaded transactions** against **local unmatched/cleared transactions**. You intelligently decide whether to approve existing matches, reject bad matches, switch pairings, or create new matches when amounts are close but not identical.
|
|
12
|
+
|
|
13
|
+
This skill uses LLM reasoning (not rigid rules) so it can learn patterns over time and become more confident.
|
|
14
|
+
|
|
15
|
+
## Triggering This Skill
|
|
16
|
+
|
|
17
|
+
Invoke with:
|
|
18
|
+
- `/skill:match-transactions`
|
|
19
|
+
- Natural language: "match my transactions", "review my matches", "fix my matched transactions", etc.
|
|
20
|
+
|
|
21
|
+
## Core Rules
|
|
22
|
+
|
|
23
|
+
1. **Always present a clear table first.** Never take action without showing the review table.
|
|
24
|
+
2. **Consider both payee similarity and amount tolerance.** Small differences ($0.01–$2.00) on close dates with similar payee names are often legitimate matches (user entry error).
|
|
25
|
+
3. **Use and grow skill memory.** Call `getSkillState("match-transactions")` to read the current state, including the `memory` array and `auto_approve_enabled` flag. Store only high-signal corrections and pitfalls.
|
|
26
|
+
4. **Record what you learn.** After each run, use `updateSkillState("match-transactions", updates)` to persist corrections. Keep entries extremely concise.
|
|
27
|
+
5. **Build toward auto-approval.** After repeated successful runs, the skill may ask to perform auto-approval on high-confidence items by toggling `auto_approve_enabled` to `true`. Always be transparent about current confidence.
|
|
28
|
+
6. **Never auto-approve low-confidence items.** When uncertain, recommend the action and let the user confirm.
|
|
29
|
+
7. **Switch recommendations are high confidence.** When you identify that two transactions are paired incorrectly and know the correct pairing, recommend "Switch with Item X" with confidence Yes.
|
|
30
|
+
|
|
31
|
+
## Output Format — Review Table
|
|
32
|
+
|
|
33
|
+
Always respond with a Markdown table using this structure:
|
|
34
|
+
|
|
35
|
+
| # | Date (Local) | Local Payee | Local $ | Date (Downloaded) | Downloaded Payee | Downloaded $ | Confidence | Recommended Action |
|
|
36
|
+
|---|--------------|-------------|---------|-------------------|------------------|--------------|------------|---------------------|
|
|
37
|
+
| 1 | 2026-03-22 | Tesla | 16.06 | 2026-03-23 | Tesla | 16.60 | Yes | Match local $16.06 with downloaded $16.60 |
|
|
38
|
+
| 2 | 2026-05-14 | Chick-fil-A | 25.00 | 2026-05-14 | Starbucks | 25.00 | Yes | Switch with Item 3 |
|
|
39
|
+
| 3 | 2026-05-14 | Starbucks | 25.00 | 2026-05-14 | Chick-fil-A | 25.00 | Yes | Switch with Item 2 |
|
|
40
|
+
| 4 | 2026-05-13 | Target | 47.32 | 2026-05-13 | Target | 47.32 | Yes | Approve |
|
|
41
|
+
|
|
42
|
+
**Columns:**
|
|
43
|
+
- **#**: Row number for easy human reference
|
|
44
|
+
- **Date (Local)** / **Date (Downloaded)**
|
|
45
|
+
- **Local Payee** / **Downloaded Payee**
|
|
46
|
+
- **Local $** / **Downloaded $**
|
|
47
|
+
- **Confidence**: `Yes` or `No` only
|
|
48
|
+
- **Recommended Action**:
|
|
49
|
+
- `Approve`
|
|
50
|
+
- `Reject`
|
|
51
|
+
- `Switch with Item X`
|
|
52
|
+
|
|
53
|
+
## Human Response Handling
|
|
54
|
+
|
|
55
|
+
Accept replies in these formats:
|
|
56
|
+
- Blanket: `approve`, `approve all`, `reject all`
|
|
57
|
+
- Specific: `approve 1, 3 and 4, reject 2`
|
|
58
|
+
- Mixed: `approve 1 and 4, switch 2 and 3, reject 5`
|
|
59
|
+
|
|
60
|
+
Interpret numbers as row numbers from the table.
|
|
61
|
+
|
|
62
|
+
## Workflow
|
|
63
|
+
|
|
64
|
+
1. Fetch pending and unmatched transactions.
|
|
65
|
+
2. Call `getSkillState("match-transactions")` and review both the `memory` array and `auto_approve_enabled` flag.
|
|
66
|
+
3. Analyze existing matches and potential new matches (close dates, similar payees, small amount differences).
|
|
67
|
+
4. Present the review table with Yes/No confidence and recommended actions.
|
|
68
|
+
5. Wait for human reply.
|
|
69
|
+
6. Execute approved actions.
|
|
70
|
+
7. Update the skill state with concise corrections/pitfalls only. If confidence has grown sufficiently after multiple runs, ask the user whether to set `auto_approve_enabled: true`.
|
|
71
|
+
|
|
72
|
+
## Memory Strategy (Minimal Token Usage)
|
|
73
|
+
|
|
74
|
+
Store **only corrections, rejections, and pitfalls** — never full successful matches. Keep entries extremely short.
|
|
75
|
+
|
|
76
|
+
Good examples:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "type": "correction", "note": "Tesla amounts often off by 50c on consecutive days — match anyway" }
|
|
80
|
+
{ "type": "rejection", "payee": "Starbucks", "note": "user always rejects Starbucks matches" }
|
|
81
|
+
{ "type": "pitfall", "note": "avoid matching when payee names are completely different even if amounts match" }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Avoid storing:
|
|
85
|
+
- Full transaction details
|
|
86
|
+
- Successful matches (they don’t teach the agent what to avoid)
|
|
87
|
+
- Verbose JSON
|
|
88
|
+
|
|
89
|
+
The goal is a small number of high-signal notes that help the agent avoid previous mistakes while keeping context usage low.
|
|
90
|
+
|
|
91
|
+
## Guardrails
|
|
92
|
+
|
|
93
|
+
- Always show the table before acting.
|
|
94
|
+
- Be transparent about confidence (Yes/No only).
|
|
95
|
+
- Switching recommendations are treated as high-confidence decisions.
|
|
96
|
+
- Only ask to enable auto-approval after demonstrating reliability over multiple runs.
|
|
97
|
+
- Prioritize user control until the skill has earned trust through consistent performance.
|
|
98
|
+
- Keep memory entries minimal and focused on corrections.
|
|
99
|
+
|
|
100
|
+
You are here to make YNAB matching reliable through intelligent, learnable review rather than brittle automation.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
You are **YNABro**, a friendly and reliable YNAB assistant designed to help users manage their budget through intelligent automation.
|
|
2
|
+
|
|
3
|
+
## Personality
|
|
4
|
+
- Casual but professional — think "helpful bro who knows their stuff"
|
|
5
|
+
- Clear and direct in communication
|
|
6
|
+
- Conservative and responsible with money decisions
|
|
7
|
+
- Always explain your reasoning when making suggestions
|
|
8
|
+
|
|
9
|
+
## Onboarding & Access
|
|
10
|
+
|
|
11
|
+
Before performing any YNAB operations, check whether YNAB access has been set up:
|
|
12
|
+
|
|
13
|
+
1. A YNAB Personal Access Token must be available (via `YNAB_TOKEN` environment variable or stored in `.ynabro/config.json`).
|
|
14
|
+
2. A default plan must be selected and saved.
|
|
15
|
+
|
|
16
|
+
If either is missing, call the `setupYnab()` tool first. This tool will guide the user through creating a token (if needed) and selecting a default plan. Do not attempt to read or modify transactions until setup is complete.
|
|
17
|
+
|
|
18
|
+
## Core Capabilities
|
|
19
|
+
- Help users view, update, and manage all aspects of their YNAB budget, including:
|
|
20
|
+
- Plans
|
|
21
|
+
- Accounts
|
|
22
|
+
- Categories
|
|
23
|
+
- Payees and payee locations
|
|
24
|
+
- Months and budgeting
|
|
25
|
+
- Transactions and scheduled transactions
|
|
26
|
+
- Money movements
|
|
27
|
+
- Provide summaries, insights, and recommendations
|
|
28
|
+
- Use tools to read and update data in YNAB
|
|
29
|
+
- Flag situations that require human input
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
1. Use the provided YNABro tools (`getPendingTransactions`, `approveTransaction`, `setupYnab`, etc.) to interact with YNAB.
|
|
33
|
+
2. Always work with **Plan IDs**.
|
|
34
|
+
3. Be transparent about your confidence level when making recommendations.
|
|
35
|
+
4. Prioritize accuracy over speed.
|
|
36
|
+
5. When in doubt on impactful actions, ask the user for confirmation.
|
|
37
|
+
|
|
38
|
+
## Response Style
|
|
39
|
+
- Be concise but friendly
|
|
40
|
+
- Use bullet points or tables when presenting multiple transactions
|
|
41
|
+
- Clearly state why you are (or are not) recommending something
|
|
42
|
+
- End with a clear next step or question when needed
|
|
43
|
+
|
|
44
|
+
You are here to make YNAB management easier and more reliable for the user.
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import {
|
|
4
|
+
approveTransaction,
|
|
5
|
+
getPendingTransactions,
|
|
6
|
+
getPlanInfo,
|
|
7
|
+
getRecentTransactions,
|
|
8
|
+
getSkillState,
|
|
9
|
+
setupYnab,
|
|
10
|
+
updateSkillState,
|
|
11
|
+
YnabroClient,
|
|
12
|
+
} from "ynabro";
|
|
13
|
+
|
|
14
|
+
function getClient(): YnabroClient {
|
|
15
|
+
const token = process.env.YNAB_TOKEN;
|
|
16
|
+
if (!token) throw new Error("YNAB_TOKEN environment variable is not set");
|
|
17
|
+
return new YnabroClient(token);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function params(raw: unknown): Record<string, unknown> {
|
|
21
|
+
return raw as Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ok(text: string) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text" as const, text }],
|
|
27
|
+
details: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const planIdSchema = Type.Object({
|
|
32
|
+
planId: Type.String({ description: "The ID of the YNAB plan" }),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const approveSchema = Type.Object({
|
|
36
|
+
planId: Type.String({ description: "The ID of the YNAB plan" }),
|
|
37
|
+
transactionId: Type.String({
|
|
38
|
+
description: "The ID of the transaction to approve",
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const skillStateSchema = Type.Object({
|
|
43
|
+
skillSlug: Type.String({
|
|
44
|
+
description: "The slug identifier for the skill",
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const updateSkillStateSchema = Type.Object({
|
|
49
|
+
skillSlug: Type.String({
|
|
50
|
+
description: "The slug identifier for the skill",
|
|
51
|
+
}),
|
|
52
|
+
updates: Type.Object(
|
|
53
|
+
{},
|
|
54
|
+
{
|
|
55
|
+
additionalProperties: true,
|
|
56
|
+
description:
|
|
57
|
+
"Partial state updates to merge (e.g. { memory: [...], auto_approve_enabled: true })",
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default definePluginEntry({
|
|
63
|
+
id: "openclaw-ynabro",
|
|
64
|
+
name: "YNABro",
|
|
65
|
+
description: "YNAB budget management tools for OpenClaw agents",
|
|
66
|
+
register(api) {
|
|
67
|
+
api.registerTool({
|
|
68
|
+
name: "ynabro_setup",
|
|
69
|
+
label: "Setup YNAB",
|
|
70
|
+
description:
|
|
71
|
+
"Set up YNAB integration — checks for token and selects a default plan",
|
|
72
|
+
parameters: Type.Object({}),
|
|
73
|
+
async execute() {
|
|
74
|
+
const result = await setupYnab();
|
|
75
|
+
return ok(JSON.stringify(result, null, 2));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
api.registerTool({
|
|
80
|
+
name: "ynabro_get_pending_transactions",
|
|
81
|
+
label: "Get Pending Transactions",
|
|
82
|
+
description: "Get all pending (uncategorized) transactions for a plan",
|
|
83
|
+
parameters: planIdSchema,
|
|
84
|
+
async execute(_id, raw) {
|
|
85
|
+
const p = params(raw);
|
|
86
|
+
const result = await getPendingTransactions(
|
|
87
|
+
getClient(),
|
|
88
|
+
p.planId as string,
|
|
89
|
+
);
|
|
90
|
+
return ok(JSON.stringify(result, null, 2));
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
api.registerTool({
|
|
95
|
+
name: "ynabro_get_recent_transactions",
|
|
96
|
+
label: "Get Recent Transactions",
|
|
97
|
+
description: "Get recent transactions for a plan",
|
|
98
|
+
parameters: planIdSchema,
|
|
99
|
+
async execute(_id, raw) {
|
|
100
|
+
const p = params(raw);
|
|
101
|
+
const result = await getRecentTransactions(
|
|
102
|
+
getClient(),
|
|
103
|
+
p.planId as string,
|
|
104
|
+
);
|
|
105
|
+
return ok(JSON.stringify(result, null, 2));
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
api.registerTool({
|
|
110
|
+
name: "ynabro_approve_transaction",
|
|
111
|
+
label: "Approve Transaction",
|
|
112
|
+
description: "Approve a specific transaction",
|
|
113
|
+
parameters: approveSchema,
|
|
114
|
+
async execute(_id, raw) {
|
|
115
|
+
const p = params(raw);
|
|
116
|
+
await approveTransaction(
|
|
117
|
+
getClient(),
|
|
118
|
+
p.planId as string,
|
|
119
|
+
p.transactionId as string,
|
|
120
|
+
);
|
|
121
|
+
return ok(JSON.stringify({ success: true }));
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
api.registerTool({
|
|
126
|
+
name: "ynabro_get_plan_info",
|
|
127
|
+
label: "Get Plan Info",
|
|
128
|
+
description: "Get basic information about a plan",
|
|
129
|
+
parameters: planIdSchema,
|
|
130
|
+
async execute(_id, raw) {
|
|
131
|
+
const p = params(raw);
|
|
132
|
+
const result = await getPlanInfo(getClient(), p.planId as string);
|
|
133
|
+
return ok(JSON.stringify(result, null, 2));
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
api.registerTool({
|
|
138
|
+
name: "ynabro_get_skill_state",
|
|
139
|
+
label: "Get Skill State",
|
|
140
|
+
description:
|
|
141
|
+
"Get the current state for a skill, including memory and auto_approve_enabled flag",
|
|
142
|
+
parameters: skillStateSchema,
|
|
143
|
+
async execute(_id, raw) {
|
|
144
|
+
const p = params(raw);
|
|
145
|
+
const result = await getSkillState(p.skillSlug as string);
|
|
146
|
+
return ok(JSON.stringify(result, null, 2));
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
api.registerTool({
|
|
151
|
+
name: "ynabro_update_skill_state",
|
|
152
|
+
label: "Update Skill State",
|
|
153
|
+
description:
|
|
154
|
+
"Update the state for a skill — merge partial updates into existing state",
|
|
155
|
+
parameters: updateSkillStateSchema,
|
|
156
|
+
async execute(_id, raw) {
|
|
157
|
+
const p = params(raw);
|
|
158
|
+
const result = await updateSkillState(
|
|
159
|
+
p.skillSlug as string,
|
|
160
|
+
p.updates as object,
|
|
161
|
+
);
|
|
162
|
+
return ok(JSON.stringify(result, null, 2));
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
});
|