openclaw-ynabro 2.1.0 → 2.2.1

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.
@@ -0,0 +1,9 @@
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" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
8
+ export default _default;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;AA0DA,wBAwQG"}
package/dist/index.js ADDED
@@ -0,0 +1,267 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { approveTransaction, checkOnboardingStatus, getPendingTransactions, getPlanInfo, getRecentTransactions, getSkillState, setupYnab, updateSkillState, YnabroClient, } from "ynabro";
4
+ function params(raw) {
5
+ return raw;
6
+ }
7
+ function ok(text) {
8
+ return {
9
+ content: [{ type: "text", text }],
10
+ details: undefined,
11
+ };
12
+ }
13
+ const approveSchema = Type.Object({
14
+ transactionId: Type.String({
15
+ description: "The ID of the transaction to approve",
16
+ }),
17
+ });
18
+ const saveDefaultPlanSchema = Type.Object({
19
+ planId: Type.String({
20
+ description: "The ID of the YNAB plan to set as the default",
21
+ }),
22
+ });
23
+ const skillStateSchema = Type.Object({
24
+ skillSlug: Type.String({
25
+ description: "The slug identifier for the skill",
26
+ }),
27
+ });
28
+ const updateSkillStateSchema = Type.Object({
29
+ skillSlug: Type.String({
30
+ description: "The slug identifier for the skill",
31
+ }),
32
+ updates: Type.Object({}, {
33
+ additionalProperties: true,
34
+ description: "Partial state updates to merge (e.g. { memory: [...], auto_approve_enabled: true })",
35
+ }),
36
+ });
37
+ export default definePluginEntry({
38
+ id: "openclaw-ynabro",
39
+ name: "YNABro",
40
+ description: "YNAB budget management tools for OpenClaw agents",
41
+ register(api) {
42
+ let cachedPlanId;
43
+ const openClawAdapter = {
44
+ async getDefaultPlanId() {
45
+ if (cachedPlanId !== undefined)
46
+ return cachedPlanId;
47
+ return api.pluginConfig
48
+ ?.defaultPlanId;
49
+ },
50
+ async setDefaultPlanId(planId) {
51
+ cachedPlanId = planId;
52
+ await api.runtime.config.mutateConfigFile({
53
+ afterWrite: { mode: "auto" },
54
+ mutate: (draft) => {
55
+ // Ensure the path exists before writing
56
+ if (!draft.plugins) {
57
+ draft.plugins = {
58
+ entries: {},
59
+ };
60
+ }
61
+ // TypeScript flow analysis requires explicit check after conditional assignment
62
+ if (draft.plugins) {
63
+ if (!draft.plugins.entries) {
64
+ draft.plugins.entries = {};
65
+ }
66
+ const existing = draft.plugins.entries["openclaw-ynabro"] ?? {};
67
+ const existingConfig = existing.config ?? {};
68
+ existingConfig.defaultPlanId = planId;
69
+ draft.plugins.entries["openclaw-ynabro"] = {
70
+ ...existing,
71
+ config: existingConfig,
72
+ };
73
+ }
74
+ },
75
+ });
76
+ },
77
+ async hasToken() {
78
+ return !!api.pluginConfig?.token;
79
+ },
80
+ };
81
+ function getClient() {
82
+ const token = api.pluginConfig?.token;
83
+ if (!token) {
84
+ throw new Error("YNAB token not configured. " +
85
+ "Set it via plugins.entries.openclaw-ynabro.config.token in openclaw.json.");
86
+ }
87
+ return new YnabroClient(token);
88
+ }
89
+ async function getDefaultPlanId() {
90
+ const planId = await openClawAdapter.getDefaultPlanId();
91
+ if (!planId) {
92
+ throw new Error("No default plan configured. Run ynabro_setup then ynabro_save_default_plan to complete onboarding.");
93
+ }
94
+ return planId;
95
+ }
96
+ api.registerTool({
97
+ name: "ynabro_onboarding_status",
98
+ label: "Onboarding Status",
99
+ description: "Check whether YNABro is fully configured. Returns ready status, any missing configuration, and token generation instructions.",
100
+ parameters: Type.Object({}),
101
+ async execute() {
102
+ const status = await checkOnboardingStatus(openClawAdapter);
103
+ return ok(JSON.stringify(status));
104
+ },
105
+ });
106
+ api.registerTool({
107
+ name: "ynabro_setup",
108
+ label: "Setup YNAB",
109
+ description: "Fetch available YNAB plans for onboarding. Returns a list of plans. " +
110
+ "After the user selects one, call ynabro_save_default_plan with the chosen plan ID.",
111
+ parameters: Type.Object({}),
112
+ async execute() {
113
+ const client = getClient();
114
+ const plans = await client.getPlans();
115
+ if (plans.length === 0) {
116
+ return ok(JSON.stringify({ error: "No plans found in your YNAB account." }));
117
+ }
118
+ return ok(JSON.stringify({
119
+ plans: plans.map((p) => ({ id: p.id, name: p.name })),
120
+ }));
121
+ },
122
+ });
123
+ api.registerTool({
124
+ name: "ynabro_save_default_plan",
125
+ label: "Save Default Plan",
126
+ description: "Save a YNAB plan as the default for all subsequent tool calls. " +
127
+ "Call ynabro_setup first to get the list of available plan IDs.",
128
+ parameters: saveDefaultPlanSchema,
129
+ async execute(_id, raw) {
130
+ try {
131
+ const p = params(raw);
132
+ const client = getClient();
133
+ const plans = await client.getPlans();
134
+ await setupYnab(client, plans, p.planId, openClawAdapter);
135
+ const saved = plans.find((plan) => plan.id === p.planId);
136
+ return ok(JSON.stringify({
137
+ message: `Default plan set to: ${saved?.name ?? p.planId}`,
138
+ defaultPlanId: p.planId,
139
+ }));
140
+ }
141
+ catch (_error) {
142
+ const status = await checkOnboardingStatus(openClawAdapter);
143
+ return ok(JSON.stringify({
144
+ error: "onboarding_required",
145
+ ...status,
146
+ }));
147
+ }
148
+ },
149
+ });
150
+ api.registerTool({
151
+ name: "ynabro_get_pending_transactions",
152
+ label: "Get Pending Transactions",
153
+ description: "Get all pending (uncategorized) transactions for a plan",
154
+ parameters: Type.Object({}),
155
+ async execute() {
156
+ try {
157
+ const [client, planId] = await Promise.all([
158
+ Promise.resolve(getClient()),
159
+ getDefaultPlanId(),
160
+ ]);
161
+ const result = await getPendingTransactions(client, planId);
162
+ return ok(JSON.stringify(result, null, 2));
163
+ }
164
+ catch (_error) {
165
+ const status = await checkOnboardingStatus(openClawAdapter);
166
+ return ok(JSON.stringify({
167
+ error: "onboarding_required",
168
+ ...status,
169
+ }));
170
+ }
171
+ },
172
+ });
173
+ api.registerTool({
174
+ name: "ynabro_get_recent_transactions",
175
+ label: "Get Recent Transactions",
176
+ description: "Get recent transactions for a plan",
177
+ parameters: Type.Object({}),
178
+ async execute() {
179
+ try {
180
+ const [client, planId] = await Promise.all([
181
+ Promise.resolve(getClient()),
182
+ getDefaultPlanId(),
183
+ ]);
184
+ const result = await getRecentTransactions(client, planId);
185
+ return ok(JSON.stringify(result, null, 2));
186
+ }
187
+ catch (_error) {
188
+ const status = await checkOnboardingStatus(openClawAdapter);
189
+ return ok(JSON.stringify({
190
+ error: "onboarding_required",
191
+ ...status,
192
+ }));
193
+ }
194
+ },
195
+ });
196
+ api.registerTool({
197
+ name: "ynabro_approve_transaction",
198
+ label: "Approve Transaction",
199
+ description: "Approve a specific transaction",
200
+ parameters: approveSchema,
201
+ async execute(_id, raw) {
202
+ try {
203
+ const p = params(raw);
204
+ const [client, planId] = await Promise.all([
205
+ Promise.resolve(getClient()),
206
+ getDefaultPlanId(),
207
+ ]);
208
+ await approveTransaction(client, planId, p.transactionId);
209
+ return ok(JSON.stringify({ success: true }));
210
+ }
211
+ catch (_error) {
212
+ const status = await checkOnboardingStatus(openClawAdapter);
213
+ return ok(JSON.stringify({
214
+ error: "onboarding_required",
215
+ ...status,
216
+ }));
217
+ }
218
+ },
219
+ });
220
+ api.registerTool({
221
+ name: "ynabro_get_plan_info",
222
+ label: "Get Plan Info",
223
+ description: "Get basic information about a plan",
224
+ parameters: Type.Object({}),
225
+ async execute() {
226
+ try {
227
+ const [client, planId] = await Promise.all([
228
+ Promise.resolve(getClient()),
229
+ getDefaultPlanId(),
230
+ ]);
231
+ const result = await getPlanInfo(client, planId);
232
+ return ok(JSON.stringify(result, null, 2));
233
+ }
234
+ catch (_error) {
235
+ const status = await checkOnboardingStatus(openClawAdapter);
236
+ return ok(JSON.stringify({
237
+ error: "onboarding_required",
238
+ ...status,
239
+ }));
240
+ }
241
+ },
242
+ });
243
+ api.registerTool({
244
+ name: "ynabro_get_skill_state",
245
+ label: "Get Skill State",
246
+ description: "Get the current state for a skill, including memory and auto_approve_enabled flag",
247
+ parameters: skillStateSchema,
248
+ async execute(_id, raw) {
249
+ const p = params(raw);
250
+ const result = await getSkillState(p.skillSlug);
251
+ return ok(JSON.stringify(result, null, 2));
252
+ },
253
+ });
254
+ api.registerTool({
255
+ name: "ynabro_update_skill_state",
256
+ label: "Update Skill State",
257
+ description: "Update the state for a skill — merge partial updates into existing state",
258
+ parameters: updateSkillStateSchema,
259
+ async execute(_id, raw) {
260
+ const p = params(raw);
261
+ const result = await updateSkillState(p.skillSlug, p.updates);
262
+ return ok(JSON.stringify(result, null, 2));
263
+ },
264
+ });
265
+ },
266
+ });
267
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "contracts": {
37
37
  "tools": [
38
+ "ynabro_onboarding_status",
38
39
  "ynabro_setup",
39
40
  "ynabro_save_default_plan",
40
41
  "ynabro_get_pending_transactions",
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "openclaw-ynabro",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "OpenClaw plugin that registers YNABro tools for YNAB integration",
5
5
  "type": "module",
6
- "main": "./src/index.ts",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
7
13
  "files": [
14
+ "dist",
8
15
  "src",
9
16
  "openclaw.plugin.json",
10
17
  "full_logo.png",
@@ -18,8 +25,8 @@
18
25
  "directory": "packages/openclaw-ynabro"
19
26
  },
20
27
  "openclaw": {
21
- "extensions": [
22
- "./src/index.ts"
28
+ "hooks": [
29
+ "./dist/index.js"
23
30
  ],
24
31
  "compat": {
25
32
  "pluginApi": ">=2026.3.24-beta.2",
@@ -40,6 +47,7 @@
40
47
  "typescript": "^6.0.3"
41
48
  },
42
49
  "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
43
51
  "typecheck": "tsc --noEmit"
44
52
  }
45
53
  }
@@ -8,12 +8,29 @@ You are **YNABro**, a friendly and reliable YNAB assistant designed to help user
8
8
 
9
9
  ## Onboarding & Access
10
10
 
11
- Before performing any YNAB operations, check whether YNAB access has been set up:
11
+ Before performing any YNAB operations, call `ynabro_onboarding_status` to check
12
+ whether YNAB access is configured.
12
13
 
13
- 1. A YNAB Personal Access Token must be available. In OpenClaw, the preferred storage location is `plugins.entries.openclaw-ynabro.config.token` in `openclaw.json` (surfaced as a sensitive field in the settings UI). The `YNAB_TOKEN` environment variable is also accepted as a fallback.
14
- 2. A default plan must be selected and saved.
14
+ If `ready` is `false`, walk the user through setup:
15
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.
16
+ 1. **Missing token:** Share the `tokenInstructions` field from the status response.
17
+ The token must never be entered into the chat.
18
+ - **pi:** Call `ynabro_setup` — it presents a native TUI input popup where the
19
+ user enters the token directly. It goes straight to pi's AuthStorage and
20
+ never appears in the conversation.
21
+ - **OpenClaw:** Instruct the user to add the token to `openclaw.json` or via
22
+ the OpenClaw settings UI, then ask them to confirm when done.
23
+
24
+ 2. **Missing plan:** Call `ynabro_setup` to list available plans. Help the user
25
+ pick one. On OpenClaw, follow up with `ynabro_save_default_plan`.
26
+
27
+ 3. **After onboarding completes:** If the user's original message was a functional
28
+ request (e.g., "show my pending transactions"), fulfill it immediately.
29
+ Do not make them repeat themselves.
30
+
31
+ If a tool returns `{ "error": "onboarding_required" }` during a conversation,
32
+ treat it the same as a failed status check — initiate onboarding, then retry
33
+ the original operation.
17
34
 
18
35
  ## Core Capabilities
19
36
  - Help users view, update, and manage all aspects of their YNAB budget, including:
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
3
  import type { YnabroConfigAdapter } from "ynabro";
4
4
  import {
5
5
  approveTransaction,
6
+ checkOnboardingStatus,
6
7
  getPendingTransactions,
7
8
  getPlanInfo,
8
9
  getRecentTransactions,
@@ -96,6 +97,9 @@ export default definePluginEntry({
96
97
  },
97
98
  });
98
99
  },
100
+ async hasToken(): Promise<boolean> {
101
+ return !!(api.pluginConfig as { token?: string } | undefined)?.token;
102
+ },
99
103
  };
