@promptbook/cli 0.112.0-101 → 0.112.0-102

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 (78) hide show
  1. package/apps/agents-server/README.md +6 -0
  2. package/apps/agents-server/package.json +1 -1
  3. package/apps/agents-server/scripts/prerender-homepage.js +76 -1
  4. package/apps/agents-server/src/app/actions.ts +0 -6
  5. package/apps/agents-server/src/app/admin/about/page.tsx +1 -1
  6. package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +365 -0
  7. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +3 -3
  8. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +12 -3
  9. package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +1 -1
  10. package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +21 -14
  11. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatPageLayout.tsx +2 -2
  12. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +11 -7
  13. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +27 -123
  14. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +33 -125
  15. package/apps/agents-server/src/app/api/auth/login/route.ts +0 -10
  16. package/apps/agents-server/src/app/api/auth/shibboleth/acs/route.ts +77 -57
  17. package/apps/agents-server/src/app/api/auth/shibboleth/login/route.ts +57 -33
  18. package/apps/agents-server/src/app/api/auth/shibboleth/metadata/route.ts +4 -29
  19. package/apps/agents-server/src/app/api/auth/shibboleth/status/route.ts +17 -0
  20. package/apps/agents-server/src/app/api/upload/route.ts +230 -18
  21. package/apps/agents-server/src/app/api/users/[username]/route.ts +1 -1
  22. package/apps/agents-server/src/app/api/users/route.ts +5 -5
  23. package/apps/agents-server/src/app/dashboard/page.tsx +1 -1
  24. package/apps/agents-server/src/app/docs/[docId]/page.tsx +1 -1
  25. package/apps/agents-server/src/app/docs/page.tsx +1 -1
  26. package/apps/agents-server/src/app/globals.css +100 -0
  27. package/apps/agents-server/src/app/layout.tsx +7 -0
  28. package/apps/agents-server/src/app/recycle-bin/page.tsx +1 -1
  29. package/apps/agents-server/src/app/system/settings/KeybindingsSettingsClient.tsx +13 -7
  30. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +29 -1
  31. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +3 -3
  32. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +8 -2
  33. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +4 -4
  34. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +9 -9
  35. package/apps/agents-server/src/components/Footer/Footer.tsx +7 -7
  36. package/apps/agents-server/src/components/Header/Header.tsx +24 -4
  37. package/apps/agents-server/src/components/Header/HeaderTypes.ts +6 -0
  38. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +51 -1
  39. package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
  40. package/apps/agents-server/src/components/Homepage/Section.tsx +3 -1
  41. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +12 -1
  42. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +100 -149
  43. package/apps/agents-server/src/components/Skeleton/ConsolePageLoadingSkeleton.tsx +1 -1
  44. package/apps/agents-server/src/components/Skeleton/DocumentationRouteLoadingSkeleton.tsx +1 -1
  45. package/apps/agents-server/src/components/Skeleton/HomepageLoadingSkeleton.tsx +1 -1
  46. package/apps/agents-server/src/components/UsersList/UsersList.tsx +20 -4
  47. package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +3 -0
  48. package/apps/agents-server/src/constants/shibbolethAuth.ts +139 -0
  49. package/apps/agents-server/src/database/metadataDefaults.ts +54 -80
  50. package/apps/agents-server/src/database/migrate.ts +30 -1
  51. package/apps/agents-server/src/database/migrations/2026-06-0100-shibboleth-auth.sql +136 -0
  52. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +88 -36
  53. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +4 -2
  54. package/apps/agents-server/src/languages/translations/czech.yaml +4 -2
  55. package/apps/agents-server/src/languages/translations/english.yaml +5 -3
  56. package/apps/agents-server/src/tools/$provideCdnForServer.ts +69 -23
  57. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +54 -6
  58. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +4 -6
  59. package/apps/agents-server/src/utils/cdn/resolveCdnStorageProvider.ts +40 -0
  60. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +11 -0
  61. package/apps/agents-server/src/utils/createAdminTerminalRouteHandlers.ts +264 -0
  62. package/apps/agents-server/src/utils/shareTargetPayloads.ts +11 -10
  63. package/apps/agents-server/src/utils/shibbolethAuthentication.ts +729 -621
  64. package/apps/agents-server/src/utils/upload/createBookEditorUploadHandler.ts +137 -19
  65. package/esm/index.es.js +1 -1
  66. package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  67. package/esm/src/version.d.ts +1 -1
  68. package/package.json +2 -2
  69. package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +65 -4
  70. package/src/other/templates/getTemplatesPipelineCollection.ts +788 -719
  71. package/src/version.ts +2 -2
  72. package/src/versions.txt +1 -0
  73. package/umd/index.umd.js +1 -1
  74. package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  75. package/umd/src/version.d.ts +1 -1
  76. package/apps/agents-server/src/app/api/auth/methods/route.ts +0 -44
  77. package/apps/agents-server/src/constants/authenticationMethods.ts +0 -74
  78. package/apps/agents-server/src/constants/shibbolethAuthentication.ts +0 -107
