@superinterface/server 1.0.0

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 (217) hide show
  1. package/README.md +36 -0
  2. package/eslint.config.mjs +31 -0
  3. package/next.config.ts +7 -0
  4. package/package.json +176 -0
  5. package/prisma/Account.prisma +18 -0
  6. package/prisma/ApiKey.prisma +13 -0
  7. package/prisma/Assistant.prisma +32 -0
  8. package/prisma/AssistantHandler.prisma +12 -0
  9. package/prisma/Avatar.prisma +10 -0
  10. package/prisma/ClientToolHandler.prisma +13 -0
  11. package/prisma/CodeInterpreterTool.prisma +7 -0
  12. package/prisma/ComputerUseTool.prisma +12 -0
  13. package/prisma/CreateTaskHandler.prisma +10 -0
  14. package/prisma/DeleteTaskHandler.prisma +10 -0
  15. package/prisma/FileSearchTool.prisma +9 -0
  16. package/prisma/FirecrawlHandler.prisma +13 -0
  17. package/prisma/Function.prisma +13 -0
  18. package/prisma/Handler.prisma +19 -0
  19. package/prisma/HttpTransport.prisma +12 -0
  20. package/prisma/IconAvatar.prisma +8 -0
  21. package/prisma/ImageAvatar.prisma +8 -0
  22. package/prisma/ImageGenerationTool.prisma +11 -0
  23. package/prisma/InitialMessage.prisma +17 -0
  24. package/prisma/Invitation.prisma +15 -0
  25. package/prisma/ListTasksHandler.prisma +10 -0
  26. package/prisma/Log.prisma +21 -0
  27. package/prisma/McpServer.prisma +14 -0
  28. package/prisma/Message.prisma +30 -0
  29. package/prisma/ModelProvider.prisma +15 -0
  30. package/prisma/Organization.prisma +10 -0
  31. package/prisma/OrganizationApiKey.prisma +12 -0
  32. package/prisma/OrganizationInvitation.prisma +15 -0
  33. package/prisma/OrganizationUserRole.prisma +14 -0
  34. package/prisma/ReplicateHandler.prisma +14 -0
  35. package/prisma/RequestHandler.prisma +15 -0
  36. package/prisma/Run.prisma +36 -0
  37. package/prisma/RunStep.prisma +30 -0
  38. package/prisma/Session.prisma +7 -0
  39. package/prisma/SseTransport.prisma +12 -0
  40. package/prisma/StdioTransport.prisma +11 -0
  41. package/prisma/Task.prisma +15 -0
  42. package/prisma/Thread.prisma +20 -0
  43. package/prisma/Tool.prisma +13 -0
  44. package/prisma/UpdateTaskHandler.prisma +10 -0
  45. package/prisma/User.prisma +16 -0
  46. package/prisma/UserRole.prisma +14 -0
  47. package/prisma/VerificationToken.prisma +7 -0
  48. package/prisma/WebSearchTool.prisma +7 -0
  49. package/prisma/Workspace.prisma +14 -0
  50. package/prisma/enums/ApiKeyType.prisma +4 -0
  51. package/prisma/enums/AvatarType.prisma +5 -0
  52. package/prisma/enums/ClientToolHandlerType.prisma +3 -0
  53. package/prisma/enums/ComputerUseToolEnvironment.prisma +6 -0
  54. package/prisma/enums/FirecrawlHandlerType.prisma +6 -0
  55. package/prisma/enums/HandlerType.prisma +11 -0
  56. package/prisma/enums/IconAvatarName.prisma +14 -0
  57. package/prisma/enums/ImageGenerationToolOutputFormat.prisma +5 -0
  58. package/prisma/enums/ImageGenerationToolQuality.prisma +6 -0
  59. package/prisma/enums/ImageGenerationToolSize.prisma +6 -0
  60. package/prisma/enums/LogLevel.prisma +5 -0
  61. package/prisma/enums/LogRequestMethod.prisma +7 -0
  62. package/prisma/enums/LogRequestRoute.prisma +6 -0
  63. package/prisma/enums/MessageRole.prisma +4 -0
  64. package/prisma/enums/MessageStatus.prisma +5 -0
  65. package/prisma/enums/MethodType.prisma +7 -0
  66. package/prisma/enums/ModelProviderType.prisma +13 -0
  67. package/prisma/enums/OrganizationUserRoleType.prisma +3 -0
  68. package/prisma/enums/ReplicateHandlerType.prisma +3 -0
  69. package/prisma/enums/RunStatus.prisma +10 -0
  70. package/prisma/enums/RunStepStatus.prisma +7 -0
  71. package/prisma/enums/RunStepType.prisma +4 -0
  72. package/prisma/enums/StorageProviderType.prisma +7 -0
  73. package/prisma/enums/ToolType.prisma +7 -0
  74. package/prisma/enums/TransportType.prisma +5 -0
  75. package/prisma/enums/TruncationType.prisma +5 -0
  76. package/prisma/enums/UserRoleType.prisma +3 -0
  77. package/prisma/migrations/20251006235143_initial_setup/migration.sql +986 -0
  78. package/prisma/migrations/20251007163926_add_truncation_type/migration.sql +6 -0
  79. package/prisma/migrations/20251007190703_add_organizations/migration.sql +97 -0
  80. package/prisma/migrations/migration_lock.toml +3 -0
  81. package/prisma/schema.prisma +13 -0
  82. package/prisma.config.ts +6 -0
  83. package/scripts/cli.ts +84 -0
  84. package/scripts/commands/organizations/api-keys/create.ts +159 -0
  85. package/scripts/commands/organizations/create.ts +82 -0
  86. package/scripts/utils/env.ts +31 -0
  87. package/scripts/utils/errors.ts +6 -0
  88. package/src/app/api/api-keys/[apiKeyId]/route.ts +178 -0
  89. package/src/app/api/api-keys/route.ts +147 -0
  90. package/src/app/api/assistants/[assistantId]/functions/[functionId]/route.ts +245 -0
  91. package/src/app/api/assistants/[assistantId]/functions/route.ts +157 -0
  92. package/src/app/api/assistants/[assistantId]/initial-messages/route.ts +127 -0
  93. package/src/app/api/assistants/[assistantId]/mcp-servers/[mcpServerId]/route.ts +243 -0
  94. package/src/app/api/assistants/[assistantId]/mcp-servers/route.ts +163 -0
  95. package/src/app/api/assistants/[assistantId]/route.ts +336 -0
  96. package/src/app/api/assistants/route.ts +196 -0
  97. package/src/app/api/files/[fileId]/contents/route.ts +145 -0
  98. package/src/app/api/files/route.ts +117 -0
  99. package/src/app/api/messages/lib/getWorkspaceId.ts +12 -0
  100. package/src/app/api/messages/lib/initialMessagesResponse.ts +90 -0
  101. package/src/app/api/messages/lib/serializeThread.ts +22 -0
  102. package/src/app/api/messages/route.ts +710 -0
  103. package/src/app/api/providers/[modelProviderId]/assistants/route.ts +68 -0
  104. package/src/app/api/providers/[modelProviderId]/models/route.ts +68 -0
  105. package/src/app/api/providers/[modelProviderId]/route.ts +202 -0
  106. package/src/app/api/providers/route.ts +105 -0
  107. package/src/app/api/tasks/[taskId]/route.ts +213 -0
  108. package/src/app/api/tasks/callback/route.ts +280 -0
  109. package/src/app/api/tasks/route.ts +121 -0
  110. package/src/app/api/threads/runs/submit-client-tool-outputs/route.ts +54 -0
  111. package/src/app/api/workspaces/[workspaceId]/route.ts +137 -0
  112. package/src/app/api/workspaces/route.ts +139 -0
  113. package/src/app/layout.tsx +9 -0
  114. package/src/app/page.tsx +3 -0
  115. package/src/lib/apiKeys/formatApiKeyName.ts +13 -0
  116. package/src/lib/apiKeys/getApiKey.ts +25 -0
  117. package/src/lib/apiKeys/serializeApiKey.ts +11 -0
  118. package/src/lib/apiKeys/workspaceAccessWhere.ts +21 -0
  119. package/src/lib/assistants/assistantClientAdapter/buildGetOpenaiAssistant.ts +96 -0
  120. package/src/lib/assistants/assistantClientAdapter/index.ts +165 -0
  121. package/src/lib/assistants/formatName.ts +9 -0
  122. package/src/lib/assistants/serializeApiAssistant.ts +40 -0
  123. package/src/lib/assistants/serializeAssistant.ts +31 -0
  124. package/src/lib/assistants/storageAssistantId.ts +29 -0
  125. package/src/lib/avatars/defaultAvatar.ts +15 -0
  126. package/src/lib/avatars/serializeAvatar.ts +26 -0
  127. package/src/lib/cache/cacheHeaders.ts +5 -0
  128. package/src/lib/computerCalls/handleComputerCall/index.ts +173 -0
  129. package/src/lib/errors/index.ts +10 -0
  130. package/src/lib/errors/serializeError.ts +11 -0
  131. package/src/lib/functions/createFunction.ts +32 -0
  132. package/src/lib/functions/functionSchema.ts +201 -0
  133. package/src/lib/functions/handleFunction/getFunction.ts +43 -0
  134. package/src/lib/functions/handleFunction/handleAssistant.ts +399 -0
  135. package/src/lib/functions/handleFunction/handleClientTool.ts +127 -0
  136. package/src/lib/functions/handleFunction/handleFirecrawl.ts +212 -0
  137. package/src/lib/functions/handleFunction/handleReplicate.ts +109 -0
  138. package/src/lib/functions/handleFunction/handleRequest.ts +272 -0
  139. package/src/lib/functions/handleFunction/index.ts +474 -0
  140. package/src/lib/functions/handleFunction/tasks/handleCreateTask.ts +58 -0
  141. package/src/lib/functions/handleFunction/tasks/handleDeleteTask.ts +62 -0
  142. package/src/lib/functions/handleFunction/tasks/handleListTasks.ts +53 -0
  143. package/src/lib/functions/handleFunction/tasks/handleUpdateTask.ts +70 -0
  144. package/src/lib/functions/interpolateFunctionValue.ts +26 -0
  145. package/src/lib/functions/serializeApiFunction.ts +32 -0
  146. package/src/lib/functions/updateFunction.ts +42 -0
  147. package/src/lib/handlers/handlerPrismaInput.tsx +141 -0
  148. package/src/lib/handlers/serializeApiHandler.ts +94 -0
  149. package/src/lib/iconAvatars/serializeIconAvatar.ts +9 -0
  150. package/src/lib/imageAvatars/serializeImageAvatar.ts +9 -0
  151. package/src/lib/initialMessages/schema.ts +11 -0
  152. package/src/lib/initialMessages/serializeApiInitialMessage.ts +15 -0
  153. package/src/lib/initialMessages/updateInitialMessages.ts +33 -0
  154. package/src/lib/logs/createLog.ts +17 -0
  155. package/src/lib/mcpServers/closeMcpConnection.ts +16 -0
  156. package/src/lib/mcpServers/connectMcpServer.ts +117 -0
  157. package/src/lib/mcpServers/getToolCallMcpServer.ts +62 -0
  158. package/src/lib/mcpServers/headers.ts +113 -0
  159. package/src/lib/mcpServers/mcpServerSchema.ts +77 -0
  160. package/src/lib/mcpServers/serializeApiMcpServer.ts +51 -0
  161. package/src/lib/mcpServers/url.ts +98 -0
  162. package/src/lib/messages/content.ts +60 -0
  163. package/src/lib/messages/textContent.ts +13 -0
  164. package/src/lib/metadata/serializeMetadata.ts +34 -0
  165. package/src/lib/misc/isJSON.ts +9 -0
  166. package/src/lib/misc/merge/customizer.ts +8 -0
  167. package/src/lib/misc/merge/index.ts +6 -0
  168. package/src/lib/modelProviders/buildAzureOpenaiClientAdapter.ts +33 -0
  169. package/src/lib/modelProviders/buildOpenaiClient.ts +14 -0
  170. package/src/lib/modelProviders/buildOpenaiClientAdapter.ts +30 -0
  171. package/src/lib/modelProviders/clientAdapter.ts +121 -0
  172. package/src/lib/modelProviders/isModelProviderValid.ts +23 -0
  173. package/src/lib/modelProviders/modelProviderConfigs.ts +221 -0
  174. package/src/lib/modelProviders/serializeModelProvider.ts +19 -0
  175. package/src/lib/models/getModels.ts +27 -0
  176. package/src/lib/models/serializeApiModel.ts +5 -0
  177. package/src/lib/openai/getOpenaiAssistants.ts +30 -0
  178. package/src/lib/organizationApiKeys/getOrganizationApiKey.ts +23 -0
  179. package/src/lib/prisma/index.ts +29 -0
  180. package/src/lib/redis/index.ts +3 -0
  181. package/src/lib/runs/createRunOpts.ts +80 -0
  182. package/src/lib/storageProviders/getStorageProviderAssistants.ts +19 -0
  183. package/src/lib/storageProviders/getStorageProviderType.ts +21 -0
  184. package/src/lib/storageProviders/isOpenaiAssistantsStorageProvider.ts +8 -0
  185. package/src/lib/storageProviders/isResponsesStorageProvider.ts +8 -0
  186. package/src/lib/storageProviders/openaiAssistantsStorageProviderTypes.ts +6 -0
  187. package/src/lib/storageProviders/responsesStorageProviderTypes.ts +6 -0
  188. package/src/lib/storageProviders/serializeApiStorageProviderAssistant.ts +14 -0
  189. package/src/lib/tasks/cancelScheduledTask.ts +13 -0
  190. package/src/lib/tasks/getNextOccurrence.ts +77 -0
  191. package/src/lib/tasks/getTaskToolKey.ts +49 -0
  192. package/src/lib/tasks/parseTaskToolArgs.ts +101 -0
  193. package/src/lib/tasks/scheduleSchema.ts +34 -0
  194. package/src/lib/tasks/scheduleTask.ts +37 -0
  195. package/src/lib/tasks/schemas.ts +25 -0
  196. package/src/lib/tasks/serializeTask.ts +14 -0
  197. package/src/lib/tasks/validateSchedule.ts +23 -0
  198. package/src/lib/themes/defaultTheme.ts +7 -0
  199. package/src/lib/themes/serializeAccentColor.ts +9 -0
  200. package/src/lib/themes/serializeTheme/index.ts +25 -0
  201. package/src/lib/themes/serializeTheme/serializeScaling.ts +14 -0
  202. package/src/lib/threads/createThread/index.ts +185 -0
  203. package/src/lib/threads/createThread/initialMessages.ts +36 -0
  204. package/src/lib/threads/managedOpenaiThreadId.ts +72 -0
  205. package/src/lib/threads/storageThreadId.ts +49 -0
  206. package/src/lib/threads/validThreadId.ts +10 -0
  207. package/src/lib/toolCalls/handleToolCall.ts +121 -0
  208. package/src/lib/toolCalls/messagesToOutput.ts +8 -0
  209. package/src/lib/toolCalls/streamOutput.ts +106 -0
  210. package/src/lib/tools/tools/index.ts +316 -0
  211. package/src/lib/upstash/qstash.ts +5 -0
  212. package/src/lib/upstash/upstashWorkflowClient.ts +5 -0
  213. package/src/lib/workspaces/serializeApiWorkspace.ts +12 -0
  214. package/src/types/index.ts +133 -0
  215. package/tsconfig.build.json +13 -0
  216. package/tsconfig.json +27 -0
  217. package/types/prisma.d.ts +4 -0
