@promptbook/cli 0.103.0-48 → 0.103.0-50

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 (114) hide show
  1. package/apps/agents-server/README.md +1 -1
  2. package/apps/agents-server/TODO.txt +6 -5
  3. package/apps/agents-server/config.ts +130 -0
  4. package/apps/agents-server/next.config.ts +1 -1
  5. package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
  6. package/apps/agents-server/public/fonts/download-font.js +22 -0
  7. package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +11 -0
  8. package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
  9. package/apps/agents-server/src/app/actions.ts +37 -2
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
  13. package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
  15. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +121 -25
  16. package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
  18. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
  19. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +29 -10
  20. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +4 -5
  21. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
  22. package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +4 -4
  24. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
  25. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
  27. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
  28. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +117 -106
  30. package/apps/agents-server/src/app/agents/page.tsx +1 -1
  31. package/apps/agents-server/src/app/api/agents/route.ts +34 -0
  32. package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
  33. package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
  34. package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
  35. package/apps/agents-server/src/app/api/upload/route.ts +7 -3
  36. package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
  37. package/apps/agents-server/src/app/api/users/route.ts +71 -0
  38. package/apps/agents-server/src/app/globals.css +35 -1
  39. package/apps/agents-server/src/app/layout.tsx +43 -23
  40. package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
  41. package/apps/agents-server/src/app/metadata/page.tsx +13 -0
  42. package/apps/agents-server/src/app/not-found.tsx +5 -0
  43. package/apps/agents-server/src/app/page.tsx +117 -46
  44. package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
  45. package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
  46. package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
  47. package/apps/agents-server/src/components/Header/Header.tsx +146 -0
  48. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
  49. package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
  50. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
  51. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
  52. package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
  53. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
  54. package/apps/agents-server/src/database/$getTableName.ts +18 -0
  55. package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
  56. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
  57. package/apps/agents-server/src/database/getMetadata.ts +31 -0
  58. package/apps/agents-server/src/database/metadataDefaults.ts +37 -0
  59. package/apps/agents-server/src/database/schema.sql +81 -33
  60. package/apps/agents-server/src/database/schema.ts +35 -1
  61. package/apps/agents-server/src/middleware.ts +200 -0
  62. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
  63. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  64. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
  65. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
  66. package/apps/agents-server/src/tools/$provideServer.ts +39 -0
  67. package/apps/agents-server/src/utils/auth.ts +33 -0
  68. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
  69. package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
  70. package/apps/agents-server/src/utils/getFederatedAgents.ts +66 -0
  71. package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
  72. package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
  73. package/apps/agents-server/src/utils/session.ts +50 -0
  74. package/apps/agents-server/tailwind.config.ts +2 -0
  75. package/esm/index.es.js +147 -31
  76. package/esm/index.es.js.map +1 -1
  77. package/esm/typings/servers.d.ts +1 -0
  78. package/esm/typings/src/_packages/components.index.d.ts +2 -0
  79. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  80. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  81. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
  82. package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +20 -0
  83. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  84. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  85. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  86. package/esm/typings/src/commitments/index.d.ts +2 -1
  87. package/esm/typings/src/config.d.ts +1 -0
  88. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  89. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  90. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  91. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  92. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  93. package/esm/typings/src/llm-providers/agent/Agent.d.ts +19 -3
  94. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +13 -1
  95. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +11 -2
  96. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
  97. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  98. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  99. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  100. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  101. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  102. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  103. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  104. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  105. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  106. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  107. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  108. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  109. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  110. package/esm/typings/src/version.d.ts +1 -1
  111. package/package.json +1 -1
  112. package/umd/index.umd.js +147 -31
  113. package/umd/index.umd.js.map +1 -1
  114. package/apps/agents-server/config.ts.todo +0 -38
@@ -4,17 +4,20 @@
4
4
  -- To update, search for [💽]
5
5
 
6
6
 