@@ -1,46 +1,70 @@
1
- import { NextRequest, NextResponse } from 'next/server';
1
+ import { NextResponse } from 'next/server';
2
2
  import {
3
3
  createShibbolethSamlClient,
4
- loadShibbolethConfiguration,
5
- logShibbolethAuthenticationEvent,
6
- resolveSafeShibbolethRelayState,
7
- ShibbolethConfigurationError,
4
+ getShibbolethRequestDetails,
5
+ recordShibbolethAuthenticationAttempt,
6
+ resolveShibbolethAuthenticationConfiguration,
7
+ sanitizeShibbolethRelayState,
8
8
  } from '../../../../../utils/shibbolethAuthentication';
9
9
 
10
- /**
11
- * Forces this route to run in Node.js because node-saml depends on Node crypto/zlib APIs.
12
- */
13
- export const runtime = 'nodejs';
14
-
15
10
  /**
16
11
  * Handles get.
17
12
  */
18
- export async function GET(request: NextRequest) {
19
- const configuration = await loadShibbolethConfiguration(request);
13
+ export async function GET(request: Request) {
14
+ const requestDetails = getShibbolethRequestDetails(request);
15
+ const relayState = sanitizeShibbolethRelayState(new URL(request.url).searchParams.get('returnTo'));
20
16
 
21
- if (!configuration.isEnabled) {
22
- logShibbolethAuthenticationEvent('login_rejected_disabled');
23
- return NextResponse.json({ error: 'Shibboleth authentication is not enabled.' }, { status: 404 });
24
- }
25
-
26
- if (!configuration.isConfigured) {
27
- const error = new ShibbolethConfigurationError(configuration.missingConfiguration);
28
- logShibbolethAuthenticationEvent('login_rejected_incomplete_configuration', {
29
- missingConfiguration: configuration.missingConfiguration,
17
+ try {
18
+ const configuration = await resolveShibbolethAuthenticationConfiguration({
19
+ requestUrl: request.url,
20
+ isIdentityProviderMetadataValidationEnabled: true,
30
21
  });
31
- return NextResponse.json({ error: error.message }, { status: 503 });
32
- }
33
22
 
34
- const relayState = resolveSafeShibbolethRelayState(request.nextUrl.searchParams.get('redirectTo'));
35
- const saml = createShibbolethSamlClient(configuration);
36
- const redirectUrl = await saml.getAuthorizeUrlAsync(relayState, undefined, {});
23
+ if (!configuration.isActive) {
24
+ await recordShibbolethAuthenticationAttempt({
25
+ stage: 'LOGIN_REQUEST',
26
+ status: 'REJECTED',
27
+ requestDetails,
28
+ relayState,
29
+ errorMessage: 'Shibboleth authentication is not active.',
30
+ });
31
+ return NextResponse.json({ error: 'Shibboleth authentication is not active.' }, { status: 404 });
32
+ }
37
33
 
38
- logShibbolethAuthenticationEvent('login_redirect_created', {
39
- issuer: configuration.issuer,
40
- callbackUrl: configuration.callbackUrl,
41
- entryPoint: configuration.entryPoint,
42
- relayState,
43
- });
34
+ if (!configuration.isConfigured) {
35
+ const errorMessage = configuration.errors.join(' ');
36
+ await recordShibbolethAuthenticationAttempt({
37
+ stage: 'LOGIN_REQUEST',
38
+ status: 'FAILED',
39
+ requestDetails,
40
+ relayState,
41
+ errorMessage,
42
+ });
43
+ return NextResponse.json({ error: errorMessage }, { status: 503 });
44
+ }
44
45
 
45
- return NextResponse.redirect(redirectUrl);
46
+ const saml = createShibbolethSamlClient(configuration);
47
+ const authorizeUrl = await saml.getAuthorizeUrlAsync(relayState, undefined, {});
48
+
49
+ await recordShibbolethAuthenticationAttempt({
50
+ stage: 'LOGIN_REQUEST',
51
+ status: 'REDIRECTED',
52
+ requestDetails,
53
+ relayState,
54
+ });
55
+
56
+ return NextResponse.redirect(authorizeUrl);
57
+ } catch (error) {
58
+ const errorMessage = error instanceof Error ? error.message : 'Failed to start Shibboleth authentication.';
59
+ await recordShibbolethAuthenticationAttempt({
60
+ stage: 'LOGIN_REQUEST',
61
+ status: 'FAILED',
62
+ requestDetails,
63
+ relayState,
64
+ errorMessage,
65
+ });
66
+
67
+ console.error('Shibboleth login start error:', error);
68
+ return NextResponse.json({ error: errorMessage }, { status: 500 });
69
+ }
46
70
  }
@@ -1,40 +1,15 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import {
3
- createShibbolethServiceProviderMetadata,
4
- loadShibbolethConfiguration,
5
- logShibbolethAuthenticationEvent,
6
- } from '../../../../../utils/shibbolethAuthentication';
7
-
8
- /**
9
- * Forces this route to run in Node.js because node-saml metadata generation depends on Node APIs.
10
- */
11
- export const runtime = 'nodejs';
1
+ import { NextResponse } from 'next/server';
2
+ import { createShibbolethServiceProviderMetadataXml } from '../../../../../utils/shibbolethAuthentication';
12
3
 
13
4
  /**
14
5
  * Handles get.
15
6
  */
16
- export async function GET(request: NextRequest) {
17
- const configuration = await loadShibbolethConfiguration(request);
18
-
19
- if (!configuration.isEnabled || !configuration.issuer || !configuration.callbackUrl) {
20
- logShibbolethAuthenticationEvent('metadata_rejected_disabled');
21
- return NextResponse.json({ error: 'Shibboleth authentication is not enabled.' }, { status: 404 });
22
- }
23
-
24
- const metadataXml = createShibbolethServiceProviderMetadata({
25
- issuer: configuration.issuer,
26
- callbackUrl: configuration.callbackUrl,
27
- });
28
-
29
- logShibbolethAuthenticationEvent('metadata_served', {
30
- issuer: configuration.issuer,
31
- callbackUrl: configuration.callbackUrl,
32
- });
7
+ export async function GET(request: Request) {
8
+ const metadataXml = await createShibbolethServiceProviderMetadataXml(request.url);
33
9
 
34
10
  return new NextResponse(metadataXml, {
35
11
  headers: {
36
12
  'Content-Type': 'application/samlmetadata+xml; charset=utf-8',
37
- 'Cache-Control': 'no-store',
38
13
  },
39
14
  });
40
15
  }
@@ -0,0 +1,17 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { resolveShibbolethAuthenticationConfiguration } from '../../../../../utils/shibbolethAuthentication';
3
+
4
+ /**
5
+ * Handles get.
6
+ */
7
+ export async function GET(request: Request) {
8
+ const configuration = await resolveShibbolethAuthenticationConfiguration({
9
+ requestUrl: request.url,
10
+ isIdentityProviderMetadataValidationEnabled: false,
11
+ });
12
+
13
+ return NextResponse.json({
14
+ isActive: configuration.isActive,
15
+ isConfigured: configuration.isConfigured,
16
+ });
17
+ }
@@ -1,10 +1,15 @@
1
1
  import { $getTableName } from '@/src/database/$getTableName';
2
2
  import { $provideSupabase } from '@/src/database/$provideSupabase';
3
+ import { $provideUntrackedCdnForServer } from '@/src/tools/$provideCdnForServer';
4
+ import { getSafeCdnPath } from '@/src/utils/cdn/utils/getSafeCdnPath';
3
5
  import { serializeError } from '@promptbook-local/utils';
4
6
  import type { PostgrestSingleResponse, SupabaseClient } from '@supabase/supabase-js';
5
7
  import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
6
8
  import { NextRequest, NextResponse } from 'next/server';
7
9
  import { assertsError } from '../../../../../../src/errors/assertsError';
10
+ import { DatabaseError } from '../../../../../../src/errors/DatabaseError';
11
+ import { LimitReachedError } from '../../../../../../src/errors/LimitReachedError';
12
+ import { NotAllowed } from '../../../../../../src/errors/NotAllowed';
8
13
  import { getUserIdFromRequest } from '../../../../src/utils/getUserIdFromRequest';
9
14
  import type { AgentsServerDatabase } from '../../../database/schema';
10
15
  import { FILE_SECURITY_CHECKERS } from '../../../file-security-checkers';
@@ -48,6 +53,41 @@ const DEFAULT_UPLOAD_CONTENT_TYPE = 'application/octet-stream';
48
53
  */
49
54
  const MIME_TYPE_PATTERN = /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i;
50
55
 
56
+ /**
57
+ * Form-data field containing the uploaded file for S3-backed server uploads.
58
+ *
59
+ * @private
60
+ */
61
+ const SERVER_ROUTED_UPLOAD_FILE_FIELD = 'file';
62
+
63
+ /**
64
+ * Form-data field containing the requested CDN object key for S3-backed server uploads.
65
+ *
66
+ * @private
67
+ */
68
+ const SERVER_ROUTED_UPLOAD_PATHNAME_FIELD = 'pathname';
69
+
70
+ /**
71
+ * Form-data field containing upload purpose for S3-backed server uploads.
72
+ *
73
+ * @private
74
+ */
75
+ const SERVER_ROUTED_UPLOAD_PURPOSE_FIELD = 'purpose';
76
+
77
+ /**
78
+ * Form-data field containing content type for S3-backed server uploads.
79
+ *
80
+ * @private
81
+ */
82
+ const SERVER_ROUTED_UPLOAD_CONTENT_TYPE_FIELD = 'contentType';
83
+
84
+ /**
85
+ * Multipart content type prefix used by server-routed S3 uploads.
86
+ *
87
+ * @private
88
+ */
89
+ const MULTIPART_FORM_DATA_CONTENT_TYPE = 'multipart/form-data';
90
+
51
91
  /**
52
92
  * Safely parses a JSON string into an object; returns empty object on invalid payload.
53
93
  *
@@ -119,11 +159,200 @@ function resolveUploadClientPayload(clientPayload: string | null | undefined): {
119
159
  };
120
160
  }
121
161
 
162
+ /**
163
+ * Checks whether the upload request uses the server-routed multipart protocol.
164
+ *
165
+ * @private
166
+ */
167
+ function isServerRoutedUploadRequest(request: NextRequest): boolean {
168
+ return (request.headers.get('content-type') || '').toLowerCase().includes(MULTIPART_FORM_DATA_CONTENT_TYPE);
169
+ }
170
+
171
+ /**
172
+ * Reads a string field from multipart form data.
173
+ *
174
+ * @private
175
+ */
176
+ function getFormDataString(formData: FormData, fieldName: string): string | null {
177
+ const value = formData.get(fieldName);
178
+
179
+ return typeof value === 'string' ? value : null;
180
+ }
181
+
182
+ /**
183
+ * Normalizes a client-provided CDN pathname into a relative object key.
184
+ *
185
+ * @private
186
+ */
187
+ function normalizeServerUploadPathname(value: unknown, fallbackFilename: string): string {
188
+ const rawPathname = typeof value === 'string' ? value : fallbackFilename;
189
+ const normalizedPathname = rawPathname
190
+ .trim()
191
+ .replace(/\\/g, '/')
192
+ .replace(/^\/+/g, '')
193
+ .replace(/\/+/g, '/');
194
+
195
+ if (!normalizedPathname || normalizedPathname.split('/').includes('..')) {
196
+ throw new NotAllowed('Upload pathname must be a safe relative CDN path.');
197
+ }
198
+
199
+ return getSafeCdnPath({ pathname: normalizedPathname });
200
+ }
201
+
202
+ /**
203
+ * Runs configured file-security checks for an uploaded public URL.
204
+ *
205
+ * @private
206
+ */
207
+ async function checkUploadedFileSecurity(storageUrl: string): Promise<Record<string, unknown>> {
208
+ const securityResults: Record<string, unknown> = {};
209
+
210
+ for (const checkerId in FILE_SECURITY_CHECKERS) {
211
+ try {
212
+ const checker = FILE_SECURITY_CHECKERS[checkerId]!;
213
+ console.info(`🛡️ Checking file security with ${checker.title} (${storageUrl})...`);
214
+ const result = await checker.checkFile(storageUrl);
215
+ securityResults[checkerId] = result;
216
+ console.info(`🛡️ Security check result from ${checker.title}:`, result.status);
217
+ } catch (error) {
218
+ console.error(`🛡️ Security check failed for ${checkerId}:`, error);
219
+ securityResults[checkerId] = {
220
+ isSafe: false,
221
+ status: 'ERROR',
222
+ confidence: 0,
223
+ message: error instanceof Error ? error.message : String(error),
224
+ };
225
+ }
226
+ }
227
+
228
+ return securityResults;
229
+ }
230
+
231
+ /**
232
+ * Inserts a completed file row for a server-routed upload.
233
+ *
234
+ * @private
235
+ */
236
+ async function insertCompletedUploadFileRecord(options: {
237
+ supabase: SupabaseClient<AgentsServerDatabase>;
238
+ userId: number | null;
239
+ fileName: string;
240
+ fileSize: number;
241
+ fileType: string;
242
+ storageUrl: string;
243
+ purpose: string;
244
+ securityResult: AgentsServerDatabase['public']['Tables']['File']['Insert']['securityResult'];
245
+ }): Promise<number | null> {
246
+ const {
247
+ supabase,
248
+ userId,
249
+ fileName,
250
+ fileSize,
251
+ fileType,
252
+ storageUrl,
253
+ purpose,
254
+ securityResult,
255
+ } = options;
256
+ const { data, error }: PostgrestSingleResponse<Pick<AgentsServerDatabase['public']['Tables']['File']['Row'], 'id'>> =
257
+ await supabase
258
+ .from(await $getTableName('File'))
259
+ .insert({
260
+ userId,
261
+ fileName,
262
+ fileSize,
263
+ fileType,
264
+ storageUrl,
265
+ shortUrl: null,
266
+ purpose,
267
+ status: 'COMPLETED',
268
+ securityResult,
269
+ })
270
+ .select('id')
271
+ .single();
272
+
273
+ if (error) {
274
+ throw new DatabaseError(`Failed to create completed file record: ${error.message}`);
275
+ }
276
+
277
+ return data?.id ?? null;
278
+ }
279
+
280
+ /**
281
+ * Handles direct multipart uploads for S3-compatible storage backends.
282
+ *
283
+ * @private
284
+ */
285
+ async function handleServerRoutedUpload(request: NextRequest): Promise<NextResponse> {
286
+ const formData = await request.formData();
287
+ const uploadFile = formData.get(SERVER_ROUTED_UPLOAD_FILE_FIELD);
288
+
289
+ if (!(uploadFile instanceof File)) {
290
+ throw new NotAllowed('Upload request must contain a file.');
291
+ }
292
+
293
+ const userId = await getUserIdFromRequest(request);
294
+ const purpose = normalizeUploadPurpose(getFormDataString(formData, SERVER_ROUTED_UPLOAD_PURPOSE_FIELD));
295
+ const contentType = normalizeUploadContentType(
296
+ getFormDataString(formData, SERVER_ROUTED_UPLOAD_CONTENT_TYPE_FIELD) || uploadFile.type,
297
+ );
298
+ const pathname = normalizeServerUploadPathname(
299
+ getFormDataString(formData, SERVER_ROUTED_UPLOAD_PATHNAME_FIELD),
300
+ uploadFile.name,
301
+ );
302
+ const maxFileSize = await getMaxFileUploadSizeBytes();
303
+
304
+ if (uploadFile.size > maxFileSize) {
305
+ throw new LimitReachedError(`Uploaded file exceeds the configured maximum size of ${maxFileSize} bytes.`);
306
+ }
307
+
308
+ const buffer = Buffer.from(await uploadFile.arrayBuffer());
309
+ if (buffer.byteLength > maxFileSize) {
310
+ throw new LimitReachedError(`Uploaded file exceeds the configured maximum size of ${maxFileSize} bytes.`);
311
+ }
312
+
313
+ const cdn = $provideUntrackedCdnForServer();
314
+ await cdn.setItem(pathname, {
315
+ type: contentType,
316
+ data: buffer,
317
+ userId: userId || undefined,
318
+ purpose,
319
+ fileSize: buffer.byteLength,
320
+ });
321
+
322
+ const storageUrl = cdn.getItemUrl(pathname).href;
323
+ const securityResults = await checkUploadedFileSecurity(storageUrl);
324
+ const securityResultForDatabase =
325
+ securityResults as AgentsServerDatabase['public']['Tables']['File']['Insert']['securityResult'];
326
+ const supabase: SupabaseClient<AgentsServerDatabase> = $provideSupabase();
327
+ const fileId = await insertCompletedUploadFileRecord({
328
+ supabase,
329
+ userId: userId || null,
330
+ fileName: pathname,
331
+ fileSize: buffer.byteLength,
332
+ fileType: contentType,
333
+ storageUrl,
334
+ purpose,
335
+ securityResult: securityResultForDatabase,
336
+ });
337
+
338
+ return NextResponse.json({
339
+ url: storageUrl,
340
+ pathname,
341
+ fileId,
342
+ contentType,
343
+ size: buffer.byteLength,
344
+ });
345
+ }
346
+
122
347
  /**
123
348
  * Handles post.
124
349
  */
125
350
  export async function POST(request: NextRequest) {
126
351
  try {
352
+ if (isServerRoutedUploadRequest(request)) {
353
+ return await handleServerRoutedUpload(request);
354
+ }
355
+
127
356
  const body = (await request.json()) as HandleUploadBody;
128
357
  const userId = await getUserIdFromRequest(request);
129
358
  const supabase: SupabaseClient<AgentsServerDatabase> = $provideSupabase();
@@ -209,26 +438,9 @@ export async function POST(request: NextRequest) {
209
438
  const supabase = $provideSupabase();
210
439
 
211
440
  // Security checks
212
- const securityResults: Record<string, unknown> = {};
441
+ const securityResults = await checkUploadedFileSecurity(blob.url);
213
442
  const securityResultForDatabase =
214
443
  securityResults as AgentsServerDatabase['public']['Tables']['File']['Update']['securityResult'];
215
- for (const checkerId in FILE_SECURITY_CHECKERS) {
216
- try {
217
- const checker = FILE_SECURITY_CHECKERS[checkerId]!;
218
- console.info(`🛡️ Checking file security with ${checker.title} (${blob.url})...`);
219
- const result = await checker.checkFile(blob.url);
220
- securityResults[checkerId] = result;
221
- console.info(`🛡️ Security check result from ${checker.title}:`, result.status);
222
- } catch (error) {
223
- console.error(`🛡️ Security check failed for ${checkerId}:`, error);
224
- securityResults[checkerId] = {
225
- isSafe: false,
226
- status: 'ERROR',
227
- confidence: 0,
228
- message: error instanceof Error ? error.message : String(error),
229
- };
230
- }
231
- }
232
444
 
233
445
  if (fileId) {
234
446
  // Update the existing record by ID
@@ -34,7 +34,7 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ us
34
34
  .from(await $getTableName('User'))
35
35
  .update(updates)
36
36
  .eq('username', usernameParam)
37
- .select('id, username, createdAt, updatedAt, isAdmin')
37
+ .select('*')
38
38
  .single();
39
39
 
40
40
  if (error) {
@@ -16,7 +16,7 @@ export async function GET() {
16
16
  const supabase = $provideSupabaseForServer();
17
17
  const { data: users, error } = await supabase
18
18
  .from(await $getTableName('User'))
19
- .select('id, username, createdAt, updatedAt, isAdmin')
19
+ .select('*')
20
20
  .order('username');
21
21
 
22
22
  if (error) {
@@ -58,18 +58,18 @@ export async function POST(request: Request) {
58
58
  createdAt: new Date().toISOString(),
59
59
  updatedAt: new Date().toISOString(),
60
60
  })
61
- .select('id, username, createdAt, updatedAt, isAdmin')
61
+ .select('*')
62
62
  .single();
63
63
 
64
64
  if (error) {
65
- if (error.code === '23505') { // unique_violation
66
- return NextResponse.json({ error: 'Username already exists' }, { status: 409 });
65
+ if (error.code === '23505') {
66
+ // unique_violation
67
+ return NextResponse.json({ error: 'Username already exists' }, { status: 409 });
67
68
  }
68
69
  throw error;
69
70
  }
70
71
 
71
72
  return NextResponse.json(newUser);
72
-
73
73
  } catch (error) {
74
74
  console.error('Create user error:', error);
75
75
  const passwordValidationMessage = getPasswordValidationMessage(error);
@@ -56,7 +56,7 @@ export default async function DashboardPage() {
56
56
  const host = (await headers()).get('host') || 'unknown';
57
57
 
58
58
  return (
59
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
59
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
60
60
  <div className="container mx-auto px-4 py-16">
61
61
  <HomepagePrimarySections
62
62
  agents={agents}
@@ -32,7 +32,7 @@ export default async function DocPage(props: DocPageProps) {
32
32
  const { primary, aliases } = group;
33
33
 
34
34
  return (
35
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
35
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
36
36
  <div className="container mx-auto px-4 py-16">
37
37
  <DocsToolbar />
38
38
  <PrintHeader title={primary.type} />
@@ -19,7 +19,7 @@ export default function DocsPage() {
19
19
  const groupedCommitments = getVisibleCommitmentDefinitions();
20
20
 
21
21
  return (
22
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
22
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
23
23
  <div className="container mx-auto px-4 py-16">
24
24
  <DocsToolbar />
25
25
  <PrintHeader title="Full Documentation" />
@@ -640,6 +640,26 @@ html.dark .agent-chat-route-surface .agent-chat-sidebar-toggle.agent-chat-solid-
640
640
  box-shadow: 0 20px 38px rgba(2, 6, 23, 0.6), inset 1px 0 0 rgba(191, 219, 254, 0.18);
641
641
  }
642
642
 
643
+ html.dark .agent-chat-sidebar-item-active {
644
+ border-color: rgba(125, 211, 252, 0.52) !important;
645
+ background: linear-gradient(145deg, rgba(8, 47, 73, 0.88), rgba(15, 23, 42, 0.96)) !important;
646
+ color: #e0f2fe !important;
647
+ box-shadow: 0 14px 30px rgba(2, 6, 23, 0.4), inset 0 0 0 1px rgba(125, 211, 252, 0.16) !important;
648
+ }
649
+
650
+ html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-preview-card {
651
+ border-color: rgba(125, 211, 252, 0.34) !important;
652
+ background: rgba(15, 23, 42, 0.82) !important;
653
+ }
654
+
655
+ html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-title {
656
+ color: #f8fafc !important;
657
+ }
658
+
659
+ html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-preview {
660
+ color: #bae6fd !important;
661
+ }
662
+
643
663
  .agent-chat-route-surface .chat-scrollToBottom.chat-scrollToBottom + [role='status'] {
644
664
  top: auto;
645
665
  right: auto;
@@ -807,6 +827,36 @@ html.dark .bg-slate-100\/80 {
807
827
  background-color: #111827 !important;
808
828
  }
809
829
 
830
+ html.dark .bg-gray-200,
831
+ html.dark .bg-slate-200 {
832
+ background-color: rgba(30, 41, 59, 0.92) !important;
833
+ }
834
+
835
+ html.dark .bg-gray-300,
836
+ html.dark .bg-slate-300 {
837
+ background-color: rgba(51, 65, 85, 0.94) !important;
838
+ }
839
+
840
+ html.dark .bg-blue-50,
841
+ html.dark .bg-blue-100 {
842
+ background-color: rgba(30, 64, 175, 0.24) !important;
843
+ }
844
+
845
+ html.dark .bg-amber-50,
846
+ html.dark .bg-amber-100 {
847
+ background-color: rgba(120, 53, 15, 0.28) !important;
848
+ }
849
+
850
+ html.dark .bg-red-50,
851
+ html.dark .bg-red-100 {
852
+ background-color: rgba(127, 29, 29, 0.3) !important;
853
+ }
854
+
855
+ html.dark .bg-emerald-50,
856
+ html.dark .bg-emerald-100 {
857
+ background-color: rgba(6, 78, 59, 0.28) !important;
858
+ }
859
+
810
860
  html.dark .border-gray-100,
811
861
  html.dark .border-gray-200,
812
862
  html.dark .border-slate-100,
@@ -844,9 +894,34 @@ html.dark .text-slate-400 {
844
894
  color: #64748b !important;
845
895
  }
846
896
 
897
+ html.dark .text-blue-600,
898
+ html.dark .text-blue-700,
899
+ html.dark .hover\:text-blue-700:hover {
900
+ color: #93c5fd !important;
901
+ }
902
+
903
+ html.dark .text-amber-700,
904
+ html.dark .text-amber-800,
905
+ html.dark .text-amber-900 {
906
+ color: #fcd34d !important;
907
+ }
908
+
909
+ html.dark .text-red-600,
910
+ html.dark .text-red-700,
911
+ html.dark .text-red-800,
912
+ html.dark .text-red-900 {
913
+ color: #fca5a5 !important;
914
+ }
915
+
916
+ html.dark .text-emerald-600,
917
+ html.dark .text-emerald-700 {
918
+ color: #86efac !important;
919
+ }
920
+
847
921
  html.dark .hover\:bg-white:hover,
848
922
  html.dark .hover\:bg-gray-50:hover,
849
923
  html.dark .hover\:bg-gray-100:hover,
924
+ html.dark .hover\:bg-gray-300:hover,
850
925
  html.dark .hover\:bg-slate-50:hover,
851
926
  html.dark .hover\:bg-slate-100:hover {
852
927
  background-color: #0f172a !important;
@@ -864,6 +939,31 @@ html.dark .hover\:border-slate-400:hover {
864
939
  border-color: rgba(125, 211, 252, 0.45) !important;
865
940
  }
866
941
 
942
+ html.dark input:not([type='checkbox']):not([type='radio']):not([type='range']),
943
+ html.dark textarea,
944
+ html.dark select {
945
+ background-color: #0f172a;
946
+ color: #e2e8f0;
947
+ border-color: rgba(71, 85, 105, 0.92);
948
+ }
949
+
950
+ html.dark input:not([type='checkbox']):not([type='radio']):not([type='range'])::placeholder,
951
+ html.dark textarea::placeholder {
952
+ color: #94a3b8;
953
+ }
954
+
955
+ html.dark input:not([type='checkbox']):not([type='radio']):not([type='range']):disabled,
956
+ html.dark textarea:disabled,
957
+ html.dark select:disabled {
958
+ background-color: #111827;
959
+ color: #64748b;
960
+ }
961
+
962
+ html.dark option {
963
+ background-color: #0f172a;
964
+ color: #e2e8f0;
965
+ }
966
+
867
967
  html.dark .prose {
868
968
  color: #cbd5e1;
869
969
  }