@promptbook/cli 0.104.0-1 → 0.104.0-11

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 (229) hide show
  1. package/apps/agents-server/config.ts +1 -3
  2. package/apps/agents-server/next.config.ts +2 -2
  3. package/apps/agents-server/package.json +7 -3
  4. package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
  5. package/apps/agents-server/public/swagger.json +115 -0
  6. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
  7. package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
  8. package/apps/agents-server/src/app/AddAgentButton.tsx +47 -21
  9. package/apps/agents-server/src/app/actions.ts +22 -5
  10. package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +211 -0
  11. package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
  12. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
  13. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
  14. package/apps/agents-server/src/app/admin/files/FilesGalleryClient.tsx +263 -0
  15. package/apps/agents-server/src/app/admin/files/actions.ts +61 -0
  16. package/apps/agents-server/src/app/admin/files/page.tsx +13 -0
  17. package/apps/agents-server/src/app/admin/image-generator-test/ImageGeneratorTestClient.tsx +169 -0
  18. package/apps/agents-server/src/app/admin/image-generator-test/page.tsx +13 -0
  19. package/apps/agents-server/src/app/admin/images/ImagesGalleryClient.tsx +256 -0
  20. package/apps/agents-server/src/app/admin/images/actions.ts +60 -0
  21. package/apps/agents-server/src/app/admin/images/page.tsx +13 -0
  22. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
  23. package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
  24. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
  25. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
  26. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
  27. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
  28. package/apps/agents-server/src/app/admin/search-engine-test/SearchEngineTestClient.tsx +109 -0
  29. package/apps/agents-server/src/app/admin/search-engine-test/actions.ts +17 -0
  30. package/apps/agents-server/src/app/admin/search-engine-test/page.tsx +13 -0
  31. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
  32. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
  33. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
  34. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
  35. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
  36. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  37. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  38. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
  39. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
  40. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
  41. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
  42. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
  43. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  44. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  45. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  46. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  47. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
  48. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -0
  49. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  50. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  51. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +10 -3
  52. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
  53. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
  54. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
  55. package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
  56. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
  57. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
  58. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +10 -3
  59. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
  60. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
  61. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
  62. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -0
  63. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  64. package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +13 -14
  65. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
  66. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
  67. package/apps/agents-server/src/app/api/agents/route.ts +28 -3
  68. package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
  69. package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
  70. package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
  71. package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
  72. package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
  73. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  74. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  75. package/apps/agents-server/src/app/api/images/[filename]/route.ts +128 -0
  76. package/apps/agents-server/src/app/api/messages/route.ts +102 -0
  77. package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
  78. package/apps/agents-server/src/app/api/upload/route.ts +128 -45
  79. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  80. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  81. package/apps/agents-server/src/app/globals.css +140 -33
  82. package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
  83. package/apps/agents-server/src/app/layout.tsx +27 -22
  84. package/apps/agents-server/src/app/page.tsx +54 -6
  85. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  86. package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
  87. package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
  88. package/apps/agents-server/src/app/security.txt/route.ts +1 -1
  89. package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
  90. package/apps/agents-server/src/app/swagger/page.tsx +14 -0
  91. package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +38 -0
  92. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +44 -116
  93. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
  94. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
  95. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  96. package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
  97. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  98. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  99. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  100. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  101. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  102. package/apps/agents-server/src/components/Header/Header.tsx +130 -40
  103. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +150 -23
  104. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
  105. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
  106. package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
  107. package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
  108. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  109. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  110. package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +88 -0
  111. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  112. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  113. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  114. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  115. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
  116. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
  117. package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
  118. package/apps/agents-server/src/database/$getTableName.ts +1 -0
  119. package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
  120. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
  121. package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
  122. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  123. package/apps/agents-server/src/database/migrate.ts +34 -1
  124. package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
  125. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
  126. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  127. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  128. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  129. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  130. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  131. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  132. package/apps/agents-server/src/database/migrations/2025-12-0402-message-table.sql +42 -0
  133. package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
  134. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  135. package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
  136. package/apps/agents-server/src/database/migrations/2025-12-0830-image-purpose.sql +5 -0
  137. package/apps/agents-server/src/database/migrations/2025-12-0890-file-agent-id.sql +5 -0
  138. package/apps/agents-server/src/database/schema.ts +244 -4
  139. package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
  140. package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
  141. package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
  142. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
  143. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
  144. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
  145. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
  146. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
  147. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
  148. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
  149. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
  150. package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
  151. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  152. package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
  153. package/apps/agents-server/src/message-providers/index.ts +13 -0
  154. package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
  155. package/apps/agents-server/src/middleware.ts +19 -23
  156. package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
  157. package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
  158. package/apps/agents-server/src/utils/auth.ts +117 -17
  159. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  160. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  161. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  162. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  163. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
  164. package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
  165. package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
  166. package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
  167. package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
  168. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
  169. package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
  170. package/esm/index.es.js +1534 -1330
  171. package/esm/index.es.js.map +1 -1
  172. package/esm/typings/servers.d.ts +8 -0
  173. package/esm/typings/src/_packages/core.index.d.ts +2 -0
  174. package/esm/typings/src/_packages/types.index.d.ts +16 -2
  175. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +29 -1
  176. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  177. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
  178. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  179. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  180. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  181. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  182. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  183. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +9 -13
  184. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +3 -3
  185. package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +1 -1
  186. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  187. package/esm/typings/src/book-components/icons/AboutIcon.d.ts +1 -1
  188. package/esm/typings/src/book-components/icons/AttachmentIcon.d.ts +1 -1
  189. package/esm/typings/src/book-components/icons/CameraIcon.d.ts +1 -1
  190. package/esm/typings/src/book-components/icons/DownloadIcon.d.ts +1 -1
  191. package/esm/typings/src/book-components/icons/MenuIcon.d.ts +1 -1
  192. package/esm/typings/src/book-components/icons/SaveIcon.d.ts +1 -1
  193. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -12
  194. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +27 -15
  195. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  196. package/esm/typings/src/commitments/index.d.ts +2 -1
  197. package/esm/typings/src/llm-providers/_common/utils/count-total-usage/countUsage.d.ts +1 -1
  198. package/esm/typings/src/llm-providers/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
  199. package/esm/typings/src/llm-providers/agent/Agent.d.ts +6 -1
  200. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  201. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  202. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  203. package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
  204. package/esm/typings/src/remote-server/ui/ServerApp.d.ts +1 -1
  205. package/esm/typings/src/search-engines/SearchEngine.d.ts +9 -0
  206. package/esm/typings/src/search-engines/SearchResult.d.ts +18 -0
  207. package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +15 -0
  208. package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +15 -0
  209. package/esm/typings/src/types/Message.d.ts +49 -0
  210. package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
  211. package/esm/typings/src/types/typeAliases.d.ts +23 -1
  212. package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
  213. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  214. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  215. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  216. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  217. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  218. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  219. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  220. package/esm/typings/src/utils/random/$randomAgentPersona.d.ts +3 -2
  221. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  222. package/esm/typings/src/version.d.ts +1 -1
  223. package/package.json +1 -1
  224. package/umd/index.umd.js +1542 -1338
  225. package/umd/index.umd.js.map +1 -1
  226. package/apps/agents-server/package-lock.json +0 -27
  227. package/apps/agents-server/public/fonts/download-font.js +0 -22
  228. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
  229. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