7
-
8
- CREATE TABLE IF NOT EXISTS "EnvironmentVariable" (
7
+ CREATE TABLE IF NOT EXISTS "prefix_Metadata" (
9
8
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
9
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
10
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
10
11
  "key" TEXT NOT NULL,
11
12
  "value" TEXT NOT NULL,
12
13
  "note" TEXT NULL,
13
- CONSTRAINT EnvironmentVariable_key_key UNIQUE ("key")
14
+ CONSTRAINT prefix_Metadata_key_key UNIQUE ("key")
14
15
  );
15
16
 
17
+ ALTER TABLE "prefix_Metadata" ENABLE ROW LEVEL SECURITY;
16
18
 
17
- CREATE TABLE IF NOT EXISTS "Agent" (
19
+
20
+ CREATE TABLE IF NOT EXISTS "prefix_Agent" (
18
21
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
19
22
  "agentName" TEXT NOT NULL,
20
23
 
@@ -33,18 +36,20 @@ CREATE TABLE IF NOT EXISTS "Agent" (
33
36
  "preparedExternals" JSONB NULL
34
37
  );
35
38
  -- Ensure uniqueness of agentName even on repeated schema application without duplicate constraint creation
36
- CREATE UNIQUE INDEX IF NOT EXISTS agent_agentname_key ON "Agent" ("agentName");
37
- COMMENT ON COLUMN "Agent"."agentName" IS 'The unique name of the agent derived from the first line of the `agentSource`, automatically updated on `agentSource` change';
38
- COMMENT ON COLUMN "Agent"."agentHash" IS 'The hash of the `agentSource`, automatically updated on `agentSource` change';
39
- COMMENT ON COLUMN "Agent"."agentSource" IS 'The source code of the agent';
40
- COMMENT ON COLUMN "Agent"."agentProfile" IS 'The profile of the agent generated from the `agentSource`, automatically updated on `agentSource` change <- TODO: [🕛]';
41
- COMMENT ON COLUMN "Agent"."promptbookEngineVersion" IS 'The version of the Promptbook engine used for last update of the agent';
42
- COMMENT ON COLUMN "Agent"."usage" IS 'Usage and spending statistics for the agent';
43
- COMMENT ON COLUMN "Agent"."preparedModelRequirements" IS 'The prepared model requirements for the agent, generated from the `agentSource` but not after every change (only on explicit request), there is `agentHash` to identify the version of the `agentSource` this was prepared from';
44
- COMMENT ON COLUMN "Agent"."preparedExternals" IS 'The prepared externals for the agent, generated from the `agentSource` but not after every change (only on explicit request), there is `agentHash` to identify the version of the `agentSource` this was prepared from';
45
-
46
-
47
- CREATE TABLE IF NOT EXISTS "AgentHistory" (
39
+ CREATE UNIQUE INDEX IF NOT EXISTS prefix_agent_agentname_key ON "prefix_Agent" ("agentName");
40
+ COMMENT ON COLUMN "prefix_Agent"."agentName" IS 'The unique name of the agent derived from the first line of the `agentSource`, automatically updated on `agentSource` change';
41
+ COMMENT ON COLUMN "prefix_Agent"."agentHash" IS 'The hash of the `agentSource`, automatically updated on `agentSource` change';
42
+ COMMENT ON COLUMN "prefix_Agent"."agentSource" IS 'The source code of the agent';
43
+ COMMENT ON COLUMN "prefix_Agent"."agentProfile" IS 'The profile of the agent generated from the `agentSource`, automatically updated on `agentSource` change <- TODO: [🕛]';
44
+ COMMENT ON COLUMN "prefix_Agent"."promptbookEngineVersion" IS 'The version of the Promptbook engine used for last update of the agent';
45
+ COMMENT ON COLUMN "prefix_Agent"."usage" IS 'Usage and spending statistics for the agent';
46
+ COMMENT ON COLUMN "prefix_Agent"."preparedModelRequirements" IS 'The prepared model requirements for the agent, generated from the `agentSource` but not after every change (only on explicit request), there is `agentHash` to identify the version of the `agentSource` this was prepared from';
47
+ COMMENT ON COLUMN "prefix_Agent"."preparedExternals" IS 'The prepared externals for the agent, generated from the `agentSource` but not after every change (only on explicit request), there is `agentHash` to identify the version of the `agentSource` this was prepared from';
48
+
49
+ ALTER TABLE "prefix_Agent" ENABLE ROW LEVEL SECURITY;
50
+
51
+
52
+ CREATE TABLE IF NOT EXISTS "prefix_AgentHistory" (
48
53
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
49
54
  "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
50
55
 
@@ -56,11 +61,13 @@ CREATE TABLE IF NOT EXISTS "AgentHistory" (
56
61
 
57
62
  );
58
63
 
59
- CREATE INDEX IF NOT EXISTS "AgentHistory_agentName_idx" ON "AgentHistory" ("agentName");
60
- CREATE INDEX IF NOT EXISTS "AgentHistory_agentHash_idx" ON "AgentHistory" ("agentHash");
64
+ CREATE INDEX IF NOT EXISTS "prefix_AgentHistory_agentName_idx" ON "prefix_AgentHistory" ("agentName");
65
+ CREATE INDEX IF NOT EXISTS "prefix_AgentHistory_agentHash_idx" ON "prefix_AgentHistory" ("agentHash");
61
66
 
67
+ ALTER TABLE "prefix_AgentHistory" ENABLE ROW LEVEL SECURITY;
62
68
 
63
- CREATE TABLE IF NOT EXISTS "ChatHistory" (
69
+
70
+ CREATE TABLE IF NOT EXISTS "prefix_ChatHistory" (
64
71
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
65
72
  "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
66
73
 
@@ -78,14 +85,16 @@ CREATE TABLE IF NOT EXISTS "ChatHistory" (
78
85
  "language" TEXT NULL,
79
86
  "platform" TEXT NULL
80
87
  );
81
- COMMENT ON COLUMN "ChatHistory"."url" IS 'The URL where the chat was happening';
82
- COMMENT ON COLUMN "ChatHistory"."ip" IS 'The IP address of the user';
83
- COMMENT ON COLUMN "ChatHistory"."userAgent" IS 'The user agent (browser) of the user';
84
- COMMENT ON COLUMN "ChatHistory"."language" IS 'The language (from the browser) of the user';
85
- COMMENT ON COLUMN "ChatHistory"."platform" IS 'The platform of the user';
88
+ COMMENT ON COLUMN "prefix_ChatHistory"."url" IS 'The URL where the chat was happening';
89
+ COMMENT ON COLUMN "prefix_ChatHistory"."ip" IS 'The IP address of the user';
90
+ COMMENT ON COLUMN "prefix_ChatHistory"."userAgent" IS 'The user agent (browser) of the user';
91
+ COMMENT ON COLUMN "prefix_ChatHistory"."language" IS 'The language (from the browser) of the user';
92
+ COMMENT ON COLUMN "prefix_ChatHistory"."platform" IS 'The platform of the user';
93
+
94
+ ALTER TABLE "prefix_ChatHistory" ENABLE ROW LEVEL SECURITY;
86
95
 
87
96
 
88
- CREATE TABLE IF NOT EXISTS "ChatFeedback" (
97
+ CREATE TABLE IF NOT EXISTS "prefix_ChatFeedback" (
89
98
  "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
90
99
  "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
91
100
 
@@ -105,15 +114,31 @@ CREATE TABLE IF NOT EXISTS "ChatFeedback" (
105
114
  "language" TEXT NULL,
106
115
  "platform" TEXT NULL
107
116
  );
108
- COMMENT ON COLUMN "ChatFeedback"."url" IS 'The URL where the chat was happening';
109
- COMMENT ON COLUMN "ChatFeedback"."ip" IS 'The IP address of the user';
110
- COMMENT ON COLUMN "ChatFeedback"."userAgent" IS 'The user agent (browser) of the user';
111
- COMMENT ON COLUMN "ChatFeedback"."language" IS 'The language (from the browser) of the user';
112
- COMMENT ON COLUMN "ChatFeedback"."platform" IS 'The platform of the user';
117
+ COMMENT ON COLUMN "prefix_ChatFeedback"."url" IS 'The URL where the chat was happening';
118
+ COMMENT ON COLUMN "prefix_ChatFeedback"."ip" IS 'The IP address of the user';
119
+ COMMENT ON COLUMN "prefix_ChatFeedback"."userAgent" IS 'The user agent (browser) of the user';
120
+ COMMENT ON COLUMN "prefix_ChatFeedback"."language" IS 'The language (from the browser) of the user';
121
+ COMMENT ON COLUMN "prefix_ChatFeedback"."platform" IS 'The platform of the user';
122
+
123
+ ALTER TABLE "prefix_ChatFeedback" ENABLE ROW LEVEL SECURITY;
124
+
125
+
126
+ CREATE TABLE IF NOT EXISTS "prefix_User" (
127
+ "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
128
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
129
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
130
+
131
+ "username" TEXT NOT NULL,
132
+ "passwordHash" TEXT NOT NULL,
133
+ "isAdmin" BOOLEAN NOT NULL DEFAULT FALSE
134
+ );
135
+ CREATE UNIQUE INDEX IF NOT EXISTS "prefix_User_username_idx" ON "prefix_User" ("username");
136
+
137
+ ALTER TABLE "prefix_User" ENABLE ROW LEVEL SECURITY;
113
138
 
114
139
 
115
140
  /*
116
- CREATE TABLE IF NOT EXISTS "AgentActionHistory" (
141
+ CREATE TABLE IF NOT EXISTS "prefix_AgentActionHistory" (
117
142
  id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
118
143
  createdAt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
119
144
 
@@ -122,10 +147,33 @@ createdAt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
122
147
 
123
148
 
124
149
  /*
125
- CREATE TABLE IF NOT EXISTS "Xxx" (
150
+ CREATE TABLE IF NOT EXISTS "prefix_Xxx" (
126
151
  id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
127
152
  );
128
153
  */
129
154
 
130
155
 
131
- -- TODO: !!!! Create propper database migrations
156
+ -- Foreign key relationships
157
+ -- Note: Using agentName as the foreign key reference since it's the unique identifier used across tables
158
+ ALTER TABLE "prefix_AgentHistory"
159
+ DROP CONSTRAINT IF EXISTS "prefix_AgentHistory_agentName_fkey",
160
+ ADD CONSTRAINT "prefix_AgentHistory_agentName_fkey"
161
+ FOREIGN KEY ("agentName")
162
+ REFERENCES "prefix_Agent"("agentName")
163
+ ON DELETE CASCADE;
164
+
165
+ ALTER TABLE "prefix_ChatHistory"
166
+ DROP CONSTRAINT IF EXISTS "prefix_ChatHistory_agentName_fkey",
167
+ ADD CONSTRAINT "prefix_ChatHistory_agentName_fkey"
168
+ FOREIGN KEY ("agentName")
169
+ REFERENCES "prefix_Agent"("agentName")
170
+ ON DELETE CASCADE;
171
+
172
+ ALTER TABLE "prefix_ChatFeedback"
173
+ DROP CONSTRAINT IF EXISTS "prefix_ChatFeedback_agentName_fkey",
174
+ ADD CONSTRAINT "prefix_ChatFeedback_agentName_fkey"
175
+ FOREIGN KEY ("agentName")
176
+ REFERENCES "prefix_Agent"("agentName")
177
+ ON DELETE CASCADE;
178
+
179
+ -- TODO: [🐱‍🚀] Create propper database migrations
@@ -11,23 +11,30 @@ export type Json = string | number | boolean | null | { [key: string]: Json | un
11
11
 
12
12
  // Public schema database interface (Supabase convention)
13
13
  export type AgentsServerDatabase = {
14
+ // <- TODO: [🧠][🕜] Better naming
14
15
  public: {
15
16
  Tables: {
16
- EnvironmentVariable: {
17
+ Metadata: {
17
18
  Row: {
18
19
  id: number;
20
+ createdAt: string;
21
+ updatedAt: string;
19
22
  key: string;
20
23
  value: string;
21
24
  note: string | null;
22
25
  };
23
26
  Insert: {
24
27
  id?: number;
28
+ createdAt?: string;
29
+ updatedAt?: string;
25
30
  key: string;
26
31
  value: string;
27
32
  note?: string | null;
28
33
  };
29
34
  Update: {
30
35
  id?: number;
36
+ createdAt?: string;
37
+ updatedAt?: string;
31
38
  key?: string;
32
39
  value?: string;
33
40
  note?: string | null;
@@ -208,6 +215,33 @@ export type AgentsServerDatabase = {
208
215
  };
209
216
  Relationships: [];
210
217
  };
218
+ User: {
219
+ Row: {
220
+ id: number;
221
+ createdAt: string;
222
+ updatedAt: string;
223
+ username: string;
224
+ passwordHash: string;
225
+ isAdmin: boolean;
226
+ };
227
+ Insert: {
228
+ id?: number;
229
+ createdAt?: string;
230
+ updatedAt?: string;
231
+ username: string;
232
+ passwordHash: string;
233
+ isAdmin?: boolean;
234
+ };
235
+ Update: {
236
+ id?: number;
237
+ createdAt?: string;
238
+ updatedAt?: string;
239
+ username?: string;
240
+ passwordHash?: string;
241
+ isAdmin?: boolean;
242
+ };
243
+ Relationships: [];
244
+ };
211
245
  };
212
246
  Views: Record<string, never>;
213
247
  Functions: Record<string, never>;
@@ -0,0 +1,200 @@
1
+ import { TODO_any } from '@promptbook-local/types';
2
+ import { createClient } from '@supabase/supabase-js';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import { SERVERS, SUPABASE_TABLE_PREFIX } from '../config';
5
+ import { isIpAllowed } from './utils/isIpAllowed';
6
+
7
+ // Note: Re-implementing normalizeTo_PascalCase to avoid importing from @promptbook-local/utils which might have Node.js dependencies
8
+ function normalizeTo_PascalCase(text: string): string {
9
+ return text
10
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
11
+ return word.toUpperCase();
12
+ })
13
+ .replace(/\s+/g, '');
14
+ }
15
+
16
+ export async function middleware(req: NextRequest) {
17
+ // 1. Get client IP
18
+ let ip = (req as TODO_any).ip;
19
+ const xForwardedFor = req.headers.get('x-forwarded-for');
20
+ if (!ip && xForwardedFor) {
21
+ ip = xForwardedFor.split(',')[0].trim();
22
+ }
23
+ // Fallback for local development if needed, though req.ip is usually ::1 or 127.0.0.1
24
+ ip = ip || '127.0.0.1';
25
+
26
+ // 2. Determine allowed IPs
27
+ // Priority: Metadata > Environment Variable
28
+
29
+ const allowedIpsEnv = process.env.RESTRICT_IP;
30
+ let allowedIpsMetadata: string | null = null;
31
+
32
+ // To fetch metadata, we need to know the table name, which depends on the host
33
+ const host = req.headers.get('host');
34
+
35
+ if (host) {
36
+ let tablePrefix = SUPABASE_TABLE_PREFIX;
37
+
38
+ if (SERVERS && SERVERS.length > 0) {
39
+ // Logic mirrored from src/tools/$provideServer.ts
40
+ if (SERVERS.some((server) => server === host)) {
41
+ let serverName = host;
42
+ serverName = serverName.replace(/\.ptbk\.io$/, '');
43
+ serverName = normalizeTo_PascalCase(serverName);
44
+ tablePrefix = `server_${serverName}_`;
45
+ }
46
+ }
47
+
48
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
49
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
50
+
51
+ if (supabaseUrl && supabaseKey) {
52
+ try {
53
+ const supabase = createClient(supabaseUrl, supabaseKey, {
54
+ auth: {
55
+ persistSession: false,
56
+ autoRefreshToken: false,
57
+ },
58
+ });
59
+
60
+ const { data } = await supabase
61
+ .from(`${tablePrefix}Metadata`)
62
+ .select('value')
63
+ .eq('key', 'RESTRICT_IP')
64
+ .single();
65
+
66
+ if (data && data.value) {
67
+ allowedIpsMetadata = data.value;
68
+ }
69
+ } catch (error) {
70
+ console.error('Error fetching metadata in middleware:', error);
71
+ }
72
+ }
73
+ }
74
+
75
+ const allowedIps =
76
+ allowedIpsMetadata !== null && allowedIpsMetadata !== undefined ? allowedIpsMetadata : allowedIpsEnv;
77
+
78
+ if (isIpAllowed(ip, allowedIps)) {
79
+ // Handle OPTIONS (preflight) requests before any redirects
80
+ // to avoid CORS issues ("Redirect is not allowed for a preflight request")
81
+ if (req.method === 'OPTIONS') {
82
+ return new NextResponse(null, {
83
+ status: 200,
84
+ headers: {
85
+ 'Access-Control-Allow-Origin': '*',
86
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
87
+ 'Access-Control-Allow-Headers': 'Content-Type',
88
+ },
89
+ });
90
+ }
91
+
92
+ // 3. Redirect /:agentName/* to /agents/:agentName/*
93
+ // This enables accessing agents from the root path
94
+ const pathParts = req.nextUrl.pathname.split('/');
95
+ const potentialAgentName = pathParts[1];
96
+
97
+ if (
98
+ potentialAgentName &&
99
+ !['agents', 'api', '_next', 'favicon.ico'].includes(potentialAgentName) &&
100
+ !potentialAgentName.startsWith('.') &&
101
+ // Note: Other static files are excluded by the matcher configuration below
102
+ true
103
+ ) {
104
+ const url = req.nextUrl.clone();
105
+ url.pathname = `/agents${req.nextUrl.pathname}`;
106
+ const response = NextResponse.redirect(url);
107
+
108
+ // Enable CORS for the redirect
109
+ response.headers.set('Access-Control-Allow-Origin', '*');
110
+ response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
111
+ response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
112
+
113
+ return response;
114
+ }
115
+
116
+ // 4. Custom Domain Routing
117
+ // If the host is not one of the configured SERVERS, try to find an agent with a matching META LINK
118
+
119
+ if (host && SERVERS && !SERVERS.some((server) => server === host)) {
120
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
121
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
122
+
123
+ if (supabaseUrl && supabaseKey) {
124
+ const supabase = createClient(supabaseUrl, supabaseKey, {
125
+ auth: {
126
+ persistSession: false,
127
+ autoRefreshToken: false,
128
+ },
129
+ });
130
+
131
+ // Determine prefixes to check
132
+ // We check all configured servers because the custom domain could point to any of them
133
+ // (or if they share the database, we need to check the relevant tables)
134
+ const serversToCheck = SERVERS;
135
+
136
+ // TODO: [🧠] If there are many servers, this loop might be slow. Optimize if needed.
137
+ for (const serverHost of serversToCheck) {
138
+ let serverName = serverHost;
139
+ serverName = serverName.replace(/\.ptbk\.io$/, '');
140
+ serverName = normalizeTo_PascalCase(serverName);
141
+ const prefix = `server_${serverName}_`;
142
+
143
+ // Search for agent with matching META LINK
144
+ // agentProfile->links is an array of strings
145
+ // We check if it contains the host, or https://host, or http://host
146
+
147
+ const searchLinks = [host, `https://${host}`, `http://${host}`];
148
+
149
+ // Construct OR filter: agentProfile.cs.{"links":["link1"]},agentProfile.cs.{"links":["link2"]},...
150
+ const orFilter = searchLinks.map((link) => `agentProfile.cs.{"links":["${link}"]}`).join(',');
151
+
152
+ try {
153
+ const { data } = await supabase
154
+ .from(`${prefix}Agent`)
155
+ .select('agentName')
156
+ .or(orFilter)
157
+ .limit(1)
158
+ .single();
159
+
160
+ if (data && data.agentName) {
161
+ // Found the agent!
162
+ const url = req.nextUrl.clone();
163
+ url.pathname = `/${data.agentName}`;
164
+
165
+ // Pass the server context to the app via header
166
+ const requestHeaders = new Headers(req.headers);
167
+ requestHeaders.set('x-promptbook-server', serverHost);
168
+
169
+ return NextResponse.rewrite(url, {
170
+ request: {
171
+ headers: requestHeaders,
172
+ },
173
+ });
174
+ }
175
+ } catch (error) {
176
+ // Ignore error (e.g. table not found, or agent not found) and continue to next server
177
+ // console.error(`Error checking server ${serverHost} for custom domain ${host}:`, error);
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ return NextResponse.next();
184
+ }
185
+
186
+ return new NextResponse('Forbidden', { status: 403 });
187
+ }
188
+
189
+ export const config = {
190
+ matcher: [
191
+ /*
192
+ * Match all request paths except for the ones starting with:
193
+ * - _next/static (static files)
194
+ * - _next/image (image optimization files)
195
+ * - favicon.ico (favicon file)
196
+ * - public folder
197
+ */
198
+ '/((?!_next/static|_next/image|favicon.ico|logo-|fonts/).*)',
199
+ ],
200
+ };
@@ -2,7 +2,9 @@
2
2
 
3
3
  import { AgentCollectionInSupabase } from '@promptbook-local/core';
4
4
  import { AgentCollection } from '@promptbook-local/types';
5
+ import { just } from '../../../../src/utils/organization/just';
5
6
  import { $provideSupabaseForServer } from '../database/$provideSupabaseForServer';
7
+ import { $provideServer } from './$provideServer';
6
8
 
7
9
  /**
8
10
  * Cache of provided agent collection
@@ -12,22 +14,22 @@ import { $provideSupabaseForServer } from '../database/$provideSupabaseForServer
12
14
  let agentCollection: null | AgentCollection = null;
13
15
 
14
16
  /**
15
- * !!!!
17
+ * [🐱‍🚀]
16
18
  */
17
19
  export async function $provideAgentCollectionForServer(): Promise<AgentCollection> {
18
20
  // <- Note: This function is potentially async
19
21
 
20
- // TODO: !!!! [🌕] DRY
22
+ // TODO: [🐱‍🚀] [🌕] DRY
21
23
 
22
- const isVerbose = true; // <- TODO: !!!! Pass
24
+ const isVerbose = true; // <- TODO: [🐱‍🚀] Pass
23
25
 
24
- if (agentCollection !== null) {
25
- console.log('!!! Returning cached agent collection');
26
+ if (agentCollection !== null && just(false /* <- TODO: [🐱‍🚀] Fix caching */)) {
27
+ console.log('[🐱‍🚀] Returning cached agent collection');
26
28
  return agentCollection;
27
- // TODO: !!!! Be aware of options changes
29
+ // TODO: [🐱‍🚀] Be aware of options changes
28
30
  }
29
31
 
30
- console.log('!!! Creating NEW agent collection');
32
+ console.log('[🐱‍🚀] Creating NEW agent collection');
31
33
 
32
34
  /*
33
35
  // TODO: [🧟‍♂️][◽] DRY:
@@ -41,9 +43,11 @@ export async function $provideAgentCollectionForServer(): Promise<AgentCollectio
41
43
  */
42
44
 
43
45
  const supabase = $provideSupabaseForServer();
46
+ const { tablePrefix } = await $provideServer();
44
47
 
45
48
  agentCollection = new AgentCollectionInSupabase(supabase, {
46
49
  isVerbose,
50
+ tablePrefix,
47
51
  });
48
52
 
49
53
  return agentCollection;
@@ -9,7 +9,7 @@ import { IIFilesStorageWithCdn } from '../utils/cdn/interfaces/IFilesStorage';
9
9
  let cdn: IIFilesStorageWithCdn | null = null;
10
10
 
11
11
  /**
12
- * !!!
12
+ * [🐱‍🚀]
13
13
  */
14
14
  export function $provideCdnForServer(): IIFilesStorageWithCdn {
15
15
  if (!cdn) {
@@ -43,11 +43,11 @@ $sideEffect(
43
43
  _MarkitdownScraperMetadataRegistration,
44
44
  _PdfScraperMetadataRegistration,
45
45
  _WebsiteScraperMetadataRegistration,
46
- // <- TODO: !!! Export all registrations from one variabile in `@promptbook/core`
46
+ // <- TODO: [🐱‍🚀] Export all registrations from one variabile in `@promptbook/core`
47
47
  );
48
48
  $sideEffect(/* [㊗] */ _OpenAiRegistration);
49
49
  $sideEffect(/* [㊗] */ _GoogleRegistration);
50
- // <- TODO: !!!! Allow to dynamically install required metadata
50
+ // <- TODO: [🐱‍🚀] Allow to dynamically install required metadata
51
51
 
52
52
  /**
53
53
  * Cache of provided execution tools
@@ -56,8 +56,6 @@ $sideEffect(/* [㊗] */ _GoogleRegistration);
56
56
  */
57
57
  let executionTools: null | ExecutionTools = null;
58
58
 
59
-
60
-
61
59
  /*
62
60
  TODO: [▶️]
63
61
  type ProvideExecutionToolsForServerOptions = {
@@ -66,25 +64,25 @@ type ProvideExecutionToolsForServerOptions = {
66
64
  */
67
65
 
68
66
  /**
69
- * !!!!
67
+ * [🐱‍🚀]
70
68
  */
71
69
  export async function $provideExecutionToolsForServer(): Promise<ExecutionTools> {
72
- // TODO: !!!! [🌕] DRY
70
+ // TODO: [🐱‍🚀] [🌕] DRY
73
71
 
74
- // const path = '../../agents'; // <- TODO: !!!! Pass
75
- const isVerbose = true; // <- TODO: !!!! Pass
76
- const isCacheReloaded = false; // <- TODO: !!!! Pass
72
+ // const path = '../../agents'; // <- TODO: [🐱‍🚀] Pass
73
+ const isVerbose = true; // <- TODO: [🐱‍🚀] Pass
74
+ const isCacheReloaded = false; // <- TODO: [🐱‍🚀] Pass
77
75
  const cliOptions = {
78
76
  provider: 'BRING_YOUR_OWN_KEYS',
79
- } as TODO_any; // <- TODO: !!!! Pass
77
+ } as TODO_any; // <- TODO: [🐱‍🚀] Pass
80
78
 
81
79
  if (executionTools !== null) {
82
- console.log('!!! Returning cached execution tools');
80
+ console.log('[🐱‍🚀] Returning cached execution tools');
83
81
  return executionTools;
84
- // TODO: !!!! Be aware of options changes
82
+ // TODO: [🐱‍🚀] Be aware of options changes
85
83
  }
86
84
 
87
- console.log('!!! Creating NEW execution tools');
85
+ console.log('[🐱‍🚀] Creating NEW execution tools');
88
86
 
89
87
  // TODO: DRY [◽]
90
88
  const prepareAndScrapeOptions = {
@@ -10,23 +10,23 @@ import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
10
10
  let executionTools: null | OpenAiAssistantExecutionTools = null;
11
11
 
12
12
  /**
13
- * !!!!
13
+ * [🐱‍🚀]
14
14
  */
15
15
  export async function $provideOpenAiAssistantExecutionToolsForServer(): Promise<OpenAiAssistantExecutionTools> {
16
- // TODO: !!!! [🌕] DRY
17
- const isVerbose = true; // <- TODO: !!!! Pass
16
+ // TODO: [🐱‍🚀] [🌕] DRY
17
+ const isVerbose = true; // <- TODO: [🐱‍🚀] Pass
18
18
 
19
19
  if (executionTools !== null) {
20
- console.log('!!! Returning cached OpenAiAssistantExecutionTools');
20
+ console.log('[🐱‍🚀] Returning cached OpenAiAssistantExecutionTools');
21
21
  return executionTools;
22
- // TODO: !!!! Be aware of options changes
22
+ // TODO: [🐱‍🚀] Be aware of options changes
23
23
  }
24
24
 
25
- console.log('!!! Creating NEW OpenAiAssistantExecutionTools');
25
+ console.log('[🐱‍🚀] Creating NEW OpenAiAssistantExecutionTools');
26
26
 
27
27
  executionTools = new OpenAiAssistantExecutionTools({
28
28
  apiKey: process.env.OPENAI_API_KEY,
29
- assistantId: 'abstract_assistant', // <- TODO: !!!! In `OpenAiAssistantExecutionTools` Allow to create abstract assistants with `isCreatingNewAssistantsAllowed`
29
+ assistantId: 'abstract_assistant', // <- TODO: [🐱‍🚀] In `OpenAiAssistantExecutionTools` Allow to create abstract assistants with `isCreatingNewAssistantsAllowed`
30
30
  isCreatingNewAssistantsAllowed: true,
31
31
  isVerbose,
32
32
  });
@@ -0,0 +1,39 @@
1
+ import { NEXT_PUBLIC_URL, SERVERS, SUPABASE_TABLE_PREFIX } from '@/config';
2
+ import { normalizeTo_PascalCase } from '@promptbook-local/utils';
3
+ import { headers } from 'next/headers';
4
+
5
+ export async function $provideServer() {
6
+ if (!SERVERS) {
7
+ return {
8
+ publicUrl: NEXT_PUBLIC_URL || new URL(`https://${(await headers()).get('host') || 'localhost:4440'}`),
9
+ tablePrefix: SUPABASE_TABLE_PREFIX,
10
+ };
11
+ }
12
+
13
+ const headersList = await headers();
14
+ let host = headersList.get('host');
15
+ const xPromptbookServer = headersList.get('x-promptbook-server');
16
+
17
+ if (host === null) {
18
+ throw new Error('Host header is missing');
19
+ }
20
+
21
+ // If host is not in known servers, check if we have a context header from middleware
22
+ if (!SERVERS.some((server) => server === host)) {
23
+ if (xPromptbookServer && SERVERS.some((server) => server === xPromptbookServer)) {
24
+ host = xPromptbookServer;
25
+ } else {
26
+ throw new Error(`Server with host "${host}" is not configured in SERVERS`);
27
+ }
28
+ }
29
+
30
+ let serverName = host;
31
+
32
+ serverName = serverName.replace(/\.ptbk\.io$/, '');
33
+ serverName = normalizeTo_PascalCase(serverName);
34
+
35
+ return {
36
+ publicUrl: new URL(`https://${host}`),
37
+ tablePrefix: `server_${serverName}_`,
38
+ };
39
+ }
@@ -0,0 +1,33 @@
1
+ import { randomBytes, scrypt, timingSafeEqual } from 'crypto';
2
+ import { promisify } from 'util';
3
+
4
+ const scryptAsync = promisify(scrypt);
5
+
6
+ /**
7
+ * Hashes a password using scrypt
8
+ *
9
+ * @param password The plain text password
10
+ * @returns The salt and hash formatted as "salt:hash"
11
+ */
12
+ export async function hashPassword(password: string): Promise<string> {
13
+ const salt = randomBytes(16).toString('hex');
14
+ const derivedKey = (await scryptAsync(password, salt, 64)) as Buffer;
15
+ return `${salt}:${derivedKey.toString('hex')}`;
16
+ }
17
+
18
+ /**
19
+ * Verifies a password against a stored hash
20
+ *
21
+ * @param password The plain text password
22
+ * @param storedHash The stored hash in format "salt:hash"
23
+ * @returns True if the password matches
24
+ */
25
+ export async function verifyPassword(password: string, storedHash: string): Promise<boolean> {
26
+ const [salt, key] = storedHash.split(':');
27
+ if (!salt || !key) return false;
28
+
29
+ const derivedKey = (await scryptAsync(password, salt, 64)) as Buffer;
30
+ const keyBuffer = Buffer.from(key, 'hex');
31
+
32
+ return timingSafeEqual(derivedKey, keyBuffer);
33
+ }