pi-ynabro 1.0.0 → 2.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 +3 -0
- package/package.json +4 -3
- package/skills/ynabro/SKILL.md +2 -2
- package/skills/ynabro/prompts/match-transactions.md +100 -0
- package/skills/ynabro/prompts/ynabro.md +17 -8
- package/src/index.ts +141 -95
package/README.md
CHANGED
|
@@ -18,10 +18,13 @@ pi install npm:pi-ynabro
|
|
|
18
18
|
|
|
19
19
|
## Available Tools
|
|
20
20
|
|
|
21
|
+
- `ynabro_setup`
|
|
21
22
|
- `ynabro_get_pending_transactions`
|
|
22
23
|
- `ynabro_get_recent_transactions`
|
|
23
24
|
- `ynabro_approve_transaction`
|
|
24
25
|
- `ynabro_get_plan_info`
|
|
26
|
+
- `ynabro_get_skill_state`
|
|
27
|
+
- `ynabro_update_skill_state`
|
|
25
28
|
|
|
26
29
|
## Requirements
|
|
27
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ynabro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Pi extension that registers YNABro tools for YNAB integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"./src/index.ts"
|
|
22
22
|
],
|
|
23
23
|
"skills": [
|
|
24
|
-
"
|
|
24
|
+
"match-transactions"
|
|
25
25
|
]
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"ynabro": "file
|
|
28
|
+
"ynabro": "file:../ynabro"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
32
32
|
"@types/node": "^25.8.0",
|
|
33
|
+
"typebox": "^1.1.37",
|
|
33
34
|
"typescript": "^6.0.3"
|
|
34
35
|
},
|
|
35
36
|
"scripts": {
|
package/skills/ynabro/SKILL.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: match-transactions
|
|
3
3
|
description: Reviews YNAB's matched transactions and provides clear recommendations before approval.
|
|
4
4
|
version: 0.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
This skill uses the prompt defined in `prompts/
|
|
7
|
+
This skill uses the prompt defined in `prompts/match-transactions.md`.
|
|
8
8
|
```
|
|
@@ -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.
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
You are **YNABro**, a friendly and reliable YNAB assistant designed to help users manage their budget through intelligent automation.
|
|
2
2
|
|
|
3
3
|
## Personality
|
|
4
|
-
- Casual but professional — think "helpful bro who knows
|
|
4
|
+
- Casual but professional — think "helpful bro who knows their stuff"
|
|
5
5
|
- Clear and direct in communication
|
|
6
6
|
- Conservative and responsible with money decisions
|
|
7
7
|
- Always explain your reasoning when making suggestions
|
|
8
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
|
+
|
|
9
18
|
## Core Capabilities
|
|
10
19
|
- Help users view, update, and manage all aspects of their YNAB budget, including:
|
|
11
20
|
- Plans
|
|
@@ -20,16 +29,16 @@ You are **YNABro**, a friendly and reliable YNAB assistant designed to help user
|
|
|
20
29
|
- Flag situations that require human input
|
|
21
30
|
|
|
22
31
|
## Rules
|
|
23
|
-
1.
|
|
24
|
-
2.
|
|
25
|
-
3.
|
|
26
|
-
4.
|
|
27
|
-
5.
|
|
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.
|
|
28
37
|
|
|
29
38
|
## Response Style
|
|
30
39
|
- Be concise but friendly
|
|
31
40
|
- Use bullet points or tables when presenting multiple transactions
|
|
32
|
-
- Clearly state why you are (or are not)
|
|
41
|
+
- Clearly state why you are (or are not) recommending something
|
|
33
42
|
- End with a clear next step or question when needed
|
|
34
43
|
|
|
35
|
-
You are here to make YNAB management easier and more reliable for the user.
|
|
44
|
+
You are here to make YNAB management easier and more reliable for the user.
|
package/src/index.ts
CHANGED
|
@@ -1,110 +1,156 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
ToolDefinition,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
5
3
|
import {
|
|
6
4
|
approveTransaction,
|
|
7
5
|
getPendingTransactions,
|
|
8
6
|
getPlanInfo,
|
|
9
7
|
getRecentTransactions,
|
|
8
|
+
getSkillState,
|
|
9
|
+
setupYnab,
|
|
10
|
+
updateSkillState,
|
|
10
11
|
YnabroClient,
|
|
11
12
|
} from "ynabro";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
const planIdSchema = Type.Object({
|
|
21
|
+
planId: Type.String({ description: "The ID of the YNAB plan" }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const approveSchema = Type.Object({
|
|
25
|
+
planId: Type.String({ description: "The ID of the YNAB plan" }),
|
|
26
|
+
transactionId: Type.String({
|
|
27
|
+
description: "The ID of the transaction to approve",
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const skillStateSchema = Type.Object({
|
|
32
|
+
skillSlug: Type.String({
|
|
33
|
+
description: "The slug identifier for the skill",
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const updateSkillStateSchema = Type.Object({
|
|
38
|
+
skillSlug: Type.String({
|
|
39
|
+
description: "The slug identifier for the skill",
|
|
40
|
+
}),
|
|
41
|
+
updates: Type.Object(
|
|
42
|
+
{},
|
|
16
43
|
{
|
|
17
|
-
|
|
18
|
-
description:
|
|
19
|
-
|
|
20
|
-
type: "object",
|
|
21
|
-
properties: {
|
|
22
|
-
planId: {
|
|
23
|
-
type: "string",
|
|
24
|
-
description: "The ID of the YNAB plan",
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
required: ["planId"],
|
|
28
|
-
},
|
|
29
|
-
handler: async ({ planId }) => {
|
|
30
|
-
const token = process.env.YNAB_TOKEN;
|
|
31
|
-
if (!token)
|
|
32
|
-
throw new Error("YNAB_TOKEN environment variable is not set");
|
|
33
|
-
const client = new YnabroClient(token);
|
|
34
|
-
return getPendingTransactions(client, planId);
|
|
35
|
-
},
|
|
44
|
+
additionalProperties: true,
|
|
45
|
+
description:
|
|
46
|
+
"Partial state updates to merge (e.g. { memory: [...], auto_approve_enabled: true })",
|
|
36
47
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!token)
|
|
53
|
-
throw new Error("YNAB_TOKEN environment variable is not set");
|
|
54
|
-
const client = new YnabroClient(token);
|
|
55
|
-
return getRecentTransactions(client, planId);
|
|
56
|
-
},
|
|
48
|
+
),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default function ynabroExtension(api: ExtensionAPI): void {
|
|
52
|
+
api.registerTool({
|
|
53
|
+
name: "ynabro_get_pending_transactions",
|
|
54
|
+
label: "Get Pending Transactions",
|
|
55
|
+
description: "Get all pending (uncategorized) transactions for a plan",
|
|
56
|
+
parameters: planIdSchema,
|
|
57
|
+
async execute(_toolCallId, params) {
|
|
58
|
+
const result = await getPendingTransactions(getClient(), params.planId);
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
61
|
+
details: undefined,
|
|
62
|
+
};
|
|
57
63
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
required: ["planId", "transactionId"],
|
|
74
|
-
},
|
|
75
|
-
handler: async ({ planId, transactionId }) => {
|
|
76
|
-
const token = process.env.YNAB_TOKEN;
|
|
77
|
-
if (!token)
|
|
78
|
-
throw new Error("YNAB_TOKEN environment variable is not set");
|
|
79
|
-
const client = new YnabroClient(token);
|
|
80
|
-
await approveTransaction(client, planId, transactionId);
|
|
81
|
-
return { success: true };
|
|
82
|
-
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
api.registerTool({
|
|
67
|
+
name: "ynabro_get_recent_transactions",
|
|
68
|
+
label: "Get Recent Transactions",
|
|
69
|
+
description: "Get recent transactions for a plan",
|
|
70
|
+
parameters: planIdSchema,
|
|
71
|
+
async execute(_toolCallId, params) {
|
|
72
|
+
const result = await getRecentTransactions(getClient(), params.planId);
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
75
|
+
details: undefined,
|
|
76
|
+
};
|
|
83
77
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const client = new YnabroClient(token);
|
|
102
|
-
return getPlanInfo(client, planId);
|
|
103
|
-
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
api.registerTool({
|
|
81
|
+
name: "ynabro_approve_transaction",
|
|
82
|
+
label: "Approve Transaction",
|
|
83
|
+
description: "Approve a specific transaction",
|
|
84
|
+
parameters: approveSchema,
|
|
85
|
+
async execute(_toolCallId, params) {
|
|
86
|
+
await approveTransaction(
|
|
87
|
+
getClient(),
|
|
88
|
+
params.planId,
|
|
89
|
+
params.transactionId,
|
|
90
|
+
);
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text", text: JSON.stringify({ success: true }) }],
|
|
93
|
+
details: undefined,
|
|
94
|
+
};
|
|
104
95
|
},
|
|
105
|
-
|
|
96
|
+
});
|
|
106
97
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
api.registerTool({
|
|
99
|
+
name: "ynabro_get_plan_info",
|
|
100
|
+
label: "Get Plan Info",
|
|
101
|
+
description: "Get basic information about a plan",
|
|
102
|
+
parameters: planIdSchema,
|
|
103
|
+
async execute(_toolCallId, params) {
|
|
104
|
+
const result = await getPlanInfo(getClient(), params.planId);
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
107
|
+
details: undefined,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
api.registerTool({
|
|
113
|
+
name: "ynabro_setup",
|
|
114
|
+
label: "Setup YNAB",
|
|
115
|
+
description:
|
|
116
|
+
"Set up YNAB integration — checks for token and selects a default plan",
|
|
117
|
+
parameters: Type.Object({}),
|
|
118
|
+
async execute() {
|
|
119
|
+
const result = await setupYnab();
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
122
|
+
details: undefined,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
api.registerTool({
|
|
128
|
+
name: "ynabro_get_skill_state",
|
|
129
|
+
label: "Get Skill State",
|
|
130
|
+
description:
|
|
131
|
+
"Get the current state for a skill, including memory and auto_approve_enabled flag",
|
|
132
|
+
parameters: skillStateSchema,
|
|
133
|
+
async execute(_toolCallId, params) {
|
|
134
|
+
const result = await getSkillState(params.skillSlug);
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
137
|
+
details: undefined,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
api.registerTool({
|
|
143
|
+
name: "ynabro_update_skill_state",
|
|
144
|
+
label: "Update Skill State",
|
|
145
|
+
description:
|
|
146
|
+
"Update the state for a skill — merge partial updates into existing state",
|
|
147
|
+
parameters: updateSkillStateSchema,
|
|
148
|
+
async execute(_toolCallId, params) {
|
|
149
|
+
const result = await updateSkillState(params.skillSlug, params.updates);
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
152
|
+
details: undefined,
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
});
|
|
110
156
|
}
|