@tuturuuu/ai 0.0.10
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 +76 -0
- package/package.json +106 -0
- package/src/api-key-hash.ts +28 -0
- package/src/calendar/events.ts +34 -0
- package/src/calendar/route.ts +114 -0
- package/src/chat/credit-source.ts +1 -0
- package/src/chat/google/chat-request-schema.ts +150 -0
- package/src/chat/google/default-system-instruction.ts +198 -0
- package/src/chat/google/message-file-processing.ts +212 -0
- package/src/chat/google/mira-step-preparation.ts +221 -0
- package/src/chat/google/new/route.ts +368 -0
- package/src/chat/google/route-auth.ts +81 -0
- package/src/chat/google/route-chat-resolution.ts +98 -0
- package/src/chat/google/route-credits.ts +61 -0
- package/src/chat/google/route-message-preparation.ts +331 -0
- package/src/chat/google/route-mira-runtime.ts +206 -0
- package/src/chat/google/route.ts +632 -0
- package/src/chat/google/stream-finish-persistence.ts +722 -0
- package/src/chat/google/summary/route.ts +153 -0
- package/src/chat/mira-render-ui-policy.ts +540 -0
- package/src/chat/mira-system-instruction.ts +484 -0
- package/src/chat-sdk/adapters.ts +389 -0
- package/src/chat-sdk/registry.ts +197 -0
- package/src/chat-sdk.ts +33 -0
- package/src/core.ts +3 -0
- package/src/credits/cap-output-tokens.ts +90 -0
- package/src/credits/check-credits.ts +232 -0
- package/src/credits/constants.ts +30 -0
- package/src/credits/index.ts +46 -0
- package/src/credits/model-mapping.ts +92 -0
- package/src/credits/reservations.ts +514 -0
- package/src/credits/resolve-plan-model.ts +219 -0
- package/src/credits/sync-gateway-models.ts +351 -0
- package/src/credits/types.ts +109 -0
- package/src/credits/use-ai-credits.ts +3 -0
- package/src/embeddings/metered.ts +283 -0
- package/src/executions/route.ts +137 -0
- package/src/generate/route.ts +411 -0
- package/src/hooks.ts +7 -0
- package/src/meetings/summary/route.ts +7 -0
- package/src/meetings/transcription/route.ts +134 -0
- package/src/memory/client.ts +158 -0
- package/src/memory/config.ts +38 -0
- package/src/memory/index.ts +32 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/middleware.ts +35 -0
- package/src/memory/operations.ts +480 -0
- package/src/memory/scope.ts +102 -0
- package/src/memory/settings.ts +121 -0
- package/src/memory/types.ts +101 -0
- package/src/memory/workspace.ts +36 -0
- package/src/memory.ts +1 -0
- package/src/mind/patch.ts +146 -0
- package/src/mind/route.ts +687 -0
- package/src/mind/tools.ts +1500 -0
- package/src/mind/types.ts +20 -0
- package/src/object/core.ts +3 -0
- package/src/object/flashcards/route.ts +140 -0
- package/src/object/quizzes/explanation/route.ts +145 -0
- package/src/object/quizzes/route.ts +142 -0
- package/src/object/types.ts +187 -0
- package/src/object/year-plan/route.ts +196 -0
- package/src/react.ts +1 -0
- package/src/scheduling/algorithm.ts +791 -0
- package/src/scheduling/default.ts +36 -0
- package/src/scheduling/duration-optimizer.ts +689 -0
- package/src/scheduling/index.ts +79 -0
- package/src/scheduling/priority-calculator.ts +187 -0
- package/src/scheduling/recurrence-calculator.ts +621 -0
- package/src/scheduling/templates.ts +892 -0
- package/src/scheduling/types.ts +136 -0
- package/src/scheduling/web-adapter.ts +308 -0
- package/src/scheduling.ts +6 -0
- package/src/supported-actions.ts +1 -0
- package/src/supported-providers.ts +6 -0
- package/src/tools/context-builder.ts +372 -0
- package/src/tools/core.ts +1 -0
- package/src/tools/definitions/calendar.ts +106 -0
- package/src/tools/definitions/finance.ts +197 -0
- package/src/tools/definitions/image.ts +74 -0
- package/src/tools/definitions/memory.ts +83 -0
- package/src/tools/definitions/meta.ts +154 -0
- package/src/tools/definitions/render-ui.ts +81 -0
- package/src/tools/definitions/tasks.ts +343 -0
- package/src/tools/definitions/time-tracking.ts +381 -0
- package/src/tools/definitions/workspace-context.ts +45 -0
- package/src/tools/definitions/workspace-user-chat.ts +111 -0
- package/src/tools/executors/calendar.ts +371 -0
- package/src/tools/executors/chat.ts +15 -0
- package/src/tools/executors/finance.ts +638 -0
- package/src/tools/executors/helpers/encryption.ts +107 -0
- package/src/tools/executors/image.ts +247 -0
- package/src/tools/executors/markitdown.ts +684 -0
- package/src/tools/executors/memory.ts +277 -0
- package/src/tools/executors/parallel-checks.ts +176 -0
- package/src/tools/executors/qr.ts +170 -0
- package/src/tools/executors/scope-helpers.ts +192 -0
- package/src/tools/executors/search.ts +149 -0
- package/src/tools/executors/settings.ts +40 -0
- package/src/tools/executors/tasks.ts +1087 -0
- package/src/tools/executors/theme.ts +23 -0
- package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
- package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
- package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
- package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
- package/src/tools/executors/timer/timer-helpers.ts +372 -0
- package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
- package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
- package/src/tools/executors/timer/timer-mutations.ts +19 -0
- package/src/tools/executors/timer/timer-queries.ts +18 -0
- package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
- package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
- package/src/tools/executors/timer/timer-session-queries.ts +153 -0
- package/src/tools/executors/timer/timer-session-updates.ts +200 -0
- package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
- package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
- package/src/tools/executors/timer.ts +22 -0
- package/src/tools/executors/user.ts +60 -0
- package/src/tools/executors/workspace.ts +135 -0
- package/src/tools/json-render-catalog.ts +875 -0
- package/src/tools/mira-tool-definitions.ts +55 -0
- package/src/tools/mira-tool-dispatcher.ts +265 -0
- package/src/tools/mira-tool-metadata.ts +164 -0
- package/src/tools/mira-tool-names.ts +95 -0
- package/src/tools/mira-tool-render-ui.ts +54 -0
- package/src/tools/mira-tool-types.ts +17 -0
- package/src/tools/mira-tools.ts +167 -0
- package/src/tools/normalize-render-ui-input.ts +321 -0
- package/src/tools/workspace-context.ts +233 -0
- package/src/types.ts +38 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import type { TablesInsert, TablesUpdate } from '@tuturuuu/types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { MiraToolContext } from '../mira-tools';
|
|
4
|
+
import { getWorkspaceContextWorkspaceId } from '../workspace-context';
|
|
5
|
+
import { hasTransactionCategoryAccess, hasWalletAccess } from './scope-helpers';
|
|
6
|
+
|
|
7
|
+
const createWalletArgsSchema = z.object({
|
|
8
|
+
name: z.string().trim().min(1),
|
|
9
|
+
currency: z.string().trim().min(1).optional(),
|
|
10
|
+
balance: z.coerce.number().optional(),
|
|
11
|
+
type: z.string().trim().min(1).optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const updateWalletArgsSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
walletId: z.guid(),
|
|
17
|
+
name: z.string().trim().min(1).optional(),
|
|
18
|
+
currency: z.string().trim().min(1).optional(),
|
|
19
|
+
balance: z.coerce.number().optional(),
|
|
20
|
+
type: z.string().trim().min(1).optional(),
|
|
21
|
+
})
|
|
22
|
+
.refine(
|
|
23
|
+
(value) =>
|
|
24
|
+
value.name !== undefined ||
|
|
25
|
+
value.currency !== undefined ||
|
|
26
|
+
value.balance !== undefined ||
|
|
27
|
+
value.type !== undefined,
|
|
28
|
+
{ message: 'At least one field to update is required' }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const createTransactionTagArgsSchema = z.object({
|
|
32
|
+
name: z.string().trim().min(1),
|
|
33
|
+
color: z
|
|
34
|
+
.string()
|
|
35
|
+
.regex(/^[#][0-9a-fA-F]{3,8}$/)
|
|
36
|
+
.optional(),
|
|
37
|
+
description: z.string().trim().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const updateTransactionTagArgsSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
tagId: z.guid(),
|
|
43
|
+
name: z.string().trim().min(1).optional(),
|
|
44
|
+
color: z
|
|
45
|
+
.string()
|
|
46
|
+
.regex(/^[#][0-9a-fA-F]{3,8}$/)
|
|
47
|
+
.optional(),
|
|
48
|
+
description: z.string().trim().optional(),
|
|
49
|
+
})
|
|
50
|
+
.refine(
|
|
51
|
+
(value) =>
|
|
52
|
+
value.name !== undefined ||
|
|
53
|
+
value.color !== undefined ||
|
|
54
|
+
value.description !== undefined,
|
|
55
|
+
{ message: 'At least one field to update is required' }
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// ── Workspace default currency ──
|
|
59
|
+
|
|
60
|
+
export async function executeSetDefaultCurrency(
|
|
61
|
+
args: Record<string, unknown>,
|
|
62
|
+
ctx: MiraToolContext
|
|
63
|
+
) {
|
|
64
|
+
const currency = (args.currency as string).toUpperCase();
|
|
65
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
66
|
+
|
|
67
|
+
const { error } = await ctx.supabase.from('workspace_configs').upsert(
|
|
68
|
+
{
|
|
69
|
+
id: 'DEFAULT_CURRENCY',
|
|
70
|
+
ws_id: workspaceId,
|
|
71
|
+
value: currency,
|
|
72
|
+
updated_at: new Date().toISOString(),
|
|
73
|
+
},
|
|
74
|
+
{ onConflict: 'ws_id,id' }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (error) return { error: error.message };
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
message: `Default workspace currency set to ${currency}`,
|
|
81
|
+
currency,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function executeLogTransaction(
|
|
86
|
+
args: Record<string, unknown>,
|
|
87
|
+
ctx: MiraToolContext
|
|
88
|
+
) {
|
|
89
|
+
const amount = args.amount as number;
|
|
90
|
+
let walletId = args.walletId as string | null;
|
|
91
|
+
|
|
92
|
+
if (!walletId) {
|
|
93
|
+
const { data: wallet } = await ctx.supabase
|
|
94
|
+
.schema('private')
|
|
95
|
+
.from('workspace_wallets')
|
|
96
|
+
.select('id')
|
|
97
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx))
|
|
98
|
+
.limit(1)
|
|
99
|
+
.single();
|
|
100
|
+
|
|
101
|
+
if (!wallet) return { error: 'No wallet found in workspace' };
|
|
102
|
+
walletId = wallet.id;
|
|
103
|
+
} else {
|
|
104
|
+
try {
|
|
105
|
+
if (!(await hasWalletAccess(ctx, walletId))) {
|
|
106
|
+
return { error: 'Wallet not found in current workspace' };
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
error: error instanceof Error ? error.message : 'Wallet lookup failed',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (args.categoryId) {
|
|
116
|
+
try {
|
|
117
|
+
if (
|
|
118
|
+
!(await hasTransactionCategoryAccess(ctx, args.categoryId as string))
|
|
119
|
+
) {
|
|
120
|
+
return { error: 'Category not found in current workspace' };
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
error:
|
|
125
|
+
error instanceof Error ? error.message : 'Category lookup failed',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { data: tx, error } = await ctx.supabase
|
|
131
|
+
.from('wallet_transactions')
|
|
132
|
+
.insert({
|
|
133
|
+
amount,
|
|
134
|
+
description: (args.description as string) ?? null,
|
|
135
|
+
wallet_id: walletId,
|
|
136
|
+
category_id: (args.categoryId as string | undefined) ?? null,
|
|
137
|
+
taken_at: new Date().toISOString(),
|
|
138
|
+
})
|
|
139
|
+
.select('id, amount, description, taken_at')
|
|
140
|
+
.single();
|
|
141
|
+
|
|
142
|
+
if (error) return { error: error.message };
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
message: `Transaction of ${amount} logged`,
|
|
146
|
+
transaction: tx,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function executeGetSpendingSummary(
|
|
151
|
+
args: Record<string, unknown>,
|
|
152
|
+
ctx: MiraToolContext
|
|
153
|
+
) {
|
|
154
|
+
const days = (args.days as number) || 30;
|
|
155
|
+
const since = new Date();
|
|
156
|
+
since.setDate(since.getDate() - days);
|
|
157
|
+
|
|
158
|
+
const { data: wallets } = await ctx.supabase
|
|
159
|
+
.schema('private')
|
|
160
|
+
.from('workspace_wallets')
|
|
161
|
+
.select('id, name, currency, balance')
|
|
162
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
163
|
+
|
|
164
|
+
if (!wallets?.length)
|
|
165
|
+
return { wallets: [], totalIncome: 0, totalExpenses: 0, net: 0 };
|
|
166
|
+
|
|
167
|
+
type Wallet = { id: string; name: string; currency: string; balance: number };
|
|
168
|
+
const walletIds = (wallets as Wallet[]).map((w) => w.id);
|
|
169
|
+
|
|
170
|
+
const { data: transactions } = await ctx.supabase
|
|
171
|
+
.from('wallet_transactions')
|
|
172
|
+
.select('amount, wallet_id')
|
|
173
|
+
.in('wallet_id', walletIds)
|
|
174
|
+
.gte('taken_at', since.toISOString());
|
|
175
|
+
|
|
176
|
+
let totalIncome = 0;
|
|
177
|
+
let totalExpenses = 0;
|
|
178
|
+
|
|
179
|
+
for (const tx of transactions || []) {
|
|
180
|
+
if (tx.amount && tx.amount > 0) totalIncome += tx.amount;
|
|
181
|
+
else if (tx.amount && tx.amount < 0) totalExpenses += Math.abs(tx.amount);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
period: `Last ${days} days`,
|
|
186
|
+
totalIncome,
|
|
187
|
+
totalExpenses,
|
|
188
|
+
net: totalIncome - totalExpenses,
|
|
189
|
+
wallets: (wallets as Wallet[]).map((w) => ({
|
|
190
|
+
id: w.id,
|
|
191
|
+
name: w.name,
|
|
192
|
+
currency: w.currency,
|
|
193
|
+
balance: w.balance,
|
|
194
|
+
})),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── New CRUD tools ──
|
|
199
|
+
|
|
200
|
+
export async function executeListWallets(
|
|
201
|
+
_args: Record<string, unknown>,
|
|
202
|
+
ctx: MiraToolContext
|
|
203
|
+
) {
|
|
204
|
+
const { data, error } = await ctx.supabase
|
|
205
|
+
.schema('private')
|
|
206
|
+
.from('workspace_wallets')
|
|
207
|
+
.select('id, name, currency, balance, type, created_at')
|
|
208
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx))
|
|
209
|
+
.order('created_at', { ascending: true });
|
|
210
|
+
|
|
211
|
+
if (error) return { error: error.message };
|
|
212
|
+
return { count: data?.length ?? 0, wallets: data ?? [] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function executeCreateWallet(
|
|
216
|
+
args: Record<string, unknown>,
|
|
217
|
+
ctx: MiraToolContext
|
|
218
|
+
) {
|
|
219
|
+
const parsed = createWalletArgsSchema.safeParse(args);
|
|
220
|
+
if (!parsed.success) {
|
|
221
|
+
return { error: parsed.error.issues[0]?.message ?? 'Invalid wallet data' };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const validated = parsed.data;
|
|
225
|
+
const insertData: TablesInsert<{ schema: 'private' }, 'workspace_wallets'> = {
|
|
226
|
+
name: validated.name,
|
|
227
|
+
ws_id: getWorkspaceContextWorkspaceId(ctx),
|
|
228
|
+
};
|
|
229
|
+
if (validated.currency) insertData.currency = validated.currency;
|
|
230
|
+
if (validated.balance !== undefined) insertData.balance = validated.balance;
|
|
231
|
+
if (validated.type) insertData.type = validated.type;
|
|
232
|
+
|
|
233
|
+
const { data, error } = await ctx.supabase
|
|
234
|
+
.schema('private')
|
|
235
|
+
.from('workspace_wallets')
|
|
236
|
+
.insert(insertData)
|
|
237
|
+
.select('id, name, currency, balance')
|
|
238
|
+
.single();
|
|
239
|
+
|
|
240
|
+
if (error) return { error: error.message };
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
message: `Wallet "${validated.name}" created`,
|
|
244
|
+
wallet: data,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function executeUpdateWallet(
|
|
249
|
+
args: Record<string, unknown>,
|
|
250
|
+
ctx: MiraToolContext
|
|
251
|
+
) {
|
|
252
|
+
const parsed = updateWalletArgsSchema.safeParse(args);
|
|
253
|
+
if (!parsed.success) {
|
|
254
|
+
return { error: parsed.error.issues[0]?.message ?? 'Invalid wallet data' };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const validated = parsed.data;
|
|
258
|
+
const walletId = validated.walletId;
|
|
259
|
+
const updates: TablesUpdate<{ schema: 'private' }, 'workspace_wallets'> = {};
|
|
260
|
+
|
|
261
|
+
if (validated.name !== undefined) updates.name = validated.name;
|
|
262
|
+
if (validated.currency !== undefined) updates.currency = validated.currency;
|
|
263
|
+
if (validated.balance !== undefined) updates.balance = validated.balance;
|
|
264
|
+
if (validated.type !== undefined) updates.type = validated.type;
|
|
265
|
+
|
|
266
|
+
const { error } = await ctx.supabase
|
|
267
|
+
.schema('private')
|
|
268
|
+
.from('workspace_wallets')
|
|
269
|
+
.update(updates)
|
|
270
|
+
.eq('id', walletId)
|
|
271
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
272
|
+
|
|
273
|
+
if (error) return { error: error.message };
|
|
274
|
+
return { success: true, message: `Wallet ${walletId} updated` };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function executeDeleteWallet(
|
|
278
|
+
args: Record<string, unknown>,
|
|
279
|
+
ctx: MiraToolContext
|
|
280
|
+
) {
|
|
281
|
+
const walletId = args.walletId as string;
|
|
282
|
+
|
|
283
|
+
const { error } = await ctx.supabase
|
|
284
|
+
.schema('private')
|
|
285
|
+
.from('workspace_wallets')
|
|
286
|
+
.delete()
|
|
287
|
+
.eq('id', walletId)
|
|
288
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
289
|
+
|
|
290
|
+
if (error) return { error: error.message };
|
|
291
|
+
return { success: true, message: `Wallet ${walletId} deleted` };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function executeListTransactions(
|
|
295
|
+
args: Record<string, unknown>,
|
|
296
|
+
ctx: MiraToolContext
|
|
297
|
+
) {
|
|
298
|
+
const limit = (args.limit as number) || 50;
|
|
299
|
+
|
|
300
|
+
// Get all wallet IDs in workspace to scope query
|
|
301
|
+
const { data: wallets } = await ctx.supabase
|
|
302
|
+
.schema('private')
|
|
303
|
+
.from('workspace_wallets')
|
|
304
|
+
.select('id')
|
|
305
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
306
|
+
|
|
307
|
+
if (!wallets?.length) return { count: 0, transactions: [] };
|
|
308
|
+
|
|
309
|
+
const walletIds = wallets.map((w: { id: string }) => w.id);
|
|
310
|
+
|
|
311
|
+
let query = ctx.supabase
|
|
312
|
+
.from('wallet_transactions')
|
|
313
|
+
.select('id, amount, description, taken_at, wallet_id, category_id')
|
|
314
|
+
.in('wallet_id', walletIds)
|
|
315
|
+
.order('taken_at', { ascending: false })
|
|
316
|
+
.limit(limit);
|
|
317
|
+
|
|
318
|
+
if (args.walletId) query = query.eq('wallet_id', args.walletId as string);
|
|
319
|
+
if (args.categoryId)
|
|
320
|
+
query = query.eq('category_id', args.categoryId as string);
|
|
321
|
+
if (args.days) {
|
|
322
|
+
const since = new Date();
|
|
323
|
+
since.setDate(since.getDate() - (args.days as number));
|
|
324
|
+
query = query.gte('taken_at', since.toISOString());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const { data, error } = await query;
|
|
328
|
+
|
|
329
|
+
if (error) return { error: error.message };
|
|
330
|
+
return { count: data?.length ?? 0, transactions: data ?? [] };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export async function executeGetTransaction(
|
|
334
|
+
args: Record<string, unknown>,
|
|
335
|
+
ctx: MiraToolContext
|
|
336
|
+
) {
|
|
337
|
+
const transactionId = args.transactionId as string;
|
|
338
|
+
|
|
339
|
+
const { data: wallets } = await ctx.supabase
|
|
340
|
+
.schema('private')
|
|
341
|
+
.from('workspace_wallets')
|
|
342
|
+
.select('id')
|
|
343
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
344
|
+
const walletIds = wallets?.map((w) => w.id) ?? [];
|
|
345
|
+
|
|
346
|
+
if (!walletIds.length) return { error: 'No wallets in workspace' };
|
|
347
|
+
|
|
348
|
+
const { data, error } = await ctx.supabase
|
|
349
|
+
.from('wallet_transactions')
|
|
350
|
+
.select('id, amount, description, taken_at, wallet_id, category_id')
|
|
351
|
+
.eq('id', transactionId)
|
|
352
|
+
.in('wallet_id', walletIds)
|
|
353
|
+
.single();
|
|
354
|
+
|
|
355
|
+
if (error) return { error: error.message };
|
|
356
|
+
return { transaction: data };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export async function executeUpdateTransaction(
|
|
360
|
+
args: Record<string, unknown>,
|
|
361
|
+
ctx: MiraToolContext
|
|
362
|
+
) {
|
|
363
|
+
const transactionId = args.transactionId as string;
|
|
364
|
+
const updates: TablesUpdate<'wallet_transactions'> = {};
|
|
365
|
+
|
|
366
|
+
if (args.amount !== undefined) {
|
|
367
|
+
if (args.amount !== null && typeof args.amount !== 'number') {
|
|
368
|
+
return { error: 'amount must be a number or null' };
|
|
369
|
+
}
|
|
370
|
+
updates.amount = args.amount;
|
|
371
|
+
}
|
|
372
|
+
if (args.description !== undefined) {
|
|
373
|
+
if (args.description !== null && typeof args.description !== 'string') {
|
|
374
|
+
return { error: 'description must be a string or null' };
|
|
375
|
+
}
|
|
376
|
+
updates.description = args.description;
|
|
377
|
+
}
|
|
378
|
+
if (args.categoryId !== undefined) {
|
|
379
|
+
const categoryId = args.categoryId as string | null;
|
|
380
|
+
if (categoryId) {
|
|
381
|
+
try {
|
|
382
|
+
if (!(await hasTransactionCategoryAccess(ctx, categoryId))) {
|
|
383
|
+
return { error: 'Category not found in current workspace' };
|
|
384
|
+
}
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return {
|
|
387
|
+
error:
|
|
388
|
+
error instanceof Error ? error.message : 'Category lookup failed',
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
updates.category_id = categoryId;
|
|
393
|
+
}
|
|
394
|
+
if (args.walletId !== undefined) {
|
|
395
|
+
if (args.walletId === null) {
|
|
396
|
+
return { error: 'walletId cannot be null' };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const walletId = args.walletId as string;
|
|
400
|
+
if (walletId) {
|
|
401
|
+
try {
|
|
402
|
+
if (!(await hasWalletAccess(ctx, walletId))) {
|
|
403
|
+
return { error: 'Wallet not found in current workspace' };
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return {
|
|
407
|
+
error:
|
|
408
|
+
error instanceof Error ? error.message : 'Wallet lookup failed',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
updates.wallet_id = walletId;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (Object.keys(updates).length === 0) {
|
|
416
|
+
return { success: true, message: 'No fields to update' };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const { data: wallets } = await ctx.supabase
|
|
420
|
+
.schema('private')
|
|
421
|
+
.from('workspace_wallets')
|
|
422
|
+
.select('id')
|
|
423
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
424
|
+
const walletIds = wallets?.map((w) => w.id) ?? [];
|
|
425
|
+
|
|
426
|
+
if (!walletIds.length) return { error: 'No wallets in workspace' };
|
|
427
|
+
|
|
428
|
+
const { error } = await ctx.supabase
|
|
429
|
+
.from('wallet_transactions')
|
|
430
|
+
.update(updates)
|
|
431
|
+
.eq('id', transactionId)
|
|
432
|
+
.in('wallet_id', walletIds);
|
|
433
|
+
|
|
434
|
+
if (error) return { error: error.message };
|
|
435
|
+
return { success: true, message: `Transaction ${transactionId} updated` };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export async function executeDeleteTransaction(
|
|
439
|
+
args: Record<string, unknown>,
|
|
440
|
+
ctx: MiraToolContext
|
|
441
|
+
) {
|
|
442
|
+
const transactionId = args.transactionId as string;
|
|
443
|
+
|
|
444
|
+
const { data: wallets } = await ctx.supabase
|
|
445
|
+
.schema('private')
|
|
446
|
+
.from('workspace_wallets')
|
|
447
|
+
.select('id')
|
|
448
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
449
|
+
const walletIds = wallets?.map((w) => w.id) ?? [];
|
|
450
|
+
|
|
451
|
+
if (!walletIds.length) return { error: 'No wallets in workspace' };
|
|
452
|
+
|
|
453
|
+
const { error } = await ctx.supabase
|
|
454
|
+
.from('wallet_transactions')
|
|
455
|
+
.delete()
|
|
456
|
+
.eq('id', transactionId)
|
|
457
|
+
.in('wallet_id', walletIds);
|
|
458
|
+
|
|
459
|
+
if (error) return { error: error.message };
|
|
460
|
+
return { success: true, message: `Transaction ${transactionId} deleted` };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export async function executeListTransactionCategories(
|
|
464
|
+
_args: Record<string, unknown>,
|
|
465
|
+
ctx: MiraToolContext
|
|
466
|
+
) {
|
|
467
|
+
const { data, error } = await ctx.supabase
|
|
468
|
+
.from('transaction_categories')
|
|
469
|
+
.select('id, name, is_expense, ws_id')
|
|
470
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
471
|
+
|
|
472
|
+
if (error) return { error: error.message };
|
|
473
|
+
return { count: data?.length ?? 0, categories: data ?? [] };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export async function executeCreateTransactionCategory(
|
|
477
|
+
args: Record<string, unknown>,
|
|
478
|
+
ctx: MiraToolContext
|
|
479
|
+
) {
|
|
480
|
+
const { data, error } = await ctx.supabase
|
|
481
|
+
.from('transaction_categories')
|
|
482
|
+
.insert({
|
|
483
|
+
name: args.name as string,
|
|
484
|
+
is_expense: (args.isExpense as boolean) ?? true,
|
|
485
|
+
ws_id: getWorkspaceContextWorkspaceId(ctx),
|
|
486
|
+
})
|
|
487
|
+
.select('id, name, is_expense')
|
|
488
|
+
.single();
|
|
489
|
+
|
|
490
|
+
if (error) return { error: error.message };
|
|
491
|
+
return {
|
|
492
|
+
success: true,
|
|
493
|
+
message: `Category "${args.name}" created`,
|
|
494
|
+
category: data,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export async function executeUpdateTransactionCategory(
|
|
499
|
+
args: Record<string, unknown>,
|
|
500
|
+
ctx: MiraToolContext
|
|
501
|
+
) {
|
|
502
|
+
const categoryId = args.categoryId as string;
|
|
503
|
+
const updates: TablesUpdate<'transaction_categories'> = {};
|
|
504
|
+
|
|
505
|
+
if (args.name !== undefined) {
|
|
506
|
+
if (typeof args.name !== 'string') {
|
|
507
|
+
return { error: 'name must be a string' };
|
|
508
|
+
}
|
|
509
|
+
updates.name = args.name;
|
|
510
|
+
}
|
|
511
|
+
if (args.isExpense !== undefined) {
|
|
512
|
+
if (args.isExpense !== null && typeof args.isExpense !== 'boolean') {
|
|
513
|
+
return { error: 'isExpense must be a boolean or null' };
|
|
514
|
+
}
|
|
515
|
+
updates.is_expense = args.isExpense;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (Object.keys(updates).length === 0) {
|
|
519
|
+
return { success: true, message: 'No fields to update' };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const { error } = await ctx.supabase
|
|
523
|
+
.from('transaction_categories')
|
|
524
|
+
.update(updates)
|
|
525
|
+
.eq('id', categoryId)
|
|
526
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
527
|
+
|
|
528
|
+
if (error) return { error: error.message };
|
|
529
|
+
return { success: true, message: `Category ${categoryId} updated` };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export async function executeDeleteTransactionCategory(
|
|
533
|
+
args: Record<string, unknown>,
|
|
534
|
+
ctx: MiraToolContext
|
|
535
|
+
) {
|
|
536
|
+
const categoryId = args.categoryId as string;
|
|
537
|
+
|
|
538
|
+
const { error } = await ctx.supabase
|
|
539
|
+
.from('transaction_categories')
|
|
540
|
+
.delete()
|
|
541
|
+
.eq('id', categoryId)
|
|
542
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
543
|
+
|
|
544
|
+
if (error) return { error: error.message };
|
|
545
|
+
return { success: true, message: `Category ${categoryId} deleted` };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export async function executeListTransactionTags(
|
|
549
|
+
_args: Record<string, unknown>,
|
|
550
|
+
ctx: MiraToolContext
|
|
551
|
+
) {
|
|
552
|
+
const { data, error } = await ctx.supabase
|
|
553
|
+
.from('transaction_tags')
|
|
554
|
+
.select('id, name, color, description, ws_id')
|
|
555
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
556
|
+
|
|
557
|
+
if (error) return { error: error.message };
|
|
558
|
+
return { count: data?.length ?? 0, tags: data ?? [] };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export async function executeCreateTransactionTag(
|
|
562
|
+
args: Record<string, unknown>,
|
|
563
|
+
ctx: MiraToolContext
|
|
564
|
+
) {
|
|
565
|
+
const parsed = createTransactionTagArgsSchema.safeParse(args);
|
|
566
|
+
if (!parsed.success) {
|
|
567
|
+
return {
|
|
568
|
+
error: parsed.error.issues[0]?.message ?? 'Invalid transaction tag data',
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const validated = parsed.data;
|
|
573
|
+
const insertData: TablesInsert<'transaction_tags'> = {
|
|
574
|
+
name: validated.name,
|
|
575
|
+
ws_id: getWorkspaceContextWorkspaceId(ctx),
|
|
576
|
+
};
|
|
577
|
+
if (validated.color) insertData.color = validated.color;
|
|
578
|
+
if (validated.description) insertData.description = validated.description;
|
|
579
|
+
|
|
580
|
+
const { data, error } = await ctx.supabase
|
|
581
|
+
.from('transaction_tags')
|
|
582
|
+
.insert(insertData)
|
|
583
|
+
.select('id, name, color')
|
|
584
|
+
.single();
|
|
585
|
+
|
|
586
|
+
if (error) return { error: error.message };
|
|
587
|
+
return {
|
|
588
|
+
success: true,
|
|
589
|
+
message: `Tag "${validated.name}" created`,
|
|
590
|
+
tag: data,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export async function executeUpdateTransactionTag(
|
|
595
|
+
args: Record<string, unknown>,
|
|
596
|
+
ctx: MiraToolContext
|
|
597
|
+
) {
|
|
598
|
+
const parsed = updateTransactionTagArgsSchema.safeParse(args);
|
|
599
|
+
if (!parsed.success) {
|
|
600
|
+
return {
|
|
601
|
+
error: parsed.error.issues[0]?.message ?? 'Invalid transaction tag data',
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const validated = parsed.data;
|
|
606
|
+
const tagId = validated.tagId;
|
|
607
|
+
const updates: TablesUpdate<'transaction_tags'> = {};
|
|
608
|
+
|
|
609
|
+
if (validated.name !== undefined) updates.name = validated.name;
|
|
610
|
+
if (validated.color !== undefined) updates.color = validated.color;
|
|
611
|
+
if (validated.description !== undefined)
|
|
612
|
+
updates.description = validated.description;
|
|
613
|
+
|
|
614
|
+
const { error } = await ctx.supabase
|
|
615
|
+
.from('transaction_tags')
|
|
616
|
+
.update(updates)
|
|
617
|
+
.eq('id', tagId)
|
|
618
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
619
|
+
|
|
620
|
+
if (error) return { error: error.message };
|
|
621
|
+
return { success: true, message: `Tag ${tagId} updated` };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export async function executeDeleteTransactionTag(
|
|
625
|
+
args: Record<string, unknown>,
|
|
626
|
+
ctx: MiraToolContext
|
|
627
|
+
) {
|
|
628
|
+
const tagId = args.tagId as string;
|
|
629
|
+
|
|
630
|
+
const { error } = await ctx.supabase
|
|
631
|
+
.from('transaction_tags')
|
|
632
|
+
.delete()
|
|
633
|
+
.eq('id', tagId)
|
|
634
|
+
.eq('ws_id', getWorkspaceContextWorkspaceId(ctx));
|
|
635
|
+
|
|
636
|
+
if (error) return { error: error.message };
|
|
637
|
+
return { success: true, message: `Tag ${tagId} deleted` };
|
|
638
|
+
}
|