ultra-dex 3.1.0 ā 3.3.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.
- package/README.md +79 -74
- package/assets/code-patterns/clerk-middleware.ts +138 -0
- package/assets/code-patterns/prisma-schema.prisma +224 -0
- package/assets/code-patterns/rls-policies.sql +246 -0
- package/assets/code-patterns/server-actions.ts +191 -0
- package/assets/code-patterns/trpc-router.ts +258 -0
- package/assets/cursor-rules/13-ai-integration.mdc +155 -0
- package/assets/cursor-rules/14-server-components.mdc +81 -0
- package/assets/cursor-rules/15-server-actions.mdc +102 -0
- package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
- package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
- package/bin/ultra-dex.js +50 -1
- package/lib/commands/agents.js +16 -13
- package/lib/commands/banner.js +43 -21
- package/lib/commands/build.js +26 -17
- package/lib/commands/cloud.js +780 -0
- package/lib/commands/doctor.js +98 -79
- package/lib/commands/exec.js +434 -0
- package/lib/commands/generate.js +19 -16
- package/lib/commands/github.js +475 -0
- package/lib/commands/init.js +52 -56
- package/lib/commands/scaffold.js +151 -0
- package/lib/commands/search.js +477 -0
- package/lib/commands/serve.js +15 -13
- package/lib/commands/state.js +43 -70
- package/lib/commands/swarm.js +31 -9
- package/lib/config/theme.js +47 -0
- package/lib/mcp/client.js +502 -0
- package/lib/providers/agent-sdk.js +630 -0
- package/lib/providers/anthropic-agents.js +580 -0
- package/lib/templates/code/clerk-middleware.ts +138 -0
- package/lib/templates/code/prisma-schema.prisma +224 -0
- package/lib/templates/code/rls-policies.sql +246 -0
- package/lib/templates/code/server-actions.ts +191 -0
- package/lib/templates/code/trpc-router.ts +258 -0
- package/lib/themes/doomsday.js +229 -0
- package/lib/ui/index.js +5 -0
- package/lib/ui/interface.js +241 -0
- package/lib/ui/spinners.js +116 -0
- package/lib/ui/theme.js +183 -0
- package/lib/utils/agents.js +32 -0
- package/lib/utils/browser.js +373 -0
- package/lib/utils/help.js +64 -0
- package/lib/utils/messages.js +35 -0
- package/lib/utils/progress.js +24 -0
- package/lib/utils/prompts.js +47 -0
- package/lib/utils/spinners.js +46 -0
- package/lib/utils/status.js +31 -0
- package/lib/utils/tables.js +41 -0
- package/lib/utils/theme-state.js +9 -0
- package/lib/utils/version-display.js +32 -0
- package/package.json +19 -4
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# AI/LLM Integration (2026 Patterns)
|
|
2
|
+
|
|
3
|
+
> Patterns for integrating AI into production applications.
|
|
4
|
+
|
|
5
|
+
## Vercel AI SDK (Recommended)
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// app/api/chat/route.ts
|
|
9
|
+
import { streamText } from 'ai';
|
|
10
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
11
|
+
|
|
12
|
+
export async function POST(req: Request) {
|
|
13
|
+
const { messages } = await req.json();
|
|
14
|
+
|
|
15
|
+
const result = streamText({
|
|
16
|
+
model: anthropic('claude-sonnet-4-20250514'),
|
|
17
|
+
messages,
|
|
18
|
+
system: 'You are a helpful assistant.',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return result.toDataStreamResponse();
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Client-Side Streaming
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
'use client';
|
|
29
|
+
|
|
30
|
+
import { useChat } from 'ai/react';
|
|
31
|
+
|
|
32
|
+
export function Chat() {
|
|
33
|
+
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
{messages.map(m => (
|
|
38
|
+
<div key={m.id}>
|
|
39
|
+
<strong>{m.role}:</strong> {m.content}
|
|
40
|
+
</div>
|
|
41
|
+
))}
|
|
42
|
+
|
|
43
|
+
<form onSubmit={handleSubmit}>
|
|
44
|
+
<input
|
|
45
|
+
value={input}
|
|
46
|
+
onChange={handleInputChange}
|
|
47
|
+
placeholder="Type a message..."
|
|
48
|
+
disabled={isLoading}
|
|
49
|
+
/>
|
|
50
|
+
<button type="submit" disabled={isLoading}>
|
|
51
|
+
{isLoading ? 'Thinking...' : 'Send'}
|
|
52
|
+
</button>
|
|
53
|
+
</form>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Structured Output with Zod
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { generateObject } from 'ai';
|
|
63
|
+
import { z } from 'zod';
|
|
64
|
+
|
|
65
|
+
const ProductSchema = z.object({
|
|
66
|
+
name: z.string(),
|
|
67
|
+
description: z.string(),
|
|
68
|
+
price: z.number(),
|
|
69
|
+
category: z.enum(['electronics', 'clothing', 'food']),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const { object } = await generateObject({
|
|
73
|
+
model: anthropic('claude-sonnet-4-20250514'),
|
|
74
|
+
schema: ProductSchema,
|
|
75
|
+
prompt: 'Generate a product for an e-commerce store.',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// object is fully typed as { name: string, description: string, ... }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tool Use / Function Calling
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { generateText, tool } from 'ai';
|
|
85
|
+
import { z } from 'zod';
|
|
86
|
+
|
|
87
|
+
const result = await generateText({
|
|
88
|
+
model: anthropic('claude-sonnet-4-20250514'),
|
|
89
|
+
tools: {
|
|
90
|
+
weather: tool({
|
|
91
|
+
description: 'Get the weather for a location',
|
|
92
|
+
parameters: z.object({
|
|
93
|
+
location: z.string().describe('City name'),
|
|
94
|
+
}),
|
|
95
|
+
execute: async ({ location }) => {
|
|
96
|
+
// Call weather API
|
|
97
|
+
return { temperature: 72, condition: 'sunny' };
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
prompt: 'What is the weather in San Francisco?',
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Rate Limiting for AI Endpoints
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
109
|
+
import { Redis } from '@upstash/redis';
|
|
110
|
+
|
|
111
|
+
const ratelimit = new Ratelimit({
|
|
112
|
+
redis: Redis.fromEnv(),
|
|
113
|
+
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export async function POST(req: Request) {
|
|
117
|
+
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
|
|
118
|
+
const { success } = await ratelimit.limit(ip);
|
|
119
|
+
|
|
120
|
+
if (!success) {
|
|
121
|
+
return new Response('Rate limit exceeded', { status: 429 });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Process AI request...
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Caching AI Responses
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { unstable_cache } from 'next/cache';
|
|
132
|
+
|
|
133
|
+
const getCachedSummary = unstable_cache(
|
|
134
|
+
async (documentId: string) => {
|
|
135
|
+
const result = await generateText({
|
|
136
|
+
model: anthropic('claude-sonnet-4-20250514'),
|
|
137
|
+
prompt: `Summarize document ${documentId}`,
|
|
138
|
+
});
|
|
139
|
+
return result.text;
|
|
140
|
+
},
|
|
141
|
+
['document-summary'],
|
|
142
|
+
{ revalidate: 3600 } // Cache for 1 hour
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Rules
|
|
147
|
+
|
|
148
|
+
- Always stream long responses (better UX)
|
|
149
|
+
- Use structured output for data extraction
|
|
150
|
+
- Implement rate limiting on AI endpoints
|
|
151
|
+
- Cache deterministic AI responses
|
|
152
|
+
- Handle errors gracefully (retries, fallbacks)
|
|
153
|
+
- Log AI usage for cost monitoring
|
|
154
|
+
- Use environment variables for API keys
|
|
155
|
+
- Validate all AI outputs before using
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Server Components (React 19 / Next.js 15)
|
|
2
|
+
|
|
3
|
+
> Default to Server Components. Use Client Components only when necessary.
|
|
4
|
+
|
|
5
|
+
## When to Use Server Components
|
|
6
|
+
|
|
7
|
+
- Data fetching
|
|
8
|
+
- Accessing backend resources directly
|
|
9
|
+
- Keeping sensitive logic on server
|
|
10
|
+
- Large dependencies that shouldn't be in client bundle
|
|
11
|
+
- SEO-critical content
|
|
12
|
+
|
|
13
|
+
## When to Use Client Components
|
|
14
|
+
|
|
15
|
+
- Interactivity (onClick, onChange, etc.)
|
|
16
|
+
- Browser APIs (localStorage, geolocation)
|
|
17
|
+
- React hooks (useState, useEffect, useContext)
|
|
18
|
+
- Third-party libraries that use browser APIs
|
|
19
|
+
|
|
20
|
+
## Patterns
|
|
21
|
+
|
|
22
|
+
### Data Fetching in Server Components
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// app/users/page.tsx (Server Component)
|
|
26
|
+
import { prisma } from '@/lib/prisma';
|
|
27
|
+
|
|
28
|
+
export default async function UsersPage() {
|
|
29
|
+
const users = await prisma.user.findMany({
|
|
30
|
+
orderBy: { createdAt: 'desc' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
{users.map(user => (
|
|
36
|
+
<UserCard key={user.id} user={user} />
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Composition Pattern
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Server Component wrapper
|
|
47
|
+
async function UserList() {
|
|
48
|
+
const users = await getUsers();
|
|
49
|
+
return <UserListClient users={users} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Client Component for interactivity
|
|
53
|
+
'use client';
|
|
54
|
+
function UserListClient({ users }) {
|
|
55
|
+
const [selected, setSelected] = useState(null);
|
|
56
|
+
return (/* interactive UI */);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Passing Server Data to Client
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Server Component
|
|
64
|
+
export default async function Page() {
|
|
65
|
+
const data = await fetchData(); // Server-side fetch
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ClientComponent
|
|
69
|
+
initialData={data} // Serializable data only
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Rules
|
|
76
|
+
|
|
77
|
+
- Never import Server Components into Client Components
|
|
78
|
+
- Keep 'use client' boundary as low as possible
|
|
79
|
+
- Don't pass functions as props across the boundary
|
|
80
|
+
- Use Server Actions for mutations, not API routes
|
|
81
|
+
- Async components are Server Components by default
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Server Actions (Next.js 15)
|
|
2
|
+
|
|
3
|
+
> Use Server Actions for mutations. They replace API routes for form submissions.
|
|
4
|
+
|
|
5
|
+
## Basic Pattern
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// app/actions.ts
|
|
9
|
+
'use server';
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { revalidatePath } from 'next/cache';
|
|
13
|
+
|
|
14
|
+
const schema = z.object({
|
|
15
|
+
name: z.string().min(2),
|
|
16
|
+
email: z.string().email(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export async function createUser(formData: FormData) {
|
|
20
|
+
const data = schema.parse({
|
|
21
|
+
name: formData.get('name'),
|
|
22
|
+
email: formData.get('email'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await db.user.create({ data });
|
|
26
|
+
revalidatePath('/users');
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## With useActionState (React 19)
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
'use client';
|
|
34
|
+
|
|
35
|
+
import { useActionState } from 'react';
|
|
36
|
+
import { createUser } from './actions';
|
|
37
|
+
|
|
38
|
+
type State = { error?: string; success?: boolean };
|
|
39
|
+
|
|
40
|
+
export function Form() {
|
|
41
|
+
const [state, formAction, isPending] = useActionState<State, FormData>(
|
|
42
|
+
async (prevState, formData) => {
|
|
43
|
+
try {
|
|
44
|
+
await createUser(formData);
|
|
45
|
+
return { success: true };
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return { error: e.message };
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<form action={formAction}>
|
|
55
|
+
<input name="name" required />
|
|
56
|
+
<input name="email" type="email" required />
|
|
57
|
+
{state.error && <p className="error">{state.error}</p>}
|
|
58
|
+
<button disabled={isPending}>
|
|
59
|
+
{isPending ? 'Creating...' : 'Create'}
|
|
60
|
+
</button>
|
|
61
|
+
</form>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Validation Pattern
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
'use server';
|
|
70
|
+
|
|
71
|
+
type ActionResult<T = void> =
|
|
72
|
+
| { success: true; data: T }
|
|
73
|
+
| { success: false; error: string; fieldErrors?: Record<string, string[]> };
|
|
74
|
+
|
|
75
|
+
export async function action(formData: FormData): Promise<ActionResult<{ id: string }>> {
|
|
76
|
+
const validated = schema.safeParse(Object.fromEntries(formData));
|
|
77
|
+
|
|
78
|
+
if (!validated.success) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'Validation failed',
|
|
82
|
+
fieldErrors: validated.error.flatten().fieldErrors,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await db.create({ data: validated.data });
|
|
88
|
+
return { success: true, data: { id: result.id } };
|
|
89
|
+
} catch {
|
|
90
|
+
return { success: false, error: 'Database error' };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Rules
|
|
96
|
+
|
|
97
|
+
- Always use 'use server' directive
|
|
98
|
+
- Validate ALL inputs with Zod
|
|
99
|
+
- Return structured results, not throw errors
|
|
100
|
+
- Use revalidatePath/revalidateTag for cache invalidation
|
|
101
|
+
- Keep actions in separate files for organization
|
|
102
|
+
- Actions can only receive serializable arguments
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Edge Middleware (Next.js 15)
|
|
2
|
+
|
|
3
|
+
> Middleware runs before every request. Use for auth, redirects, and headers.
|
|
4
|
+
|
|
5
|
+
## Basic Middleware
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// middleware.ts
|
|
9
|
+
import { NextResponse } from 'next/server';
|
|
10
|
+
import type { NextRequest } from 'next/server';
|
|
11
|
+
|
|
12
|
+
export function middleware(request: NextRequest) {
|
|
13
|
+
// Check auth
|
|
14
|
+
const token = request.cookies.get('session')?.value;
|
|
15
|
+
|
|
16
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
17
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return NextResponse.next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const config = {
|
|
24
|
+
matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## With Clerk Auth
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
|
|
32
|
+
|
|
33
|
+
const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/api/webhooks(.*)']);
|
|
34
|
+
const isAdminRoute = createRouteMatcher(['/admin(.*)']);
|
|
35
|
+
|
|
36
|
+
export default clerkMiddleware(async (auth, req) => {
|
|
37
|
+
if (isPublicRoute(req)) return;
|
|
38
|
+
|
|
39
|
+
const { userId, sessionClaims } = await auth();
|
|
40
|
+
|
|
41
|
+
if (!userId) {
|
|
42
|
+
return Response.redirect(new URL('/sign-in', req.url));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isAdminRoute(req) && sessionClaims?.role !== 'admin') {
|
|
46
|
+
return Response.redirect(new URL('/unauthorized', req.url));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Geolocation & A/B Testing
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
export function middleware(request: NextRequest) {
|
|
55
|
+
const country = request.geo?.country || 'US';
|
|
56
|
+
const response = NextResponse.next();
|
|
57
|
+
|
|
58
|
+
// Set header for downstream use
|
|
59
|
+
response.headers.set('x-user-country', country);
|
|
60
|
+
|
|
61
|
+
// A/B test assignment
|
|
62
|
+
const bucket = Math.random() < 0.5 ? 'control' : 'variant';
|
|
63
|
+
response.cookies.set('ab-bucket', bucket);
|
|
64
|
+
|
|
65
|
+
return response;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Rate Limiting Pattern
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
73
|
+
import { Redis } from '@upstash/redis';
|
|
74
|
+
|
|
75
|
+
const ratelimit = new Ratelimit({
|
|
76
|
+
redis: Redis.fromEnv(),
|
|
77
|
+
limiter: Ratelimit.slidingWindow(10, '10 s'),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export async function middleware(request: NextRequest) {
|
|
81
|
+
const ip = request.ip ?? '127.0.0.1';
|
|
82
|
+
const { success, limit, remaining } = await ratelimit.limit(ip);
|
|
83
|
+
|
|
84
|
+
if (!success) {
|
|
85
|
+
return new NextResponse('Too Many Requests', {
|
|
86
|
+
status: 429,
|
|
87
|
+
headers: {
|
|
88
|
+
'X-RateLimit-Limit': limit.toString(),
|
|
89
|
+
'X-RateLimit-Remaining': remaining.toString(),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return NextResponse.next();
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Rules
|
|
99
|
+
|
|
100
|
+
- Middleware runs on Edge Runtime (limited Node.js APIs)
|
|
101
|
+
- Keep middleware fast (<50ms)
|
|
102
|
+
- Use matcher to limit which routes trigger middleware
|
|
103
|
+
- Don't do heavy computation or database calls
|
|
104
|
+
- Use for: auth, redirects, headers, A/B tests, geolocation
|
|
105
|
+
- Avoid: data fetching, heavy validation, logging
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Streaming SSR (Next.js 15)
|
|
2
|
+
|
|
3
|
+
> Stream UI to the client progressively. Show content as it loads.
|
|
4
|
+
|
|
5
|
+
## Suspense Boundaries
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// app/dashboard/page.tsx
|
|
9
|
+
import { Suspense } from 'react';
|
|
10
|
+
import { UserProfile } from './user-profile';
|
|
11
|
+
import { Analytics } from './analytics';
|
|
12
|
+
import { RecentActivity } from './recent-activity';
|
|
13
|
+
|
|
14
|
+
export default function DashboardPage() {
|
|
15
|
+
return (
|
|
16
|
+
<div className="grid grid-cols-3 gap-4">
|
|
17
|
+
{/* Loads first - critical content */}
|
|
18
|
+
<Suspense fallback={<ProfileSkeleton />}>
|
|
19
|
+
<UserProfile />
|
|
20
|
+
</Suspense>
|
|
21
|
+
|
|
22
|
+
{/* Loads second - important but slower */}
|
|
23
|
+
<Suspense fallback={<AnalyticsSkeleton />}>
|
|
24
|
+
<Analytics />
|
|
25
|
+
</Suspense>
|
|
26
|
+
|
|
27
|
+
{/* Loads last - can wait */}
|
|
28
|
+
<Suspense fallback={<ActivitySkeleton />}>
|
|
29
|
+
<RecentActivity />
|
|
30
|
+
</Suspense>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Loading States
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// app/dashboard/loading.tsx
|
|
40
|
+
export default function Loading() {
|
|
41
|
+
return (
|
|
42
|
+
<div className="animate-pulse">
|
|
43
|
+
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
|
|
44
|
+
<div className="h-64 bg-gray-200 rounded" />
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Streaming with Data
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// Server Component that fetches data
|
|
54
|
+
async function SlowDataComponent() {
|
|
55
|
+
// This will stream when ready
|
|
56
|
+
const data = await fetch('https://api.example.com/slow-endpoint', {
|
|
57
|
+
next: { revalidate: 60 },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return <DataDisplay data={data} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Usage with Suspense
|
|
64
|
+
export default function Page() {
|
|
65
|
+
return (
|
|
66
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
67
|
+
<SlowDataComponent />
|
|
68
|
+
</Suspense>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Parallel Data Fetching
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// Fetch in parallel, stream as each resolves
|
|
77
|
+
export default async function Page() {
|
|
78
|
+
// Start all fetches immediately
|
|
79
|
+
const userPromise = getUser();
|
|
80
|
+
const postsPromise = getPosts();
|
|
81
|
+
const analyticsPromise = getAnalytics();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<Suspense fallback={<UserSkeleton />}>
|
|
86
|
+
<UserSection promise={userPromise} />
|
|
87
|
+
</Suspense>
|
|
88
|
+
|
|
89
|
+
<Suspense fallback={<PostsSkeleton />}>
|
|
90
|
+
<PostsSection promise={postsPromise} />
|
|
91
|
+
</Suspense>
|
|
92
|
+
|
|
93
|
+
<Suspense fallback={<AnalyticsSkeleton />}>
|
|
94
|
+
<AnalyticsSection promise={analyticsPromise} />
|
|
95
|
+
</Suspense>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Component that awaits the promise
|
|
101
|
+
async function UserSection({ promise }: { promise: Promise<User> }) {
|
|
102
|
+
const user = await promise;
|
|
103
|
+
return <div>{user.name}</div>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Error Boundaries
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// app/dashboard/error.tsx
|
|
111
|
+
'use client';
|
|
112
|
+
|
|
113
|
+
export default function Error({
|
|
114
|
+
error,
|
|
115
|
+
reset,
|
|
116
|
+
}: {
|
|
117
|
+
error: Error;
|
|
118
|
+
reset: () => void;
|
|
119
|
+
}) {
|
|
120
|
+
return (
|
|
121
|
+
<div className="p-4 bg-red-50 rounded">
|
|
122
|
+
<h2>Something went wrong!</h2>
|
|
123
|
+
<p>{error.message}</p>
|
|
124
|
+
<button onClick={reset}>Try again</button>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Rules
|
|
131
|
+
|
|
132
|
+
- Wrap slow components in Suspense
|
|
133
|
+
- Show meaningful loading states (skeletons > spinners)
|
|
134
|
+
- Place Suspense boundaries at logical UI sections
|
|
135
|
+
- Use error.tsx for error boundaries
|
|
136
|
+
- Start data fetches as early as possible
|
|
137
|
+
- Don't nest too many Suspense boundaries (causes waterfall)
|
|
138
|
+
- Consider using loading.tsx for route-level loading
|
package/bin/ultra-dex.js
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
process.env.FORCE_COLOR = '3';
|
|
4
|
+
|
|
3
5
|
import { Command } from 'commander';
|
|
6
|
+
import updateNotifier from 'update-notifier';
|
|
7
|
+
import boxen from 'boxen';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { setDoomsdayMode } from '../lib/utils/theme-state.js';
|
|
10
|
+
|
|
11
|
+
// Check for doomsday flag early
|
|
12
|
+
if (process.argv.includes('--doomsday')) {
|
|
13
|
+
setDoomsdayMode(true);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
import { showHelp as showDoomsdayHelp } from '../lib/themes/doomsday.js';
|
|
17
|
+
|
|
18
|
+
if (process.argv.includes('--help') && process.argv.includes('--doomsday')) {
|
|
19
|
+
showDoomsdayHelp();
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for updates
|
|
24
|
+
const pkg = { name: 'ultra-dex', version: '3.3.0' };
|
|
25
|
+
const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 });
|
|
26
|
+
|
|
27
|
+
if (notifier.update) {
|
|
28
|
+
console.log(boxen(
|
|
29
|
+
`Update available! ${chalk.dim(notifier.update.current)} ā ${chalk.green(notifier.update.latest)}\n` +
|
|
30
|
+
`Run ${chalk.cyan('npm install -g ultra-dex')} to update`,
|
|
31
|
+
{
|
|
32
|
+
padding: 1,
|
|
33
|
+
margin: 1,
|
|
34
|
+
borderStyle: 'round',
|
|
35
|
+
borderColor: 'yellow'
|
|
36
|
+
}
|
|
37
|
+
));
|
|
38
|
+
}
|
|
4
39
|
|
|
5
40
|
import { banner } from '../lib/commands/banner.js';
|
|
6
41
|
import { registerInitCommand } from '../lib/commands/init.js';
|
|
@@ -39,6 +74,13 @@ import { registerFetchCommand } from '../lib/commands/fetch.js';
|
|
|
39
74
|
import { registerSyncCommand } from '../lib/commands/sync.js';
|
|
40
75
|
import { registerTeamCommand } from '../lib/commands/team.js';
|
|
41
76
|
import { registerMemoryCommand } from '../lib/commands/memory.js';
|
|
77
|
+
import { registerScaffoldCommand } from '../lib/commands/scaffold.js';
|
|
78
|
+
|
|
79
|
+
// v3.3.0 Commands - 2026 Competitive Features
|
|
80
|
+
import { registerExecCommand } from '../lib/commands/exec.js';
|
|
81
|
+
import { registerGitHubCommand } from '../lib/commands/github.js';
|
|
82
|
+
import { registerSearchCommand } from '../lib/commands/search.js';
|
|
83
|
+
import { registerCloudCommand } from '../lib/commands/cloud.js';
|
|
42
84
|
|
|
43
85
|
const program = new Command();
|
|
44
86
|
program.banner = banner;
|
|
@@ -46,7 +88,7 @@ program.banner = banner;
|
|
|
46
88
|
program
|
|
47
89
|
.name('ultra-dex')
|
|
48
90
|
.description('CLI for Ultra-Dex SaaS Implementation Framework')
|
|
49
|
-
.version('3.
|
|
91
|
+
.version('3.3.0');
|
|
50
92
|
|
|
51
93
|
registerInitCommand(program);
|
|
52
94
|
registerAuditCommand(program);
|
|
@@ -126,5 +168,12 @@ registerSyncCommand(program);
|
|
|
126
168
|
registerAgentBuilderCommand(program);
|
|
127
169
|
registerTeamCommand(program);
|
|
128
170
|
registerMemoryCommand(program);
|
|
171
|
+
registerScaffoldCommand(program);
|
|
172
|
+
|
|
173
|
+
// v3.3.0 Commands - 2026 Competitive Features
|
|
174
|
+
registerExecCommand(program);
|
|
175
|
+
registerGitHubCommand(program);
|
|
176
|
+
registerSearchCommand(program);
|
|
177
|
+
registerCloudCommand(program);
|
|
129
178
|
|
|
130
179
|
program.parse();
|
package/lib/commands/agents.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ASSETS_ROOT, ROOT_FALLBACK } from '../config/paths.js';
|
|
|
5
5
|
import { githubBlobUrl } from '../config/urls.js';
|
|
6
6
|
import { readWithFallback } from '../utils/fallback.js';
|
|
7
7
|
import { pathExists } from '../utils/files.js';
|
|
8
|
+
import { showAgentsTable } from '../utils/tables.js';
|
|
8
9
|
|
|
9
10
|
export const AGENTS = [
|
|
10
11
|
{ name: 'orchestrator', description: 'Multi-agent coordination', file: '0-orchestration/orchestrator.md', tier: 'Orchestration' },
|
|
@@ -88,24 +89,26 @@ async function listAgents() {
|
|
|
88
89
|
const customAgents = await listCustomAgents();
|
|
89
90
|
const totalAgents = AGENTS.length + customAgents.length;
|
|
90
91
|
console.log(chalk.bold(`\nš¤ Ultra-Dex AI Agents (${totalAgents} Total)\n`));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
console.log(chalk.cyan(` ${agent.name}`) + chalk.gray(` - ${agent.description}`));
|
|
100
|
-
});
|
|
92
|
+
|
|
93
|
+
// Format agents for the table
|
|
94
|
+
const agentsForTable = AGENTS.map(agent => ({
|
|
95
|
+
tier: agent.tier,
|
|
96
|
+
name: agent.name,
|
|
97
|
+
status: 'ready'
|
|
98
|
+
}));
|
|
101
99
|
|
|
102
100
|
if (customAgents.length > 0) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
customAgents.forEach(name => {
|
|
102
|
+
agentsForTable.push({
|
|
103
|
+
tier: 'Custom',
|
|
104
|
+
name: name,
|
|
105
|
+
status: 'ready'
|
|
106
|
+
});
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
showAgentsTable(agentsForTable);
|
|
111
|
+
|
|
109
112
|
console.log('\n' + chalk.bold('Usage:'));
|
|
110
113
|
console.log(chalk.gray(' ultra-dex agent show <name> Show agent prompt'));
|
|
111
114
|
console.log(chalk.gray(' ultra-dex pack <name> Package agent + context'));
|