@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,10 @@
1
+ export {
2
+ executeCreateTimeTrackingEntry,
3
+ executeStartTimer,
4
+ executeStopTimer,
5
+ } from './timer-session-lifecycle';
6
+ export { executeMoveTimeTrackingSession } from './timer-session-queries';
7
+ export {
8
+ executeDeleteTimeTrackingSession,
9
+ executeUpdateTimeTrackingSession,
10
+ } from './timer-session-updates';
@@ -0,0 +1,153 @@
1
+ import { verifyWorkspaceMembershipType } from '@tuturuuu/utils/workspace-helper';
2
+ import type { MiraToolContext } from '../../mira-tools';
3
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
4
+ import { coerceOptionalString } from './timer-helpers';
5
+ import {
6
+ getZodErrorMessage,
7
+ type MoveTimeTrackingSessionArgs,
8
+ moveTimeTrackingSessionArgsSchema,
9
+ } from './timer-mutation-schemas';
10
+ import {
11
+ type MoveTimeTrackingSessionResult,
12
+ toTimerSession,
13
+ } from './timer-mutation-types';
14
+
15
+ export async function executeMoveTimeTrackingSession(
16
+ args: Record<string, unknown>,
17
+ ctx: MiraToolContext
18
+ ): Promise<MoveTimeTrackingSessionResult> {
19
+ let parsedArgs: MoveTimeTrackingSessionArgs;
20
+ try {
21
+ parsedArgs = moveTimeTrackingSessionArgsSchema.parse(args);
22
+ } catch (error) {
23
+ return { error: getZodErrorMessage(error) };
24
+ }
25
+
26
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
27
+ const sessionId =
28
+ coerceOptionalString(parsedArgs.sessionId) ??
29
+ coerceOptionalString(parsedArgs.id);
30
+ const targetWorkspaceId = coerceOptionalString(parsedArgs.targetWorkspaceId);
31
+
32
+ if (!sessionId) return { error: 'sessionId is required' };
33
+ if (!targetWorkspaceId) return { error: 'targetWorkspaceId is required' };
34
+
35
+ const [sourceCheck, targetCheck] = await Promise.all([
36
+ verifyWorkspaceMembershipType({
37
+ wsId: workspaceId,
38
+ userId: ctx.userId,
39
+ supabase: ctx.supabase,
40
+ requiredType: 'MEMBER',
41
+ }),
42
+ verifyWorkspaceMembershipType({
43
+ wsId: targetWorkspaceId,
44
+ userId: ctx.userId,
45
+ supabase: ctx.supabase,
46
+ requiredType: 'MEMBER',
47
+ }),
48
+ ]);
49
+
50
+ if (
51
+ sourceCheck.error === 'membership_lookup_failed' ||
52
+ targetCheck.error === 'membership_lookup_failed'
53
+ ) {
54
+ return { error: 'Could not verify workspace access' };
55
+ }
56
+
57
+ if (!sourceCheck.ok) {
58
+ return { error: 'Source workspace access denied' };
59
+ }
60
+
61
+ if (!targetCheck.ok) {
62
+ return { error: 'Target workspace access denied' };
63
+ }
64
+
65
+ const { data: session, error: sessionError } = await ctx.supabase
66
+ .from('time_tracking_sessions')
67
+ .select(
68
+ `
69
+ *,
70
+ category:time_tracking_categories(id, name),
71
+ task:tasks(id, name)
72
+ `
73
+ )
74
+ .eq('id', sessionId)
75
+ .eq('ws_id', workspaceId)
76
+ .eq('user_id', ctx.userId)
77
+ .maybeSingle();
78
+
79
+ if (sessionError) return { error: sessionError.message };
80
+ if (!session) return { error: 'Session not found' };
81
+ if (session.is_running) {
82
+ return {
83
+ error: 'Cannot move running sessions. Please stop the session first.',
84
+ };
85
+ }
86
+
87
+ let targetCategoryId: string | null = null;
88
+ if (session.category?.name) {
89
+ const { data: targetCategory, error: targetCategoryError } =
90
+ await ctx.supabase
91
+ .from('time_tracking_categories')
92
+ .select('id')
93
+ .eq('ws_id', targetWorkspaceId)
94
+ .eq('name', session.category.name)
95
+ .limit(1)
96
+ .maybeSingle();
97
+
98
+ if (targetCategoryError) {
99
+ return {
100
+ error: `Failed to resolve matching category in target workspace: ${targetCategoryError.message}`,
101
+ };
102
+ }
103
+
104
+ targetCategoryId = targetCategory?.id ?? null;
105
+ }
106
+
107
+ let targetTaskId: string | null = null;
108
+ if (session.task?.name) {
109
+ const { data: targetTask, error: targetTaskError } = await ctx.supabase
110
+ .from('tasks')
111
+ .select('id, list:task_lists!inner(board:workspace_boards!inner(ws_id))')
112
+ .eq('list.board.ws_id', targetWorkspaceId)
113
+ .eq('name', session.task.name)
114
+ .limit(1)
115
+ .maybeSingle();
116
+
117
+ if (targetTaskError) {
118
+ return {
119
+ error: `Failed to resolve matching task in target workspace: ${targetTaskError.message}`,
120
+ };
121
+ }
122
+
123
+ targetTaskId = targetTask?.id ?? null;
124
+ }
125
+
126
+ const { data: movedSession, error: moveError } = await ctx.supabase
127
+ .from('time_tracking_sessions')
128
+ .update({
129
+ ws_id: targetWorkspaceId,
130
+ category_id: targetCategoryId,
131
+ task_id: targetTaskId,
132
+ updated_at: new Date().toISOString(),
133
+ })
134
+ .eq('id', sessionId)
135
+ .eq('ws_id', workspaceId)
136
+ .eq('user_id', ctx.userId)
137
+ .select(
138
+ `
139
+ *,
140
+ category:time_tracking_categories(*),
141
+ task:tasks(*)
142
+ `
143
+ )
144
+ .single();
145
+
146
+ if (moveError) return { error: moveError.message };
147
+
148
+ return {
149
+ success: true,
150
+ message: 'Session moved successfully',
151
+ session: toTimerSession(movedSession as Record<string, unknown>),
152
+ };
153
+ }
@@ -0,0 +1,200 @@
1
+ import type { TablesUpdate } from '@tuturuuu/types';
2
+ import type { MiraToolContext } from '../../mira-tools';
3
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
4
+ import { hasTaskAccess, hasTimeTrackingCategoryAccess } from '../scope-helpers';
5
+ import {
6
+ coerceOptionalString,
7
+ MIN_DURATION_SECONDS,
8
+ parseFlexibleDateTime,
9
+ } from './timer-helpers';
10
+ import {
11
+ type DeleteTimeTrackingSessionArgs,
12
+ deleteTimeTrackingSessionArgsSchema,
13
+ getZodErrorMessage,
14
+ type UpdateTimeTrackingSessionArgs,
15
+ updateTimeTrackingSessionArgsSchema,
16
+ } from './timer-mutation-schemas';
17
+ import {
18
+ type DeleteTimeTrackingSessionResult,
19
+ toTimerSession,
20
+ type UpdateTimeTrackingSessionResult,
21
+ } from './timer-mutation-types';
22
+
23
+ export async function executeUpdateTimeTrackingSession(
24
+ args: Record<string, unknown>,
25
+ ctx: MiraToolContext
26
+ ): Promise<UpdateTimeTrackingSessionResult> {
27
+ let parsedArgs: UpdateTimeTrackingSessionArgs;
28
+ try {
29
+ parsedArgs = updateTimeTrackingSessionArgsSchema.parse(args);
30
+ } catch (error) {
31
+ return { error: getZodErrorMessage(error) };
32
+ }
33
+
34
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
35
+ const sessionId =
36
+ coerceOptionalString(parsedArgs.sessionId) ??
37
+ coerceOptionalString(parsedArgs.id);
38
+ if (!sessionId) return { error: 'sessionId is required' };
39
+
40
+ const { data: existing, error: existingError } = await ctx.supabase
41
+ .from('time_tracking_sessions')
42
+ .select('*')
43
+ .eq('id', sessionId)
44
+ .eq('ws_id', workspaceId)
45
+ .eq('user_id', ctx.userId)
46
+ .maybeSingle();
47
+
48
+ if (existingError) return { error: existingError.message };
49
+ if (!existing) return { error: 'Session not found' };
50
+
51
+ const updates: TablesUpdate<'time_tracking_sessions'> = {};
52
+ if (parsedArgs.title !== undefined) {
53
+ if (typeof parsedArgs.title !== 'string') {
54
+ return { error: 'title must be a string' };
55
+ }
56
+
57
+ const normalizedTitle = parsedArgs.title.trim();
58
+ if (normalizedTitle.length === 0) {
59
+ return { error: 'title cannot be blank' };
60
+ }
61
+ updates.title = normalizedTitle;
62
+ }
63
+ if (parsedArgs.description !== undefined) {
64
+ updates.description = coerceOptionalString(parsedArgs.description);
65
+ }
66
+ if (parsedArgs.categoryId !== undefined) {
67
+ const categoryId = coerceOptionalString(parsedArgs.categoryId);
68
+
69
+ if (categoryId) {
70
+ try {
71
+ if (!(await hasTimeTrackingCategoryAccess(ctx, categoryId))) {
72
+ return { error: 'Category not found in current workspace' };
73
+ }
74
+ } catch (error) {
75
+ return {
76
+ error:
77
+ error instanceof Error ? error.message : 'Category lookup failed',
78
+ };
79
+ }
80
+ }
81
+
82
+ updates.category_id = categoryId;
83
+ }
84
+ if (parsedArgs.taskId !== undefined) {
85
+ const taskId = coerceOptionalString(parsedArgs.taskId);
86
+
87
+ if (taskId) {
88
+ try {
89
+ if (!(await hasTaskAccess(ctx, taskId))) {
90
+ return { error: 'Task not found in current workspace' };
91
+ }
92
+ } catch (error) {
93
+ return {
94
+ error: error instanceof Error ? error.message : 'Task lookup failed',
95
+ };
96
+ }
97
+ }
98
+
99
+ updates.task_id = taskId;
100
+ }
101
+
102
+ let nextStartTime = existing.start_time;
103
+ let nextEndTime = existing.end_time;
104
+
105
+ if (parsedArgs.startTime !== undefined) {
106
+ const parsed = parseFlexibleDateTime(parsedArgs.startTime, 'startTime', {
107
+ date: parsedArgs.date,
108
+ timezone: ctx.timezone,
109
+ });
110
+ if (!parsed.ok) return { error: parsed.error };
111
+ nextStartTime = parsed.value.toISOString();
112
+ updates.start_time = nextStartTime;
113
+ }
114
+
115
+ if (parsedArgs.endTime !== undefined) {
116
+ const parsed = parseFlexibleDateTime(parsedArgs.endTime, 'endTime', {
117
+ date: parsedArgs.date,
118
+ timezone: ctx.timezone,
119
+ });
120
+ if (!parsed.ok) return { error: parsed.error };
121
+ nextEndTime = parsed.value.toISOString();
122
+ updates.end_time = nextEndTime;
123
+ }
124
+
125
+ if (parsedArgs.startTime !== undefined || parsedArgs.endTime !== undefined) {
126
+ if (!nextEndTime) {
127
+ return { error: 'Cannot compute duration without endTime' };
128
+ }
129
+
130
+ const start = new Date(nextStartTime);
131
+ const end = new Date(nextEndTime);
132
+ if (end <= start) {
133
+ return { error: 'endTime must be after startTime' };
134
+ }
135
+
136
+ const durationSeconds = Math.floor(
137
+ (end.getTime() - start.getTime()) / 1000
138
+ );
139
+ if (durationSeconds < MIN_DURATION_SECONDS) {
140
+ return { error: 'Session must be at least 1 minute long' };
141
+ }
142
+ updates.duration_seconds = durationSeconds;
143
+ }
144
+
145
+ if (Object.keys(updates).length === 0) {
146
+ return { success: true, message: 'No fields to update' };
147
+ }
148
+
149
+ const { data, error } = await ctx.supabase
150
+ .from('time_tracking_sessions')
151
+ .update(updates)
152
+ .eq('id', sessionId)
153
+ .eq('ws_id', workspaceId)
154
+ .eq('user_id', ctx.userId)
155
+ .select(
156
+ `
157
+ *,
158
+ category:time_tracking_categories(*),
159
+ task:tasks(*)
160
+ `
161
+ )
162
+ .single();
163
+
164
+ if (error) return { error: error.message };
165
+ return {
166
+ success: true,
167
+ message: 'Session updated',
168
+ session: toTimerSession(data as Record<string, unknown>),
169
+ };
170
+ }
171
+
172
+ export async function executeDeleteTimeTrackingSession(
173
+ args: Record<string, unknown>,
174
+ ctx: MiraToolContext
175
+ ): Promise<DeleteTimeTrackingSessionResult> {
176
+ let parsedArgs: DeleteTimeTrackingSessionArgs;
177
+ try {
178
+ parsedArgs = deleteTimeTrackingSessionArgsSchema.parse(args);
179
+ } catch (error) {
180
+ return { error: getZodErrorMessage(error) };
181
+ }
182
+
183
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
184
+ const sessionId =
185
+ coerceOptionalString(parsedArgs.sessionId) ??
186
+ coerceOptionalString(parsedArgs.id);
187
+ if (!sessionId) return { error: 'sessionId is required' };
188
+
189
+ const { data, error } = await ctx.supabase
190
+ .from('time_tracking_sessions')
191
+ .delete()
192
+ .eq('id', sessionId)
193
+ .eq('ws_id', workspaceId)
194
+ .eq('user_id', ctx.userId)
195
+ .select('id');
196
+
197
+ if (error) return { error: error.message };
198
+ if (!data?.length) return { error: 'Session not found' };
199
+ return { success: true, message: 'Session deleted' };
200
+ }
@@ -0,0 +1,91 @@
1
+ import type { MiraToolContext } from '../../mira-tools';
2
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
3
+ import { coerceOptionalString, normalizeCursor } from './timer-helpers';
4
+
5
+ export async function executeListTimeTrackingSessions(
6
+ args: Record<string, unknown>,
7
+ ctx: MiraToolContext
8
+ ) {
9
+ const includePending = Boolean(args.includePending);
10
+ const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
11
+ const cursor = args.cursor;
12
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
13
+
14
+ let query = ctx.supabase
15
+ .from('time_tracking_sessions')
16
+ .select(
17
+ `
18
+ id, title, description, start_time, end_time, duration_seconds,
19
+ is_running, category_id, task_id, pending_approval, ws_id,
20
+ category:time_tracking_categories(id, name, color),
21
+ task:tasks(id, name)
22
+ `
23
+ )
24
+ .eq('ws_id', workspaceId)
25
+ .eq('user_id', ctx.userId)
26
+ .order('start_time', { ascending: false })
27
+ .order('id', { ascending: false })
28
+ .limit(limit + 1);
29
+
30
+ if (!includePending) {
31
+ query = query.eq('pending_approval', false);
32
+ }
33
+
34
+ if (cursor !== undefined) {
35
+ const normalized = normalizeCursor(cursor);
36
+ if (!normalized.ok) return { error: normalized.error };
37
+
38
+ const esc = (value: string) =>
39
+ value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
40
+
41
+ query = query.or(
42
+ `start_time.lt."${esc(normalized.lastStartTime)}",and(start_time.eq."${esc(normalized.lastStartTime)}",id.lt."${esc(normalized.lastId)}")`
43
+ );
44
+ }
45
+
46
+ const { data, error } = await query;
47
+ if (error) return { error: error.message };
48
+
49
+ const rows = data ?? [];
50
+ const hasMore = rows.length > limit;
51
+ const sessions = hasMore ? rows.slice(0, limit) : rows;
52
+ const last = sessions[sessions.length - 1];
53
+
54
+ return {
55
+ success: true,
56
+ count: sessions.length,
57
+ sessions,
58
+ hasMore,
59
+ nextCursor: last ? `${last.start_time}|${last.id}` : null,
60
+ };
61
+ }
62
+
63
+ export async function executeGetTimeTrackingSession(
64
+ args: Record<string, unknown>,
65
+ ctx: MiraToolContext
66
+ ) {
67
+ const sessionIdNormalized = coerceOptionalString(args.sessionId);
68
+ const idNormalized = coerceOptionalString(args.id);
69
+ const sessionId = sessionIdNormalized ?? idNormalized;
70
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
71
+ if (!sessionId) return { error: 'sessionId is required' };
72
+
73
+ const { data, error } = await ctx.supabase
74
+ .from('time_tracking_sessions')
75
+ .select(
76
+ `
77
+ *,
78
+ category:time_tracking_categories(*),
79
+ task:tasks(*)
80
+ `
81
+ )
82
+ .eq('id', sessionId)
83
+ .eq('ws_id', workspaceId)
84
+ .eq('user_id', ctx.userId)
85
+ .maybeSingle();
86
+
87
+ if (error) return { error: error.message };
88
+ if (!data) return { error: 'Session not found' };
89
+
90
+ return { success: true, session: data };
91
+ }
@@ -0,0 +1,157 @@
1
+ import type { MiraToolContext } from '../../mira-tools';
2
+ import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
3
+ import {
4
+ buildToolFailure,
5
+ resolveTimezone,
6
+ toFiniteNumber,
7
+ } from './timer-helpers';
8
+
9
+ export type TimeTrackerStatsRow = {
10
+ today_time?: number | null;
11
+ week_time?: number | null;
12
+ month_time?: number | null;
13
+ streak?: number | null;
14
+ daily_activity?: Array<{
15
+ date?: string | null;
16
+ duration?: number | null;
17
+ sessions?: number | null;
18
+ }> | null;
19
+ };
20
+
21
+ export async function fetchTimeTrackerStats(
22
+ ctx: MiraToolContext,
23
+ options?: {
24
+ timezone?: string;
25
+ summaryOnly?: boolean;
26
+ daysBack?: number;
27
+ }
28
+ ) {
29
+ const summaryOnly = options?.summaryOnly ?? true;
30
+ const daysBack = Math.max(0, Math.min(options?.daysBack ?? 365, 365));
31
+ const appliedDaysBack = summaryOnly ? 0 : daysBack;
32
+ const timezoneResolution = resolveTimezone(options?.timezone, ctx.timezone);
33
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
34
+
35
+ const { data, error } = await ctx.supabase.rpc('get_time_tracker_stats', {
36
+ p_user_id: ctx.userId,
37
+ p_ws_id: workspaceId,
38
+ p_is_personal: ctx.workspaceContext?.personal ?? false,
39
+ p_timezone: timezoneResolution.resolved,
40
+ p_days_back: appliedDaysBack,
41
+ });
42
+
43
+ if (error) {
44
+ return buildToolFailure('TT_STATS_RPC_FAILED', error.message, true);
45
+ }
46
+
47
+ const row = ((Array.isArray(data) ? data[0] : null) ??
48
+ {}) as TimeTrackerStatsRow;
49
+ const dailyActivity = Array.isArray(row.daily_activity)
50
+ ? row.daily_activity.map((entry) => ({
51
+ date: entry.date ?? '',
52
+ duration: toFiniteNumber(entry.duration),
53
+ sessions: toFiniteNumber(entry.sessions),
54
+ }))
55
+ : [];
56
+
57
+ const todayTime = toFiniteNumber(row.today_time);
58
+ const weekTime = toFiniteNumber(row.week_time);
59
+ const monthTime = toFiniteNumber(row.month_time);
60
+ const streak = toFiniteNumber(row.streak);
61
+ const totalDuration = dailyActivity.reduce(
62
+ (sum, entry) => sum + toFiniteNumber(entry.duration),
63
+ 0
64
+ );
65
+ const totalSessions = dailyActivity.reduce(
66
+ (sum, entry) => sum + toFiniteNumber(entry.sessions),
67
+ 0
68
+ );
69
+ const averageDailyDuration =
70
+ dailyActivity.length > 0 ? totalDuration / dailyActivity.length : 0;
71
+
72
+ return {
73
+ success: true,
74
+ workspaceId,
75
+ timezone: timezoneResolution.resolved,
76
+ timezoneResolution,
77
+ summaryOnly,
78
+ daysBack: appliedDaysBack,
79
+ todayTime,
80
+ weekTime,
81
+ monthTime,
82
+ streak,
83
+ dailyActivity,
84
+ insights: {
85
+ totalDuration,
86
+ totalSessions,
87
+ averageDailyDuration,
88
+ averageSessionDuration:
89
+ totalSessions > 0 ? Math.round(totalDuration / totalSessions) : 0,
90
+ },
91
+ };
92
+ }
93
+
94
+ export async function executeGetTimeTrackerStats(
95
+ args: Record<string, unknown>,
96
+ ctx: MiraToolContext
97
+ ) {
98
+ const summaryOnly =
99
+ typeof args.summaryOnly === 'boolean' ? args.summaryOnly : true;
100
+ const daysBackRaw = Number(args.daysBack);
101
+ const daysBack =
102
+ Number.isFinite(daysBackRaw) && daysBackRaw > 0
103
+ ? Math.min(Math.floor(daysBackRaw), 3650)
104
+ : 365;
105
+ const timezoneArg =
106
+ typeof args.timezone === 'string' ? args.timezone : undefined;
107
+
108
+ const result = await fetchTimeTrackerStats(ctx, {
109
+ timezone: timezoneArg,
110
+ summaryOnly,
111
+ daysBack,
112
+ });
113
+
114
+ if (!result.success) return result;
115
+
116
+ return {
117
+ success: true,
118
+ todayTime: result.todayTime,
119
+ weekTime: result.weekTime,
120
+ monthTime: result.monthTime,
121
+ streak: result.streak,
122
+ dailyActivity: result.dailyActivity,
123
+ stats: {
124
+ todayTimeSeconds: result.todayTime,
125
+ weekTimeSeconds: result.weekTime,
126
+ monthTimeSeconds: result.monthTime,
127
+ streakDays: result.streak,
128
+ dailyActivity: result.dailyActivity.map((entry) => ({
129
+ date: entry.date,
130
+ durationSeconds: entry.duration,
131
+ sessionCount: entry.sessions,
132
+ })),
133
+ },
134
+ insights: result.insights,
135
+ meta: {
136
+ workspaceId: result.workspaceId,
137
+ workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
138
+ isPersonalContext: ctx.workspaceContext?.personal ?? false,
139
+ filtersApplied: {
140
+ summaryOnly: result.summaryOnly,
141
+ daysBack: result.daysBack,
142
+ },
143
+ timezone: {
144
+ requested: result.timezoneResolution.requested,
145
+ resolved: result.timezoneResolution.resolved,
146
+ validRequested: result.timezoneResolution.validRequested,
147
+ usedFallback: result.timezoneResolution.usedFallback,
148
+ },
149
+ units: {
150
+ durations: 'seconds',
151
+ streak: 'days',
152
+ goalTargets: 'minutes',
153
+ progress: 'percent',
154
+ },
155
+ },
156
+ };
157
+ }
@@ -0,0 +1,22 @@
1
+ export { executeListTimeTrackingCategories } from './timer/timer-categories-executor';
2
+ export { executeGetTimeTrackerGoals } from './timer/timer-goals-executor';
3
+ export { parseFlexibleDateTime } from './timer/timer-helpers';
4
+ export {
5
+ executeCreateTimeTrackerGoal,
6
+ executeCreateTimeTrackingCategory,
7
+ executeCreateTimeTrackingEntry,
8
+ executeDeleteTimeTrackerGoal,
9
+ executeDeleteTimeTrackingCategory,
10
+ executeDeleteTimeTrackingSession,
11
+ executeMoveTimeTrackingSession,
12
+ executeStartTimer,
13
+ executeStopTimer,
14
+ executeUpdateTimeTrackerGoal,
15
+ executeUpdateTimeTrackingCategory,
16
+ executeUpdateTimeTrackingSession,
17
+ } from './timer/timer-mutations';
18
+ export {
19
+ executeGetTimeTrackingSession,
20
+ executeListTimeTrackingSessions,
21
+ } from './timer/timer-sessions-executor';
22
+ export { executeGetTimeTrackerStats } from './timer/timer-stats-executor';
@@ -0,0 +1,60 @@
1
+ import type { MiraToolContext } from '../mira-tools';
2
+
3
+ export async function executeUpdateUserName(
4
+ args: Record<string, unknown>,
5
+ ctx: MiraToolContext
6
+ ) {
7
+ const displayName = args.displayName as string | undefined;
8
+ const fullName = args.fullName as string | undefined;
9
+
10
+ if (!displayName && !fullName) {
11
+ return {
12
+ success: false,
13
+ error: 'No name fields provided to update',
14
+ message: 'You must provide at least one of displayName or fullName.',
15
+ };
16
+ }
17
+
18
+ const results: Record<string, any> = {};
19
+
20
+ if (displayName) {
21
+ const { error } = await ctx.supabase
22
+ .from('users')
23
+ .update({ display_name: displayName })
24
+ .eq('id', ctx.userId);
25
+
26
+ if (error) {
27
+ results.displayNameError = error.message;
28
+ } else {
29
+ results.displayNameUpdated = true;
30
+ }
31
+ }
32
+
33
+ if (fullName) {
34
+ const { error } = await ctx.supabase
35
+ .from('user_private_details')
36
+ .update({ full_name: fullName })
37
+ .eq('user_id', ctx.userId);
38
+
39
+ if (error) {
40
+ results.fullNameError = error.message;
41
+ } else {
42
+ results.fullNameUpdated = true;
43
+ }
44
+ }
45
+
46
+ if (results.displayNameError || results.fullNameError) {
47
+ return {
48
+ success: false,
49
+ error: 'Partial or complete failure',
50
+ message: 'Failed to update some or all name fields',
51
+ details: results,
52
+ };
53
+ }
54
+
55
+ return {
56
+ success: true,
57
+ message: 'User name updated successfully',
58
+ details: results,
59
+ };
60
+ }