@promptbook/cli 0.103.0-55 → 0.103.0-66

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 (91) hide show
  1. package/apps/agents-server/TODO.txt +5 -1
  2. package/apps/agents-server/package-lock.json +2336 -0
  3. package/apps/agents-server/package.json +9 -0
  4. package/apps/agents-server/src/app/actions.ts +3 -1
  5. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +3 -1
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +282 -0
  7. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +91 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +44 -0
  9. package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +4 -4
  10. package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +2 -2
  11. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +80 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +3 -1
  13. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +11 -1
  14. package/apps/agents-server/src/app/agents/[agentName]/api/openai/models/route.ts +93 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/chat/completions/route.ts +10 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/models/route.ts +93 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +4 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +9 -2
  19. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +7 -3
  20. package/apps/agents-server/src/app/agents/[agentName]/integration/SdkCodeTabs.tsx +31 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +271 -30
  22. package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +41 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +61 -97
  24. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +47 -157
  25. package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +70 -0
  26. package/apps/agents-server/src/app/api/openai/v1/chat/completions/route.ts +6 -0
  27. package/apps/agents-server/src/app/api/openai/v1/models/route.ts +65 -0
  28. package/apps/agents-server/src/app/docs/[docId]/page.tsx +12 -32
  29. package/apps/agents-server/src/app/docs/page.tsx +42 -17
  30. package/apps/agents-server/src/app/embed/page.tsx +2 -2
  31. package/apps/agents-server/src/app/globals.css +129 -0
  32. package/apps/agents-server/src/app/layout.tsx +16 -26
  33. package/apps/agents-server/src/app/manifest.ts +9 -4
  34. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +334 -0
  35. package/apps/agents-server/src/components/AgentProfile/AgentProfileFromSource.tsx +23 -0
  36. package/apps/agents-server/src/{app/agents/[agentName] → components/AgentProfile}/AgentQrCode.tsx +8 -1
  37. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +90 -0
  38. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +87 -0
  39. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +7 -6
  40. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +20 -0
  41. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +18 -0
  42. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +18 -0
  43. package/apps/agents-server/src/database/metadataDefaults.ts +6 -0
  44. package/apps/agents-server/src/database/migrations/2025-12-0070-chat-history-source.sql +2 -0
  45. package/apps/agents-server/src/database/schema.ts +6 -0
  46. package/apps/agents-server/src/utils/handleChatCompletion.ts +186 -14
  47. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +13 -6
  48. package/apps/agents-server/src/utils/validateApiKey.ts +128 -0
  49. package/apps/agents-server/tailwind.config.ts +1 -1
  50. package/esm/index.es.js +953 -474
  51. package/esm/index.es.js.map +1 -1
  52. package/esm/typings/src/_packages/components.index.d.ts +2 -2
  53. package/esm/typings/src/_packages/core.index.d.ts +6 -8
  54. package/esm/typings/src/_packages/types.index.d.ts +7 -1
  55. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -1
  56. package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +1 -1
  57. package/esm/typings/src/book-components/Chat/AgentChat/AgentChat.d.ts +3 -0
  58. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  59. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +5 -0
  60. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentIntegration.d.ts +52 -0
  61. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgentSeamlessIntegration.d.ts +14 -0
  62. package/esm/typings/src/book-components/icons/SendIcon.d.ts +3 -0
  63. package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +4 -0
  64. package/esm/typings/src/commitments/CLOSED/CLOSED.test.d.ts +4 -0
  65. package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +6 -0
  66. package/esm/typings/src/commitments/META_FONT/META_FONT.d.ts +42 -0
  67. package/esm/typings/src/commitments/USE/USE.d.ts +53 -0
  68. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +42 -0
  69. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.test.d.ts +1 -0
  70. package/esm/typings/src/commitments/{IMPORTANT/IMPORTANT.d.ts → USE_MCP/USE_MCP.d.ts} +16 -5
  71. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +38 -0
  72. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +6 -0
  73. package/esm/typings/src/commitments/index.d.ts +93 -1
  74. package/esm/typings/src/llm-providers/agent/Agent.d.ts +3 -1
  75. package/esm/typings/src/other/templates/getTemplatesPipelineCollection.d.ts +1 -1
  76. package/esm/typings/src/playground/playground.d.ts +3 -0
  77. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  78. package/esm/typings/src/utils/color/Color.d.ts +9 -1
  79. package/esm/typings/src/utils/color/css-colors.d.ts +1 -0
  80. package/esm/typings/src/utils/random/$generateBookBoilerplate.d.ts +6 -0
  81. package/esm/typings/src/utils/random/CzechNamePool.d.ts +7 -0
  82. package/esm/typings/src/utils/random/EnglishNamePool.d.ts +7 -0
  83. package/esm/typings/src/utils/random/NamePool.d.ts +17 -0
  84. package/esm/typings/src/utils/random/getNamePool.d.ts +10 -0
  85. package/esm/typings/src/version.d.ts +1 -1
  86. package/package.json +2 -2
  87. package/umd/index.umd.js +902 -423
  88. package/umd/index.umd.js.map +1 -1
  89. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +0 -29
  90. package/esm/typings/src/commitments/registry.d.ts +0 -68
  91. package/esm/typings/src/playground/playground1.d.ts +0 -2
