@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
@@ -46,6 +46,10 @@ import {
46
46
  CONTROL_PANEL_OPTION_AVAILABILITY_METADATA_KEYS,
47
47
  getControlPanelOptionAvailability,
48
48
  } from '../utils/getControlPanelOptionAvailability';
49
+ import {
50
+ SHIBBOLETH_AUTHENTICATION_METADATA_KEYS,
51
+ resolveShibbolethAuthenticationMenuStatus,
52
+ } from '../constants/shibbolethAuth';
49
53
  import '@prisma/studio-core/ui/index.css';
50
54
  import './globals.css';
51
55
 
@@ -257,6 +261,7 @@ export default async function RootLayout({
257
261
  DEFAULT_THEME_METADATA_KEY,
258
262
  SERVER_LANGUAGE_METADATA_KEY,
259
263
  IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY,
264
+ ...SHIBBOLETH_AUTHENTICATION_METADATA_KEYS,
260
265
  ...CONTROL_PANEL_OPTION_AVAILABILITY_METADATA_KEYS,
261
266
  ]);
262
267
  const currentUserPromise = getCurrentUser();
@@ -380,6 +385,7 @@ export default async function RootLayout({
380
385
  metadata: layoutMetadata,
381
386
  isPushNotificationsConfigured: Boolean(webPushPublicKey),
382
387
  });
388
+ const shibbolethAuthenticationStatus = resolveShibbolethAuthenticationMenuStatus(layoutMetadata);
383
389
  const themeModeBootstrapScript = createThemeModeBootstrapScript(defaultThemeMode);
384
390
 