@@ -0,0 +1,61 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import spaceTrim from 'spacetrim';
3
+ import { getGroupedCommitmentDefinitions } from '../../../../../../../src/commitments';
4
+ import { keepUnused } from '../../../../../../../src/utils/organization/keepUnused';
5
+
6
+ export const dynamic = 'force-static';
7
+
8
+ export async function GET(request: NextRequest) {
9
+ keepUnused(request);
10
+
11
+ const groupedCommitments = getGroupedCommitmentDefinitions();
12
+
13
+ const content = spaceTrim(
14
+ (block) => `
15
+ # Promptbook Documentation
16
+
17
+ Promptbook is a language for defining AI agents. It is based on Markdown and uses a set of commitments to define the behavior of the agent.
18
+
19
+ ## Commitments
20
+
21
+ The following commands (commitments) are available in Promptbook:
22
+
23
+ ${block(
24
+ groupedCommitments
25
+ .map(({ primary, aliases }) => {
26
+ const title = primary.type;
27
+ const description = primary.description;
28
+ const documentation = primary.documentation;
29
+ const aliasList = aliases.length > 0 ? `**Aliases:** ${aliases.join(', ')}` : '';
30
+
31
+ return spaceTrim(
32
+ (block) => `
33
+ ### ${title}
34
+
35
+ ${description}
36
+
37
+ ${aliasList}
38
+
39
+ #### Usage
40
+
41
+ ${block(getSafeCodeBlock(documentation))}
42
+ `,
43
+ );
44
+ })
45
+ .join('\n\n'),
46
+ )}
47
+ `,
48
+ );
49
+
50
+ return new NextResponse(content, {
51
+ headers: {
52
+ 'Content-Type': 'text/markdown; charset=utf-8',
53
+ },
54
+ });
55
+ }
56
+
57
+ function getSafeCodeBlock(content: string, lang = 'markdown'): string {
58
+ const maxBackticks = Math.max(0, ...(content.match(/`+/g) || []).map((m) => m.length));
59
+ const fence = '`'.repeat(Math.max(3, maxBackticks + 1));
60
+ return `${fence}${lang}\n${content}\n${fence}`;
61
+ }
@@ -0,0 +1,48 @@
1
+ import { $getTableName } from '../../../../../database/$getTableName';
2
+ import { $provideSupabaseForServer } from '../../../../../database/$provideSupabaseForServer';
3
+ import { parseInboundSendgridEmail } from '../../../../../message-providers/email/sendgrid/parseInboundSendgridEmail';
4
+ import { NextRequest, NextResponse } from 'next/server';
5
+
6
+ export async function POST(request: NextRequest) {
7
+ try {
8
+ const formData = await request.formData();
9
+ const rawEmail = formData.get('email');
10
+
11
+ if (typeof rawEmail !== 'string') {
12
+ return NextResponse.json({ error: 'Missing email field' }, { status: 400 });
13
+ }
14
+
15
+ const email = await parseInboundSendgridEmail(rawEmail);
16
+
17
+ const supabase = await $provideSupabaseForServer();
18
+ const { error } = await supabase
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ .from(await $getTableName('Message'))
21
+ .insert({
22
+ channel: 'EMAIL',
23
+ direction: 'INBOUND',
24
+ sender: email.sender,
25
+ recipients: email.recipients,
26
+ content: email.content,
27
+ metadata: {
28
+ subject: email.subject,
29
+ cc: email.cc,
30
+ ...email.metadata,
31
+ },
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ } as any);
34
+
35
+ if (error) {
36
+ console.error('Failed to insert message', error);
37
+ return NextResponse.json({ error: error.message }, { status: 500 });
38
+ }
39
+
40
+ return NextResponse.json({ success: true });
41
+ } catch (error) {
42
+ console.error('Error processing inbound email', error);
43
+ return NextResponse.json(
44
+ { error: error instanceof Error ? error.message : 'Unknown error' },
45
+ { status: 500 },
46
+ );
47
+ }
48
+ }
@@ -1,10 +1,22 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { getFederatedServersFromMetadata } from '../../../utils/getFederatedServersFromMetadata';
3
+ import { getMetadata } from '../../../database/getMetadata';
4
+ import { getCurrentUser } from '../../../utils/getCurrentUser';
3
5
 
4
6
  export const dynamic = 'force-dynamic';
5
7
 
6
8
  export async function GET() {
7
9
  try {
10
+ const currentUser = await getCurrentUser();
11
+ const showFederatedServersPublicly = ((await getMetadata('SHOW_FEDERATED_SERVERS_PUBLICLY')) || 'false') === 'true';
12
+
13
+ // Only show federated servers if user is authenticated or if SHOW_FEDERATED_SERVERS_PUBLICLY is true
14
+ if (!currentUser && !showFederatedServersPublicly) {
15
+ return NextResponse.json({
16
+ federatedServers: [],
17
+ });
18
+ }
19
+
8
20
  const federatedServers = await getFederatedServersFromMetadata();
9
21
 
10
22
  return NextResponse.json({
@@ -0,0 +1,128 @@
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { serializeError } from '@promptbook-local/utils';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import { assertsError } from '../../../../../../../src/errors/assertsError';
5
+ import type { LlmExecutionTools } from '../../../../../../../src/execution/LlmExecutionTools';
6
+ import { getSingleLlmExecutionTools } from '../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
7
+ import { string_url } from '../../../../../../../src/types/typeAliases';
8
+ import { $provideSupabaseForServer } from '../../../../database/$provideSupabaseForServer';
9
+ import { $provideCdnForServer } from '../../../../tools/$provideCdnForServer';
10
+ import { $provideExecutionToolsForServer } from '../../../../tools/$provideExecutionToolsForServer';
11
+ import { filenameToPrompt } from '../../../../utils/normalization/filenameToPrompt';
12
+
13
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ filename: string }> }) {
14
+ try {
15
+ const { filename } = await params;
16
+ const searchParams = request.nextUrl.searchParams;
17
+ const modelName = searchParams.get('modelName');
18
+ const isRaw = searchParams.get('raw') === 'true';
19
+
20
+ if (!filename) {
21
+ return NextResponse.json({ error: 'Filename is required' }, { status: 400 });
22
+ }
23
+
24
+ const supabase = $provideSupabaseForServer();
25
+
26
+ // Check if image already exists in database
27
+ const { data: existingImage, error: selectError } = await supabase
28
+ .from(await $getTableName(`Image`))
29
+ .select('cdnUrl')
30
+ .eq('filename', filename)
31
+ .single();
32
+
33
+ if (selectError && selectError.code !== 'PGRST116') {
34
+ // PGRST116 is "not found"
35
+ throw selectError;
36
+ }
37
+
38
+ if (existingImage) {
39
+ if (isRaw) {
40
+ return NextResponse.json({
41
+ source: 'cache',
42
+ filename,
43
+ cdnUrl: existingImage.cdnUrl,
44
+ });
45
+ }
46
+ // Image exists, redirect to CDN
47
+ return NextResponse.redirect(existingImage.cdnUrl as string_url);
48
+ }
49
+
50
+ // Image doesn't exist, generate it
51
+ const prompt = filenameToPrompt(filename);
52
+
53
+ const executionTools = await $provideExecutionToolsForServer();
54
+ const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
55
+
56
+ if (!llmTools.callImageGenerationModel) {
57
+ throw new Error('Image generation is not supported by the current LLM configuration');
58
+ }
59
+
60
+ const imageResult = await llmTools.callImageGenerationModel({
61
+ title: `Generate image for ${filename}`,
62
+ content: prompt,
63
+ parameters: {},
64
+ modelRequirements: {
65
+ modelVariant: 'IMAGE_GENERATION',
66
+ modelName: modelName || 'dall-e-3', // Use DALL-E 3 for high quality
67
+ },
68
+ });
69
+
70
+ if (!imageResult.content) {
71
+ throw new Error('Failed to generate image: no content returned');
72
+ }
73
+
74
+ // Download the generated image
75
+ const imageResponse = await fetch(imageResult.content);
76
+ if (!imageResponse.ok) {
77
+ throw new Error(`Failed to download generated image: ${imageResponse.status}`);
78
+ }
79
+
80
+ const imageBuffer = await imageResponse.arrayBuffer();
81
+ const buffer = Buffer.from(imageBuffer);
82
+
83
+ // Upload to CDN
84
+ const cdn = $provideCdnForServer();
85
+ const cdnKey = `generated-images/${filename}`;
86
+ await cdn.setItem(cdnKey, {
87
+ type: 'image/png', // DALL-E generates PNG
88
+ data: buffer,
89
+ });
90
+
91
+ const cdnUrl = cdn.getItemUrl(cdnKey);
92
+
93
+ // Save to database
94
+ const { error: insertError } = await supabase.from(await $getTableName(`Image`)).insert({
95
+ filename,
96
+ prompt,
97
+ cdnUrl: cdnUrl.href,
98
+ cdnKey,
99
+ });
100
+
101
+ if (insertError) {
102
+ throw insertError;
103
+ }
104
+
105
+ if (isRaw) {
106
+ return NextResponse.json({
107
+ source: 'generated',
108
+ filename,
109
+ prompt,
110
+ modelName: modelName || 'dall-e-3',
111
+ cdnUrl: cdnUrl.href,
112
+ imageResult,
113
+ });
114
+ }
115
+
116
+ // Redirect to the newly created image
117
+ return NextResponse.redirect(cdnUrl.href as string_url);
118
+ } catch (error) {
119
+ assertsError(error);
120
+
121
+ console.error('Error serving image:', error);
122
+
123
+ return new Response(JSON.stringify(serializeError(error), null, 4), {
124
+ status: 500,
125
+ headers: { 'Content-Type': 'application/json' },
126
+ });
127
+ }
128
+ }
@@ -0,0 +1,102 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { $getTableName } from '../../../database/$getTableName';
3
+ import { $provideSupabase } from '../../../database/$provideSupabase';
4
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
5
+
6
+ const DEFAULT_PAGE_SIZE = 20;
7
+ const MAX_PAGE_SIZE = 100;
8
+
9
+ function parsePositiveInt(value: string | null, fallback: number): number {
10
+ if (!value) return fallback;
11
+ const parsed = parseInt(value, 10);
12
+ if (Number.isNaN(parsed) || parsed <= 0) return fallback;
13
+ return parsed;
14
+ }
15
+
16
+ /**
17
+ * List messages with filters, search and pagination.
18
+ */
19
+ export async function GET(request: NextRequest) {
20
+ if (!(await isUserAdmin())) {
21
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
22
+ }
23
+
24
+ try {
25
+ const searchParams = request.nextUrl.searchParams;
26
+
27
+ const page = parsePositiveInt(searchParams.get('page'), 1);
28
+ const pageSize = Math.min(MAX_PAGE_SIZE, parsePositiveInt(searchParams.get('pageSize'), DEFAULT_PAGE_SIZE));
29
+ const search = searchParams.get('search')?.trim() || '';
30
+ const channel = searchParams.get('channel');
31
+ const direction = searchParams.get('direction');
32
+
33
+ const supabase = $provideSupabase();
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ let query = supabase.from(await $getTableName('Message')).select('*', { count: 'exact' });
37
+
38
+ if (channel) {
39
+ query = query.eq('channel', channel);
40
+ }
41
+
42
+ if (direction) {
43
+ query = query.eq('direction', direction);
44
+ }
45
+
46
+ if (search) {
47
+ // Search in content, subject (if in metadata?), sender/recipient emails
48
+ // Note: sender and recipients are JSONB, so ilike might not work directly on them unless cast to text
49
+ // Content is TEXT.
50
+ const escaped = search.replace(/%/g, '\\%').replace(/_/g, '\\_');
51
+ // Assuming simple search on content for now to avoid complexity with JSONB search in generic supabase client
52
+ query = query.ilike('content', `%${escaped}%`);
53
+ }
54
+
55
+ // Default sort by createdAt desc
56
+ query = query.order('createdAt', { ascending: false });
57
+
58
+ const from = (page - 1) * pageSize;
59
+ const to = from + pageSize - 1;
60
+
61
+ query = query.range(from, to);
62
+
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const { data: messages, error, count } = (await query) as { data: any[]; error: any; count: number };
65
+
66
+ if (error) {
67
+ console.error('List messages error:', error);
68
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
69
+ }
70
+
71
+ // Fetch attempts for these messages
72
+ if (messages && messages.length > 0) {
73
+ const messageIds = messages.map((m) => m.id);
74
+ const { data: attempts, error: attemptsError } = await supabase
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ .from(await $getTableName('MessageSendAttempt'))
77
+ .select('*')
78
+ .in('messageId', messageIds);
79
+
80
+ if (attemptsError) {
81
+ console.error('Fetch message attempts error:', attemptsError);
82
+ // We don't fail the whole request, just log it.
83
+ } else {
84
+ // Attach attempts to messages
85
+ for (const message of messages) {
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ (message as any).sendAttempts = attempts?.filter((a: any) => a.messageId === message.id) || [];
88
+ }
89
+ }
90
+ }
91
+
92
+ return NextResponse.json({
93
+ items: messages ?? [],
94
+ total: count ?? 0,
95
+ page,
96
+ pageSize,
97
+ });
98
+ } catch (error) {
99
+ console.error('List messages error:', error);
100
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
101
+ }
102
+ }
@@ -1,9 +1,12 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
1
3
  import { $getTableName } from '../../../database/$getTableName';
2
4
  import { $provideSupabase } from '../../../database/$provideSupabase';
3
5
  import { isUserAdmin } from '../../../utils/isUserAdmin';
4
- import { NextRequest, NextResponse } from 'next/server';
5
6
 
6
7
  export async function GET(request: NextRequest) {
8
+ keepUnused(request);
9
+
7
10
  if (!(await isUserAdmin())) {
8
11
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
9
12
  }
@@ -36,11 +39,7 @@ export async function POST(request: NextRequest) {
36
39
  const supabase = $provideSupabase();
37
40
  const table = await $getTableName('Metadata');
38
41
 
39
- const { data, error } = await supabase
40
- .from(table)
41
- .insert({ key, value, note })
42
- .select()
43
- .single();
42
+ const { data, error } = await supabase.from(table).insert({ key, value, note }).select().single();
44
43
 
45
44
  if (error) {
46
45
  return NextResponse.json({ error: error.message }, { status: 500 });
@@ -1,70 +1,144 @@
1
- import { nextRequestToNodeRequest } from '@/src/utils/cdn/utils/nextRequestToNodeRequest';
2
- import { TODO_any } from '@promptbook-local/types';
1
+ import { $getTableName } from '@/src/database/$getTableName';
2
+ import { $provideSupabase } from '@/src/database/$provideSupabase';
3
3
  import { serializeError } from '@promptbook-local/utils';
4
- import formidable from 'formidable';
5
- import { readFile } from 'fs/promises';
4
+ import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
6
5
  import { NextRequest, NextResponse } from 'next/server';
7
- import { forTime } from 'waitasecond';
8
6
  import { assertsError } from '../../../../../../src/errors/assertsError';
9
- import { string_url } from '../../../../../../src/types/typeAliases';
10
- import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
11
- import { $provideCdnForServer } from '../../../../src/tools/$provideCdnForServer';
12
- import { getUserFileCdnKey } from '../../../../src/utils/cdn/utils/getUserFileCdnKey';
13
- import { validateMimeType } from '../../../../src/utils/validators/validateMimeType';
7
+ import { getUserIdFromRequest } from '../../../../src/utils/getUserIdFromRequest';
14
8
  import { getMetadata } from '../../../database/getMetadata';
15
9
 
16
10
  export async function POST(request: NextRequest) {
17
11
  try {
18
- await forTime(1);
19
- // await forTime(5000);
12
+ const body = (await request.json()) as HandleUploadBody;
13
+ const userId = await getUserIdFromRequest(request);
14
+ const supabase = $provideSupabase();
20
15
 
21
- const nodeRequest = await nextRequestToNodeRequest(request);
22
- let maxFileSizeMb = Number((await getMetadata('MAX_FILE_UPLOAD_SIZE_MB')) || '50'); // <- TODO: [🌲] To /config.ts
16
+ // Handle Vercel Blob client upload protocol
17
+ const jsonResponse = await handleUpload({
18
+ body,
19
+ request,
20
+ token: process.env.VERCEL_BLOB_READ_WRITE_TOKEN!,
21
+ onBeforeGenerateToken: async (pathname, clientPayload) => {
22
+ // Authenticate user and validate upload
23
23
 
24
- if (Number.isNaN(maxFileSizeMb)) {
25
- maxFileSizeMb = 50; // <- TODO: [🌲] To /config.ts
26
- }
24
+ // Parse client payload for additional metadata
25
+ const payload = clientPayload ? JSON.parse(clientPayload) : {};
26
+ const { purpose, contentType } = payload;
27
27
 
28
- const maxFileSize = maxFileSizeMb * 1024 * 1024;
28
+ let maxFileSizeMb = Number((await getMetadata('MAX_FILE_UPLOAD_SIZE_MB')) || '50'); // <- TODO: [🌲] To /config.ts
29
+ if (Number.isNaN(maxFileSizeMb)) {
30
+ maxFileSizeMb = 50; // <- TODO: [🌲] To /config.ts
31
+ }
32
+ const maxFileSize = maxFileSizeMb * 1024 * 1024;
33
+
34
+ // Generate the proper path with prefix
35
+ // Note: With client uploads, we use the original filename provided by the client
36
+ // The file will be stored at: {pathPrefix}/user/files/{filename}
37
+ const pathPrefix = process.env.NEXT_PUBLIC_CDN_PATH_PREFIX || '';
29
38
 
30
- const files = await new Promise<formidable.Files>((resolve, reject) => {
31
- const form = formidable({ maxFileSize });
32
- form.parse(nodeRequest as TODO_any, (error, fields, files) => {
33
- keepUnused(fields);
39
+ // Create a DB record at the start of the upload to track it
40
+ const uploadPurpose = purpose || 'GENERIC_UPLOAD';
41
+ const { data: insertedFile, error: insertError } = await supabase
42
+ .from(await $getTableName('File'))
43
+ .insert({
44
+ userId: userId || null,
45
+ fileName: pathname,
46
+ fileSize: 0, // <- Will be updated when upload completes
47
+ fileType: contentType || 'application/octet-stream',
48
+ storageUrl: null, // <- To be updated on completion
49
+ shortUrl: null, // <- To be updated on completion
50
+ purpose: uploadPurpose,
51
+ status: 'UPLOADING',
52
+ })
53
+ .select('id')
54
+ .single();
34
55
 
35
- if (error) {
36
- return reject(error);
56
+ if (insertError) {
57
+ console.error('🔼 Failed to create file record:', insertError);
37
58
  }
38
- resolve(files);
39
- });
40
- });
41
59
 
42
- const uploadedFiles = files.file;
60
+ console.info('🔼 Upload started, tracking file:', {
61
+ pathname,
62
+ fileId: insertedFile?.id,
63
+ purpose: uploadPurpose,
64
+ });
43
65
 
44
- if (!uploadedFiles || uploadedFiles.length !== 1) {
45
- return NextResponse.json(
46
- { message: 'In form data there is not EXACTLY one "file" field' },
47
- { status: 400 },
48
- );
49
- }
66
+ return {
67
+ allowedContentTypes: contentType ? [contentType] : undefined,
68
+ maximumSizeInBytes: maxFileSize,
69
+ addRandomSuffix: true, // Add random suffix to avoid filename collisions since we can't hash content
70
+ tokenPayload: JSON.stringify({
71
+ userId: userId || null,
72
+ purpose: uploadPurpose,
73
+ fileId: insertedFile?.id || null,
74
+ uploadPath: pathname,
75
+ pathPrefix,
76
+ }),
77
+ };
78
+ },
79
+ onUploadCompleted: async ({ blob, tokenPayload }) => {
80
+ // !!!!
81
+ // ⚠️ IMPORTANT: This callback is a WEBHOOK called by Vercel's servers AFTER the upload completes
82
+ // - It runs in a DIFFERENT request context (not the original user request)
83
+ // - It WON'T work in local development (Vercel can't reach localhost)
84
+ // - All data must come from tokenPayload (userId, fileId, etc.)
85
+ // - Need to create a fresh supabase client here
86
+ console.info('🔼 Upload completed (webhook callback):', { blob, tokenPayload });
50
87
 
51
- const uploadedFile = uploadedFiles[0]!;
52
- const fileBuffer = await readFile(uploadedFile.filepath);
53
- const cdn = $provideCdnForServer();
54
- const key = getUserFileCdnKey(fileBuffer, uploadedFile.originalFilename || uploadedFile.newFilename);
88
+ try {
89
+ const payload = tokenPayload ? JSON.parse(tokenPayload) : {};
90
+ const { fileId, userId: tokenUserId, purpose: tokenPurpose, uploadPath } = payload;
55
91
 
56
- await cdn.setItem(key, {
57
- type: validateMimeType(uploadedFile.mimetype),
58
- data: fileBuffer,
59
- });
92
+ // Create fresh supabase client for this webhook context
93
+ const supabase = $provideSupabase();
94
+
95
+ if (fileId) {
96
+ // Update the existing record by ID
97
+ const { error: updateError } = await supabase
98
+ .from(await $getTableName('File'))
99
+ .update({
100
+ userId: tokenUserId || null,
101
+ fileSize: 0, // <- !!!!
102
+ fileType: blob.contentType,
103
+ storageUrl: blob.url,
104
+ // <- TODO: !!!! Split between storageUrl and shortUrl
105
+ purpose: tokenPurpose || 'GENERIC_UPLOAD',
106
+ status: 'COMPLETED',
107
+ })
108
+ .eq('id', fileId);
60
109
 
61
- const fileUrl = cdn.getItemUrl(key);
110
+ if (updateError) {
111
+ console.error('🔼 Failed to update file record:', updateError);
112
+ } else {
113
+ console.info('🔼 File record updated successfully:', { fileId, shortUrl: blob.url });
114
+ }
115
+ } else if (uploadPath) {
116
+ // Fallback: Update by uploadPath if fileId is not available
117
+ const { error: updateError } = await supabase
118
+ .from(await $getTableName('File'))
119
+ .update({
120
+ fileSize: 0, // <- !!!!
121
+ fileType: blob.contentType,
122
+ storageUrl: blob.url,
123
+ status: 'COMPLETED',
124
+ })
125
+ .eq('id', fileId);
62
126
 
63
- return NextResponse.json({ fileUrl: fileUrl.href as string_url }, { status: 201 });
127
+ if (updateError) {
128
+ console.error('🔼 Failed to update file record by uploadPath:', updateError);
129
+ }
130
+ }
131
+ } catch (error) {
132
+ console.error('🔼 Error in onUploadCompleted:', error);
133
+ }
134
+ },
135
+ });
136
+
137
+ return NextResponse.json(jsonResponse);
64
138
  } catch (error) {
65
139
  assertsError(error);
66
140
 
67
- console.error(error);
141
+ console.error('🔼', error);
68
142
 
69
143
  return new Response(
70
144
  JSON.stringify(
@@ -81,3 +155,12 @@ export async function POST(request: NextRequest) {
81
155
  );
82
156
  }
83
157
  }
158
+
159
+ /**
160
+ * TODO: !!!! Change uploaded URLs from `storageUrl` to `shortUrl`
161
+ * TODO: !!!! Record both `storageUrl` (actual storage location) and `shortUrl` in `File` table
162
+ * TODO: !!!! Record `purpose` in `File` table
163
+ * TODO: !!!! Record `userId` in `File` table
164
+ * TODO: !!!! Record all things into `File` table
165
+ * TODO: !!!! File type (mime type) of `.book` files should be `application/book` <- [🧠] !!!! Best mime type?!
166
+ */
@@ -1,7 +1,7 @@
1
1
  import { notFound } from 'next/navigation';
2
2
  import { BookCommitment } from '../../../../../../src/commitments/_base/BookCommitment';
3
3
  import { getVisibleCommitmentDefinitions } from '../../../utils/getVisibleCommitmentDefinitions';
4
- import { PrintButton } from '../../../components/PrintButton/PrintButton';
4
+ import { DocsToolbar } from '../../../components/DocsToolbar/DocsToolbar';
5
5
  import { PrintHeader } from '../../../components/PrintHeader/PrintHeader';
6
6
  import { DocumentationContent } from '../../../components/DocumentationContent/DocumentationContent';
7
7
 
@@ -27,9 +27,8 @@ export default async function DocPage(props: DocPageProps) {
27
27
 
28
28
  return (
29
29
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
30
- <PrintButton />
31
-
32
30
  <div className="container mx-auto px-4 py-16">
31
+ <DocsToolbar />
33
32
  <PrintHeader title={primary.type} />
34
33
 
35
34
  <DocumentationContent