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 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": "1.0.0",
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
- "matched-transaction-reviewer"
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": {
@@ -1,8 +1,8 @@
1
1
  ---
2
- name: matched-transaction-reviewer
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/matching-rules.md`.
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 his stuff"
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. **Never auto-approve low-confidence matches.** When in doubt, ask the user or flag it.
24
- 2. Use the provided YNABro tools (`getPendingTransactions`, `approveTransaction`, etc.) to interact with YNAB.
25
- 3. Always work with **Plan IDs**.
26
- 4. Be transparent about your confidence level when suggesting approvals.
27
- 5. Prioritize accuracy over speed.
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) approving something
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
- ExtensionAPI,
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
- export default function ynabroExtension(api: ExtensionAPI) {
14
- // Register YNABro tools
15
- const tools: ToolDefinition[] = [
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
- name: "ynabro_get_pending_transactions",
18
- description: "Get all pending (uncategorized) transactions for a plan",
19
- parameters: {
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
- name: "ynabro_get_recent_transactions",
39
- description: "Get recent transactions for a plan",
40
- parameters: {
41
- type: "object",
42
- properties: {
43
- planId: {
44
- type: "string",
45
- description: "The ID of the YNAB plan",
46
- },
47
- },
48
- required: ["planId"],
49
- },
50
- handler: async ({ planId }) => {
51
- const token = process.env.YNAB_TOKEN;
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
- name: "ynabro_approve_transaction",
60
- description: "Approve a specific transaction",
61
- parameters: {
62
- type: "object",
63
- properties: {
64
- planId: {
65
- type: "string",
66
- description: "The ID of the YNAB plan",
67
- },
68
- transactionId: {
69
- type: "string",
70
- description: "The ID of the transaction to approve",
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
- name: "ynabro_get_plan_info",
86
- description: "Get basic information about a plan",
87
- parameters: {
88
- type: "object",
89
- properties: {
90
- planId: {
91
- type: "string",
92
- description: "The ID of the YNAB plan",
93
- },
94
- },
95
- required: ["planId"],
96
- },
97
- handler: async ({ planId }) => {
98
- const token = process.env.YNAB_TOKEN;
99
- if (!token)
100
- throw new Error("YNAB_TOKEN environment variable is not set");
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
- for (const tool of tools) {
108
- api.registerTool(tool);
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
  }