100
104
 
101
105
  function getClient(): YnabroClient {
@@ -119,6 +123,18 @@ export default definePluginEntry({
119
123
  return planId;
120
124
  }
121
125
 
126
+ api.registerTool({
127
+ name: "ynabro_onboarding_status",
128
+ label: "Onboarding Status",
129
+ description:
130
+ "Check whether YNABro is fully configured. Returns ready status, any missing configuration, and token generation instructions.",
131
+ parameters: Type.Object({}),
132
+ async execute() {
133
+ const status = await checkOnboardingStatus(openClawAdapter);
134
+ return ok(JSON.stringify(status));
135
+ },
136
+ });
137
+
122
138
  api.registerTool({
123
139
  name: "ynabro_setup",
124
140
  label: "Setup YNAB",
@@ -150,17 +166,27 @@ export default definePluginEntry({
150
166
  "Call ynabro_setup first to get the list of available plan IDs.",
151
167
  parameters: saveDefaultPlanSchema,
152
168
  async execute(_id, raw) {
153
- const p = params(raw);
154
- const client = getClient();
155
- const plans = await client.getPlans();
156
- await setupYnab(client, plans, p.planId as string, openClawAdapter);
157
- const saved = plans.find((plan) => plan.id === p.planId);
158
- return ok(
159
- JSON.stringify({
160
- message: `Default plan set to: ${saved?.name ?? p.planId}`,
161
- defaultPlanId: p.planId,
162
- }),
163
- );
169
+ try {
170
+ const p = params(raw);
171
+ const client = getClient();
172
+ const plans = await client.getPlans();
173
+ await setupYnab(client, plans, p.planId as string, openClawAdapter);
174
+ const saved = plans.find((plan) => plan.id === p.planId);
175
+ return ok(
176
+ JSON.stringify({
177
+ message: `Default plan set to: ${saved?.name ?? p.planId}`,
178
+ defaultPlanId: p.planId,
179
+ }),
180
+ );
181
+ } catch (_error) {
182
+ const status = await checkOnboardingStatus(openClawAdapter);
183
+ return ok(
184
+ JSON.stringify({
185
+ error: "onboarding_required",
186
+ ...status,
187
+ }),
188
+ );
189
+ }
164
190
  },
165
191
  });
166
192
 
@@ -170,12 +196,22 @@ export default definePluginEntry({
170
196
  description: "Get all pending (uncategorized) transactions for a plan",
171
197
  parameters: Type.Object({}),
172
198
  async execute() {
173
- const [client, planId] = await Promise.all([
174
- Promise.resolve(getClient()),
175
- getDefaultPlanId(),
176
- ]);
177
- const result = await getPendingTransactions(client, planId);
178
- return ok(JSON.stringify(result, null, 2));
199
+ try {
200
+ const [client, planId] = await Promise.all([
201
+ Promise.resolve(getClient()),
202
+ getDefaultPlanId(),
203
+ ]);
204
+ const result = await getPendingTransactions(client, planId);
205
+ return ok(JSON.stringify(result, null, 2));
206
+ } catch (_error) {
207
+ const status = await checkOnboardingStatus(openClawAdapter);
208
+ return ok(
209
+ JSON.stringify({
210
+ error: "onboarding_required",
211
+ ...status,
212
+ }),
213
+ );
214
+ }
179
215
  },
180
216
  });
181
217
 
@@ -185,12 +221,22 @@ export default definePluginEntry({
185
221
  description: "Get recent transactions for a plan",
186
222
  parameters: Type.Object({}),
187
223
  async execute() {
188
- const [client, planId] = await Promise.all([
189
- Promise.resolve(getClient()),
190
- getDefaultPlanId(),
191
- ]);
192
- const result = await getRecentTransactions(client, planId);
193
- return ok(JSON.stringify(result, null, 2));
224
+ try {
225
+ const [client, planId] = await Promise.all([
226
+ Promise.resolve(getClient()),
227
+ getDefaultPlanId(),
228
+ ]);
229
+ const result = await getRecentTransactions(client, planId);
230
+ return ok(JSON.stringify(result, null, 2));
231
+ } catch (_error) {
232
+ const status = await checkOnboardingStatus(openClawAdapter);
233
+ return ok(
234
+ JSON.stringify({
235
+ error: "onboarding_required",
236
+ ...status,
237
+ }),
238
+ );
239
+ }
194
240
  },
195
241
  });
196
242
 
@@ -200,13 +246,23 @@ export default definePluginEntry({
200
246
  description: "Approve a specific transaction",
201
247
  parameters: approveSchema,
202
248
  async execute(_id, raw) {
203
- const p = params(raw);
204
- const [client, planId] = await Promise.all([
205
- Promise.resolve(getClient()),
206
- getDefaultPlanId(),
207
- ]);
208
- await approveTransaction(client, planId, p.transactionId as string);
209
- return ok(JSON.stringify({ success: true }));
249
+ try {
250
+ const p = params(raw);
251
+ const [client, planId] = await Promise.all([
252
+ Promise.resolve(getClient()),
253
+ getDefaultPlanId(),
254
+ ]);
255
+ await approveTransaction(client, planId, p.transactionId as string);
256
+ return ok(JSON.stringify({ success: true }));
257
+ } catch (_error) {
258
+ const status = await checkOnboardingStatus(openClawAdapter);
259
+ return ok(
260
+ JSON.stringify({
261
+ error: "onboarding_required",
262
+ ...status,
263
+ }),
264
+ );
265
+ }
210
266
  },
211
267
  });
212
268
 
@@ -216,12 +272,22 @@ export default definePluginEntry({
216
272
  description: "Get basic information about a plan",
217
273
  parameters: Type.Object({}),
218
274
  async execute() {
219
- const [client, planId] = await Promise.all([
220
- Promise.resolve(getClient()),
221
- getDefaultPlanId(),
222
- ]);
223
- const result = await getPlanInfo(client, planId);
224
- return ok(JSON.stringify(result, null, 2));
275
+ try {
276
+ const [client, planId] = await Promise.all([
277
+ Promise.resolve(getClient()),
278
+ getDefaultPlanId(),
279
+ ]);
280
+ const result = await getPlanInfo(client, planId);
281
+ return ok(JSON.stringify(result, null, 2));
282
+ } catch (_error) {
283
+ const status = await checkOnboardingStatus(openClawAdapter);
284
+ return ok(
285
+ JSON.stringify({
286
+ error: "onboarding_required",
287
+ ...status,
288
+ }),
289
+ );
290
+ }
225
291
  },
226
292
  });
227
293