@lantos1618/better-ui 0.1.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/lib/aui/README.md +136 -0
  4. package/lib/aui/__tests__/aui-complete.test.ts +251 -0
  5. package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
  6. package/lib/aui/__tests__/aui-concise.test.ts +278 -0
  7. package/lib/aui/__tests__/aui-integration.test.ts +309 -0
  8. package/lib/aui/__tests__/aui-simple.test.ts +116 -0
  9. package/lib/aui/__tests__/aui.test.ts +269 -0
  10. package/lib/aui/__tests__/concise-api.test.ts +165 -0
  11. package/lib/aui/__tests__/core.test.ts +265 -0
  12. package/lib/aui/__tests__/simple-api.test.ts +200 -0
  13. package/lib/aui/ai-assistant.ts +408 -0
  14. package/lib/aui/ai-control.ts +353 -0
  15. package/lib/aui/client/use-aui.ts +55 -0
  16. package/lib/aui/client-control.ts +551 -0
  17. package/lib/aui/client-executor.ts +417 -0
  18. package/lib/aui/components/ToolRenderer.tsx +22 -0
  19. package/lib/aui/core.ts +137 -0
  20. package/lib/aui/demo.tsx +89 -0
  21. package/lib/aui/examples/ai-complete-demo.tsx +359 -0
  22. package/lib/aui/examples/ai-control-demo.tsx +356 -0
  23. package/lib/aui/examples/ai-control-tools.ts +308 -0
  24. package/lib/aui/examples/concise-api.tsx +153 -0
  25. package/lib/aui/examples/index.tsx +163 -0
  26. package/lib/aui/examples/quick-demo.tsx +91 -0
  27. package/lib/aui/examples/simple-demo.tsx +71 -0
  28. package/lib/aui/examples/simple-tools.tsx +160 -0
  29. package/lib/aui/examples/user-api.tsx +208 -0
  30. package/lib/aui/examples/user-requested.tsx +174 -0
  31. package/lib/aui/examples/weather-search-tools.tsx +119 -0
  32. package/lib/aui/examples.tsx +367 -0
  33. package/lib/aui/hooks/useAUITool.ts +142 -0
  34. package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
  35. package/lib/aui/hooks/useAUITools.ts +195 -0
  36. package/lib/aui/index.ts +156 -0
  37. package/lib/aui/provider.tsx +45 -0
  38. package/lib/aui/server-control.ts +386 -0
  39. package/lib/aui/server-executor.ts +165 -0
  40. package/lib/aui/server.ts +167 -0
  41. package/lib/aui/tool-registry.ts +380 -0
  42. package/lib/aui/tools/advanced-examples.tsx +86 -0
  43. package/lib/aui/tools/ai-complete.ts +375 -0
  44. package/lib/aui/tools/api-tools.tsx +230 -0
  45. package/lib/aui/tools/data-tools.tsx +232 -0
  46. package/lib/aui/tools/dom-tools.tsx +202 -0
  47. package/lib/aui/tools/examples.ts +43 -0
  48. package/lib/aui/tools/file-tools.tsx +202 -0
  49. package/lib/aui/tools/form-tools.tsx +233 -0
  50. package/lib/aui/tools/index.ts +8 -0
  51. package/lib/aui/tools/navigation-tools.tsx +172 -0
  52. package/lib/aui/tools/notification-tools.ts +213 -0
  53. package/lib/aui/tools/state-tools.tsx +209 -0
  54. package/lib/aui/types.ts +47 -0
  55. package/lib/aui/vercel-ai.ts +100 -0
  56. package/package.json +51 -0
