ultra-dex 3.1.0 → 3.2.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 (44) hide show
  1. package/README.md +79 -74
  2. package/assets/code-patterns/clerk-middleware.ts +138 -0
  3. package/assets/code-patterns/prisma-schema.prisma +224 -0
  4. package/assets/code-patterns/rls-policies.sql +246 -0
  5. package/assets/code-patterns/server-actions.ts +191 -0
  6. package/assets/code-patterns/trpc-router.ts +258 -0
  7. package/assets/cursor-rules/13-ai-integration.mdc +155 -0
  8. package/assets/cursor-rules/14-server-components.mdc +81 -0
  9. package/assets/cursor-rules/15-server-actions.mdc +102 -0
  10. package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
  11. package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
  12. package/bin/ultra-dex.js +38 -1
  13. package/lib/commands/agents.js +16 -13
  14. package/lib/commands/banner.js +43 -21
  15. package/lib/commands/build.js +26 -17
  16. package/lib/commands/doctor.js +98 -79
  17. package/lib/commands/generate.js +19 -16
  18. package/lib/commands/init.js +52 -56
  19. package/lib/commands/scaffold.js +151 -0
  20. package/lib/commands/serve.js +15 -13
  21. package/lib/commands/state.js +43 -70
  22. package/lib/commands/swarm.js +31 -9
  23. package/lib/config/theme.js +47 -0
  24. package/lib/templates/code/clerk-middleware.ts +138 -0
  25. package/lib/templates/code/prisma-schema.prisma +224 -0
  26. package/lib/templates/code/rls-policies.sql +246 -0
  27. package/lib/templates/code/server-actions.ts +191 -0
  28. package/lib/templates/code/trpc-router.ts +258 -0
  29. package/lib/themes/doomsday.js +229 -0
  30. package/lib/ui/index.js +5 -0
  31. package/lib/ui/interface.js +241 -0
  32. package/lib/ui/spinners.js +116 -0
  33. package/lib/ui/theme.js +183 -0
  34. package/lib/utils/agents.js +32 -0
  35. package/lib/utils/help.js +64 -0
  36. package/lib/utils/messages.js +35 -0
  37. package/lib/utils/progress.js +24 -0
  38. package/lib/utils/prompts.js +47 -0
  39. package/lib/utils/spinners.js +46 -0
  40. package/lib/utils/status.js +31 -0
  41. package/lib/utils/tables.js +41 -0
  42. package/lib/utils/theme-state.js +9 -0
  43. package/lib/utils/version-display.js +32 -0
  44. package/package.json +10 -1
@@ -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.2.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,7 @@ 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';
42
78
 
43
79
  const program = new Command();
44
80
  program.banner = banner;
@@ -46,7 +82,7 @@ program.banner = banner;
46
82
  program
47
83
  .name('ultra-dex')
48
84
  .description('CLI for Ultra-Dex SaaS Implementation Framework')
49
- .version('3.1.0');
85
+ .version('3.2.0');
50
86
 
51
87
  registerInitCommand(program);
52
88
  registerAuditCommand(program);
@@ -126,5 +162,6 @@ registerSyncCommand(program);
126
162
  registerAgentBuilderCommand(program);
127
163
  registerTeamCommand(program);
128
164
  registerMemoryCommand(program);
165
+ registerScaffoldCommand(program);
129
166
 
130
167
  program.parse();
@@ -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
- console.log(chalk.gray('Organized by tier for production pipeline\n'));
92
-
93
- let currentTier = '';
94
- AGENTS.forEach((agent) => {
95
- if (agent.tier !== currentTier) {
96
- currentTier = agent.tier;
97
- console.log(chalk.bold(`\n ${currentTier} Tier:`));
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
- console.log(chalk.bold('\n Custom Agents:'));
104
- customAgents.forEach((name) => {
105
- console.log(chalk.cyan(` ${name}`));
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'));