openclaw-ynabro 2.1.0 → 2.2.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/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/ynabro/prompts/ynabro.md +21 -4
- package/src/index.ts +102 -36
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -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,
|
|
11
|
+
Before performing any YNAB operations, call `ynabro_onboarding_status` to check
|
|
12
|
+
whether YNAB access is configured.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
2. A default plan must be selected and saved.
|
|
14
|
+
If `ready` is `false`, walk the user through setup:
|
|
15
15
|
|
|
16
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
174
|
-
Promise.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
189
|
-
Promise.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
Promise.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
220
|
-
Promise.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|