@@ -0,0 +1,356 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import aui from '../index';
5
+ import { z } from 'zod';
6
+
7
+ // ============================================
8
+ // BACKEND CONTROL TOOLS
9
+ // ============================================
10
+
11
+ // Database query tool - AI can query your database
12
+ const queryDatabaseTool = aui
13
+ .tool('queryDatabase')
14
+ .input(z.object({
15
+ table: z.string(),
16
+ query: z.object({
17
+ where: z.record(z.any()).optional(),
18
+ limit: z.number().optional(),
19
+ orderBy: z.string().optional()
20
+ })
21
+ }))
22
+ .execute(async ({ input }) => {
23
+ // Server-side database query (would connect to your actual DB)
24
+ console.log('AI executing database query:', input);
25
+ return {
26
+ rows: [
27
+ { id: 1, name: 'User 1', email: 'user1@example.com' },
28
+ { id: 2, name: 'User 2', email: 'user2@example.com' }
29
+ ],
30
+ count: 2
31
+ };
32
+ })
33
+ .render(({ data }) => (
34
+ <div className="p-2 bg-gray-100 rounded">
35
+ <h4 className="font-bold">Database Results</h4>
36
+ <pre className="text-sm">{JSON.stringify(data.rows, null, 2)}</pre>
37
+ <p className="text-xs mt-2">Found {data.count} records</p>
38
+ </div>
39
+ ));
40
+
41
+ // API endpoint control - AI can call your API endpoints
42
+ const callAPITool = aui
43
+ .tool('callAPI')
44
+ .input(z.object({
45
+ endpoint: z.string(),
46
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
47
+ body: z.any().optional(),
48
+ headers: z.record(z.string()).optional()
49
+ }))
50
+ .execute(async ({ input }) => {
51
+ // Server-side API call
52
+ const response = await fetch(input.endpoint, {
53
+ method: input.method,
54
+ headers: input.headers,
55
+ body: input.body ? JSON.stringify(input.body) : undefined
56
+ });
57
+ return await response.json();
58
+ })
59
+ .clientExecute(async ({ input, ctx }) => {
60
+ // Client-side with caching
61
+ const cacheKey = `${input.method}:${input.endpoint}`;
62
+ if (input.method === 'GET') {
63
+ const cached = ctx.cache.get(cacheKey);
64
+ if (cached) return cached;
65
+ }
66
+
67
+ const result = await ctx.fetch(input.endpoint, {
68
+ method: input.method,
69
+ headers: input.headers,
70
+ body: input.body ? JSON.stringify(input.body) : undefined
71
+ }).then(r => r.json());
72
+
73
+ if (input.method === 'GET') {
74
+ ctx.cache.set(cacheKey, result);
75
+ }
76
+
77
+ return result;
78
+ })
79
+ .render(({ data }) => (
80
+ <div className="p-2 bg-blue-50 rounded">
81
+ <h4 className="font-bold">API Response</h4>
82
+ <pre className="text-sm">{JSON.stringify(data, null, 2)}</pre>
83
+ </div>
84
+ ));
85
+
86
+ // ============================================
87
+ // FRONTEND CONTROL TOOLS
88
+ // ============================================
89
+
90
+ // Update DOM - AI can modify page content
91
+ const updateDOMTool = aui
92
+ .tool('updateDOM')
93
+ .input(z.object({
94
+ selector: z.string(),
95
+ action: z.enum(['setText', 'setHTML', 'addClass', 'removeClass', 'setStyle', 'setAttribute']),
96
+ value: z.string()
97
+ }))
98
+ .clientExecute(async ({ input }) => {
99
+ const element = document.querySelector(input.selector);
100
+ if (!element) throw new Error(`Element not found: ${input.selector}`);
101
+
102
+ switch (input.action) {
103
+ case 'setText':
104
+ element.textContent = input.value;
105
+ break;
106
+ case 'setHTML':
107
+ element.innerHTML = input.value;
108
+ break;
109
+ case 'addClass':
110
+ element.classList.add(input.value);
111
+ break;
112
+ case 'removeClass':
113
+ element.classList.remove(input.value);
114
+ break;
115
+ case 'setStyle':
116
+ Object.assign(element as HTMLElement, { style: input.value });
117
+ break;
118
+ case 'setAttribute':
119
+ const [attr, val] = input.value.split('=');
120
+ element.setAttribute(attr, val);
121
+ break;
122
+ }
123
+
124
+ return { success: true, selector: input.selector, action: input.action };
125
+ })
126
+ .render(({ data }) => (
127
+ <div className="p-2 bg-green-50 rounded">
128
+ <p className="text-sm">✅ DOM updated: {data.action} on {data.selector}</p>
129
+ </div>
130
+ ));
131
+
132
+ // Form submission - AI can submit forms
133
+ const submitFormTool = aui
134
+ .tool('submitForm')
135
+ .input(z.object({
136
+ formId: z.string(),
137
+ data: z.record(z.any())
138
+ }))
139
+ .clientExecute(async ({ input }) => {
140
+ const form = document.getElementById(input.formId) as HTMLFormElement;
141
+ if (!form) throw new Error(`Form not found: ${input.formId}`);
142
+
143
+ // Set form values
144
+ Object.entries(input.data).forEach(([name, value]) => {
145
+ const field = form.elements.namedItem(name) as HTMLInputElement;
146
+ if (field) field.value = String(value);
147
+ });
148
+
149
+ // Trigger submit event
150
+ const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
151
+ form.dispatchEvent(submitEvent);
152
+
153
+ return { submitted: true, formId: input.formId, data: input.data };
154
+ })
155
+ .render(({ data }) => (
156
+ <div className="p-2 bg-purple-50 rounded">
157
+ <p className="text-sm">📝 Form submitted: {data.formId}</p>
158
+ </div>
159
+ ));
160
+
161
+ // Navigate - AI can navigate the app
162
+ const navigateTool = aui
163
+ .tool('navigate')
164
+ .input(z.object({
165
+ path: z.string(),
166
+ params: z.record(z.string()).optional()
167
+ }))
168
+ .clientExecute(async ({ input }) => {
169
+ const url = new URL(input.path, window.location.origin);
170
+ if (input.params) {
171
+ Object.entries(input.params).forEach(([key, value]) => {
172
+ url.searchParams.set(key, value);
173
+ });
174
+ }
175
+
176
+ // For Next.js navigation
177
+ if (typeof window !== 'undefined' && (window as any).next?.router) {
178
+ await (window as any).next.router.push(url.pathname + url.search);
179
+ } else {
180
+ window.location.href = url.toString();
181
+ }
182
+
183
+ return { navigatedTo: url.pathname, params: input.params };
184
+ })
185
+ .render(({ data }) => (
186
+ <div className="p-2 bg-indigo-50 rounded">
187
+ <p className="text-sm">🔗 Navigated to: {data.navigatedTo}</p>
188
+ </div>
189
+ ));
190
+
191
+ // Local storage - AI can persist data
192
+ const localStorageTool = aui
193
+ .tool('localStorage')
194
+ .input(z.object({
195
+ action: z.enum(['get', 'set', 'remove', 'clear']),
196
+ key: z.string().optional(),
197
+ value: z.any().optional()
198
+ }))
199
+ .clientExecute(async ({ input }) => {
200
+ switch (input.action) {
201
+ case 'get':
202
+ return { value: localStorage.getItem(input.key!) };
203
+ case 'set':
204
+ localStorage.setItem(input.key!, JSON.stringify(input.value));
205
+ return { stored: true, key: input.key };
206
+ case 'remove':
207
+ localStorage.removeItem(input.key!);
208
+ return { removed: true, key: input.key };
209
+ case 'clear':
210
+ localStorage.clear();
211
+ return { cleared: true };
212
+ default:
213
+ throw new Error(`Unknown action: ${input.action}`);
214
+ }
215
+ })
216
+ .render(({ data }) => (
217
+ <div className="p-2 bg-yellow-50 rounded">
218
+ <p className="text-sm">💾 Storage: {JSON.stringify(data)}</p>
219
+ </div>
220
+ ));
221
+
222
+ // ============================================
223
+ // DEMO COMPONENT
224
+ // ============================================
225
+
226
+ export function AIControlDemo() {
227
+ const [results, setResults] = React.useState<any[]>([]);
228
+ const [loading, setLoading] = React.useState(false);
229
+
230
+ const executeAICommand = async (tool: any, input: any) => {
231
+ setLoading(true);
232
+ try {
233
+ const result = await tool.run(input);
234
+ setResults(prev => [...prev, { tool: tool.name, result, timestamp: new Date() }]);
235
+ } catch (error) {
236
+ console.error('AI command failed:', error);
237
+ setResults(prev => [...prev, { tool: tool.name, error: String(error), timestamp: new Date() }]);
238
+ } finally {
239
+ setLoading(false);
240
+ }
241
+ };
242
+
243
+ return (
244
+ <div className="p-6 max-w-4xl mx-auto">
245
+ <h1 className="text-3xl font-bold mb-6">AI Control Demo - Frontend & Backend</h1>
246
+
247
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
248
+ {/* Backend Control Examples */}
249
+ <div className="border rounded-lg p-4">
250
+ <h2 className="text-xl font-semibold mb-4">Backend Control</h2>
251
+
252
+ <div className="space-y-3">
253
+ <button
254
+ onClick={() => executeAICommand(queryDatabaseTool, {
255
+ table: 'users',
256
+ query: { limit: 10, orderBy: 'created_at' }
257
+ })}
258
+ className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
259
+ disabled={loading}
260
+ >
261
+ AI: Query Database
262
+ </button>
263
+
264
+ <button
265
+ onClick={() => executeAICommand(callAPITool, {
266
+ endpoint: '/api/health',
267
+ method: 'GET'
268
+ })}
269
+ className="w-full px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
270
+ disabled={loading}
271
+ >
272
+ AI: Call API Endpoint
273
+ </button>
274
+ </div>
275
+ </div>
276
+
277
+ {/* Frontend Control Examples */}
278
+ <div className="border rounded-lg p-4">
279
+ <h2 className="text-xl font-semibold mb-4">Frontend Control</h2>
280
+
281
+ <div className="space-y-3">
282
+ <button
283
+ onClick={() => executeAICommand(updateDOMTool, {
284
+ selector: 'h1',
285
+ action: 'setText',
286
+ value: 'AI Modified This Title!'
287
+ })}
288
+ className="w-full px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
289
+ disabled={loading}
290
+ >
291
+ AI: Update Page Title
292
+ </button>
293
+
294
+ <button
295
+ onClick={() => executeAICommand(localStorageTool, {
296
+ action: 'set',
297
+ key: 'ai_data',
298
+ value: { message: 'AI was here', timestamp: Date.now() }
299
+ })}
300
+ className="w-full px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
301
+ disabled={loading}
302
+ >
303
+ AI: Store Data Locally
304
+ </button>
305
+
306
+ <button
307
+ onClick={() => executeAICommand(navigateTool, {
308
+ path: '/',
309
+ params: { ai_action: 'demo' }
310
+ })}
311
+ className="w-full px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600"
312
+ disabled={loading}
313
+ >
314
+ AI: Navigate Home
315
+ </button>
316
+ </div>
317
+ </div>
318
+ </div>
319
+
320
+ {/* Results Display */}
321
+ <div className="mt-8">
322
+ <h2 className="text-xl font-semibold mb-4">AI Execution Results</h2>
323
+ <div className="space-y-2 max-h-96 overflow-y-auto">
324
+ {results.map((item, idx) => (
325
+ <div key={idx} className="border rounded p-3">
326
+ <div className="flex justify-between text-sm text-gray-600 mb-2">
327
+ <span className="font-medium">{item.tool}</span>
328
+ <span>{item.timestamp.toLocaleTimeString()}</span>
329
+ </div>
330
+ {item.error ? (
331
+ <div className="text-red-600 text-sm">Error: {item.error}</div>
332
+ ) : (
333
+ <pre className="text-sm bg-gray-50 p-2 rounded overflow-x-auto">
334
+ {JSON.stringify(item.result, null, 2)}
335
+ </pre>
336
+ )}
337
+ </div>
338
+ ))}
339
+ {results.length === 0 && (
340
+ <p className="text-gray-500 text-center py-4">No AI commands executed yet</p>
341
+ )}
342
+ </div>
343
+ </div>
344
+ </div>
345
+ );
346
+ }
347
+
348
+ // Export all tools for AI integration
349
+ export const aiControlTools = {
350
+ queryDatabaseTool,
351
+ callAPITool,
352
+ updateDOMTool,
353
+ submitFormTool,
354
+ navigateTool,
355
+ localStorageTool
356
+ };
@@ -0,0 +1,308 @@
1
+ import aui, { z } from '../index';
2
+
3
+ // AI Control Tools for Frontend/Backend Operations
4
+
5
+ // Database operations tool
6
+ export const databaseTool = aui
7
+ .tool('database')
8
+ .input(z.object({
9
+ operation: z.enum(['find', 'create', 'update', 'delete']),
10
+ collection: z.string(),
11
+ query: z.any().optional(),
12
+ data: z.any().optional()
13
+ }))
14
+ .execute(async ({ input }) => {
15
+ // Server-side database operations
16
+ switch (input.operation) {
17
+ case 'find':
18
+ return { results: [], count: 0 }; // Mock implementation
19
+ case 'create':
20
+ return { id: crypto.randomUUID(), ...input.data };
21
+ case 'update':
22
+ return { modified: 1 };
23
+ case 'delete':
24
+ return { deleted: 1 };
25
+ }
26
+ })
27
+ .clientExecute(async ({ input, ctx }) => {
28
+ // Client-side API call
29
+ return ctx.fetch('/api/tools/database', {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify(input)
33
+ }).then(r => r.json());
34
+ });
35
+
36
+ // File system operations tool
37
+ export const fileSystemTool = aui
38
+ .tool('filesystem')
39
+ .input(z.object({
40
+ operation: z.enum(['read', 'write', 'list', 'delete']),
41
+ path: z.string(),
42
+ content: z.string().optional()
43
+ }))
44
+ .execute(async ({ input }) => {
45
+ // Server-only file operations
46
+ switch (input.operation) {
47
+ case 'read':
48
+ return { content: 'file content', path: input.path };
49
+ case 'write':
50
+ return { success: true, path: input.path };
51
+ case 'list':
52
+ return { files: [], directories: [] };
53
+ case 'delete':
54
+ return { success: true };
55
+ }
56
+ });
57
+
58
+ // API request tool for external services
59
+ export const apiRequestTool = aui
60
+ .tool('api_request')
61
+ .input(z.object({
62
+ url: z.string().url(),
63
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
64
+ headers: z.record(z.string()).optional(),
65
+ body: z.any().optional()
66
+ }))
67
+ .execute(async ({ input }) => {
68
+ const response = await fetch(input.url, {
69
+ method: input.method,
70
+ headers: input.headers,
71
+ body: input.body ? JSON.stringify(input.body) : undefined
72
+ });
73
+ return response.json();
74
+ });
75
+
76
+ // DOM manipulation tool
77
+ export const domTool = aui
78
+ .tool('dom_manipulate')
79
+ .input(z.object({
80
+ selector: z.string(),
81
+ action: z.enum(['click', 'type', 'scroll', 'focus', 'getValue']),
82
+ value: z.string().optional()
83
+ }))
84
+ .clientExecute(async ({ input }) => {
85
+ // Client-only DOM operations
86
+ const element = document.querySelector(input.selector) as HTMLElement;
87
+ if (!element) throw new Error(`Element not found: ${input.selector}`);
88
+
89
+ switch (input.action) {
90
+ case 'click':
91
+ element.click();
92
+ return { success: true };
93
+ case 'type':
94
+ if (element instanceof HTMLInputElement && input.value) {
95
+ element.value = input.value;
96
+ element.dispatchEvent(new Event('input', { bubbles: true }));
97
+ }
98
+ return { success: true };
99
+ case 'scroll':
100
+ element.scrollIntoView({ behavior: 'smooth' });
101
+ return { success: true };
102
+ case 'focus':
103
+ element.focus();
104
+ return { success: true };
105
+ case 'getValue':
106
+ return { value: (element as HTMLInputElement).value || element.textContent };
107
+ }
108
+ });
109
+
110
+ // State management tool
111
+ export const stateTool = aui
112
+ .tool('state')
113
+ .input(z.object({
114
+ action: z.enum(['get', 'set', 'update', 'subscribe']),
115
+ key: z.string(),
116
+ value: z.any().optional()
117
+ }))
118
+ .clientExecute(async ({ input, ctx }) => {
119
+ // Client-side state management
120
+ const state = (ctx as any).state || new Map();
121
+
122
+ switch (input.action) {
123
+ case 'get':
124
+ return state.get(input.key);
125
+ case 'set':
126
+ state.set(input.key, input.value);
127
+ return { success: true };
128
+ case 'update':
129
+ const current = state.get(input.key) || {};
130
+ state.set(input.key, { ...current, ...input.value });
131
+ return { success: true };
132
+ default:
133
+ return null;
134
+ }
135
+ });
136
+
137
+ // Navigation tool
138
+ export const navigationTool = aui
139
+ .tool('navigate')
140
+ .input(z.object({
141
+ url: z.string(),
142
+ type: z.enum(['push', 'replace', 'reload']).default('push')
143
+ }))
144
+ .clientExecute(async ({ input }) => {
145
+ if (typeof window === 'undefined') {
146
+ throw new Error('Navigation only available on client');
147
+ }
148
+
149
+ switch (input.type) {
150
+ case 'push':
151
+ window.history.pushState({}, '', input.url);
152
+ break;
153
+ case 'replace':
154
+ window.history.replaceState({}, '', input.url);
155
+ break;
156
+ case 'reload':
157
+ window.location.href = input.url;
158
+ break;
159
+ }
160
+
161
+ return { success: true, url: input.url };
162
+ });
163
+
164
+ // Form submission tool
165
+ export const formTool = aui
166
+ .tool('form')
167
+ .input(z.object({
168
+ formId: z.string().optional(),
169
+ data: z.record(z.any()),
170
+ endpoint: z.string().optional()
171
+ }))
172
+ .execute(async ({ input }) => {
173
+ // Server-side form processing
174
+ return {
175
+ success: true,
176
+ data: input.data,
177
+ timestamp: new Date().toISOString()
178
+ };
179
+ })
180
+ .clientExecute(async ({ input, ctx }) => {
181
+ const endpoint = input.endpoint || '/api/tools/form';
182
+ return ctx.fetch(endpoint, {
183
+ method: 'POST',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify(input.data)
186
+ }).then(r => r.json());
187
+ });
188
+
189
+ // WebSocket communication tool
190
+ export const websocketTool = aui
191
+ .tool('websocket')
192
+ .input(z.object({
193
+ action: z.enum(['connect', 'send', 'close']),
194
+ url: z.string().optional(),
195
+ message: z.any().optional()
196
+ }))
197
+ .clientExecute(async ({ input }) => {
198
+ // Client-only WebSocket operations
199
+ const wsMap = (globalThis as any).__wsConnections || new Map();
200
+
201
+ switch (input.action) {
202
+ case 'connect':
203
+ if (!input.url) throw new Error('URL required for connect');
204
+ const ws = new WebSocket(input.url);
205
+ wsMap.set(input.url, ws);
206
+ return { connected: true, url: input.url };
207
+
208
+ case 'send':
209
+ if (!input.url) throw new Error('URL required for send');
210
+ const connection = wsMap.get(input.url);
211
+ if (!connection) throw new Error('No connection found');
212
+ connection.send(JSON.stringify(input.message));
213
+ return { sent: true };
214
+
215
+ case 'close':
216
+ if (!input.url) throw new Error('URL required for close');
217
+ const conn = wsMap.get(input.url);
218
+ if (conn) {
219
+ conn.close();
220
+ wsMap.delete(input.url);
221
+ }
222
+ return { closed: true };
223
+ }
224
+ });
225
+
226
+ // Email sending tool
227
+ export const emailTool = aui
228
+ .tool('email')
229
+ .input(z.object({
230
+ to: z.string().email(),
231
+ subject: z.string(),
232
+ body: z.string(),
233
+ html: z.boolean().optional()
234
+ }))
235
+ .execute(async ({ input }) => {
236
+ // Server-only email sending
237
+ console.log('Sending email:', input);
238
+ return {
239
+ success: true,
240
+ messageId: crypto.randomUUID(),
241
+ timestamp: new Date().toISOString()
242
+ };
243
+ });
244
+
245
+ // Cache management tool
246
+ export const cacheTool = aui
247
+ .tool('cache')
248
+ .input(z.object({
249
+ action: z.enum(['get', 'set', 'delete', 'clear']),
250
+ key: z.string().optional(),
251
+ value: z.any().optional(),
252
+ ttl: z.number().optional()
253
+ }))
254
+ .execute(async ({ input, ctx }) => {
255
+ const cache = ctx?.cache || new Map();
256
+
257
+ switch (input.action) {
258
+ case 'get':
259
+ return cache.get(input.key);
260
+ case 'set':
261
+ cache.set(input.key, input.value);
262
+ return { success: true };
263
+ case 'delete':
264
+ return { success: cache.delete(input.key) };
265
+ case 'clear':
266
+ cache.clear();
267
+ return { success: true };
268
+ }
269
+ })
270
+ .clientExecute(async ({ input, ctx }) => {
271
+ // Use browser cache APIs when available
272
+ if ('caches' in globalThis) {
273
+ const cache = await caches.open('aui-cache');
274
+
275
+ switch (input.action) {
276
+ case 'get':
277
+ const response = await cache.match(input.key!);
278
+ return response ? await response.json() : null;
279
+ case 'set':
280
+ await cache.put(input.key!, new Response(JSON.stringify(input.value)));
281
+ return { success: true };
282
+ case 'delete':
283
+ return { success: await cache.delete(input.key!) };
284
+ case 'clear':
285
+ // Clear all cache entries
286
+ return { success: true };
287
+ }
288
+ }
289
+
290
+ // Fallback to context cache
291
+ return ctx.cache;
292
+ });
293
+
294
+ // Export all tools for easy import
295
+ export const aiControlTools = {
296
+ database: databaseTool,
297
+ filesystem: fileSystemTool,
298
+ apiRequest: apiRequestTool,
299
+ dom: domTool,
300
+ state: stateTool,
301
+ navigation: navigationTool,
302
+ form: formTool,
303
+ websocket: websocketTool,
304
+ email: emailTool,
305
+ cache: cacheTool
306
+ };
307
+
308
+ export default aiControlTools;