385
391
  return (
@@ -414,6 +420,7 @@ export default async function RootLayout({
414
420
  defaultIsNotificationsOn={defaultIsNotificationsOn}
415
421
  isExperimental={isExperimental}
416
422
  feedbackMode={feedbackMode}
423
+ shibbolethAuthenticationStatus={shibbolethAuthenticationStatus}
417
424
  isExperimentalPwaAppEnabled={isExperimentalPwaAppEnabled}
418
425
  controlPanelOptionAvailability={controlPanelOptionAvailability}
419
426
  defaultServerLanguage={serverLanguage}
@@ -22,7 +22,7 @@ export default async function RecycleBinPage() {
22
22
  const canRestore = Boolean(currentUser);
23
23
 
24
24
  return (
25
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
25
+ <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">
26
26
  <div className="container mx-auto px-4 py-16">
27
27
  <header className="flex items-center gap-4 mb-8">
28
28
  <div className="bg-red-100 p-3 rounded-full">
@@ -14,19 +14,25 @@ export function KeybindingsSettingsClient() {
14
14
  return (
15
15
  <div className="space-y-8">
16
16
  <div className="space-y-2">
17
- <p className="text-xs uppercase tracking-[0.4em] text-gray-400">{t('header.systemMenuLabel')}</p>
18
- <h1 className="text-3xl font-semibold text-gray-900">{t('header.settings')}</h1>
19
- <p className="max-w-3xl text-sm text-gray-600">{t('systemSettings.pageDescription')}</p>
17
+ <p className="text-xs uppercase tracking-[0.4em] text-gray-400 dark:text-slate-500">
18
+ {t('header.systemMenuLabel')}
19
+ </p>
20
+ <h1 className="text-3xl font-semibold text-gray-900 dark:text-slate-100">{t('header.settings')}</h1>
21
+ <p className="max-w-3xl text-sm text-gray-600 dark:text-slate-300">
22
+ {t('systemSettings.pageDescription')}
23
+ </p>
20
24
  </div>
21
25
 
22
- <section className="rounded-[32px] border border-slate-200 bg-[radial-gradient(circle_at_top_left,_rgba(191,219,254,0.4),_transparent_34%),linear-gradient(155deg,_rgba(255,255,255,0.98),_rgba(248,250,252,0.96))] p-6 shadow-[0_20px_60px_rgba(15,23,42,0.08)]">
26
+ <section className="rounded-[32px] border border-slate-200 bg-[radial-gradient(circle_at_top_left,_rgba(191,219,254,0.4),_transparent_34%),linear-gradient(155deg,_rgba(255,255,255,0.98),_rgba(248,250,252,0.96))] p-6 shadow-[0_20px_60px_rgba(15,23,42,0.08)] dark:border-slate-700 dark:bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_34%),linear-gradient(155deg,_rgba(15,23,42,0.98),_rgba(8,15,28,0.96))] dark:shadow-[0_24px_70px_rgba(2,6,23,0.42)]">
23
27
  <div className="space-y-3">
24
- <p className="text-[0.7rem] font-semibold uppercase tracking-[0.34em] text-slate-500">
28
+ <p className="text-[0.7rem] font-semibold uppercase tracking-[0.34em] text-slate-500 dark:text-slate-400">
25
29
  {t('chatEnterBehavior.sectionEyebrow')}
26
30
  </p>
27
31
  <div className="space-y-2">
28
- <h2 className="text-2xl font-semibold text-slate-950">{t('chatEnterBehavior.sectionTitle')}</h2>
29
- <p className="max-w-3xl text-sm leading-7 text-slate-600">
32
+ <h2 className="text-2xl font-semibold text-slate-950 dark:text-slate-100">
33
+ {t('chatEnterBehavior.sectionTitle')}
34
+ </h2>
35
+ <p className="max-w-3xl text-sm leading-7 text-slate-600 dark:text-slate-300">
30
36
  {t('chatEnterBehavior.sectionDescription')}
31
37
  </p>
32
38
  </div>
@@ -264,7 +264,9 @@ export function useAdminTerminalSession<TSession extends AdminTerminalSession>(
264
264
  }
265
265
 
266
266
  if (payload.session) {
267
- setSession(payload.session);
267
+ setSession((currentSession) =>
268
+ mergeAdminTerminalSessionSnapshot(currentSession, payload.session as TSession),
269
+ );
268
270
  }
269
271
  } catch (error) {
270
272
  setErrorMessage(error instanceof Error ? error.message : options.sendErrorMessage);
@@ -346,3 +348,29 @@ export function useAdminTerminalSession<TSession extends AdminTerminalSession>(
346
348
  stopSession,
347
349
  };
348
350
  }
351
+
352
+ /**
353
+ * Merges a terminal API snapshot without letting a stale write acknowledgement hide newer SSE output.
354
+ *
355
+ * @private internal utility of `useAdminTerminalSession`
356
+ */
357
+ export function mergeAdminTerminalSessionSnapshot<TSession extends AdminTerminalSession>(
358
+ currentSession: TSession | null,
359
+ nextSession: TSession,
360
+ ): TSession {
361
+ if (!currentSession || currentSession.id !== nextSession.id) {
362
+ return nextSession;
363
+ }
364
+
365
+ const isStaleSessionOutput =
366
+ currentSession.output.length > nextSession.output.length && currentSession.output.startsWith(nextSession.output);
367
+
368
+ if (!isStaleSessionOutput) {
369
+ return nextSession;
370
+ }
371
+
372
+ return {
373
+ ...nextSession,
374
+ output: currentSession.output,
375
+ };
376
+ }
@@ -177,9 +177,9 @@ export function AgentProfile(props: AgentProfileProps) {
177
177
  <AgentProfileImage
178
178
  src={resolvedAgentAvatar.imageUrl}
179
179
  alt={fullname}
180
- className="w-full h-full object-cover"
180
+ className="w-full h-full"
181
+ imageClassName="agent-avatar-pixelated w-full h-full object-contain"
181
182
  style={{
182
- objectFit: 'cover',
183
183
  backgroundImage: `url(${colorToDataUrl(brandColorLightHex)})`,
184
184
  }}
185
185
  />
@@ -196,7 +196,7 @@ export function AgentProfile(props: AgentProfileProps) {
196
196
  surface="transparent"
197
197
  size={420}
198
198
  alt={fullname}
199
- className="h-full w-full pointer-events-none select-none"
199
+ className="!h-full !w-full pointer-events-none select-none"
200
200
  style={{
201
201
  width: '100%',
202
202
  height: '100%',
@@ -8,13 +8,19 @@ type AgentProfileImageProps = {
8
8
  readonly src: string;
9
9
  readonly alt: string;
10
10
  readonly className?: string;
11
+ readonly imageClassName?: string;
11
12
  readonly style?: React.CSSProperties;
12
13
  };
13
14
 
15
+ /**
16
+ * Default image sizing used by compact profile-image surfaces.
17
+ */
18
+ const DEFAULT_AGENT_PROFILE_IMAGE_CLASS_NAME = 'agent-avatar-pixelated w-full h-full object-cover';
19
+
14
20
  /**
15
21
  * Handles agent profile image.
16
22
  */
17
- export function AgentProfileImage({ src, alt, className, style }: AgentProfileImageProps) {
23
+ export function AgentProfileImage({ src, alt, className, imageClassName, style }: AgentProfileImageProps) {
18
24
  const [imageSrc, setImageSrc] = useState<string | null>(null);
19
25
  const [isLoading, setIsLoading] = useState(true);
20
26
  const [error, setError] = useState<Error | null>(null);
@@ -99,7 +105,7 @@ export function AgentProfileImage({ src, alt, className, style }: AgentProfileIm
99
105
  <img
100
106
  src={imageSrc}
101
107
  alt={alt}
102
- className="agent-avatar-pixelated w-full h-full object-cover"
108
+ className={imageClassName || DEFAULT_AGENT_PROFILE_IMAGE_CLASS_NAME}
103
109
  // We don't pass style here because it is applied to container
104
110
  />
105
111
  )}
@@ -9,16 +9,16 @@ import { OpenMojiIcon } from '../OpenMojiIcon/OpenMojiIcon';
9
9
  */
10
10
  export function DocsToolbar() {
11
11
  return (
12
- <div className="flex flex-wrap items-center justify-between gap-4 p-4 mb-8 bg-white rounded-xl shadow-sm border border-gray-100 print:hidden">
12
+ <div className="flex flex-wrap items-center justify-between gap-4 p-4 mb-8 bg-white rounded-xl shadow-sm border border-gray-100 print:hidden dark:border-slate-700 dark:bg-slate-900/92 dark:shadow-slate-950/30">
13
13
  <div className="flex items-center gap-2">
14
14
  <OpenMojiIcon icon="📚" className="text-2xl" />
15
- <span className="font-semibold text-gray-700">Documentation</span>
15
+ <span className="font-semibold text-gray-700 dark:text-slate-100">Documentation</span>
16
16
  </div>
17
17
 
18
18
  <div className="flex flex-wrap gap-2">
19
19
  <button
20
20
  onClick={() => window.print()}
21
- className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
21
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors dark:border-slate-600 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-800 dark:focus:ring-offset-slate-950"
22
22
  title="Print this page or save as PDF"
23
23
  >
24
24
  <PrinterIcon className="w-4 h-4" />
@@ -29,7 +29,7 @@ export function DocsToolbar() {
29
29
  href="/api/docs/book-language.md"
30
30
  download="book-language.md"
31
31
  target="_blank"
32
- className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
32
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors dark:border-blue-500/40 dark:bg-blue-500/15 dark:text-blue-100 dark:hover:bg-blue-500/25 dark:focus:ring-offset-slate-950"
33
33
  title="Download raw Markdown documentation"
34
34
  >
35
35
  <Download className="w-4 h-4" />
@@ -40,12 +40,12 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
40
40
 
41
41
  return (
42
42
  <div
43
- className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden ${
43
+ className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden dark:border-slate-700 dark:bg-slate-900/92 dark:shadow-slate-950/40 ${
44
44
  isPrintOnly ? 'shadow-none border-none' : ''
45
45
  } print:shadow-none print:border-none print:rounded-none`}
46
46
  >
47
47
  <div
48
- className={`p-8 border-b border-gray-100 bg-gray-50/50 ${
48
+ className={`p-8 border-b border-gray-100 bg-gray-50/50 dark:border-slate-700 dark:bg-slate-800/72 ${
49
49
  isPrintOnly ? 'border-none bg-white p-0 mb-4' : ''
50
50
  } ${
51
51
  isLowVisibilityNotice ? 'opacity-90 print:opacity-100' : ''
@@ -56,23 +56,23 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
56
56
  <OpenMojiIcon icon={primary.icon} variant="color" className="mr-3" />
57
57
  {primary.type}
58
58
  {aliases.length > 0 && (
59
- <span className="text-gray-400 font-normal ml-4 text-2xl print:text-xl">
59
+ <span className="text-gray-400 font-normal ml-4 text-2xl dark:text-slate-500 print:text-xl">
60
60
  / {aliases.join(' / ')}
61
61
  </span>
62
62
  )}
63
63
  </h1>
64
64
  {!isPrintOnly && (
65
- <span className="px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 print:hidden">
65
+ <span className="px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-100 print:hidden">
66
66
  Commitment
67
67
  </span>
68
68
  )}
69
69
  {!isPrintOnly && notice?.kind === 'deprecated' && (
70
- <span className="px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800 print:hidden">
70
+ <span className="px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800 dark:bg-amber-500/20 dark:text-amber-100 print:hidden">
71
71
  Deprecated
72
72
  </span>
73
73
  )}
74
74
  {!isPrintOnly && isLowVisibilityNotice && (
75
- <span className="px-3 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-700 print:hidden">
75
+ <span className="px-3 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-700 dark:bg-slate-700 dark:text-slate-100 print:hidden">
76
76
  {notice?.badgeLabel}
77
77
  </span>
78
78
  )}
@@ -86,8 +86,8 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
86
86
  <div
87
87
  className={`mt-4 rounded-xl border px-4 py-3 text-sm print:border ${
88
88
  isLowVisibilityNotice
89
- ? 'border-slate-200 bg-slate-50 text-slate-700 print:border-slate-200'
90
- : 'border-amber-200 bg-amber-50 text-amber-900 print:border-amber-200'
89
+ ? 'border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-950/72 dark:text-slate-200 print:border-slate-200'
90
+ : 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-950/35 dark:text-amber-100 print:border-amber-200'
91
91
  }`}
92
92
  >
93
93
  <div className="font-semibold mb-1">{notice?.detailLabel || 'Deprecated'}</div>
@@ -105,7 +105,7 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
105
105
  </div>
106
106
 
107
107
  <div className={`p-8 ${isPrintOnly ? 'p-0' : ''} print:p-0`}>
108
- <article className="prose prose-lg prose-slate max-w-none prose-headings:font-bold prose-headings:tracking-tight prose-headings:text-gray-900 prose-h1:text-4xl prose-h1:mb-8 prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:pb-2 prose-h2:border-b prose-h2:border-gray-200 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 prose-h3:text-gray-800 prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-6 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:text-blue-700 hover:prose-a:underline prose-a:transition-colors prose-strong:font-bold prose-strong:text-gray-900 prose-code:text-blue-600 prose-code:bg-blue-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none prose-code:font-medium prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:shadow-lg prose-pre:rounded-xl prose-pre:p-6 prose-ul:list-disc prose-ul:pl-6 prose-li:marker:text-gray-400 prose-li:mb-2 prose-ol:list-decimal prose-ol:pl-6 prose-li:mb-2 prose-blockquote:border-l-4 prose-blockquote:border-blue-500 prose-blockquote:bg-blue-50/50 prose-blockquote:py-2 prose-blockquote:px-4 prose-blockquote:rounded-r-lg prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-blockquote:my-8 prose-img:rounded-xl prose-img:shadow-md prose-img:my-8 prose-hr:border-gray-200 prose-hr:my-10 prose-table:w-full prose-th:text-left prose-th:py-2 prose-th:px-3 prose-th:bg-gray-100 prose-th:font-semibold prose-th:text-gray-900 prose-td:py-2 prose-td:px-3 prose-td:border-b prose-td:border-gray-200 prose-tr:hover:bg-gray-50 print:prose-base print:max-w-none">
108
+ <article className="prose prose-lg prose-slate max-w-none prose-headings:font-bold prose-headings:tracking-tight prose-headings:text-gray-900 prose-h1:text-4xl prose-h1:mb-8 prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:pb-2 prose-h2:border-b prose-h2:border-gray-200 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 prose-h3:text-gray-800 prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-6 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:text-blue-700 hover:prose-a:underline prose-a:transition-colors prose-strong:font-bold prose-strong:text-gray-900 prose-code:text-blue-600 prose-code:bg-blue-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none prose-code:font-medium prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:shadow-lg prose-pre:rounded-xl prose-pre:p-6 prose-ul:list-disc prose-ul:pl-6 prose-li:marker:text-gray-400 prose-li:mb-2 prose-ol:list-decimal prose-ol:pl-6 prose-li:mb-2 prose-blockquote:border-l-4 prose-blockquote:border-blue-500 prose-blockquote:bg-blue-50/50 prose-blockquote:py-2 prose-blockquote:px-4 prose-blockquote:rounded-r-lg prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-blockquote:my-8 prose-img:rounded-xl prose-img:shadow-md prose-img:my-8 prose-hr:border-gray-200 prose-hr:my-10 prose-table:w-full prose-th:text-left prose-th:py-2 prose-th:px-3 prose-th:bg-gray-100 prose-th:font-semibold prose-th:text-gray-900 prose-td:py-2 prose-td:px-3 prose-td:border-b prose-td:border-gray-200 prose-tr:hover:bg-gray-50 dark:prose-invert dark:prose-headings:text-slate-100 dark:prose-h2:border-slate-700 dark:prose-h3:text-slate-100 dark:prose-p:text-slate-300 dark:prose-a:text-sky-300 dark:prose-strong:text-slate-100 dark:prose-code:bg-slate-800 dark:prose-code:text-sky-200 dark:prose-blockquote:border-sky-400 dark:prose-blockquote:bg-slate-950/60 dark:prose-blockquote:text-slate-200 dark:prose-hr:border-slate-700 dark:prose-th:bg-slate-800 dark:prose-th:text-slate-100 dark:prose-td:border-slate-700 dark:prose-tr:hover:bg-slate-800/60 print:prose-base print:max-w-none">
109
109
  <MarkdownContent
110
110
  content={documentationMarkdown}
111
111
  /* TODO: !!!!
@@ -34,18 +34,18 @@ export function Footer(props: FooterProps) {
34
34
  const { t } = useServerLanguage();
35
35
 
36
36
  return (
37
- <footer className="border-t bg-white">
37
+ <footer className="border-t border-gray-200 bg-white dark:border-slate-700 dark:bg-slate-950">
38
38
  <div className="container mx-auto px-6 py-12">
39
39
  <div className="grid grid-cols-1 md:grid-cols-5 gap-8">
40
40
  {/* Company Info */}
41
41
  <div className="space-y-4">
42
- <h3 className="font-bold">{NAME}</h3>
42
+ <h3 className="font-bold text-gray-900 dark:text-slate-100">{NAME}</h3>
43
43
  <p className="text-sm text-muted-foreground text-gray-500">{CLAIM}</p>
44
44
  </div>
45
45
 
46
46
  {/* Products */}
47
47
  <div className="space-y-4">
48
- <h3 className="font-bold">{t('footer.productSectionTitle')}</h3>
48
+ <h3 className="font-bold text-gray-900 dark:text-slate-100">{t('footer.productSectionTitle')}</h3>
49
49
  <ul className="space-y-2 text-sm">
50
50
  <li>
51
51
  <HeadlessLink href="/get-started" className="text-gray-500 hover:text-gray-900">
@@ -80,7 +80,7 @@ export function Footer(props: FooterProps) {
80
80
 
81
81
  {/* Company */}
82
82
  <div className="space-y-4">
83
- <h3 className="font-bold">{t('footer.companySectionTitle')}</h3>
83
+ <h3 className="font-bold text-gray-900 dark:text-slate-100">{t('footer.companySectionTitle')}</h3>
84
84
  <ul className="space-y-2 text-sm">
85
85
  <li>
86
86
  <a
@@ -110,7 +110,7 @@ export function Footer(props: FooterProps) {
110
110
 
111
111
  {/* Social */}
112
112
  <div className="space-y-4">
113
- <h3 className="font-bold">{t('footer.connectSectionTitle')}</h3>
113
+ <h3 className="font-bold text-gray-900 dark:text-slate-100">{t('footer.connectSectionTitle')}</h3>
114
114
  <ul className="space-y-2 text-sm">
115
115
  <li>
116
116
  <a
@@ -144,7 +144,7 @@ export function Footer(props: FooterProps) {
144
144
  {/* Extra Links from Metadata */}
145
145
  {extraLinks.length > 0 && (
146
146
  <div className="space-y-4">
147
- <h3 className="font-bold">{t('footer.linksSectionTitle')}</h3>
147
+ <h3 className="font-bold text-gray-900 dark:text-slate-100">{t('footer.linksSectionTitle')}</h3>
148
148
  <ul className="space-y-2 text-sm">
149
149
  {extraLinks.map((link, index) => (
150
150
  <li key={index}>
@@ -163,7 +163,7 @@ export function Footer(props: FooterProps) {
163
163
  )}
164
164
  </div>
165
165
 
166
- <div className="border-t mt-8 pt-8 text-center text-sm text-gray-500">
166
+ <div className="border-t border-gray-200 mt-8 pt-8 text-center text-sm text-gray-500 dark:border-slate-700 dark:text-slate-400">
167
167
  <p>
168
168
  &copy; {new Date().getFullYear()} Promptbook
169
169
  <br />
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { logoutAction } from '@/src/app/actions';
4
4
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
5
- import { ArrowRight } from 'lucide-react';
5
+ import { ArrowRight, TriangleAlert } from 'lucide-react';
6
6
  import { usePathname, useRouter } from 'next/navigation';
7
7
  import { useCallback, useMemo, useState } from 'react';
8
8
  import { HamburgerMenu } from '../../../../../src/book-components/_common/HamburgerMenu/HamburgerMenu';
@@ -56,6 +56,7 @@ export function Header(props: HeaderProps) {
56
56
  federatedServers,
57
57
  isExperimental = false,
58
58
  feedbackMode = 'stars',
59
+ shibbolethAuthenticationStatus,
59
60
  } = props;
60
61
 
61
62
  const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
@@ -248,6 +249,9 @@ export function Header(props: HeaderProps) {
248
249
  );
249
250
 
250
251
  const hasMenuAccess = Boolean(currentUser || isAdmin);
252
+ const isShibbolethConfigurationWarningShown = Boolean(
253
+ shibbolethAuthenticationStatus?.isActive && !shibbolethAuthenticationStatus.isConfigured,
254
+ );
251
255
  const systemMenuEntries = useMemo(
252
256
  () =>
253
257
  buildHeaderSystemMenuItems({
@@ -257,8 +261,21 @@ export function Header(props: HeaderProps) {
257
261
  isGlobalAdmin,
258
262
  isExperimental,
259
263
  feedbackMode,
264
+ shibbolethAuthenticationStatus,
260
265
  }),
261
- [currentUser, feedbackMode, isAdmin, isExperimental, isGlobalAdmin, t],
266
+ [currentUser, feedbackMode, isAdmin, isExperimental, isGlobalAdmin, shibbolethAuthenticationStatus, t],
267
+ );
268
+ const systemLabel = useMemo(
269
+ () =>
270
+ isShibbolethConfigurationWarningShown ? (
271
+ <span className="inline-flex items-center gap-2">
272
+ {t('header.systemMenuLabel')}
273
+ <TriangleAlert className="h-4 w-4 text-amber-500" aria-label="Warning" />
274
+ </span>
275
+ ) : (
276
+ t('header.systemMenuLabel')
277
+ ),
278
+ [isShibbolethConfigurationWarningShown, t],
262
279
  );
263
280
  const menuItems = useMemo(
264
281
  () =>
@@ -274,7 +291,7 @@ export function Header(props: HeaderProps) {
274
291
  setIsMobileDocsOpen,
275
292
  setIsMobileSystemOpen,
276
293
  setIsSystemOpen,
277
- systemLabel: t('header.systemMenuLabel'),
294
+ systemLabel,
278
295
  systemMenuEntries,
279
296
  }),
280
297
  [
@@ -288,6 +305,7 @@ export function Header(props: HeaderProps) {
288
305
  setIsMobileDocsOpen,
289
306
  setIsMobileSystemOpen,
290
307
  setIsSystemOpen,
308
+ systemLabel,
291
309
  systemMenuEntries,
292
310
  t,
293
311
  ],
@@ -389,7 +407,9 @@ export function Header(props: HeaderProps) {
389
407
  key={index}
390
408
  onClick={item.onClick}
391
409
  className={`rounded-md p-2 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-slate-100 ${
392
- item.isActive ? 'bg-gray-100 text-gray-900 dark:bg-slate-800 dark:text-slate-100' : ''
410
+ item.isActive
411
+ ? 'bg-gray-100 text-gray-900 dark:bg-slate-800 dark:text-slate-100'
412
+ : ''
393
413
  }`}
394
414
  title={item.name}
395
415
  >
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react';
2
2
  import type { AgentOrganizationAgent, AgentOrganizationFolder } from '../../utils/agentOrganization/types';
3
3
  import type { ChatFeedbackMode } from '../../utils/chatFeedbackMode';
4
4
  import type { UserInfo } from '../../utils/getCurrentUser';
5
+ import type { ShibbolethAuthenticationMenuStatus } from '../../constants/shibbolethAuth';
5
6
  import type { SubMenuItem } from './SubMenuItem';
6
7
 
7
8
  /**
@@ -57,6 +58,11 @@ export type HeaderProps = {
57
58
  * Determines which chat feedback mode is active.
58
59
  */
59
60
  feedbackMode?: ChatFeedbackMode;
61
+
62
+ /**
63
+ * Shibboleth authentication status used to show login-method menu entries and warnings.
64
+ */
65
+ shibbolethAuthenticationStatus?: ShibbolethAuthenticationMenuStatus;
60
66
  };
61
67
 
62
68
  /**
@@ -5,11 +5,15 @@ import {
5
5
  KeyRound,
6
6
  RefreshCw,
7
7
  Settings2,
8
+ ShieldCheck,
9
+ TriangleAlert,
8
10
  UserRound,
9
11
  Wrench,
10
12
  type LucideIcon,
11
13
  } from 'lucide-react';
14
+ import { createElement } from 'react';
12
15
  import type { ServerTranslationKey } from '../../languages/ServerTranslationKeys';
16
+ import type { ShibbolethAuthenticationMenuStatus } from '../../constants/shibbolethAuth';
13
17
  import type { ChatFeedbackMode } from '../../utils/chatFeedbackMode';
14
18
  import type { UserInfo } from '../../utils/getCurrentUser';
15
19
  import type { SubMenuItem } from './SubMenuItem';
@@ -31,6 +35,7 @@ type SystemCategoryLabel =
31
35
  | 'Utilities'
32
36
  | 'Super Admin'
33
37
  | 'Administration'
38
+ | 'Login Methods'
34
39
  | 'Monitoring & Usage'
35
40
  | 'Integrations & Keys'
36
41
  | 'Developer / Debug'
@@ -48,6 +53,7 @@ type BuildHeaderSystemMenuItemsOptions = {
48
53
  readonly isGlobalAdmin: boolean;
49
54
  readonly isExperimental: boolean;
50
55
  readonly feedbackMode: ChatFeedbackMode;
56
+ readonly shibbolethAuthenticationStatus?: ShibbolethAuthenticationMenuStatus;
51
57
  };
52
58
 
53
59
  /**
@@ -58,6 +64,7 @@ const SYSTEM_CATEGORY_ICON_MAP: Record<SystemCategoryLabel, LucideIcon> = {
58
64
  Utilities: Wrench,
59
65
  'Super Admin': Settings2,
60
66
  Administration: Settings2,
67
+ 'Login Methods': KeyRound,
61
68
  'Monitoring & Usage': BarChart3,
62
69
  'Integrations & Keys': KeyRound,
63
70
  'Developer / Debug': Code2,
@@ -72,6 +79,7 @@ const SYSTEM_CATEGORY_TRANSLATION_KEY_MAP: Record<SystemCategoryLabel, ServerTra
72
79
  Utilities: 'header.utilities',
73
80
  'Super Admin': 'header.superAdmin',
74
81
  Administration: 'header.administration',
82
+ 'Login Methods': 'header.loginMethods',
75
83
  'Monitoring & Usage': 'header.monitoringAndUsage',
76
84
  'Integrations & Keys': 'header.integrationsAndKeys',
77
85
  'Developer / Debug': 'header.developerDebug',
@@ -95,6 +103,21 @@ function applyFallbackSubMenuIcon(
95
103
  });
96
104
  }
97
105
 
106
+ /**
107
+ * Decorates a menu label with the warning indicator used for misconfigured login methods.
108
+ */
109
+ function createWarningMenuLabel(label: string) {
110
+ return createElement(
111
+ 'span',
112
+ { className: 'inline-flex items-center gap-2' },
113
+ label,
114
+ createElement(TriangleAlert, {
115
+ className: 'h-4 w-4 text-amber-500',
116
+ 'aria-label': 'Warning',
117
+ }),
118
+ );
119
+ }
120
+
98
121
  /**
99
122
  * Creates one category entry inside the System dropdown when there are items to show.
100
123
  */
@@ -102,6 +125,7 @@ function createSystemCategory(
102
125
  label: SystemCategoryLabel,
103
126
  items: ReadonlyArray<SubMenuItem>,
104
127
  translate: HeaderTranslate,
128
+ isWarningShown = false,
105
129
  ): SubMenuItem[] {
106
130
  if (items.length === 0) {
107
131
  return [];
@@ -110,7 +134,9 @@ function createSystemCategory(
110
134
  const categoryIcon = SYSTEM_CATEGORY_ICON_MAP[label];
111
135
  return [
112
136
  {
113
- label: translate(SYSTEM_CATEGORY_TRANSLATION_KEY_MAP[label]),
137
+ label: isWarningShown
138
+ ? createWarningMenuLabel(translate(SYSTEM_CATEGORY_TRANSLATION_KEY_MAP[label]))
139
+ : translate(SYSTEM_CATEGORY_TRANSLATION_KEY_MAP[label]),
114
140
  icon: categoryIcon,
115
141
  items: applyFallbackSubMenuIcon(items, categoryIcon),
116
142
  },
@@ -129,6 +155,7 @@ export function buildHeaderSystemMenuItems({
129
155
  isGlobalAdmin,
130
156
  isExperimental,
131
157
  feedbackMode,
158
+ shibbolethAuthenticationStatus,
132
159
  }: BuildHeaderSystemMenuItemsOptions): SubMenuItem[] {
133
160
  const userAccountSystemItems: SubMenuItem[] = [
134
161
  {
@@ -274,6 +301,23 @@ export function buildHeaderSystemMenuItems({
274
301
  },
275
302
  ];
276
303
 
304
+ const isShibbolethConfigurationWarningShown = Boolean(
305
+ shibbolethAuthenticationStatus?.isActive && !shibbolethAuthenticationStatus.isConfigured,
306
+ );
307
+ const loginMethodsSystemItems: SubMenuItem[] = shibbolethAuthenticationStatus?.isActive
308
+ ? [
309
+ {
310
+ label: isShibbolethConfigurationWarningShown
311
+ ? createWarningMenuLabel(translate('header.shibboleth'))
312
+ : translate('header.shibboleth'),
313
+ href: isShibbolethConfigurationWarningShown
314
+ ? '/admin/login-methods/shibboleth#setup-instructions'
315
+ : '/admin/login-methods/shibboleth',
316
+ icon: ShieldCheck,
317
+ },
318
+ ]
319
+ : [];
320
+
277
321
  const monitoringAndUsageSystemItems: SubMenuItem[] = [
278
322
  {
279
323
  label: translate('header.usageAnalytics'),
@@ -345,6 +389,12 @@ export function buildHeaderSystemMenuItems({
345
389
  ...createSystemCategory('Utilities', utilitiesSystemItems, translate),
346
390
  ...createSystemCategory('Super Admin', superAdminSystemItems, translate),
347
391
  ...createSystemCategory('Administration', administrationSystemItems, translate),
392
+ ...createSystemCategory(
393
+ 'Login Methods',
394
+ loginMethodsSystemItems,
395
+ translate,
396
+ isShibbolethConfigurationWarningShown,
397
+ ),
348
398
  ...createSystemCategory('Monitoring & Usage', monitoringAndUsageSystemItems, translate),
349
399
  ...createSystemCategory('Integrations & Keys', integrationsAndKeysSystemItems, translate),
350
400
  ...createSystemCategory('Developer / Debug', developerDebugSystemItems, translate),
@@ -15,7 +15,7 @@ type CardProps = {
15
15
  export function Card({ children, className = '', style }: CardProps) {
16
16
  return (
17
17
  <div
18
- className={`block h-full p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400 ${className}`}
18
+ className={`block h-full p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400 dark:border-slate-700 dark:bg-slate-900/90 dark:text-slate-100 dark:shadow-slate-950/30 dark:hover:border-blue-400/70 ${className}`}
19
19
  style={style}
20
20
  >
21
21
  {children}
@@ -51,7 +51,9 @@ export function Section({
51
51
  }: SectionProps) {
52
52
  return (
53
53
  <section className={`mt-16 first:mt-4 mb-4 ${sectionClassName || ''}`.trim()}>
54
- <h2 className={`text-3xl text-gray-900 mb-6 font-light ${titleClassName || ''}`.trim()}>{title}</h2>
54
+ <h2 className={`text-3xl text-gray-900 mb-6 font-light dark:text-slate-100 ${titleClassName || ''}`.trim()}>
55
+ {title}
56
+ </h2>
55
57
  <div className={gridClassName}>{children}</div>
56
58
  </section>
57
59
  );
@@ -7,6 +7,7 @@ import type { AgentNaming } from '../../utils/agentNaming';
7
7
  import type { AgentOrganizationAgent, AgentOrganizationFolder } from '../../utils/agentOrganization/types';
8
8
  import type { ChatFeedbackMode } from '../../utils/chatFeedbackMode';
9
9
  import type { UserInfo } from '../../utils/getCurrentUser';
10
+ import type { ShibbolethAuthenticationMenuStatus } from '../../constants/shibbolethAuth';
10
11
  import { AgentNamingProvider } from '../AgentNaming/AgentNamingContext';
11
12
  import { DefaultAgentAvatarVisualProvider } from '../AgentAvatar/DefaultAgentAvatarVisualProvider';
12
13
  import { LegacyUiAutoTranslator } from '../AgentNaming/LegacyUiAutoTranslator';
@@ -48,6 +49,10 @@ type LayoutWrapperProps = {
48
49
  federatedServers: Array<{ url: string; title: string }>;
49
50
  isExperimental: boolean;
50
51
  feedbackMode: ChatFeedbackMode;
52
+ /**
53
+ * Shibboleth authentication status used by the header menu.
54
+ */
55
+ readonly shibbolethAuthenticationStatus: ShibbolethAuthenticationMenuStatus;
51
56
  /**
52
57
  * Indicates if the install-as-app option should be shown in agent menus.
53
58
  */
@@ -88,6 +93,7 @@ export function LayoutWrapper({
88
93
  federatedServers,
89
94
  isExperimental,
90
95
  feedbackMode,
96
+ shibbolethAuthenticationStatus,
91
97
  isExperimentalPwaAppEnabled,
92
98
  controlPanelOptionAvailability,
93
99
  defaultIsSoundsOn,
@@ -152,7 +158,9 @@ export function LayoutWrapper({
152
158
  }}
153
159
  >
154
160
  {shouldRenderMinimalShell ? (
155
- <main className={minimalMainClassName}>{children}</main>
161
+ <main className={minimalMainClassName}>
162
+ {children}
163
+ </main>
156
164
  ) : (
157
165
  <div className="agents-server-app-shell flex flex-col">
158
166
  <Header
@@ -166,6 +174,9 @@ export function LayoutWrapper({
166
174
  federatedServers={federatedServers}
167
175
  isExperimental={isExperimental}
168
176
  feedbackMode={feedbackMode}
177
+ shibbolethAuthenticationStatus={
178
+ shibbolethAuthenticationStatus
179
+ }
169
180
  />
170
181
  <main className={mainClassName}>
171
182
  <HomepageOptimisticNavigation