nitrostack 1.0.1 → 1.0.2

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 (51) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli/index.js +4 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/studio/README.md +140 -0
  6. package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
  7. package/src/studio/app/api/auth/register-client/route.ts +67 -0
  8. package/src/studio/app/api/chat/route.ts +123 -0
  9. package/src/studio/app/api/health/checks/route.ts +42 -0
  10. package/src/studio/app/api/health/route.ts +13 -0
  11. package/src/studio/app/api/init/route.ts +85 -0
  12. package/src/studio/app/api/ping/route.ts +13 -0
  13. package/src/studio/app/api/prompts/[name]/route.ts +21 -0
  14. package/src/studio/app/api/prompts/route.ts +13 -0
  15. package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
  16. package/src/studio/app/api/resources/route.ts +13 -0
  17. package/src/studio/app/api/roots/route.ts +13 -0
  18. package/src/studio/app/api/sampling/route.ts +14 -0
  19. package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
  20. package/src/studio/app/api/tools/route.ts +23 -0
  21. package/src/studio/app/api/widget-examples/route.ts +44 -0
  22. package/src/studio/app/auth/callback/page.tsx +160 -0
  23. package/src/studio/app/auth/page.tsx +543 -0
  24. package/src/studio/app/chat/page.tsx +530 -0
  25. package/src/studio/app/chat/page.tsx.backup +390 -0
  26. package/src/studio/app/globals.css +410 -0
  27. package/src/studio/app/health/page.tsx +177 -0
  28. package/src/studio/app/layout.tsx +48 -0
  29. package/src/studio/app/page.tsx +337 -0
  30. package/src/studio/app/page.tsx.backup +346 -0
  31. package/src/studio/app/ping/page.tsx +204 -0
  32. package/src/studio/app/prompts/page.tsx +228 -0
  33. package/src/studio/app/resources/page.tsx +313 -0
  34. package/src/studio/components/EnlargeModal.tsx +116 -0
  35. package/src/studio/components/Sidebar.tsx +133 -0
  36. package/src/studio/components/ToolCard.tsx +108 -0
  37. package/src/studio/components/WidgetRenderer.tsx +99 -0
  38. package/src/studio/lib/api.ts +207 -0
  39. package/src/studio/lib/llm-service.ts +361 -0
  40. package/src/studio/lib/mcp-client.ts +168 -0
  41. package/src/studio/lib/store.ts +192 -0
  42. package/src/studio/lib/theme-provider.tsx +50 -0
  43. package/src/studio/lib/types.ts +107 -0
  44. package/src/studio/lib/widget-loader.ts +90 -0
  45. package/src/studio/middleware.ts +27 -0
  46. package/src/studio/next.config.js +16 -0
  47. package/src/studio/package-lock.json +2696 -0
  48. package/src/studio/package.json +34 -0
  49. package/src/studio/postcss.config.mjs +10 -0
  50. package/src/studio/tailwind.config.ts +67 -0
  51. package/src/studio/tsconfig.json +41 -0
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.listResources();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.listRoots();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,14 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function POST(request: NextRequest) {
5
+ try {
6
+ const client = getMcpClient();
7
+ const params = await request.json();
8
+ const result = await client.createCompletion(params);
9
+ return NextResponse.json(result);
10
+ } catch (error: any) {
11
+ return NextResponse.json({ error: error.message }, { status: 500 });
12
+ }
13
+ }
14
+
@@ -0,0 +1,41 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function POST(
5
+ request: NextRequest,
6
+ { params }: { params: { name: string } }
7
+ ) {
8
+ try {
9
+ const client = getMcpClient();
10
+ const body = await request.json();
11
+
12
+ // Extract args, JWT token, and API key from request body
13
+ const { args = {}, jwtToken, apiKey } = body;
14
+
15
+ // Inject auth tokens into tool arguments if available
16
+ const toolArgs = { ...args };
17
+
18
+ // Add _meta field for auth tokens
19
+ if (jwtToken || apiKey) {
20
+ toolArgs._meta = {
21
+ ...(toolArgs._meta || {}),
22
+ };
23
+
24
+ if (jwtToken) {
25
+ toolArgs._meta._jwt = jwtToken;
26
+ toolArgs._meta.authorization = `Bearer ${jwtToken}`;
27
+ }
28
+
29
+ if (apiKey) {
30
+ toolArgs._meta.apiKey = apiKey;
31
+ toolArgs._meta['x-api-key'] = apiKey;
32
+ }
33
+ }
34
+
35
+ const result = await client.callTool(params.name, toolArgs);
36
+ return NextResponse.json(result);
37
+ } catch (error: any) {
38
+ return NextResponse.json({ error: error.message }, { status: 500 });
39
+ }
40
+ }
41
+
@@ -0,0 +1,23 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+
8
+ // Check if connected, if not, throw error to trigger re-init
9
+ if (!client.isConnected()) {
10
+ console.error('❌ MCP client not connected in /api/tools');
11
+ return NextResponse.json({
12
+ error: 'MCP client not connected. Please refresh the page.'
13
+ }, { status: 500 });
14
+ }
15
+
16
+ const result = await client.listTools();
17
+ return NextResponse.json(result);
18
+ } catch (error: any) {
19
+ console.error('❌ Error in /api/tools:', error.message);
20
+ return NextResponse.json({ error: error.message }, { status: 500 });
21
+ }
22
+ }
23
+
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+
8
+ if (!client.isConnected()) {
9
+ return NextResponse.json({
10
+ error: 'MCP client not connected',
11
+ widgets: []
12
+ }, { status: 500 });
13
+ }
14
+
15
+ // Read widget examples from MCP server as a resource
16
+ try {
17
+ const result = await client.readResource('widget://examples');
18
+
19
+ // Parse the widget examples data
20
+ const widgetData = JSON.parse(result.contents[0].text);
21
+
22
+ return NextResponse.json({
23
+ widgets: widgetData.widgets || [],
24
+ count: widgetData.widgets?.length || 0,
25
+ loaded: widgetData.loaded || false
26
+ });
27
+ } catch (resourceError: any) {
28
+ // Widget examples resource not available
29
+ console.log('No widget examples resource available');
30
+ return NextResponse.json({
31
+ widgets: [],
32
+ count: 0,
33
+ loaded: false
34
+ });
35
+ }
36
+ } catch (error: any) {
37
+ console.error('❌ Error in /api/widget-examples:', error.message);
38
+ return NextResponse.json(
39
+ { error: 'Failed to fetch widget examples', widgets: [] },
40
+ { status: 500 }
41
+ );
42
+ }
43
+ }
44
+
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { useStudioStore } from '@/lib/store';
6
+ import { Loader2, CheckCircle2, XCircle } from 'lucide-react';
7
+
8
+ export default function OAuthCallback() {
9
+ const router = useRouter();
10
+ const searchParams = useSearchParams();
11
+ const { oauthState, setJwtToken } = useStudioStore();
12
+ const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing');
13
+ const [message, setMessage] = useState('Processing authorization...');
14
+
15
+ useEffect(() => {
16
+ const handleCallback = async () => {
17
+ try {
18
+ // Get authorization code and state from URL
19
+ const code = searchParams.get('code');
20
+ const state = searchParams.get('state');
21
+ const error = searchParams.get('error');
22
+ const errorDescription = searchParams.get('error_description');
23
+
24
+ // Check for errors
25
+ if (error) {
26
+ setStatus('error');
27
+ setMessage(`Authorization failed: ${errorDescription || error}`);
28
+ return;
29
+ }
30
+
31
+ // Validate state
32
+ const storedState = sessionStorage.getItem('oauth_state');
33
+ if (!state || state !== storedState) {
34
+ setStatus('error');
35
+ setMessage('Invalid state parameter. Possible CSRF attack.');
36
+ return;
37
+ }
38
+
39
+ // Validate code
40
+ if (!code) {
41
+ setStatus('error');
42
+ setMessage('Authorization code not received');
43
+ return;
44
+ }
45
+
46
+ // Get code verifier and client credentials
47
+ const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
48
+ if (!codeVerifier) {
49
+ setStatus('error');
50
+ setMessage('Code verifier not found. Please try again.');
51
+ return;
52
+ }
53
+
54
+ if (!oauthState.clientRegistration?.client_id) {
55
+ setStatus('error');
56
+ setMessage('Client ID not found. Please register again.');
57
+ return;
58
+ }
59
+
60
+ if (!oauthState.authServerMetadata?.token_endpoint) {
61
+ setStatus('error');
62
+ setMessage('Token endpoint not found in server metadata');
63
+ return;
64
+ }
65
+
66
+ setMessage('Exchanging authorization code for tokens...');
67
+
68
+ // Exchange code for tokens
69
+ const tokenResponse = await fetch(oauthState.authServerMetadata.token_endpoint, {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/x-www-form-urlencoded',
73
+ },
74
+ body: new URLSearchParams({
75
+ grant_type: 'authorization_code',
76
+ code,
77
+ redirect_uri: 'http://localhost:3000/auth/callback',
78
+ client_id: oauthState.clientRegistration.client_id,
79
+ ...(oauthState.clientRegistration.client_secret && {
80
+ client_secret: oauthState.clientRegistration.client_secret,
81
+ }),
82
+ code_verifier: codeVerifier,
83
+ }),
84
+ });
85
+
86
+ if (!tokenResponse.ok) {
87
+ const errorData = await tokenResponse.json().catch(() => ({}));
88
+ setStatus('error');
89
+ setMessage(`Token exchange failed: ${errorData.error_description || errorData.error || tokenResponse.statusText}`);
90
+ return;
91
+ }
92
+
93
+ const tokens = await tokenResponse.json();
94
+
95
+ // Store access token as JWT token
96
+ if (tokens.access_token) {
97
+ setJwtToken(tokens.access_token);
98
+
99
+ // Clean up session storage
100
+ sessionStorage.removeItem('oauth_code_verifier');
101
+ sessionStorage.removeItem('oauth_state');
102
+
103
+ setStatus('success');
104
+ setMessage('Authorization successful! Redirecting...');
105
+
106
+ // Redirect back to auth page after 2 seconds
107
+ setTimeout(() => {
108
+ router.push('/auth');
109
+ }, 2000);
110
+ } else {
111
+ setStatus('error');
112
+ setMessage('Access token not received');
113
+ }
114
+ } catch (error: any) {
115
+ console.error('OAuth callback error:', error);
116
+ setStatus('error');
117
+ setMessage(`Error: ${error.message || 'Unknown error occurred'}`);
118
+ }
119
+ };
120
+
121
+ handleCallback();
122
+ }, [searchParams, oauthState, setJwtToken, router]);
123
+
124
+ return (
125
+ <div className="min-h-screen bg-background flex items-center justify-center p-8">
126
+ <div className="card p-8 max-w-md w-full text-center">
127
+ {status === 'processing' && (
128
+ <>
129
+ <Loader2 className="w-16 h-16 text-primary mx-auto mb-4 animate-spin" />
130
+ <h1 className="text-2xl font-bold text-foreground mb-2">Processing...</h1>
131
+ <p className="text-muted-foreground">{message}</p>
132
+ </>
133
+ )}
134
+
135
+ {status === 'success' && (
136
+ <>
137
+ <CheckCircle2 className="w-16 h-16 text-emerald-500 mx-auto mb-4" />
138
+ <h1 className="text-2xl font-bold text-foreground mb-2">Success!</h1>
139
+ <p className="text-muted-foreground">{message}</p>
140
+ </>
141
+ )}
142
+
143
+ {status === 'error' && (
144
+ <>
145
+ <XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
146
+ <h1 className="text-2xl font-bold text-foreground mb-2">Authorization Failed</h1>
147
+ <p className="text-muted-foreground mb-4">{message}</p>
148
+ <button
149
+ onClick={() => router.push('/auth')}
150
+ className="btn btn-primary"
151
+ >
152
+ Back to Auth Page
153
+ </button>
154
+ </>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ }
160
+