@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,45 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useMemo, ReactNode } from 'react';
4
+ import aui, { AUI, AUIContext } from './index';
5
+
6
+ interface AUIProviderProps {
7
+ children: ReactNode;
8
+ instance?: AUI;
9
+ context?: Partial<AUIContext>;
10
+ }
11
+
12
+ const AUIReactContext = createContext<{
13
+ aui: AUI;
14
+ context: AUIContext;
15
+ } | null>(null);
16
+
17
+ export function AUIProvider({
18
+ children,
19
+ instance = aui,
20
+ context: contextOverrides = {}
21
+ }: AUIProviderProps) {
22
+ const value = useMemo(() => ({
23
+ aui: instance,
24
+ context: instance.createContext(contextOverrides)
25
+ }), [instance, contextOverrides]);
26
+
27
+ return (
28
+ <AUIReactContext.Provider value={value}>
29
+ {children}
30
+ </AUIReactContext.Provider>
31
+ );
32
+ }
33
+
34
+ export function useAUIContext() {
35
+ const context = useContext(AUIReactContext);
36
+ if (!context) {
37
+ throw new Error('useAUIContext must be used within AUIProvider');
38
+ }
39
+ return context;
40
+ }
41
+
42
+ export function useAUIInstance() {
43
+ const { aui } = useAUIContext();
44
+ return aui;
45
+ }
@@ -0,0 +1,386 @@
1
+ import { z } from 'zod';
2
+ import { createAITool, AIControlledTool } from './ai-control';
3
+ import { AUIContext } from './core';
4
+ import { NextRequest, NextResponse } from 'next/server';
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+ import { exec } from 'child_process';
8
+ import { promisify } from 'util';
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ export interface ServerControlContext extends AUIContext {
13
+ req?: NextRequest;
14
+ res?: NextResponse;
15
+ db?: any;
16
+ redis?: any;
17
+ prisma?: any;
18
+ }
19
+
20
+ export const serverTools = {
21
+ fileOperations: createAITool('server-file-ops', {
22
+ permissions: {
23
+ allowServerExecution: true,
24
+ allowFileSystemAccess: true
25
+ },
26
+ audit: true,
27
+ sandbox: true
28
+ })
29
+ .input(z.object({
30
+ operation: z.enum(['read', 'write', 'append', 'delete', 'exists', 'mkdir', 'list']),
31
+ path: z.string(),
32
+ content: z.string().optional(),
33
+ encoding: z.enum(['utf8', 'base64', 'binary']).default('utf8').optional()
34
+ }))
35
+ .describe('Perform file system operations on the server')
36
+ .tag('server', 'filesystem', 'io')
37
+ .execute(async ({ input }) => {
38
+ const safePath = path.resolve(process.cwd(), input.path);
39
+
40
+ if (!safePath.startsWith(process.cwd())) {
41
+ throw new Error('Path traversal attempt blocked');
42
+ }
43
+
44
+ switch (input.operation) {
45
+ case 'read':
46
+ const content = await fs.readFile(safePath, input.encoding || 'utf8');
47
+ return { content, path: safePath };
48
+
49
+ case 'write':
50
+ if (!input.content) throw new Error('Content required for write operation');
51
+ await fs.writeFile(safePath, input.content, input.encoding || 'utf8');
52
+ return { success: true, path: safePath };
53
+
54
+ case 'append':
55
+ if (!input.content) throw new Error('Content required for append operation');
56
+ await fs.appendFile(safePath, input.content, input.encoding || 'utf8');
57
+ return { success: true, path: safePath };
58
+
59
+ case 'delete':
60
+ await fs.unlink(safePath);
61
+ return { success: true, deleted: safePath };
62
+
63
+ case 'exists':
64
+ const exists = await fs.access(safePath).then(() => true).catch(() => false);
65
+ return { exists, path: safePath };
66
+
67
+ case 'mkdir':
68
+ await fs.mkdir(safePath, { recursive: true });
69
+ return { success: true, created: safePath };
70
+
71
+ case 'list':
72
+ const items = await fs.readdir(safePath, { withFileTypes: true });
73
+ return {
74
+ path: safePath,
75
+ items: items.map(item => ({
76
+ name: item.name,
77
+ type: item.isDirectory() ? 'directory' : 'file'
78
+ }))
79
+ };
80
+
81
+ default:
82
+ throw new Error('Unknown operation');
83
+ }
84
+ }),
85
+
86
+ processExecution: createAITool('server-process', {
87
+ permissions: {
88
+ allowServerExecution: true
89
+ },
90
+ audit: true,
91
+ sandbox: true,
92
+ rateLimit: {
93
+ requestsPerMinute: 10,
94
+ requestsPerHour: 100
95
+ }
96
+ })
97
+ .input(z.object({
98
+ command: z.string(),
99
+ args: z.array(z.string()).optional(),
100
+ cwd: z.string().optional(),
101
+ env: z.record(z.string()).optional(),
102
+ timeout: z.number().default(30000).optional()
103
+ }))
104
+ .describe('Execute system commands on the server')
105
+ .tag('server', 'process', 'system')
106
+ .execute(async ({ input }) => {
107
+ const allowedCommands = ['ls', 'pwd', 'echo', 'node', 'npm', 'yarn', 'pnpm'];
108
+ const cmd = input.command.split(' ')[0];
109
+
110
+ if (!allowedCommands.includes(cmd)) {
111
+ throw new Error(`Command "${cmd}" is not allowed`);
112
+ }
113
+
114
+ const fullCommand = input.args
115
+ ? `${input.command} ${input.args.join(' ')}`
116
+ : input.command;
117
+
118
+ try {
119
+ const { stdout, stderr } = await execAsync(fullCommand, {
120
+ cwd: input.cwd || process.cwd(),
121
+ env: { ...process.env, ...input.env },
122
+ timeout: input.timeout
123
+ });
124
+
125
+ return {
126
+ stdout,
127
+ stderr,
128
+ exitCode: 0,
129
+ command: fullCommand
130
+ };
131
+ } catch (error: any) {
132
+ return {
133
+ stdout: error.stdout || '',
134
+ stderr: error.stderr || error.message,
135
+ exitCode: error.code || 1,
136
+ command: fullCommand,
137
+ error: error.message
138
+ };
139
+ }
140
+ }),
141
+
142
+ databaseQuery: createAITool('server-database', {
143
+ permissions: {
144
+ allowServerExecution: true,
145
+ allowDatabaseAccess: true
146
+ },
147
+ audit: true,
148
+ rateLimit: {
149
+ requestsPerMinute: 50,
150
+ requestsPerHour: 1000
151
+ }
152
+ })
153
+ .input(z.object({
154
+ operation: z.enum(['find', 'findOne', 'create', 'update', 'delete', 'count', 'aggregate']),
155
+ model: z.string(),
156
+ where: z.record(z.any()).optional(),
157
+ data: z.record(z.any()).optional(),
158
+ select: z.record(z.boolean()).optional(),
159
+ include: z.record(z.any()).optional(),
160
+ orderBy: z.record(z.enum(['asc', 'desc'])).optional(),
161
+ take: z.number().optional(),
162
+ skip: z.number().optional()
163
+ }))
164
+ .describe('Execute database operations via Prisma or other ORMs')
165
+ .tag('server', 'database', 'orm')
166
+ .execute(async ({ input, ctx }) => {
167
+ const serverCtx = ctx as ServerControlContext;
168
+
169
+ if (!serverCtx.prisma) {
170
+ throw new Error('Database connection not available');
171
+ }
172
+
173
+ const model = serverCtx.prisma[input.model];
174
+ if (!model) {
175
+ throw new Error(`Model "${input.model}" not found`);
176
+ }
177
+
178
+ const query: any = {};
179
+ if (input.where) query.where = input.where;
180
+ if (input.data) query.data = input.data;
181
+ if (input.select) query.select = input.select;
182
+ if (input.include) query.include = input.include;
183
+ if (input.orderBy) query.orderBy = input.orderBy;
184
+ if (input.take) query.take = input.take;
185
+ if (input.skip) query.skip = input.skip;
186
+
187
+ const result = await model[input.operation](query);
188
+
189
+ return {
190
+ operation: input.operation,
191
+ model: input.model,
192
+ result,
193
+ count: Array.isArray(result) ? result.length : 1
194
+ };
195
+ }),
196
+
197
+ cacheControl: createAITool('server-cache', {
198
+ permissions: {
199
+ allowServerExecution: true
200
+ },
201
+ audit: true
202
+ })
203
+ .input(z.object({
204
+ operation: z.enum(['get', 'set', 'delete', 'clear', 'has']),
205
+ key: z.string(),
206
+ value: z.any().optional(),
207
+ ttl: z.number().optional()
208
+ }))
209
+ .describe('Control server-side caching')
210
+ .tag('server', 'cache', 'performance')
211
+ .execute(async ({ input, ctx }) => {
212
+ const serverCtx = ctx as ServerControlContext;
213
+ const cache = serverCtx.cache || new Map();
214
+
215
+ switch (input.operation) {
216
+ case 'get':
217
+ return { value: cache.get(input.key), key: input.key };
218
+
219
+ case 'set':
220
+ cache.set(input.key, input.value);
221
+ if (input.ttl) {
222
+ setTimeout(() => cache.delete(input.key), input.ttl * 1000);
223
+ }
224
+ return { success: true, key: input.key };
225
+
226
+ case 'delete':
227
+ const deleted = cache.delete(input.key);
228
+ return { success: deleted, key: input.key };
229
+
230
+ case 'clear':
231
+ cache.clear();
232
+ return { success: true, cleared: true };
233
+
234
+ case 'has':
235
+ return { exists: cache.has(input.key), key: input.key };
236
+
237
+ default:
238
+ throw new Error('Unknown cache operation');
239
+ }
240
+ }),
241
+
242
+ apiRoute: createAITool('server-api-route', {
243
+ permissions: {
244
+ allowServerExecution: true,
245
+ allowNetworkRequests: true
246
+ },
247
+ audit: true
248
+ })
249
+ .input(z.object({
250
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
251
+ path: z.string(),
252
+ headers: z.record(z.string()).optional(),
253
+ body: z.any().optional(),
254
+ query: z.record(z.string()).optional()
255
+ }))
256
+ .describe('Create or modify API routes')
257
+ .tag('server', 'api', 'routing')
258
+ .execute(async ({ input }) => {
259
+ return {
260
+ method: input.method,
261
+ path: input.path,
262
+ status: 200,
263
+ response: {
264
+ message: 'Route handler executed',
265
+ data: input.body
266
+ }
267
+ };
268
+ }),
269
+
270
+ environmentControl: createAITool('server-env', {
271
+ permissions: {
272
+ allowServerExecution: true
273
+ },
274
+ audit: true
275
+ })
276
+ .input(z.object({
277
+ operation: z.enum(['get', 'set', 'list']),
278
+ key: z.string().optional(),
279
+ value: z.string().optional()
280
+ }))
281
+ .describe('Manage environment variables')
282
+ .tag('server', 'environment', 'config')
283
+ .execute(async ({ input }) => {
284
+ switch (input.operation) {
285
+ case 'get':
286
+ if (!input.key) throw new Error('Key required for get operation');
287
+ return { key: input.key, value: process.env[input.key] };
288
+
289
+ case 'set':
290
+ if (!input.key || !input.value) {
291
+ throw new Error('Key and value required for set operation');
292
+ }
293
+ process.env[input.key] = input.value;
294
+ return { success: true, key: input.key };
295
+
296
+ case 'list':
297
+ const safeKeys = Object.keys(process.env).filter(
298
+ key => !key.includes('SECRET') && !key.includes('KEY') && !key.includes('TOKEN')
299
+ );
300
+ return {
301
+ keys: safeKeys,
302
+ count: safeKeys.length
303
+ };
304
+
305
+ default:
306
+ throw new Error('Unknown environment operation');
307
+ }
308
+ }),
309
+
310
+ logAnalysis: createAITool('server-logs', {
311
+ permissions: {
312
+ allowServerExecution: true,
313
+ allowFileSystemAccess: true
314
+ },
315
+ audit: true
316
+ })
317
+ .input(z.object({
318
+ source: z.enum(['console', 'file', 'database']),
319
+ level: z.enum(['error', 'warn', 'info', 'debug']).optional(),
320
+ startTime: z.string().optional(),
321
+ endTime: z.string().optional(),
322
+ limit: z.number().default(100).optional(),
323
+ filter: z.string().optional()
324
+ }))
325
+ .describe('Analyze and retrieve server logs')
326
+ .tag('server', 'logging', 'monitoring')
327
+ .execute(async ({ input }) => {
328
+ return {
329
+ source: input.source,
330
+ logs: [],
331
+ count: 0,
332
+ timeRange: {
333
+ start: input.startTime,
334
+ end: input.endTime
335
+ }
336
+ };
337
+ })
338
+ };
339
+
340
+ export function createServerControlSystem() {
341
+ const tools = new Map<string, AIControlledTool>();
342
+
343
+ Object.values(serverTools).forEach(tool => {
344
+ tools.set(tool.name, tool);
345
+ });
346
+
347
+ return {
348
+ tools,
349
+
350
+ async executeServerTool(
351
+ name: string,
352
+ input: any,
353
+ context?: ServerControlContext
354
+ ) {
355
+ const tool = tools.get(name);
356
+ if (!tool) {
357
+ throw new Error(`Server tool "${name}" not found`);
358
+ }
359
+
360
+ const serverContext: ServerControlContext = {
361
+ ...context,
362
+ isServer: true,
363
+ cache: context?.cache || new Map(),
364
+ fetch: fetch
365
+ };
366
+
367
+ return await tool.run(input, serverContext);
368
+ },
369
+
370
+ registerServerTool(tool: AIControlledTool) {
371
+ tools.set(tool.name, tool);
372
+ return this;
373
+ },
374
+
375
+ listServerTools() {
376
+ return Array.from(tools.values()).map(tool => ({
377
+ name: tool.name,
378
+ description: tool.description,
379
+ tags: tool.tags,
380
+ schema: tool.schema
381
+ }));
382
+ }
383
+ };
384
+ }
385
+
386
+ export const serverControlSystem = createServerControlSystem();
@@ -0,0 +1,165 @@
1
+ import { AUITool, AUIContext } from './core';
2
+ import aui from './index';
3
+
4
+ export interface ServerExecutorOptions {
5
+ // Security options
6
+ allowedTools?: string[];
7
+ blockedTools?: string[];
8
+ maxExecutionTime?: number;
9
+
10
+ // Context options
11
+ defaultContext?: Partial<AUIContext>;
12
+
13
+ // Logging
14
+ logExecution?: boolean;
15
+ }
16
+
17
+ export class ServerExecutor {
18
+ private options: ServerExecutorOptions;
19
+
20
+ constructor(options: ServerExecutorOptions = {}) {
21
+ this.options = {
22
+ maxExecutionTime: 30000, // 30 seconds default
23
+ logExecution: true,
24
+ ...options
25
+ };
26
+ }
27
+
28
+ async execute<TInput = any, TOutput = any>(
29
+ toolName: string,
30
+ input: TInput,
31
+ context?: Partial<AUIContext>
32
+ ): Promise<TOutput> {
33
+ // Security checks
34
+ if (this.options.blockedTools?.includes(toolName)) {
35
+ throw new Error(`Tool "${toolName}" is blocked`);
36
+ }
37
+
38
+ if (this.options.allowedTools && !this.options.allowedTools.includes(toolName)) {
39
+ throw new Error(`Tool "${toolName}" is not allowed`);
40
+ }
41
+
42
+ // Get the tool
43
+ const tool = aui.get(toolName);
44
+ if (!tool) {
45
+ throw new Error(`Tool "${toolName}" not found`);
46
+ }
47
+
48
+ // Create server context
49
+ const serverContext = aui.createContext({
50
+ isServer: true,
51
+ ...this.options.defaultContext,
52
+ ...context
53
+ });
54
+
55
+ // Log execution if enabled
56
+ if (this.options.logExecution) {
57
+ console.log(`[AUI] Executing tool: ${toolName}`, { input });
58
+ }
59
+
60
+ // Execute with timeout
61
+ let timeoutId: NodeJS.Timeout | undefined;
62
+ const timeoutPromise = new Promise((_, reject) => {
63
+ timeoutId = setTimeout(() => {
64
+ reject(new Error(`Tool execution timeout (${this.options.maxExecutionTime}ms)`));
65
+ }, this.options.maxExecutionTime!);
66
+ });
67
+
68
+ try {
69
+ const result = await Promise.race([
70
+ tool.run(input, serverContext),
71
+ timeoutPromise
72
+ ]) as TOutput;
73
+
74
+ // Clear the timeout after successful execution
75
+ if (timeoutId) clearTimeout(timeoutId);
76
+
77
+ if (this.options.logExecution) {
78
+ console.log(`[AUI] Tool executed successfully: ${toolName}`);
79
+ }
80
+
81
+ return result;
82
+ } catch (error) {
83
+ // Clear the timeout on error as well
84
+ if (timeoutId) clearTimeout(timeoutId);
85
+
86
+ if (this.options.logExecution) {
87
+ console.error(`[AUI] Tool execution failed: ${toolName}`, error);
88
+ }
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ async executeBatch(
94
+ executions: Array<{ tool: string; input: any }>
95
+ ): Promise<Array<{ tool: string; result?: any; error?: string }>> {
96
+ const results = await Promise.allSettled(
97
+ executions.map(({ tool, input }) =>
98
+ this.execute(tool, input)
99
+ )
100
+ );
101
+
102
+ return results.map((result, index) => ({
103
+ tool: executions[index].tool,
104
+ ...(result.status === 'fulfilled'
105
+ ? { result: result.value }
106
+ : { error: result.reason?.message || 'Unknown error' }
107
+ )
108
+ }));
109
+ }
110
+
111
+ // Validate tool input without executing
112
+ async validate(toolName: string, input: any): Promise<boolean> {
113
+ const tool = aui.get(toolName);
114
+ if (!tool) {
115
+ throw new Error(`Tool "${toolName}" not found`);
116
+ }
117
+
118
+ const config = tool.getConfig();
119
+ if (!config.inputSchema) {
120
+ return true; // No schema means any input is valid
121
+ }
122
+
123
+ try {
124
+ config.inputSchema.parse(input);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ // Get tool capabilities for AI
132
+ getCapabilities(): Array<{
133
+ name: string;
134
+ description?: string;
135
+ hasInput: boolean;
136
+ hasServerExecution: boolean;
137
+ hasClientExecution: boolean;
138
+ tags: string[];
139
+ }> {
140
+ return aui.getTools().map((tool: any) => {
141
+ const config = tool.getConfig();
142
+ return {
143
+ name: tool.name,
144
+ description: tool.description,
145
+ hasInput: !!config.inputSchema,
146
+ hasServerExecution: !!config.executeHandler,
147
+ hasClientExecution: !!config.clientHandler,
148
+ tags: tool.tags
149
+ };
150
+ });
151
+ }
152
+ }
153
+
154
+ // Default server executor instance
155
+ export const serverExecutor = new ServerExecutor();
156
+
157
+ // Helper function for API routes
158
+ export async function executeServerTool<TInput = any, TOutput = any>(
159
+ toolName: string,
160
+ input: TInput,
161
+ options?: ServerExecutorOptions
162
+ ): Promise<TOutput> {
163
+ const executor = options ? new ServerExecutor(options) : serverExecutor;
164
+ return executor.execute(toolName, input);
165
+ }