@@ -128,6 +128,8 @@ export type AgentsServerDatabase = {
128
128
  userAgent: string | null;
129
129
  language: string | null;
130
130
  platform: string | null;
131
+ source: 'AGENT_PAGE_CHAT' | 'OPENAI_API_COMPATIBILITY' | null;
132
+ apiKey: string | null;
131
133
  };
132
134
  Insert: {
133
135
  id?: number;
@@ -143,6 +145,8 @@ export type AgentsServerDatabase = {
143
145
  userAgent?: string | null;
144
146
  language?: string | null;
145
147
  platform?: string | null;
148
+ source?: 'AGENT_PAGE_CHAT' | 'OPENAI_API_COMPATIBILITY' | null;
149
+ apiKey?: string | null;
146
150
  };
147
151
  Update: {
148
152
  id?: number;
@@ -158,6 +162,8 @@ export type AgentsServerDatabase = {
158
162
  userAgent?: string | null;
159
163
  language?: string | null;
160
164
  platform?: string | null;
165
+ source?: 'AGENT_PAGE_CHAT' | 'OPENAI_API_COMPATIBILITY' | null;
166
+ apiKey?: string | null;
161
167
  };
162
168
  Relationships: [];
163
169
  };
@@ -1,23 +1,53 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
1
3
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
- import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
3
- import { Agent } from '@promptbook-local/core';
4
- import { ChatMessage, ChatPromptResult, Prompt, TODO_any } from '@promptbook-local/types';
4
+ import { $provideOpenAiAssistantExecutionToolsForServer } from '@/src/tools/$provideOpenAiAssistantExecutionToolsForServer';
5
+ import { Agent, computeAgentHash, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
6
+ import { ChatMessage, ChatPromptResult, Prompt, string_book, TODO_any } from '@promptbook-local/types';
7
+ import { computeHash } from '@promptbook-local/utils';
5
8
  import { NextRequest, NextResponse } from 'next/server';
9
+ import { validateApiKey } from './validateApiKey';
6
10
 
7
11
  export async function handleChatCompletion(
8
12
  request: NextRequest,
9
- params: { agentName: string },
10
- title: string = 'API Chat Completion'
13
+ params: { agentName?: string },
14
+ title: string = 'API Chat Completion',
11
15
  ) {
12
- const { agentName } = params;
16
+ const { agentName: agentNameFromParams } = params;
13
17
 
14
- // Note: Authentication is handled by middleware
15
- // If we are here, the request is either authenticated or public access is allowed (but middleware blocks it if not)
18
+ // Validate API key explicitly (in addition to middleware)
19
+ const apiKeyValidation = await validateApiKey(request);
20
+ if (!apiKeyValidation.isValid) {
21
+ return NextResponse.json(
22
+ {
23
+ error: {
24
+ message: apiKeyValidation.error || 'Invalid API key',
25
+ type: 'authentication_error',
26
+ },
27
+ },
28
+ { status: 401 },
29
+ );
30
+ }
31
+ const apiKey = apiKeyValidation.token || null;
16
32
 
17
33
  try {
18
34
  const body = await request.json();
19
35
  const { messages, stream, model } = body;
20
36
 
37
+ const agentName = agentNameFromParams || model;
38
+
39
+ if (!agentName) {
40
+ return NextResponse.json(
41
+ {
42
+ error: {
43
+ message: 'Agent name is required. Please provide it in the URL or as the "model" parameter.',
44
+ type: 'invalid_request_error',
45
+ },
46
+ },
47
+ { status: 400 },
48
+ );
49
+ }
50
+
21
51
  if (!messages || !Array.isArray(messages) || messages.length === 0) {
22
52
  return NextResponse.json(
23
53
  {
@@ -31,7 +61,7 @@ export async function handleChatCompletion(
31
61
  }
32
62
 
33
63
  const collection = await $provideAgentCollectionForServer();
34
- let agentSource;
64
+ let agentSource: string_book;
35
65
  try {
36
66
  agentSource = await collection.getAgentSource(agentName);
37
67
  } catch (error) {
@@ -48,16 +78,51 @@ export async function handleChatCompletion(
48
78
  );
49
79
  }
50
80
 
51
- const executionTools = await $provideExecutionToolsForServer();
81
+ // Note: Handle system messages as CONTEXT
82
+ const systemMessages = messages.filter((msg: TODO_any) => msg.role === 'system');
83
+ if (systemMessages.length > 0) {
84
+ const contextString = systemMessages.map((msg: TODO_any) => `CONTEXT ${msg.content}`).join('\n');
85
+ agentSource = `${agentSource}\n\n${contextString}` as string_book;
86
+ }
87
+
88
+ const threadMessages = messages.filter((msg: TODO_any) => msg.role !== 'system');
89
+
90
+ if (threadMessages.length === 0) {
91
+ return NextResponse.json(
92
+ {
93
+ error: {
94
+ message: 'Messages array must contain at least one non-system message.',
95
+ type: 'invalid_request_error',
96
+ },
97
+ },
98
+ { status: 400 },
99
+ );
100
+ }
101
+
102
+ const openAiAssistantExecutionTools = await $provideOpenAiAssistantExecutionToolsForServer();
52
103
  const agent = new Agent({
53
104
  agentSource,
54
- executionTools,
105
+ executionTools: {
106
+ llm: openAiAssistantExecutionTools, // Note: Use the same OpenAI Assistant LLM tools as the chat route
107
+ },
55
108
  isVerbose: true, // or false
56
109
  });
57
110
 
111
+ const agentHash = computeAgentHash(agentSource);
112
+ const userAgent = request.headers.get('user-agent');
113
+ const ip =
114
+ request.headers.get('x-forwarded-for') ||
115
+ request.headers.get('x-real-ip') ||
116
+ request.headers.get('x-client-ip');
117
+
118
+ // Note: Capture language and platform information
119
+ const language = request.headers.get('accept-language');
120
+ // Simple platform extraction from userAgent parentheses content (e.g., Windows NT 10.0; Win64; x64)
121
+ const platform = userAgent ? userAgent.match(/\(([^)]+)\)/)?.[1] : undefined; // <- TODO: [🧠] Improve platform parsing
122
+
58
123
  // Prepare thread and content
59
- const lastMessage = messages[messages.length - 1];
60
- const previousMessages = messages.slice(0, -1);
124
+ const lastMessage = threadMessages[threadMessages.length - 1];
125
+ const previousMessages = threadMessages.slice(0, -1);
61
126
 
62
127
  const thread: ChatMessage[] = previousMessages.map((msg: TODO_any, index: number) => ({
63
128
  id: `msg-${index}`, // Placeholder ID
@@ -67,6 +132,30 @@ export async function handleChatCompletion(
67
132
  date: new Date(), // We don't have the real date, using current
68
133
  }));
69
134
 
135
+ // Note: Identify the user message
136
+ const userMessageContent = {
137
+ role: 'USER',
138
+ content: lastMessage.content,
139
+ };
140
+
141
+ const supabase = $provideSupabaseForServer();
142
+ await supabase.from(await $getTableName('ChatHistory')).insert({
143
+ createdAt: new Date().toISOString(),
144
+ messageHash: computeHash(userMessageContent),
145
+ previousMessageHash: null,
146
+ agentName,
147
+ agentHash,
148
+ message: userMessageContent,
149
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
150
+ url: request.url,
151
+ ip,
152
+ userAgent,
153
+ language,
154
+ platform,
155
+ source: 'OPENAI_API_COMPATIBILITY',
156
+ apiKey,
157
+ });
158
+
70
159
  const prompt: Prompt = {
71
160
  title,
72
161
  content: lastMessage.content,
@@ -86,10 +175,29 @@ export async function handleChatCompletion(
86
175
  const runId = `chatcmpl-${Math.random().toString(36).substring(2, 15)}`;
87
176
  const created = Math.floor(Date.now() / 1000);
88
177
 
178
+ // Note: Send the initial chunk with role
179
+ const initialChunkData = {
180
+ id: runId,
181
+ object: 'chat.completion.chunk',
182
+ created,
183
+ model: model || 'promptbook-agent',
184
+ choices: [
185
+ {
186
+ index: 0,
187
+ delta: {
188
+ role: 'assistant',
189
+ content: '',
190
+ },
191
+ finish_reason: null,
192
+ },
193
+ ],
194
+ };
195
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(initialChunkData)}\n\n`));
196
+
89
197
  let previousContent = '';
90
198
 
91
199
  try {
92
- await agent.callChatModelStream(prompt, (chunk: ChatPromptResult) => {
200
+ const result = await agent.callChatModelStream(prompt, (chunk: ChatPromptResult) => {
93
201
  const fullContent = chunk.content;
94
202
  const deltaContent = fullContent.substring(previousContent.length);
95
203
  previousContent = fullContent;
@@ -114,6 +222,36 @@ export async function handleChatCompletion(
114
222
  }
115
223
  });
116
224
 
225
+ // Note: Identify the agent message
226
+ const agentMessageContent = {
227
+ role: 'MODEL',
228
+ content: result.content,
229
+ };
230
+
231
+ // Record the agent message
232
+ await supabase.from(await $getTableName('ChatHistory')).insert({
233
+ createdAt: new Date().toISOString(),
234
+ messageHash: computeHash(agentMessageContent),
235
+ previousMessageHash: computeHash(userMessageContent),
236
+ agentName,
237
+ agentHash,
238
+ message: agentMessageContent,
239
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
240
+ url: request.url,
241
+ ip,
242
+ userAgent,
243
+ language,
244
+ platform,
245
+ source: 'OPENAI_API_COMPATIBILITY',
246
+ apiKey,
247
+ });
248
+
249
+ // Note: [🐱‍🚀] Save the learned data
250
+ const newAgentSource = agent.agentSource.value;
251
+ if (newAgentSource !== agentSource) {
252
+ await collection.updateAgentSource(agentName, newAgentSource);
253
+ }
254
+
117
255
  const doneChunkData = {
118
256
  id: runId,
119
257
  object: 'chat.completion.chunk',
@@ -149,6 +287,36 @@ export async function handleChatCompletion(
149
287
  } else {
150
288
  const result = await agent.callChatModel(prompt);
151
289
 
290
+ // Note: Identify the agent message
291
+ const agentMessageContent = {
292
+ role: 'MODEL',
293
+ content: result.content,
294
+ };
295
+
296
+ // Record the agent message
297
+ await supabase.from(await $getTableName('ChatHistory')).insert({
298
+ createdAt: new Date().toISOString(),
299
+ messageHash: computeHash(agentMessageContent),
300
+ previousMessageHash: computeHash(userMessageContent),
301
+ agentName,
302
+ agentHash,
303
+ message: agentMessageContent,
304
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
305
+ url: request.url,
306
+ ip,
307
+ userAgent,
308
+ language,
309
+ platform,
310
+ source: 'OPENAI_API_COMPATIBILITY',
311
+ apiKey,
312
+ });
313
+
314
+ // Note: [🐱‍🚀] Save the learned data
315
+ const newAgentSource = agent.agentSource.value;
316
+ if (newAgentSource !== agentSource) {
317
+ await collection.updateAgentSource(agentName, newAgentSource);
318
+ }
319
+
152
320
  return NextResponse.json({
153
321
  id: `chatcmpl-${Math.random().toString(36).substring(2, 15)}`,
154
322
  object: 'chat.completion',
@@ -181,3 +349,7 @@ export async function handleChatCompletion(
181
349
  );
182
350
  }
183
351
  }
352
+
353
+ /**
354
+ * TODO: [🈹] Maybe move chat thread handling here
355
+ */
@@ -35,13 +35,20 @@ export async function resolveInheritedAgentSource(
35
35
  // So we might need to parse the URL to extract agent name if it matches expected pattern.
36
36
  // For now, let's rely on fetch for external and check collection if it looks like a local reference (though FROM expects URL)
37
37
 
38
- // If the URL is valid, we try to fetch it
39
- // TODO: Handle authentication/tokens for private agents if needed
40
- const response = await fetch(parentUrl);
38
+ // If the URL is valid, we try to fetch it
39
+ // TODO: Handle authentication/tokens for private agents if needed
41
40
 
42
- if (!response.ok) {
43
- throw new Error(`Failed to fetch parent agent from ${parentUrl}: ${response.status} ${response.statusText}`);
44
- }
41
+ // TODO: [🧠] Do this logic more robustly
42
+ let fetchUrl = parentUrl;
43
+ if (!fetchUrl.endsWith('/api/book') && !fetchUrl.endsWith('.book') && !fetchUrl.endsWith('.md')) {
44
+ fetchUrl = `${fetchUrl.replace(/\/$/, '')}/api/book`;
45
+ }
46
+
47
+ const response = await fetch(fetchUrl);
48
+
49
+ if (!response.ok) {
50
+ throw new Error(`Failed to fetch parent agent from ${fetchUrl}: ${response.status} ${response.statusText}`);
51
+ }
45
52
 
46
53
  // We assume the response is the agent source text
47
54
  // TODO: Handle content negotiation or JSON responses if the server returns JSON
@@ -0,0 +1,128 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import { NextRequest } from 'next/server';
3
+ import { SERVERS, SUPABASE_TABLE_PREFIX } from '../../config';
4
+
5
+ // Note: Re-implementing normalizeTo_PascalCase to avoid importing from @promptbook-local/utils which might have Node.js dependencies
6
+ function normalizeTo_PascalCase(text: string): string {
7
+ return text
8
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => {
9
+ return word.toUpperCase();
10
+ })
11
+ .replace(/\s+/g, '');
12
+ }
13
+
14
+ export type ApiKeyValidationResult = {
15
+ isValid: boolean;
16
+ token?: string;
17
+ error?: string;
18
+ };
19
+
20
+ /**
21
+ * Validates an API key from the Authorization header.
22
+ * Returns validation result with status and optional error message.
23
+ *
24
+ * Note: This function provides explicit API key validation in addition to middleware.
25
+ * Use this when you need to verify API key validity and return specific error messages.
26
+ */
27
+ export async function validateApiKey(request: NextRequest): Promise<ApiKeyValidationResult> {
28
+ const authHeader = request.headers.get('authorization');
29
+
30
+ // If no auth header, check if user has a session cookie (logged in via web)
31
+ if (!authHeader) {
32
+ const hasSession = request.cookies.has('sessionToken');
33
+ if (hasSession) {
34
+ return { isValid: true };
35
+ }
36
+ return {
37
+ isValid: false,
38
+ error: 'Missing Authorization header. Provide a valid API key using "Authorization: Bearer ptbk_..."',
39
+ };
40
+ }
41
+
42
+ if (!authHeader.startsWith('Bearer ')) {
43
+ return {
44
+ isValid: false,
45
+ error: 'Invalid Authorization header format. Expected "Bearer <token>"',
46
+ };
47
+ }
48
+
49
+ const token = authHeader.split(' ')[1];
50
+
51
+ if (!token) {
52
+ return {
53
+ isValid: false,
54
+ error: 'No token provided in Authorization header',
55
+ };
56
+ }
57
+
58
+ if (!token.startsWith('ptbk_')) {
59
+ return {
60
+ isValid: false,
61
+ error: 'Invalid API key format. API keys must start with "ptbk_"',
62
+ };
63
+ }
64
+
65
+ // Determine the table prefix based on the host
66
+ const host = request.headers.get('host');
67
+ let tablePrefix = SUPABASE_TABLE_PREFIX;
68
+
69
+ if (host && SERVERS && SERVERS.length > 0) {
70
+ if (SERVERS.some((server) => server === host)) {
71
+ let serverName = host;
72
+ serverName = serverName.replace(/\.ptbk\.io$/, '');
73
+ serverName = normalizeTo_PascalCase(serverName);
74
+ tablePrefix = `server_${serverName}_`;
75
+ }
76
+ }
77
+
78
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
79
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
80
+
81
+ if (!supabaseUrl || !supabaseKey) {
82
+ console.error('Supabase configuration missing for API key validation');
83
+ return {
84
+ isValid: false,
85
+ error: 'Server configuration error',
86
+ };
87
+ }
88
+
89
+ try {
90
+ const supabase = createClient(supabaseUrl, supabaseKey, {
91
+ auth: {
92
+ persistSession: false,
93
+ autoRefreshToken: false,
94
+ },
95
+ });
96
+
97
+ const { data, error } = await supabase
98
+ .from(`${tablePrefix}ApiTokens`)
99
+ .select('id, isRevoked')
100
+ .eq('token', token)
101
+ .single();
102
+
103
+ if (error || !data) {
104
+ return {
105
+ isValid: false,
106
+ error: 'Invalid API key',
107
+ };
108
+ }
109
+
110
+ if (data.isRevoked) {
111
+ return {
112
+ isValid: false,
113
+ error: 'API key has been revoked',
114
+ };
115
+ }
116
+
117
+ return {
118
+ isValid: true,
119
+ token,
120
+ };
121
+ } catch (error) {
122
+ console.error('Error validating API key:', error);
123
+ return {
124
+ isValid: false,
125
+ error: 'Error validating API key',
126
+ };
127
+ }
128
+ }
@@ -20,7 +20,7 @@ const config: Config = {
20
20
  },
21
21
  },
22
22
  },
23
- plugins: [],
23
+ plugins: [require('@tailwindcss/typography')],
24
24
  };
25
25
 
26
26
  export default config;