@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.
Files changed (130) hide show
  1. package/README.md +76 -0
  2. package/package.json +106 -0
  3. package/src/api-key-hash.ts +28 -0
  4. package/src/calendar/events.ts +34 -0
  5. package/src/calendar/route.ts +114 -0
  6. package/src/chat/credit-source.ts +1 -0
  7. package/src/chat/google/chat-request-schema.ts +150 -0
  8. package/src/chat/google/default-system-instruction.ts +198 -0
  9. package/src/chat/google/message-file-processing.ts +212 -0
  10. package/src/chat/google/mira-step-preparation.ts +221 -0
  11. package/src/chat/google/new/route.ts +368 -0
  12. package/src/chat/google/route-auth.ts +81 -0
  13. package/src/chat/google/route-chat-resolution.ts +98 -0
  14. package/src/chat/google/route-credits.ts +61 -0
  15. package/src/chat/google/route-message-preparation.ts +331 -0
  16. package/src/chat/google/route-mira-runtime.ts +206 -0
  17. package/src/chat/google/route.ts +632 -0
  18. package/src/chat/google/stream-finish-persistence.ts +722 -0
  19. package/src/chat/google/summary/route.ts +153 -0
  20. package/src/chat/mira-render-ui-policy.ts +540 -0
  21. package/src/chat/mira-system-instruction.ts +484 -0
  22. package/src/chat-sdk/adapters.ts +389 -0
  23. package/src/chat-sdk/registry.ts +197 -0
  24. package/src/chat-sdk.ts +33 -0
  25. package/src/core.ts +3 -0
  26. package/src/credits/cap-output-tokens.ts +90 -0
  27. package/src/credits/check-credits.ts +232 -0
  28. package/src/credits/constants.ts +30 -0
  29. package/src/credits/index.ts +46 -0
  30. package/src/credits/model-mapping.ts +92 -0
  31. package/src/credits/reservations.ts +514 -0
  32. package/src/credits/resolve-plan-model.ts +219 -0
  33. package/src/credits/sync-gateway-models.ts +351 -0
  34. package/src/credits/types.ts +109 -0
  35. package/src/credits/use-ai-credits.ts +3 -0
  36. package/src/embeddings/metered.ts +283 -0
  37. package/src/executions/route.ts +137 -0
  38. package/src/generate/route.ts +411 -0
  39. package/src/hooks.ts +7 -0
  40. package/src/meetings/summary/route.ts +7 -0
  41. package/src/meetings/transcription/route.ts +134 -0
  42. package/src/memory/client.ts +158 -0
  43. package/src/memory/config.ts +38 -0
  44. package/src/memory/index.ts +32 -0
  45. package/src/memory/ingest.ts +51 -0
  46. package/src/memory/middleware.ts +35 -0
  47. package/src/memory/operations.ts +480 -0
  48. package/src/memory/scope.ts +102 -0
  49. package/src/memory/settings.ts +121 -0
  50. package/src/memory/types.ts +101 -0
  51. package/src/memory/workspace.ts +36 -0
  52. package/src/memory.ts +1 -0
  53. package/src/mind/patch.ts +146 -0
  54. package/src/mind/route.ts +687 -0
  55. package/src/mind/tools.ts +1500 -0
  56. package/src/mind/types.ts +20 -0
  57. package/src/object/core.ts +3 -0
  58. package/src/object/flashcards/route.ts +140 -0
  59. package/src/object/quizzes/explanation/route.ts +145 -0
  60. package/src/object/quizzes/route.ts +142 -0
  61. package/src/object/types.ts +187 -0
  62. package/src/object/year-plan/route.ts +196 -0
  63. package/src/react.ts +1 -0
  64. package/src/scheduling/algorithm.ts +791 -0
  65. package/src/scheduling/default.ts +36 -0
  66. package/src/scheduling/duration-optimizer.ts +689 -0
  67. package/src/scheduling/index.ts +79 -0
  68. package/src/scheduling/priority-calculator.ts +187 -0
  69. package/src/scheduling/recurrence-calculator.ts +621 -0
  70. package/src/scheduling/templates.ts +892 -0
  71. package/src/scheduling/types.ts +136 -0
  72. package/src/scheduling/web-adapter.ts +308 -0
  73. package/src/scheduling.ts +6 -0
  74. package/src/supported-actions.ts +1 -0
  75. package/src/supported-providers.ts +6 -0
  76. package/src/tools/context-builder.ts +372 -0
  77. package/src/tools/core.ts +1 -0
  78. package/src/tools/definitions/calendar.ts +106 -0
  79. package/src/tools/definitions/finance.ts +197 -0
  80. package/src/tools/definitions/image.ts +74 -0
  81. package/src/tools/definitions/memory.ts +83 -0
  82. package/src/tools/definitions/meta.ts +154 -0
  83. package/src/tools/definitions/render-ui.ts +81 -0
  84. package/src/tools/definitions/tasks.ts +343 -0
  85. package/src/tools/definitions/time-tracking.ts +381 -0
  86. package/src/tools/definitions/workspace-context.ts +45 -0
  87. package/src/tools/definitions/workspace-user-chat.ts +111 -0
  88. package/src/tools/executors/calendar.ts +371 -0
  89. package/src/tools/executors/chat.ts +15 -0
  90. package/src/tools/executors/finance.ts +638 -0
  91. package/src/tools/executors/helpers/encryption.ts +107 -0
  92. package/src/tools/executors/image.ts +247 -0
  93. package/src/tools/executors/markitdown.ts +684 -0
  94. package/src/tools/executors/memory.ts +277 -0
  95. package/src/tools/executors/parallel-checks.ts +176 -0
  96. package/src/tools/executors/qr.ts +170 -0
  97. package/src/tools/executors/scope-helpers.ts +192 -0
  98. package/src/tools/executors/search.ts +149 -0
  99. package/src/tools/executors/settings.ts +40 -0
  100. package/src/tools/executors/tasks.ts +1087 -0
  101. package/src/tools/executors/theme.ts +23 -0
  102. package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
  103. package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
  104. package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
  105. package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
  106. package/src/tools/executors/timer/timer-helpers.ts +372 -0
  107. package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
  108. package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
  109. package/src/tools/executors/timer/timer-mutations.ts +19 -0
  110. package/src/tools/executors/timer/timer-queries.ts +18 -0
  111. package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
  112. package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
  113. package/src/tools/executors/timer/timer-session-queries.ts +153 -0
  114. package/src/tools/executors/timer/timer-session-updates.ts +200 -0
  115. package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
  116. package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
  117. package/src/tools/executors/timer.ts +22 -0
  118. package/src/tools/executors/user.ts +60 -0
  119. package/src/tools/executors/workspace.ts +135 -0
  120. package/src/tools/json-render-catalog.ts +875 -0
  121. package/src/tools/mira-tool-definitions.ts +55 -0
  122. package/src/tools/mira-tool-dispatcher.ts +265 -0
  123. package/src/tools/mira-tool-metadata.ts +164 -0
  124. package/src/tools/mira-tool-names.ts +95 -0
  125. package/src/tools/mira-tool-render-ui.ts +54 -0
  126. package/src/tools/mira-tool-types.ts +17 -0
  127. package/src/tools/mira-tools.ts +167 -0
  128. package/src/tools/normalize-render-ui-input.ts +321 -0
  129. package/src/tools/workspace-context.ts +233 -0
  130. package/src/types.ts +38 -0