@@ -0,0 +1,6 @@
1
+ -- CreateEnum
2
+ CREATE TYPE "TruncationType" AS ENUM ('AUTO', 'LAST_MESSAGES', 'DISABLED');
3
+
4
+ -- AlterTable
5
+ ALTER TABLE "Assistant" ADD COLUMN "truncationLastMessagesCount" INTEGER,
6
+ ADD COLUMN "truncationType" "TruncationType" NOT NULL DEFAULT 'AUTO';
@@ -0,0 +1,97 @@
1
+ -- CreateEnum
2
+ CREATE TYPE "OrganizationUserRoleType" AS ENUM ('ADMIN');
3
+
4
+ -- AlterTable
5
+ ALTER TABLE "Workspace" ADD COLUMN "organizationId" UUID;
6
+
7
+ -- CreateTable
8
+ CREATE TABLE "Organization" (
9
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
10
+ "name" TEXT NOT NULL DEFAULT '',
11
+ "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ "updatedAt" TIMESTAMPTZ(6) NOT NULL,
13
+
14
+ CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
15
+ );
16
+
17
+ -- CreateTable
18
+ CREATE TABLE "OrganizationApiKey" (
19
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
20
+ "name" TEXT NOT NULL DEFAULT '',
21
+ "value" UUID NOT NULL DEFAULT gen_random_uuid(),
22
+ "organizationId" UUID NOT NULL,
23
+ "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
24
+ "updatedAt" TIMESTAMPTZ(6) NOT NULL,
25
+
26
+ CONSTRAINT "OrganizationApiKey_pkey" PRIMARY KEY ("id")
27
+ );
28
+
29
+ -- CreateTable
30
+ CREATE TABLE "OrganizationInvitation" (
31
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
32
+ "email" TEXT NOT NULL,
33
+ "roleType" "OrganizationUserRoleType" NOT NULL DEFAULT 'ADMIN',
34
+ "createdByUserId" TEXT NOT NULL,
35
+ "organizationId" UUID NOT NULL,
36
+ "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+ "updatedAt" TIMESTAMPTZ(6) NOT NULL,
38
+
39
+ CONSTRAINT "OrganizationInvitation_pkey" PRIMARY KEY ("id")
40
+ );
41
+
42
+ -- CreateTable
43
+ CREATE TABLE "OrganizationUserRole" (
44
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
45
+ "type" "OrganizationUserRoleType" NOT NULL DEFAULT 'ADMIN',
46
+ "userId" TEXT NOT NULL,
47
+ "organizationId" UUID NOT NULL,
48
+ "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
49
+ "updatedAt" TIMESTAMPTZ(6) NOT NULL,
50
+
51
+ CONSTRAINT "OrganizationUserRole_pkey" PRIMARY KEY ("id")
52
+ );
53
+
54
+ -- CreateIndex
55
+ CREATE UNIQUE INDEX "OrganizationApiKey_value_key" ON "OrganizationApiKey"("value");
56
+
57
+ -- CreateIndex
58
+ CREATE INDEX "OrganizationApiKey_value_idx" ON "OrganizationApiKey"("value");
59
+
60
+ -- CreateIndex
61
+ CREATE INDEX "OrganizationApiKey_organizationId_idx" ON "OrganizationApiKey"("organizationId");
62
+
63
+ -- CreateIndex
64
+ CREATE INDEX "OrganizationInvitation_createdByUserId_idx" ON "OrganizationInvitation"("createdByUserId");
65
+
66
+ -- CreateIndex
67
+ CREATE INDEX "OrganizationInvitation_organizationId_idx" ON "OrganizationInvitation"("organizationId");
68
+
69
+ -- CreateIndex
70
+ CREATE UNIQUE INDEX "OrganizationInvitation_organizationId_email_key" ON "OrganizationInvitation"("organizationId", "email");
71
+
72
+ -- CreateIndex
73
+ CREATE INDEX "OrganizationUserRole_userId_idx" ON "OrganizationUserRole"("userId");
74
+
75
+ -- CreateIndex
76
+ CREATE INDEX "OrganizationUserRole_organizationId_idx" ON "OrganizationUserRole"("organizationId");
77
+
78
+ -- CreateIndex
79
+ CREATE UNIQUE INDEX "OrganizationUserRole_organizationId_userId_key" ON "OrganizationUserRole"("organizationId", "userId");
80
+
81
+ -- AddForeignKey
82
+ ALTER TABLE "OrganizationApiKey" ADD CONSTRAINT "OrganizationApiKey_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
83
+
84
+ -- AddForeignKey
85
+ ALTER TABLE "OrganizationInvitation" ADD CONSTRAINT "OrganizationInvitation_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
86
+
87
+ -- AddForeignKey
88
+ ALTER TABLE "OrganizationInvitation" ADD CONSTRAINT "OrganizationInvitation_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
89
+
90
+ -- AddForeignKey
91
+ ALTER TABLE "OrganizationUserRole" ADD CONSTRAINT "OrganizationUserRole_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
92
+
93
+ -- AddForeignKey
94
+ ALTER TABLE "OrganizationUserRole" ADD CONSTRAINT "OrganizationUserRole_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
95
+
96
+ -- AddForeignKey
97
+ ALTER TABLE "Workspace" ADD CONSTRAINT "Workspace_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -0,0 +1,13 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ generator json {
6
+ provider = "prisma-json-types-generator"
7
+ }
8
+
9
+ datasource db {
10
+ provider = "postgresql"
11
+ url = env("DATABASE_URL")
12
+ directUrl = env("DIRECT_URL")
13
+ }
@@ -0,0 +1,6 @@
1
+ import path from 'node:path'
2
+ import type { PrismaConfig } from 'prisma'
3
+
4
+ export default {
5
+ schema: path.join('prisma'),
6
+ } satisfies PrismaConfig
package/scripts/cli.ts ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process'
3
+ import type { PrismaClient } from '@prisma/client'
4
+ import { Command } from 'commander'
5
+ import { createOrganization } from './commands/organizations/create'
6
+ import { createOrganizationApiKey } from './commands/organizations/api-keys/create'
7
+ import { CliError } from './utils/errors'
8
+ import { ensureEnv, ensureDatabaseUrl } from './utils/env'
9
+
10
+ const program = new Command()
11
+ .name('superinterface-cli')
12
+ .description('Administrative helpers for @superinterface/server')
13
+ .showHelpAfterError('(add --help for additional information)')
14
+
15
+ type ActionOptions<Options> = Options & { prisma: PrismaClient }
16
+
17
+ const withAction = <Options extends Record<string, unknown>>(
18
+ action: (options: ActionOptions<Options>) => Promise<void>,
19
+ ) => {
20
+ return async (options: Options) => {
21
+ let prisma: PrismaClient | undefined
22
+
23
+ try {
24
+ await ensureEnv()
25
+ ensureDatabaseUrl()
26
+
27
+ const prismaModule = await import('../src/lib/prisma')
28
+ prisma = prismaModule.prisma
29
+
30
+ const actionOptions = { ...options, prisma } as ActionOptions<Options>
31
+
32
+ await action(actionOptions)
33
+ } catch (error) {
34
+ if (error instanceof CliError) {
35
+ console.error(error.message)
36
+ if (error.cause) {
37
+ console.error(error.cause)
38
+ }
39
+ } else {
40
+ console.error(error)
41
+ }
42
+ process.exitCode = 1
43
+ } finally {
44
+ await prisma?.$disconnect()
45
+ }
46
+ }
47
+ }
48
+
49
+ const organizations = program
50
+ .command('organizations')
51
+ .description('Manage organizations')
52
+
53
+ organizations
54
+ .command('create')
55
+ .description('Create a new organization')
56
+ .option('-n, --name <name>', 'Organization display name')
57
+ .action(withAction(createOrganization))
58
+
59
+ const organizationApiKeys = organizations
60
+ .command('api-keys')
61
+ .description('Manage organization API keys')
62
+
63
+ organizationApiKeys
64
+ .command('create')
65
+ .description('Create a new organization API key')
66
+ .option('-o, --organization-id <organizationId>', 'Organization UUID')
67
+ .option('-n, --name <name>', 'API key display name')
68
+ .action(withAction(createOrganizationApiKey))
69
+
70
+ if (process.argv.length <= 2) {
71
+ program.outputHelp()
72
+ }
73
+
74
+ program.parseAsync(process.argv).catch((error) => {
75
+ if (error instanceof CliError) {
76
+ console.error(error.message)
77
+ if (error.cause) {
78
+ console.error(error.cause)
79
+ }
80
+ } else {
81
+ console.error(error)
82
+ }
83
+ process.exitCode = 1
84
+ })
@@ -0,0 +1,159 @@
1
+ import type { PrismaClient } from '@prisma/client'
2
+ import {
3
+ intro,
4
+ outro,
5
+ text,
6
+ note,
7
+ spinner as createSpinner,
8
+ cancel,
9
+ isCancel,
10
+ select,
11
+ } from '@clack/prompts'
12
+ import { z } from 'zod'
13
+ import { CliError } from '../../../utils/errors'
14
+
15
+ export type CreateOrganizationApiKeyOptions = {
16
+ prisma: PrismaClient
17
+ organizationId?: string
18
+ name?: string
19
+ }
20
+
21
+ const organizationIdSchema = z
22
+ .string()
23
+ .trim()
24
+ .uuid('organization-id must be a valid UUID')
25
+ const keyNameSchema = z.string().trim().min(1, 'Name is required')
26
+
27
+ export const createOrganizationApiKey = async ({
28
+ prisma,
29
+ organizationId,
30
+ name,
31
+ }: CreateOrganizationApiKeyOptions) => {
32
+ intro('Create organization API key')
33
+
34
+ let targetOrganizationId = organizationId?.trim()
35
+ let organizationName: string | undefined
36
+
37
+ if (targetOrganizationId) {
38
+ const parsedOrgId = organizationIdSchema.safeParse(targetOrganizationId)
39
+
40
+ if (!parsedOrgId.success) {
41
+ cancel(parsedOrgId.error.issues[0]?.message ?? 'Invalid organization id')
42
+ return
43
+ }
44
+
45
+ const organization = await prisma.organization.findUnique({
46
+ where: { id: parsedOrgId.data },
47
+ select: { id: true, name: true },
48
+ })
49
+
50
+ if (!organization) {
51
+ cancel('No organization found for the provided id')
52
+ return
53
+ }
54
+
55
+ targetOrganizationId = organization.id
56
+ organizationName = organization.name
57
+ } else {
58
+ const organizations = await prisma.organization.findMany({
59
+ orderBy: { createdAt: 'desc' },
60
+ select: { id: true, name: true },
61
+ take: 25,
62
+ })
63
+
64
+ if (organizations.length === 0) {
65
+ cancel('No organizations exist yet. Create one first.')
66
+ return
67
+ }
68
+
69
+ const selection = await select({
70
+ message: 'Select organization',
71
+ options: organizations.map((org) => ({
72
+ value: org.id,
73
+ label: `${org.name || '(unnamed)'} · ${org.id}`,
74
+ })),
75
+ })
76
+
77
+ if (isCancel(selection)) {
78
+ cancel('No API key created')
79
+ return
80
+ }
81
+
82
+ const selected = organizations.find((org) => org.id === selection)
83
+
84
+ if (!selected) {
85
+ cancel('No API key created')
86
+ return
87
+ }
88
+
89
+ targetOrganizationId = selected.id
90
+ organizationName = selected.name
91
+ }
92
+
93
+ if (!targetOrganizationId) {
94
+ cancel('No API key created')
95
+ return
96
+ }
97
+
98
+ let keyName = name?.trim()
99
+
100
+ if (!keyName) {
101
+ const input = await text({
102
+ message: 'API key name',
103
+ placeholder: 'Production key',
104
+ validate: (value) => {
105
+ const parsed = keyNameSchema.safeParse(value)
106
+ return parsed.success
107
+ ? undefined
108
+ : (parsed.error.issues[0]?.message ?? 'Invalid name')
109
+ },
110
+ })
111
+
112
+ if (!input || isCancel(input)) {
113
+ cancel('No API key created')
114
+ return
115
+ }
116
+
117
+ keyName = input
118
+ }
119
+
120
+ const parsedName = keyNameSchema.safeParse(keyName)
121
+
122
+ if (!parsedName.success) {
123
+ cancel(parsedName.error.issues[0]?.message ?? 'Name is required')
124
+ return
125
+ }
126
+
127
+ const spinner = createSpinner()
128
+ spinner.start('Creating organization API key')
129
+
130
+ try {
131
+ const apiKey = await prisma.organizationApiKey.create({
132
+ data: {
133
+ organizationId: targetOrganizationId,
134
+ name: parsedName.data,
135
+ },
136
+ })
137
+
138
+ spinner.stop('Organization API key created')
139
+
140
+ note(
141
+ [
142
+ `id: ${apiKey.id}`,
143
+ `name: ${apiKey.name}`,
144
+ `organizationId: ${apiKey.organizationId}`,
145
+ `organizationName: ${organizationName ?? '(unnamed)'}`,
146
+ `value: ${apiKey.value}`,
147
+ `createdAt: ${apiKey.createdAt.toISOString()}`,
148
+ ].join('\n'),
149
+ 'Organization API Key',
150
+ )
151
+
152
+ outro('Done')
153
+ } catch (error) {
154
+ spinner.stop('Creation failed')
155
+ throw new CliError('Failed to create organization API key.', {
156
+ cause: error,
157
+ })
158
+ }
159
+ }
@@ -0,0 +1,82 @@
1
+ import type { PrismaClient } from '@prisma/client'
2
+ import {
3
+ intro,
4
+ outro,
5
+ text,
6
+ note,
7
+ spinner as createSpinner,
8
+ cancel,
9
+ isCancel,
10
+ } from '@clack/prompts'
11
+ import { z } from 'zod'
12
+ import { CliError } from '../../utils/errors'
13
+
14
+ export type CreateOrganizationOptions = {
15
+ prisma: PrismaClient
16
+ name?: string
17
+ }
18
+
19
+ const nameSchema = z.string().trim().min(1, 'Name is required')
20
+
21
+ export const createOrganization = async ({
22
+ name,
23
+ prisma,
24
+ }: CreateOrganizationOptions) => {
25
+ intro('Create organization')
26
+
27
+ let organizationName = name?.trim()
28
+
29
+ if (!organizationName) {
30
+ const input = await text({
31
+ message: 'Organization name',
32
+ placeholder: 'Acme Inc',
33
+ validate: (value) => {
34
+ const parsed = nameSchema.safeParse(value)
35
+ return parsed.success
36
+ ? undefined
37
+ : (parsed.error.issues[0]?.message ?? 'Invalid name')
38
+ },
39
+ })
40
+
41
+ if (!input || isCancel(input)) {
42
+ cancel('No organization created')
43
+ return
44
+ }
45
+
46
+ organizationName = input
47
+ }
48
+
49
+ const parsedName = nameSchema.safeParse(organizationName)
50
+
51
+ if (!parsedName.success) {
52
+ cancel(parsedName.error.issues[0]?.message ?? 'Name is required')
53
+ return
54
+ }
55
+
56
+ const spinner = createSpinner()
57
+ spinner.start('Creating organization')
58
+
59
+ try {
60
+ const organization = await prisma.organization.create({
61
+ data: {
62
+ name: parsedName.data,
63
+ },
64
+ })
65
+
66
+ spinner.stop('Organization created')
67
+
68
+ note(
69
+ [
70
+ `id: ${organization.id}`,
71
+ `name: ${organization.name}`,
72
+ `createdAt: ${organization.createdAt.toISOString()}`,
73
+ ].join('\n'),
74
+ 'Organization',
75
+ )
76
+
77
+ outro('Done')
78
+ } catch (error) {
79
+ spinner.stop('Creation failed')
80
+ throw new CliError('Failed to create organization.', { cause: error })
81
+ }
82
+ }
@@ -0,0 +1,31 @@
1
+ import process from 'node:process'
2
+
3
+ type ProcessWithLoadEnvFile = NodeJS.Process & {
4
+ loadEnvFile?: () => void
5
+ }
6
+
7
+ let envLoaded = false
8
+
9
+ export const ensureEnv = async () => {
10
+ if (envLoaded) return
11
+
12
+ const proc = process as ProcessWithLoadEnvFile
13
+
14
+ if (typeof proc.loadEnvFile === 'function') {
15
+ proc.loadEnvFile()
16
+ envLoaded = true
17
+ return
18
+ }
19
+
20
+ const dotenv = await import('dotenv')
21
+ dotenv.config({ path: '.env' })
22
+ envLoaded = true
23
+ }
24
+
25
+ export const ensureDatabaseUrl = () => {
26
+ if (!process.env.DATABASE_URL) {
27
+ throw new Error(
28
+ 'DATABASE_URL environment variable is required to run this command.',
29
+ )
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ export class CliError extends Error {
2
+ constructor(message: string, options?: { cause?: unknown }) {
3
+ super(message, options)
4
+ this.name = 'CliError'
5
+ }
6
+ }
@@ -0,0 +1,178 @@
1
+ import { type NextRequest, NextResponse } from 'next/server'
2
+ import { ApiKeyType } from '@prisma/client'
3
+ import { headers } from 'next/headers'
4
+ import { cacheHeaders } from '@/lib/cache/cacheHeaders'
5
+ import { prisma } from '@/lib/prisma'
6
+ import { serializeApiKey } from '@/lib/apiKeys/serializeApiKey'
7
+ import { z } from 'zod'
8
+ import { getApiKey } from '@/lib/apiKeys/getApiKey'
9
+ import { validate } from 'uuid'
10
+
11
+ export const GET = async (
12
+ request: NextRequest,
13
+ props: {
14
+ params: Promise<{ apiKeyId: string }>
15
+ },
16
+ ) => {
17
+ const params = await props.params
18
+ const { apiKeyId } = params
19
+
20
+ const headersList = await headers()
21
+ const authorization = headersList.get('authorization')
22
+ if (!authorization) {
23
+ return NextResponse.json(
24
+ { error: 'No authorization header found' },
25
+ { status: 400 },
26
+ )
27
+ }
28
+
29
+ const privateApiKey = await getApiKey({
30
+ type: ApiKeyType.PRIVATE,
31
+ authorization,
32
+ })
33
+
34
+ if (!privateApiKey) {
35
+ return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
36
+ }
37
+
38
+ if (!validate(apiKeyId)) {
39
+ return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
40
+ }
41
+
42
+ const apiKey = await prisma.apiKey.findFirst({
43
+ where: {
44
+ id: apiKeyId,
45
+ type: ApiKeyType.PUBLIC,
46
+ workspaceId: privateApiKey.workspaceId,
47
+ },
48
+ })
49
+
50
+ if (!apiKey) {
51
+ return NextResponse.json({ error: 'No api key found' }, { status: 400 })
52
+ }
53
+
54
+ return NextResponse.json(
55
+ { apiKey: serializeApiKey({ apiKey }) },
56
+ { headers: cacheHeaders },
57
+ )
58
+ }
59
+
60
+ export const PATCH = async (
61
+ request: NextRequest,
62
+ props: { params: Promise<{ apiKeyId: string }> },
63
+ ) => {
64
+ const params = await props.params
65
+ const { apiKeyId } = params
66
+
67
+ const headersList = await headers()
68
+ const authorization = headersList.get('authorization')
69
+ if (!authorization) {
70
+ return NextResponse.json(
71
+ { error: 'No authorization header found' },
72
+ { status: 400 },
73
+ )
74
+ }
75
+
76
+ const privateApiKey = await getApiKey({
77
+ authorization,
78
+ type: ApiKeyType.PRIVATE,
79
+ })
80
+
81
+ if (!privateApiKey) {
82
+ return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
83
+ }
84
+
85
+ if (!validate(apiKeyId)) {
86
+ return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
87
+ }
88
+
89
+ const body = await request.json().catch(() => null)
90
+ const schema = z.object({ name: z.string().min(1) })
91
+ const parsed = schema.safeParse(body)
92
+
93
+ if (!parsed.success) {
94
+ return NextResponse.json({ error: 'No name found' }, { status: 400 })
95
+ }
96
+
97
+ const { name } = parsed.data
98
+
99
+ const existingApiKey = await prisma.apiKey.findFirst({
100
+ where: {
101
+ id: apiKeyId,
102
+ type: ApiKeyType.PUBLIC,
103
+ workspaceId: privateApiKey.workspaceId,
104
+ },
105
+ })
106
+
107
+ if (!existingApiKey) {
108
+ return NextResponse.json({ error: 'No api key found' }, { status: 400 })
109
+ }
110
+
111
+ const apiKey = await prisma.apiKey.update({
112
+ where: { id: apiKeyId },
113
+ data: { name },
114
+ })
115
+
116
+ return NextResponse.json(
117
+ { apiKey: serializeApiKey({ apiKey }) },
118
+ { headers: cacheHeaders },
119
+ )
120
+ }
121
+
122
+ export const DELETE = async (
123
+ request: NextRequest,
124
+ props: { params: Promise<{ apiKeyId: string }> },
125
+ ) => {
126
+ const params = await props.params
127
+ const { apiKeyId } = params
128
+
129
+ const headersList = await headers()
130
+ const authorization = headersList.get('authorization')
131
+ if (!authorization) {
132
+ return NextResponse.json(
133
+ { error: 'No authorization header found' },
134
+ { status: 400 },
135
+ )
136
+ }
137
+
138
+ const privateApiKey = await getApiKey({
139
+ type: ApiKeyType.PRIVATE,
140
+ authorization,
141
+ })
142
+
143
+ if (!privateApiKey) {
144
+ return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
145
+ }
146
+ if (!validate(apiKeyId)) {
147
+ return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
148
+ }
149
+
150
+ const apiKey = await prisma.apiKey.findFirst({
151
+ where: {
152
+ id: apiKeyId,
153
+ type: ApiKeyType.PUBLIC,
154
+ workspaceId: privateApiKey.workspaceId,
155
+ },
156
+ })
157
+
158
+ if (!apiKey) {
159
+ return NextResponse.json({ error: 'No api key found' }, { status: 400 })
160
+ }
161
+
162
+ const deletedApiKey = await prisma.apiKey.delete({
163
+ where: { id: apiKey.id },
164
+ })
165
+
166
+ return NextResponse.json(
167
+ { apiKey: serializeApiKey({ apiKey: deletedApiKey }) },
168
+ { headers: cacheHeaders },
169
+ )
170
+ }
171
+
172
+ export const OPTIONS = () =>
173
+ NextResponse.json(
174
+ {},
175
+ {
176
+ headers: cacheHeaders,
177
+ },
178
+ )