@@ -0,0 +1,23 @@
1
+ import type { MiraToolContext } from '../mira-tools';
2
+
3
+ export async function executeSetTheme(
4
+ args: Record<string, unknown>,
5
+ _ctx: MiraToolContext
6
+ ) {
7
+ const theme = args.theme as string;
8
+ const valid = ['light', 'dark', 'system'];
9
+ if (!valid.includes(theme)) {
10
+ return {
11
+ error: `Invalid theme "${theme}". Must be one of: ${valid.join(', ')}`,
12
+ };
13
+ }
14
+
15
+ // Return a client-side action marker — the chat UI will detect this
16
+ // and apply the theme change via next-themes.
17
+ return {
18
+ success: true,
19
+ action: 'set_theme',
20
+ theme,
21
+ message: `Theme changed to ${theme}`,
22
+ };
23
+ }
@@ -0,0 +1,110 @@
1
+ import type { MiraToolContext } from '../../mira-tools';
2
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
3
+ import { buildToolFailure } from './timer-helpers';
4
+
5
+ const encodeCategoryCursorName = (value: string) => encodeURIComponent(value);
6
+
7
+ const decodeCategoryCursorName = (value: string) => {
8
+ try {
9
+ return decodeURIComponent(value);
10
+ } catch {
11
+ return value;
12
+ }
13
+ };
14
+
15
+ export type TimeTrackingCategoryRow = {
16
+ id: string;
17
+ ws_id: string;
18
+ name: string;
19
+ description: string | null;
20
+ color: string | null;
21
+ created_at: string;
22
+ updated_at: string;
23
+ };
24
+
25
+ export async function executeListTimeTrackingCategories(
26
+ args: Record<string, unknown>,
27
+ ctx: MiraToolContext
28
+ ) {
29
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
30
+ const limitRaw = Number(args.limit);
31
+ const limit =
32
+ Number.isFinite(limitRaw) && limitRaw > 0
33
+ ? Math.min(Math.floor(limitRaw), 50)
34
+ : 20;
35
+ const cursor = args.cursor;
36
+
37
+ let query = ctx.supabase
38
+ .from('time_tracking_categories')
39
+ .select('id, ws_id, name, description, color, created_at, updated_at')
40
+ .eq('ws_id', workspaceId)
41
+ .order('name', { ascending: true })
42
+ .order('id', { ascending: true })
43
+ .limit(limit + 1);
44
+
45
+ if (cursor !== undefined) {
46
+ if (typeof cursor !== 'string') {
47
+ return buildToolFailure(
48
+ 'TT_CATEGORIES_INVALID_CURSOR',
49
+ 'Invalid cursor format',
50
+ false
51
+ );
52
+ }
53
+
54
+ const separatorIndex = cursor.lastIndexOf('|');
55
+ if (separatorIndex <= 0 || separatorIndex === cursor.length - 1) {
56
+ return buildToolFailure(
57
+ 'TT_CATEGORIES_INVALID_CURSOR',
58
+ 'Invalid cursor format',
59
+ false
60
+ );
61
+ }
62
+
63
+ const rawName = cursor.slice(0, separatorIndex);
64
+ const lastId = cursor.slice(separatorIndex + 1);
65
+ const lastName = decodeCategoryCursorName(rawName);
66
+ if (!lastName || !lastId) {
67
+ return buildToolFailure(
68
+ 'TT_CATEGORIES_INVALID_CURSOR',
69
+ 'Invalid cursor format',
70
+ false
71
+ );
72
+ }
73
+
74
+ const esc = (value: string) =>
75
+ value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
76
+
77
+ query = query.or(
78
+ `name.gt."${esc(lastName)}",and(name.eq."${esc(lastName)}",id.gt."${esc(lastId)}")`
79
+ );
80
+ }
81
+
82
+ const { data, error } = await query;
83
+ if (error) {
84
+ return buildToolFailure('TT_CATEGORIES_FETCH_FAILED', error.message, true);
85
+ }
86
+
87
+ const rows = (data ?? []) as TimeTrackingCategoryRow[];
88
+ const hasMore = rows.length > limit;
89
+ const categories = hasMore ? rows.slice(0, limit) : rows;
90
+ const last = categories[categories.length - 1];
91
+
92
+ return {
93
+ success: true,
94
+ categories,
95
+ count: categories.length,
96
+ hasMore,
97
+ nextCursor: last
98
+ ? `${encodeCategoryCursorName(last.name)}|${last.id}`
99
+ : null,
100
+ meta: {
101
+ workspaceId,
102
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
103
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
104
+ filtersApplied: {
105
+ cursorProvided: cursor !== undefined,
106
+ limit,
107
+ },
108
+ },
109
+ };
110
+ }
@@ -0,0 +1,240 @@
1
+ import type { TablesUpdate } from '@tuturuuu/types';
2
+ import type { MiraToolContext } from '../../mira-tools';
3
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
4
+ import { buildToolFailure, coerceOptionalString } from './timer-helpers';
5
+ import {
6
+ type CreateTimeTrackingCategoryArgs,
7
+ createTimeTrackingCategoryArgsSchema,
8
+ type DeleteTimeTrackingCategoryArgs,
9
+ deleteTimeTrackingCategoryArgsSchema,
10
+ getZodErrorMessage,
11
+ type UpdateTimeTrackingCategoryArgs,
12
+ updateTimeTrackingCategoryArgsSchema,
13
+ } from './timer-mutation-schemas';
14
+ import type { TimeTrackingCategory } from './timer-mutation-types';
15
+
16
+ export async function executeCreateTimeTrackingCategory(
17
+ args: Record<string, unknown>,
18
+ ctx: MiraToolContext
19
+ ) {
20
+ let parsedArgs: CreateTimeTrackingCategoryArgs;
21
+ try {
22
+ parsedArgs = createTimeTrackingCategoryArgsSchema.parse(args);
23
+ } catch (error) {
24
+ return buildToolFailure(
25
+ 'TT_CATEGORY_CREATE_INVALID_ARGS',
26
+ getZodErrorMessage(error),
27
+ false
28
+ );
29
+ }
30
+
31
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
32
+ const name = parsedArgs.name.trim();
33
+ if (!name) {
34
+ return buildToolFailure(
35
+ 'TT_CATEGORY_CREATE_NAME_REQUIRED',
36
+ 'name is required',
37
+ false
38
+ );
39
+ }
40
+
41
+ const { data, error } = await ctx.supabase
42
+ .from('time_tracking_categories')
43
+ .insert({
44
+ ws_id: workspaceId,
45
+ name,
46
+ description: coerceOptionalString(parsedArgs.description),
47
+ color: coerceOptionalString(parsedArgs.color) ?? 'BLUE',
48
+ created_at: new Date().toISOString(),
49
+ updated_at: new Date().toISOString(),
50
+ })
51
+ .select('id, ws_id, name, description, color, created_at, updated_at')
52
+ .single();
53
+
54
+ if (error) {
55
+ return buildToolFailure('TT_CATEGORY_CREATE_FAILED', error.message, true);
56
+ }
57
+
58
+ return {
59
+ success: true,
60
+ message: 'Time tracking category created',
61
+ category: data as TimeTrackingCategory,
62
+ meta: {
63
+ workspaceId,
64
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
65
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
66
+ },
67
+ };
68
+ }
69
+
70
+ export async function executeUpdateTimeTrackingCategory(
71
+ args: Record<string, unknown>,
72
+ ctx: MiraToolContext
73
+ ) {
74
+ let parsedArgs: UpdateTimeTrackingCategoryArgs;
75
+ try {
76
+ parsedArgs = updateTimeTrackingCategoryArgsSchema.parse(args);
77
+ } catch (error) {
78
+ return buildToolFailure(
79
+ 'TT_CATEGORY_UPDATE_INVALID_ARGS',
80
+ getZodErrorMessage(error),
81
+ false
82
+ );
83
+ }
84
+
85
+ const categoryId =
86
+ coerceOptionalString(parsedArgs.categoryId) ??
87
+ coerceOptionalString(parsedArgs.id);
88
+ if (!categoryId) {
89
+ return buildToolFailure(
90
+ 'TT_CATEGORY_UPDATE_MISSING_ID',
91
+ 'categoryId is required',
92
+ false
93
+ );
94
+ }
95
+
96
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
97
+
98
+ const { data: existingCategory, error: existingCategoryError } =
99
+ await ctx.supabase
100
+ .from('time_tracking_categories')
101
+ .select('id')
102
+ .eq('id', categoryId)
103
+ .eq('ws_id', workspaceId)
104
+ .maybeSingle();
105
+
106
+ if (existingCategoryError) {
107
+ return buildToolFailure(
108
+ 'TT_CATEGORY_UPDATE_LOOKUP_FAILED',
109
+ existingCategoryError.message,
110
+ true
111
+ );
112
+ }
113
+
114
+ if (!existingCategory) {
115
+ return buildToolFailure(
116
+ 'TT_CATEGORY_UPDATE_NOT_FOUND',
117
+ 'Category not found',
118
+ false
119
+ );
120
+ }
121
+
122
+ const updates: TablesUpdate<'time_tracking_categories'> = {
123
+ updated_at: new Date().toISOString(),
124
+ };
125
+
126
+ if (parsedArgs.name !== undefined) {
127
+ const trimmedName = parsedArgs.name?.trim();
128
+ if (!trimmedName) {
129
+ return buildToolFailure(
130
+ 'TT_CATEGORY_UPDATE_INVALID_NAME',
131
+ 'name cannot be empty',
132
+ false
133
+ );
134
+ }
135
+ updates.name = trimmedName;
136
+ }
137
+
138
+ if (parsedArgs.description !== undefined) {
139
+ updates.description = coerceOptionalString(parsedArgs.description);
140
+ }
141
+
142
+ if (parsedArgs.color !== undefined) {
143
+ updates.color = coerceOptionalString(parsedArgs.color);
144
+ }
145
+
146
+ if (Object.keys(updates).length === 1) {
147
+ return {
148
+ success: true,
149
+ message: 'No fields to update',
150
+ meta: {
151
+ workspaceId,
152
+ workspaceContextId:
153
+ ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
154
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
155
+ },
156
+ };
157
+ }
158
+
159
+ const { data, error } = await ctx.supabase
160
+ .from('time_tracking_categories')
161
+ .update(updates)
162
+ .eq('id', categoryId)
163
+ .eq('ws_id', workspaceId)
164
+ .select('id, ws_id, name, description, color, created_at, updated_at')
165
+ .single();
166
+
167
+ if (error) {
168
+ return buildToolFailure('TT_CATEGORY_UPDATE_FAILED', error.message, true);
169
+ }
170
+
171
+ return {
172
+ success: true,
173
+ message: 'Time tracking category updated',
174
+ category: data as TimeTrackingCategory,
175
+ meta: {
176
+ workspaceId,
177
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
178
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
179
+ },
180
+ };
181
+ }
182
+
183
+ export async function executeDeleteTimeTrackingCategory(
184
+ args: Record<string, unknown>,
185
+ ctx: MiraToolContext
186
+ ) {
187
+ let parsedArgs: DeleteTimeTrackingCategoryArgs;
188
+ try {
189
+ parsedArgs = deleteTimeTrackingCategoryArgsSchema.parse(args);
190
+ } catch (error) {
191
+ return buildToolFailure(
192
+ 'TT_CATEGORY_DELETE_INVALID_ARGS',
193
+ getZodErrorMessage(error),
194
+ false
195
+ );
196
+ }
197
+
198
+ const categoryId =
199
+ coerceOptionalString(parsedArgs.categoryId) ??
200
+ coerceOptionalString(parsedArgs.id);
201
+ if (!categoryId) {
202
+ return buildToolFailure(
203
+ 'TT_CATEGORY_DELETE_MISSING_ID',
204
+ 'categoryId is required',
205
+ false
206
+ );
207
+ }
208
+
209
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
210
+ const { data, error } = await ctx.supabase
211
+ .from('time_tracking_categories')
212
+ .delete()
213
+ .eq('id', categoryId)
214
+ .eq('ws_id', workspaceId)
215
+ .select('id')
216
+ .maybeSingle();
217
+
218
+ if (error) {
219
+ return buildToolFailure('TT_CATEGORY_DELETE_FAILED', error.message, true);
220
+ }
221
+
222
+ if (!data) {
223
+ return buildToolFailure(
224
+ 'TT_CATEGORY_DELETE_NOT_FOUND',
225
+ 'Category not found',
226
+ false
227
+ );
228
+ }
229
+
230
+ return {
231
+ success: true,
232
+ message: 'Time tracking category deleted',
233
+ deletedCategoryId: categoryId,
234
+ meta: {
235
+ workspaceId,
236
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
237
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
238
+ },
239
+ };
240
+ }
@@ -0,0 +1,323 @@
1
+ import type { TablesUpdate } from '@tuturuuu/types';
2
+ import type { MiraToolContext } from '../../mira-tools';
3
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
4
+ import { buildToolFailure, coerceOptionalString } from './timer-helpers';
5
+ import {
6
+ type CreateTimeTrackerGoalArgs,
7
+ createTimeTrackerGoalArgsSchema,
8
+ type DeleteTimeTrackerGoalArgs,
9
+ deleteTimeTrackerGoalArgsSchema,
10
+ getZodErrorMessage,
11
+ type UpdateTimeTrackerGoalArgs,
12
+ updateTimeTrackerGoalArgsSchema,
13
+ } from './timer-mutation-schemas';
14
+ import {
15
+ normalizeGoalCategory,
16
+ normalizeGoalCategoryIdInput,
17
+ type TimerGoal,
18
+ } from './timer-mutation-types';
19
+
20
+ export async function executeCreateTimeTrackerGoal(
21
+ args: Record<string, unknown>,
22
+ ctx: MiraToolContext
23
+ ) {
24
+ let parsedArgs: CreateTimeTrackerGoalArgs;
25
+ try {
26
+ parsedArgs = createTimeTrackerGoalArgsSchema.parse(args);
27
+ } catch (error) {
28
+ return buildToolFailure(
29
+ 'TT_GOAL_CREATE_INVALID_ARGS',
30
+ getZodErrorMessage(error),
31
+ false
32
+ );
33
+ }
34
+
35
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
36
+ const normalizedCategory = normalizeGoalCategoryIdInput(
37
+ parsedArgs.categoryId
38
+ );
39
+ if (!normalizedCategory.ok) {
40
+ return buildToolFailure(
41
+ 'TT_GOAL_CREATE_INVALID_CATEGORY',
42
+ normalizedCategory.error,
43
+ false
44
+ );
45
+ }
46
+
47
+ if (normalizedCategory.categoryId) {
48
+ const { data: categoryRow, error: categoryError } = await ctx.supabase
49
+ .from('time_tracking_categories')
50
+ .select('id')
51
+ .eq('id', normalizedCategory.categoryId)
52
+ .eq('ws_id', workspaceId)
53
+ .maybeSingle();
54
+
55
+ if (categoryError) {
56
+ return buildToolFailure(
57
+ 'TT_GOAL_CREATE_CATEGORY_LOOKUP_FAILED',
58
+ categoryError.message,
59
+ true
60
+ );
61
+ }
62
+
63
+ if (!categoryRow) {
64
+ return buildToolFailure(
65
+ 'TT_GOAL_CREATE_CATEGORY_NOT_FOUND',
66
+ 'Category not found',
67
+ false
68
+ );
69
+ }
70
+ }
71
+
72
+ const { data, error } = await ctx.supabase
73
+ .from('time_tracking_goals')
74
+ .insert({
75
+ ws_id: workspaceId,
76
+ user_id: ctx.userId,
77
+ category_id: normalizedCategory.categoryId,
78
+ daily_goal_minutes: parsedArgs.dailyGoalMinutes,
79
+ weekly_goal_minutes: parsedArgs.weeklyGoalMinutes ?? null,
80
+ is_active: parsedArgs.isActive ?? true,
81
+ created_at: new Date().toISOString(),
82
+ updated_at: new Date().toISOString(),
83
+ })
84
+ .select(
85
+ `
86
+ *,
87
+ category:time_tracking_categories(id, name, color)
88
+ `
89
+ )
90
+ .single();
91
+
92
+ if (error) {
93
+ return buildToolFailure('TT_GOAL_CREATE_FAILED', error.message, true);
94
+ }
95
+
96
+ const goal = data as TimerGoal;
97
+ return {
98
+ success: true,
99
+ message: 'Time tracker goal created',
100
+ goal: {
101
+ ...goal,
102
+ category: normalizeGoalCategory(goal.category),
103
+ },
104
+ meta: {
105
+ workspaceId,
106
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
107
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
108
+ },
109
+ };
110
+ }
111
+
112
+ export async function executeUpdateTimeTrackerGoal(
113
+ args: Record<string, unknown>,
114
+ ctx: MiraToolContext
115
+ ) {
116
+ let parsedArgs: UpdateTimeTrackerGoalArgs;
117
+ try {
118
+ parsedArgs = updateTimeTrackerGoalArgsSchema.parse(args);
119
+ } catch (error) {
120
+ return buildToolFailure(
121
+ 'TT_GOAL_UPDATE_INVALID_ARGS',
122
+ getZodErrorMessage(error),
123
+ false
124
+ );
125
+ }
126
+
127
+ const goalId =
128
+ coerceOptionalString(parsedArgs.goalId) ??
129
+ coerceOptionalString(parsedArgs.id);
130
+ if (!goalId) {
131
+ return buildToolFailure(
132
+ 'TT_GOAL_UPDATE_MISSING_ID',
133
+ 'goalId is required',
134
+ false
135
+ );
136
+ }
137
+
138
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
139
+
140
+ const { data: existingGoal, error: existingGoalError } = await ctx.supabase
141
+ .from('time_tracking_goals')
142
+ .select('id')
143
+ .eq('id', goalId)
144
+ .eq('ws_id', workspaceId)
145
+ .eq('user_id', ctx.userId)
146
+ .maybeSingle();
147
+
148
+ if (existingGoalError) {
149
+ return buildToolFailure(
150
+ 'TT_GOAL_UPDATE_LOOKUP_FAILED',
151
+ existingGoalError.message,
152
+ true
153
+ );
154
+ }
155
+
156
+ if (!existingGoal) {
157
+ return buildToolFailure(
158
+ 'TT_GOAL_UPDATE_NOT_FOUND',
159
+ 'Goal not found',
160
+ false
161
+ );
162
+ }
163
+
164
+ const updates: TablesUpdate<'time_tracking_goals'> = {
165
+ updated_at: new Date().toISOString(),
166
+ };
167
+
168
+ if (parsedArgs.categoryId !== undefined) {
169
+ const normalizedCategory = normalizeGoalCategoryIdInput(
170
+ parsedArgs.categoryId
171
+ );
172
+ if (!normalizedCategory.ok) {
173
+ return buildToolFailure(
174
+ 'TT_GOAL_UPDATE_INVALID_CATEGORY',
175
+ normalizedCategory.error,
176
+ false
177
+ );
178
+ }
179
+
180
+ if (normalizedCategory.categoryId) {
181
+ const { data: categoryRow, error: categoryError } = await ctx.supabase
182
+ .from('time_tracking_categories')
183
+ .select('id')
184
+ .eq('id', normalizedCategory.categoryId)
185
+ .eq('ws_id', workspaceId)
186
+ .maybeSingle();
187
+
188
+ if (categoryError) {
189
+ return buildToolFailure(
190
+ 'TT_GOAL_UPDATE_CATEGORY_LOOKUP_FAILED',
191
+ categoryError.message,
192
+ true
193
+ );
194
+ }
195
+
196
+ if (!categoryRow) {
197
+ return buildToolFailure(
198
+ 'TT_GOAL_UPDATE_CATEGORY_NOT_FOUND',
199
+ 'Category not found',
200
+ false
201
+ );
202
+ }
203
+ }
204
+
205
+ updates.category_id = normalizedCategory.categoryId;
206
+ }
207
+
208
+ if (parsedArgs.dailyGoalMinutes !== undefined) {
209
+ updates.daily_goal_minutes = parsedArgs.dailyGoalMinutes;
210
+ }
211
+ if (parsedArgs.weeklyGoalMinutes !== undefined) {
212
+ updates.weekly_goal_minutes = parsedArgs.weeklyGoalMinutes ?? null;
213
+ }
214
+ if (parsedArgs.isActive !== undefined) {
215
+ updates.is_active = parsedArgs.isActive;
216
+ }
217
+
218
+ if (Object.keys(updates).length === 1) {
219
+ return {
220
+ success: true,
221
+ message: 'No fields to update',
222
+ meta: {
223
+ workspaceId,
224
+ workspaceContextId:
225
+ ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
226
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
227
+ },
228
+ };
229
+ }
230
+
231
+ const { data, error } = await ctx.supabase
232
+ .from('time_tracking_goals')
233
+ .update(updates)
234
+ .eq('id', goalId)
235
+ .eq('ws_id', workspaceId)
236
+ .eq('user_id', ctx.userId)
237
+ .select(
238
+ `
239
+ *,
240
+ category:time_tracking_categories(id, name, color)
241
+ `
242
+ )
243
+ .single();
244
+
245
+ if (error) {
246
+ return buildToolFailure('TT_GOAL_UPDATE_FAILED', error.message, true);
247
+ }
248
+
249
+ const goal = data as TimerGoal;
250
+ return {
251
+ success: true,
252
+ message: 'Time tracker goal updated',
253
+ goal: {
254
+ ...goal,
255
+ category: normalizeGoalCategory(goal.category),
256
+ },
257
+ meta: {
258
+ workspaceId,
259
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
260
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
261
+ },
262
+ };
263
+ }
264
+
265
+ export async function executeDeleteTimeTrackerGoal(
266
+ args: Record<string, unknown>,
267
+ ctx: MiraToolContext
268
+ ) {
269
+ let parsedArgs: DeleteTimeTrackerGoalArgs;
270
+ try {
271
+ parsedArgs = deleteTimeTrackerGoalArgsSchema.parse(args);
272
+ } catch (error) {
273
+ return buildToolFailure(
274
+ 'TT_GOAL_DELETE_INVALID_ARGS',
275
+ getZodErrorMessage(error),
276
+ false
277
+ );
278
+ }
279
+
280
+ const goalId =
281
+ coerceOptionalString(parsedArgs.goalId) ??
282
+ coerceOptionalString(parsedArgs.id);
283
+ if (!goalId) {
284
+ return buildToolFailure(
285
+ 'TT_GOAL_DELETE_MISSING_ID',
286
+ 'goalId is required',
287
+ false
288
+ );
289
+ }
290
+
291
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
292
+ const { data, error } = await ctx.supabase
293
+ .from('time_tracking_goals')
294
+ .delete()
295
+ .eq('id', goalId)
296
+ .eq('ws_id', workspaceId)
297
+ .eq('user_id', ctx.userId)
298
+ .select('id')
299
+ .maybeSingle();
300
+
301
+ if (error) {
302
+ return buildToolFailure('TT_GOAL_DELETE_FAILED', error.message, true);
303
+ }
304
+
305
+ if (!data) {
306
+ return buildToolFailure(
307
+ 'TT_GOAL_DELETE_NOT_FOUND',
308
+ 'Goal not found',
309
+ false
310
+ );
311
+ }
312
+
313
+ return {
314
+ success: true,
315
+ message: 'Time tracker goal deleted',
316
+ deletedGoalId: goalId,
317
+ meta: {
318
+ workspaceId,
319
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
320
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
321
+ },
322
+ };
323
+ }