the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Vercel auth fields and DB migration

+2367 -143
+16
apps/api/lexicons/sandbox/createSandbox.json
··· 110 110 "daytonaOrganizationId": { 111 111 "type": "string", 112 112 "description": "The organization ID for Daytona resources" 113 + }, 114 + "vercelApiToken": { 115 + "type": "string", 116 + "description": "A token (encrypted) for accessing Vercel resources" 117 + }, 118 + "redactedVercelApiToken": { 119 + "type": "string", 120 + "description": "A redacted token for accessing Vercel resources" 121 + }, 122 + "vercelProjectId": { 123 + "type": "string", 124 + "description": "The project ID for Vercel resources" 125 + }, 126 + "vercelTeamId": { 127 + "type": "string", 128 + "description": "The team ID for Vercel resources" 113 129 } 114 130 } 115 131 }
+8
apps/api/lexicons/sandbox/defs.json
··· 322 322 "organizationId": { 323 323 "type": "string", 324 324 "description": "The ID of the organization in the sandbox provider, if applicable. This can be used to associate the sandbox with a specific organization or team within the provider's platform." 325 + }, 326 + "vercelProjectId": { 327 + "type": "string", 328 + "description": "The project ID for Vercel, if the sandbox provider is Vercel. This is used to determine which Vercel project the sandbox should be created in." 329 + }, 330 + "vercelTeamId": { 331 + "type": "string", 332 + "description": "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with." 325 333 } 326 334 } 327 335 },
+16
apps/api/pkl/defs/sandbox/createSandbox.pkl
··· 108 108 type = "string" 109 109 description = "The organization ID for Daytona resources" 110 110 } 111 + ["vercelApiToken"] = new StringType { 112 + type = "string" 113 + description = "A token (encrypted) for accessing Vercel resources" 114 + } 115 + ["redactedVercelApiToken"] = new StringType { 116 + type = "string" 117 + description = "A redacted token for accessing Vercel resources" 118 + } 119 + ["vercelProjectId"] = new StringType { 120 + type = "string" 121 + description = "The project ID for Vercel resources" 122 + } 123 + ["vercelTeamId"] = new StringType { 124 + type = "string" 125 + description = "The team ID for Vercel resources" 126 + } 111 127 } 112 128 } 113 129 }
+10
apps/api/pkl/defs/sandbox/defs.pkl
··· 325 325 description = 326 326 "The ID of the organization in the sandbox provider, if applicable. This can be used to associate the sandbox with a specific organization or team within the provider's platform." 327 327 } 328 + ["vercelProjectId"] = new StringType { 329 + type = "string" 330 + description = 331 + "The project ID for Vercel, if the sandbox provider is Vercel. This is used to determine which Vercel project the sandbox should be created in." 332 + } 333 + ["vercelTeamId"] = new StringType { 334 + type = "string" 335 + description = 336 + "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with." 337 + } 328 338 } 329 339 } 330 340 ["preferences"] = new Array {
+27
apps/api/src/lexicon/lexicons.ts
··· 618 618 type: "string", 619 619 description: "The organization ID for Daytona resources", 620 620 }, 621 + vercelApiToken: { 622 + type: "string", 623 + description: 624 + "A token (encrypted) for accessing Vercel resources", 625 + }, 626 + redactedVercelApiToken: { 627 + type: "string", 628 + description: "A redacted token for accessing Vercel resources", 629 + }, 630 + vercelProjectId: { 631 + type: "string", 632 + description: "The project ID for Vercel resources", 633 + }, 634 + vercelTeamId: { 635 + type: "string", 636 + description: "The team ID for Vercel resources", 637 + }, 621 638 }, 622 639 }, 623 640 }, ··· 969 986 type: "string", 970 987 description: 971 988 "The ID of the organization in the sandbox provider, if applicable. This can be used to associate the sandbox with a specific organization or team within the provider's platform.", 989 + }, 990 + vercelProjectId: { 991 + type: "string", 992 + description: 993 + "The project ID for Vercel, if the sandbox provider is Vercel. This is used to determine which Vercel project the sandbox should be created in.", 994 + }, 995 + vercelTeamId: { 996 + type: "string", 997 + description: 998 + "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with.", 972 999 }, 973 1000 }, 974 1001 },
+8
apps/api/src/lexicon/types/io/pocketenv/sandbox/createSandbox.ts
··· 50 50 redactedDaytonaApiKey?: string; 51 51 /** The organization ID for Daytona resources */ 52 52 daytonaOrganizationId?: string; 53 + /** A token (encrypted) for accessing Vercel resources */ 54 + vercelApiToken?: string; 55 + /** A redacted token for accessing Vercel resources */ 56 + redactedVercelApiToken?: string; 57 + /** The project ID for Vercel resources */ 58 + vercelProjectId?: string; 59 + /** The team ID for Vercel resources */ 60 + vercelTeamId?: string; 53 61 [k: string]: unknown; 54 62 } 55 63
+4
apps/api/src/lexicon/types/io/pocketenv/sandbox/defs.ts
··· 225 225 redactedApiKey?: string; 226 226 /** The ID of the organization in the sandbox provider, if applicable. This can be used to associate the sandbox with a specific organization or team within the provider's platform. */ 227 227 organizationId?: string; 228 + /** The project ID for Vercel, if the sandbox provider is Vercel. This is used to determine which Vercel project the sandbox should be created in. */ 229 + vercelProjectId?: string; 230 + /** The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with. */ 231 + vercelTeamId?: string; 228 232 [k: string]: unknown; 229 233 } 230 234
+2
apps/api/src/schema/vercel-auth.ts
··· 15 15 .references(() => users.id), 16 16 vercelToken: text("vercel_token").notNull(), 17 17 redactedVercelToken: text("redacted_vercel_token").notNull(), 18 + projectId: text("project_id").notNull(), 19 + teamId: text("team_id").notNull(), 18 20 createdAt: timestamp("created_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [uniqueIndex("unique_vercel_auth").on(t.sandboxId, t.userId)],
+4
apps/api/src/xrpc/io/pocketenv/sandbox/createSandbox.ts
··· 116 116 daytonaApiKey: input.daytonaApiKey, 117 117 redactedDaytonaApiKey: input.redactedDaytonaApiKey, 118 118 daytonaOrganizationId: input.daytonaOrganizationId, 119 + vercelApiToken: input.vercelApiToken, 120 + redactedVercelApiToken: input.redactedVercelApiToken, 121 + vercelProjectId: input.vercelProjectId, 122 + vercelTeamId: input.vercelTeamId, 119 123 }, 120 124 { 121 125 headers: {
+2
apps/api/src/xrpc/io/pocketenv/sandbox/getPreferences.ts
··· 91 91 $type: "io.pocketenv.sandbox.defs#sandboxProviderPref" as const, 92 92 name: "vercel" as const, 93 93 redactedApiKey: vercel.redactedVercelToken, 94 + vercelProjectId: vercel.projectId, 95 + vercelTeamId: vercel.teamId, 94 96 }))!; 95 97 96 98 return [provider satisfies SandboxProviderPref];
+2
apps/api/src/xrpc/io/pocketenv/sandbox/putPreferences.ts
··· 186 186 sandboxId: input.body.sandboxId, 187 187 vercelToken: pref.apiKey!, 188 188 redactedVercelToken: pref.redactedApiKey!, 189 + projectId: pref.vercelProjectId!, 190 + teamId: pref.vercelTeamId!, 189 191 }) 190 192 .onConflictDoUpdate({ 191 193 target: [vercelAuth.sandboxId, vercelAuth.userId],
+2
apps/cf-sandbox/drizzle/0039_tense_micromax.sql
··· 1 + ALTER TABLE "vercel_auth" ADD COLUMN "project_id" text NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "vercel_auth" ADD COLUMN "team_id" text NOT NULL;
+1869
apps/cf-sandbox/drizzle/meta/0039_snapshot.json
··· 1 + { 2 + "id": "5b2005d5-aa51-4b81-aead-b68570ac709c", 3 + "prevId": "35ae1001-4336-44fd-a946-c9ffc5375255", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.daytona_auth": { 61 + "name": "daytona_auth", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "xata_id()" 70 + }, 71 + "sandbox_id": { 72 + "name": "sandbox_id", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "user_id": { 78 + "name": "user_id", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true 82 + }, 83 + "api_key": { 84 + "name": "api_key", 85 + "type": "text", 86 + "primaryKey": false, 87 + "notNull": true 88 + }, 89 + "organization_id": { 90 + "name": "organization_id", 91 + "type": "text", 92 + "primaryKey": false, 93 + "notNull": true 94 + }, 95 + "redacted_api_key": { 96 + "name": "redacted_api_key", 97 + "type": "text", 98 + "primaryKey": false, 99 + "notNull": true 100 + }, 101 + "created_at": { 102 + "name": "created_at", 103 + "type": "timestamp", 104 + "primaryKey": false, 105 + "notNull": true, 106 + "default": "now()" 107 + } 108 + }, 109 + "indexes": { 110 + "unique_daytona_auth": { 111 + "name": "unique_daytona_auth", 112 + "columns": [ 113 + { 114 + "expression": "sandbox_id", 115 + "isExpression": false, 116 + "asc": true, 117 + "nulls": "last" 118 + }, 119 + { 120 + "expression": "user_id", 121 + "isExpression": false, 122 + "asc": true, 123 + "nulls": "last" 124 + } 125 + ], 126 + "isUnique": true, 127 + "concurrently": false, 128 + "method": "btree", 129 + "with": {} 130 + } 131 + }, 132 + "foreignKeys": { 133 + "daytona_auth_sandbox_id_sandboxes_id_fk": { 134 + "name": "daytona_auth_sandbox_id_sandboxes_id_fk", 135 + "tableFrom": "daytona_auth", 136 + "tableTo": "sandboxes", 137 + "columnsFrom": [ 138 + "sandbox_id" 139 + ], 140 + "columnsTo": [ 141 + "id" 142 + ], 143 + "onDelete": "cascade", 144 + "onUpdate": "no action" 145 + }, 146 + "daytona_auth_user_id_users_id_fk": { 147 + "name": "daytona_auth_user_id_users_id_fk", 148 + "tableFrom": "daytona_auth", 149 + "tableTo": "users", 150 + "columnsFrom": [ 151 + "user_id" 152 + ], 153 + "columnsTo": [ 154 + "id" 155 + ], 156 + "onDelete": "no action", 157 + "onUpdate": "no action" 158 + } 159 + }, 160 + "compositePrimaryKeys": {}, 161 + "uniqueConstraints": {}, 162 + "policies": {}, 163 + "checkConstraints": {}, 164 + "isRLSEnabled": false 165 + }, 166 + "public.deno_auth": { 167 + "name": "deno_auth", 168 + "schema": "", 169 + "columns": { 170 + "id": { 171 + "name": "id", 172 + "type": "text", 173 + "primaryKey": true, 174 + "notNull": true, 175 + "default": "xata_id()" 176 + }, 177 + "sandbox_id": { 178 + "name": "sandbox_id", 179 + "type": "text", 180 + "primaryKey": false, 181 + "notNull": true 182 + }, 183 + "deploy_token": { 184 + "name": "deploy_token", 185 + "type": "text", 186 + "primaryKey": false, 187 + "notNull": true 188 + }, 189 + "user_id": { 190 + "name": "user_id", 191 + "type": "text", 192 + "primaryKey": false, 193 + "notNull": true 194 + }, 195 + "redacted_deno_token": { 196 + "name": "redacted_deno_token", 197 + "type": "text", 198 + "primaryKey": false, 199 + "notNull": true 200 + }, 201 + "created_at": { 202 + "name": "created_at", 203 + "type": "timestamp", 204 + "primaryKey": false, 205 + "notNull": true, 206 + "default": "now()" 207 + } 208 + }, 209 + "indexes": { 210 + "unique_deno_auth": { 211 + "name": "unique_deno_auth", 212 + "columns": [ 213 + { 214 + "expression": "sandbox_id", 215 + "isExpression": false, 216 + "asc": true, 217 + "nulls": "last" 218 + }, 219 + { 220 + "expression": "user_id", 221 + "isExpression": false, 222 + "asc": true, 223 + "nulls": "last" 224 + } 225 + ], 226 + "isUnique": true, 227 + "concurrently": false, 228 + "method": "btree", 229 + "with": {} 230 + } 231 + }, 232 + "foreignKeys": { 233 + "deno_auth_sandbox_id_sandboxes_id_fk": { 234 + "name": "deno_auth_sandbox_id_sandboxes_id_fk", 235 + "tableFrom": "deno_auth", 236 + "tableTo": "sandboxes", 237 + "columnsFrom": [ 238 + "sandbox_id" 239 + ], 240 + "columnsTo": [ 241 + "id" 242 + ], 243 + "onDelete": "cascade", 244 + "onUpdate": "no action" 245 + }, 246 + "deno_auth_user_id_users_id_fk": { 247 + "name": "deno_auth_user_id_users_id_fk", 248 + "tableFrom": "deno_auth", 249 + "tableTo": "users", 250 + "columnsFrom": [ 251 + "user_id" 252 + ], 253 + "columnsTo": [ 254 + "id" 255 + ], 256 + "onDelete": "no action", 257 + "onUpdate": "no action" 258 + } 259 + }, 260 + "compositePrimaryKeys": {}, 261 + "uniqueConstraints": {}, 262 + "policies": {}, 263 + "checkConstraints": {}, 264 + "isRLSEnabled": false 265 + }, 266 + "public.files": { 267 + "name": "files", 268 + "schema": "", 269 + "columns": { 270 + "id": { 271 + "name": "id", 272 + "type": "text", 273 + "primaryKey": true, 274 + "notNull": true, 275 + "default": "xata_id()" 276 + }, 277 + "content": { 278 + "name": "content", 279 + "type": "text", 280 + "primaryKey": false, 281 + "notNull": true 282 + }, 283 + "created_at": { 284 + "name": "created_at", 285 + "type": "timestamp", 286 + "primaryKey": false, 287 + "notNull": true, 288 + "default": "now()" 289 + }, 290 + "updated_at": { 291 + "name": "updated_at", 292 + "type": "timestamp", 293 + "primaryKey": false, 294 + "notNull": true, 295 + "default": "now()" 296 + } 297 + }, 298 + "indexes": {}, 299 + "foreignKeys": {}, 300 + "compositePrimaryKeys": {}, 301 + "uniqueConstraints": {}, 302 + "policies": {}, 303 + "checkConstraints": {}, 304 + "isRLSEnabled": false 305 + }, 306 + "public.sandbox_files": { 307 + "name": "sandbox_files", 308 + "schema": "", 309 + "columns": { 310 + "id": { 311 + "name": "id", 312 + "type": "text", 313 + "primaryKey": true, 314 + "notNull": true, 315 + "default": "file_id()" 316 + }, 317 + "sandbox_id": { 318 + "name": "sandbox_id", 319 + "type": "text", 320 + "primaryKey": false, 321 + "notNull": true 322 + }, 323 + "file_id": { 324 + "name": "file_id", 325 + "type": "text", 326 + "primaryKey": false, 327 + "notNull": true 328 + }, 329 + "path": { 330 + "name": "path", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": true 334 + }, 335 + "created_at": { 336 + "name": "created_at", 337 + "type": "timestamp", 338 + "primaryKey": false, 339 + "notNull": true, 340 + "default": "now()" 341 + }, 342 + "updated_at": { 343 + "name": "updated_at", 344 + "type": "timestamp", 345 + "primaryKey": false, 346 + "notNull": true, 347 + "default": "now()" 348 + } 349 + }, 350 + "indexes": { 351 + "unique_sandbox_file_path": { 352 + "name": "unique_sandbox_file_path", 353 + "columns": [ 354 + { 355 + "expression": "sandbox_id", 356 + "isExpression": false, 357 + "asc": true, 358 + "nulls": "last" 359 + }, 360 + { 361 + "expression": "path", 362 + "isExpression": false, 363 + "asc": true, 364 + "nulls": "last" 365 + } 366 + ], 367 + "isUnique": true, 368 + "concurrently": false, 369 + "method": "btree", 370 + "with": {} 371 + } 372 + }, 373 + "foreignKeys": { 374 + "sandbox_files_sandbox_id_sandboxes_id_fk": { 375 + "name": "sandbox_files_sandbox_id_sandboxes_id_fk", 376 + "tableFrom": "sandbox_files", 377 + "tableTo": "sandboxes", 378 + "columnsFrom": [ 379 + "sandbox_id" 380 + ], 381 + "columnsTo": [ 382 + "id" 383 + ], 384 + "onDelete": "cascade", 385 + "onUpdate": "no action" 386 + }, 387 + "sandbox_files_file_id_files_id_fk": { 388 + "name": "sandbox_files_file_id_files_id_fk", 389 + "tableFrom": "sandbox_files", 390 + "tableTo": "files", 391 + "columnsFrom": [ 392 + "file_id" 393 + ], 394 + "columnsTo": [ 395 + "id" 396 + ], 397 + "onDelete": "no action", 398 + "onUpdate": "no action" 399 + } 400 + }, 401 + "compositePrimaryKeys": {}, 402 + "uniqueConstraints": {}, 403 + "policies": {}, 404 + "checkConstraints": {}, 405 + "isRLSEnabled": false 406 + }, 407 + "public.sandbox_ports": { 408 + "name": "sandbox_ports", 409 + "schema": "", 410 + "columns": { 411 + "id": { 412 + "name": "id", 413 + "type": "text", 414 + "primaryKey": true, 415 + "notNull": true, 416 + "default": "xata_id()" 417 + }, 418 + "sandbox_id": { 419 + "name": "sandbox_id", 420 + "type": "text", 421 + "primaryKey": false, 422 + "notNull": true 423 + }, 424 + "exposed_port": { 425 + "name": "exposed_port", 426 + "type": "integer", 427 + "primaryKey": false, 428 + "notNull": true 429 + }, 430 + "preview_url": { 431 + "name": "preview_url", 432 + "type": "text", 433 + "primaryKey": false, 434 + "notNull": false 435 + }, 436 + "description": { 437 + "name": "description", 438 + "type": "text", 439 + "primaryKey": false, 440 + "notNull": false 441 + }, 442 + "service_id": { 443 + "name": "service_id", 444 + "type": "text", 445 + "primaryKey": false, 446 + "notNull": false 447 + }, 448 + "created_at": { 449 + "name": "created_at", 450 + "type": "timestamp", 451 + "primaryKey": false, 452 + "notNull": true, 453 + "default": "now()" 454 + }, 455 + "updated_at": { 456 + "name": "updated_at", 457 + "type": "timestamp", 458 + "primaryKey": false, 459 + "notNull": true, 460 + "default": "now()" 461 + } 462 + }, 463 + "indexes": { 464 + "unique_sandbox_port": { 465 + "name": "unique_sandbox_port", 466 + "columns": [ 467 + { 468 + "expression": "sandbox_id", 469 + "isExpression": false, 470 + "asc": true, 471 + "nulls": "last" 472 + }, 473 + { 474 + "expression": "exposed_port", 475 + "isExpression": false, 476 + "asc": true, 477 + "nulls": "last" 478 + } 479 + ], 480 + "isUnique": true, 481 + "concurrently": false, 482 + "method": "btree", 483 + "with": {} 484 + } 485 + }, 486 + "foreignKeys": { 487 + "sandbox_ports_sandbox_id_sandboxes_id_fk": { 488 + "name": "sandbox_ports_sandbox_id_sandboxes_id_fk", 489 + "tableFrom": "sandbox_ports", 490 + "tableTo": "sandboxes", 491 + "columnsFrom": [ 492 + "sandbox_id" 493 + ], 494 + "columnsTo": [ 495 + "id" 496 + ], 497 + "onDelete": "cascade", 498 + "onUpdate": "no action" 499 + }, 500 + "sandbox_ports_service_id_services_id_fk": { 501 + "name": "sandbox_ports_service_id_services_id_fk", 502 + "tableFrom": "sandbox_ports", 503 + "tableTo": "services", 504 + "columnsFrom": [ 505 + "service_id" 506 + ], 507 + "columnsTo": [ 508 + "id" 509 + ], 510 + "onDelete": "no action", 511 + "onUpdate": "no action" 512 + } 513 + }, 514 + "compositePrimaryKeys": {}, 515 + "uniqueConstraints": {}, 516 + "policies": {}, 517 + "checkConstraints": {}, 518 + "isRLSEnabled": false 519 + }, 520 + "public.sandbox_secrets": { 521 + "name": "sandbox_secrets", 522 + "schema": "", 523 + "columns": { 524 + "id": { 525 + "name": "id", 526 + "type": "text", 527 + "primaryKey": true, 528 + "notNull": true, 529 + "default": "xata_id()" 530 + }, 531 + "sandbox_id": { 532 + "name": "sandbox_id", 533 + "type": "text", 534 + "primaryKey": false, 535 + "notNull": true 536 + }, 537 + "secret_id": { 538 + "name": "secret_id", 539 + "type": "text", 540 + "primaryKey": false, 541 + "notNull": true 542 + }, 543 + "name": { 544 + "name": "name", 545 + "type": "text", 546 + "primaryKey": false, 547 + "notNull": false 548 + }, 549 + "created_at": { 550 + "name": "created_at", 551 + "type": "timestamp", 552 + "primaryKey": false, 553 + "notNull": true, 554 + "default": "now()" 555 + }, 556 + "updated_at": { 557 + "name": "updated_at", 558 + "type": "timestamp", 559 + "primaryKey": false, 560 + "notNull": true, 561 + "default": "now()" 562 + } 563 + }, 564 + "indexes": { 565 + "unique_sandbox_secret_by_name": { 566 + "name": "unique_sandbox_secret_by_name", 567 + "columns": [ 568 + { 569 + "expression": "sandbox_id", 570 + "isExpression": false, 571 + "asc": true, 572 + "nulls": "last" 573 + }, 574 + { 575 + "expression": "name", 576 + "isExpression": false, 577 + "asc": true, 578 + "nulls": "last" 579 + } 580 + ], 581 + "isUnique": true, 582 + "concurrently": false, 583 + "method": "btree", 584 + "with": {} 585 + } 586 + }, 587 + "foreignKeys": { 588 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 589 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 590 + "tableFrom": "sandbox_secrets", 591 + "tableTo": "sandboxes", 592 + "columnsFrom": [ 593 + "sandbox_id" 594 + ], 595 + "columnsTo": [ 596 + "id" 597 + ], 598 + "onDelete": "cascade", 599 + "onUpdate": "no action" 600 + }, 601 + "sandbox_secrets_secret_id_secrets_id_fk": { 602 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 603 + "tableFrom": "sandbox_secrets", 604 + "tableTo": "secrets", 605 + "columnsFrom": [ 606 + "secret_id" 607 + ], 608 + "columnsTo": [ 609 + "id" 610 + ], 611 + "onDelete": "no action", 612 + "onUpdate": "no action" 613 + } 614 + }, 615 + "compositePrimaryKeys": {}, 616 + "uniqueConstraints": {}, 617 + "policies": {}, 618 + "checkConstraints": {}, 619 + "isRLSEnabled": false 620 + }, 621 + "public.sandbox_variables": { 622 + "name": "sandbox_variables", 623 + "schema": "", 624 + "columns": { 625 + "id": { 626 + "name": "id", 627 + "type": "text", 628 + "primaryKey": true, 629 + "notNull": true, 630 + "default": "xata_id()" 631 + }, 632 + "sandbox_id": { 633 + "name": "sandbox_id", 634 + "type": "text", 635 + "primaryKey": false, 636 + "notNull": true 637 + }, 638 + "variable_id": { 639 + "name": "variable_id", 640 + "type": "text", 641 + "primaryKey": false, 642 + "notNull": true 643 + }, 644 + "name": { 645 + "name": "name", 646 + "type": "text", 647 + "primaryKey": false, 648 + "notNull": true 649 + }, 650 + "created_at": { 651 + "name": "created_at", 652 + "type": "timestamp", 653 + "primaryKey": false, 654 + "notNull": true, 655 + "default": "now()" 656 + }, 657 + "updated_at": { 658 + "name": "updated_at", 659 + "type": "timestamp", 660 + "primaryKey": false, 661 + "notNull": true, 662 + "default": "now()" 663 + } 664 + }, 665 + "indexes": { 666 + "unique_sandbox_variables_by_name": { 667 + "name": "unique_sandbox_variables_by_name", 668 + "columns": [ 669 + { 670 + "expression": "sandbox_id", 671 + "isExpression": false, 672 + "asc": true, 673 + "nulls": "last" 674 + }, 675 + { 676 + "expression": "name", 677 + "isExpression": false, 678 + "asc": true, 679 + "nulls": "last" 680 + } 681 + ], 682 + "isUnique": true, 683 + "concurrently": false, 684 + "method": "btree", 685 + "with": {} 686 + } 687 + }, 688 + "foreignKeys": { 689 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 690 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 691 + "tableFrom": "sandbox_variables", 692 + "tableTo": "sandboxes", 693 + "columnsFrom": [ 694 + "sandbox_id" 695 + ], 696 + "columnsTo": [ 697 + "id" 698 + ], 699 + "onDelete": "cascade", 700 + "onUpdate": "no action" 701 + }, 702 + "sandbox_variables_variable_id_variables_id_fk": { 703 + "name": "sandbox_variables_variable_id_variables_id_fk", 704 + "tableFrom": "sandbox_variables", 705 + "tableTo": "variables", 706 + "columnsFrom": [ 707 + "variable_id" 708 + ], 709 + "columnsTo": [ 710 + "id" 711 + ], 712 + "onDelete": "no action", 713 + "onUpdate": "no action" 714 + } 715 + }, 716 + "compositePrimaryKeys": {}, 717 + "uniqueConstraints": {}, 718 + "policies": {}, 719 + "checkConstraints": {}, 720 + "isRLSEnabled": false 721 + }, 722 + "public.sandbox_volumes": { 723 + "name": "sandbox_volumes", 724 + "schema": "", 725 + "columns": { 726 + "id": { 727 + "name": "id", 728 + "type": "text", 729 + "primaryKey": true, 730 + "notNull": true, 731 + "default": "volume_id()" 732 + }, 733 + "sandbox_id": { 734 + "name": "sandbox_id", 735 + "type": "text", 736 + "primaryKey": false, 737 + "notNull": true 738 + }, 739 + "volume_id": { 740 + "name": "volume_id", 741 + "type": "text", 742 + "primaryKey": false, 743 + "notNull": true 744 + }, 745 + "name": { 746 + "name": "name", 747 + "type": "text", 748 + "primaryKey": false, 749 + "notNull": false 750 + }, 751 + "path": { 752 + "name": "path", 753 + "type": "text", 754 + "primaryKey": false, 755 + "notNull": true 756 + }, 757 + "created_at": { 758 + "name": "created_at", 759 + "type": "timestamp", 760 + "primaryKey": false, 761 + "notNull": true, 762 + "default": "now()" 763 + }, 764 + "updated_at": { 765 + "name": "updated_at", 766 + "type": "timestamp", 767 + "primaryKey": false, 768 + "notNull": true, 769 + "default": "now()" 770 + } 771 + }, 772 + "indexes": { 773 + "unique_sandbox_volume_path": { 774 + "name": "unique_sandbox_volume_path", 775 + "columns": [ 776 + { 777 + "expression": "sandbox_id", 778 + "isExpression": false, 779 + "asc": true, 780 + "nulls": "last" 781 + }, 782 + { 783 + "expression": "path", 784 + "isExpression": false, 785 + "asc": true, 786 + "nulls": "last" 787 + } 788 + ], 789 + "isUnique": true, 790 + "concurrently": false, 791 + "method": "btree", 792 + "with": {} 793 + } 794 + }, 795 + "foreignKeys": { 796 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 797 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 798 + "tableFrom": "sandbox_volumes", 799 + "tableTo": "sandboxes", 800 + "columnsFrom": [ 801 + "sandbox_id" 802 + ], 803 + "columnsTo": [ 804 + "id" 805 + ], 806 + "onDelete": "cascade", 807 + "onUpdate": "no action" 808 + }, 809 + "sandbox_volumes_volume_id_volumes_id_fk": { 810 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 811 + "tableFrom": "sandbox_volumes", 812 + "tableTo": "volumes", 813 + "columnsFrom": [ 814 + "volume_id" 815 + ], 816 + "columnsTo": [ 817 + "id" 818 + ], 819 + "onDelete": "no action", 820 + "onUpdate": "no action" 821 + } 822 + }, 823 + "compositePrimaryKeys": {}, 824 + "uniqueConstraints": {}, 825 + "policies": {}, 826 + "checkConstraints": {}, 827 + "isRLSEnabled": false 828 + }, 829 + "public.sandboxes": { 830 + "name": "sandboxes", 831 + "schema": "", 832 + "columns": { 833 + "id": { 834 + "name": "id", 835 + "type": "text", 836 + "primaryKey": true, 837 + "notNull": true, 838 + "default": "sandbox_id()" 839 + }, 840 + "base": { 841 + "name": "base", 842 + "type": "text", 843 + "primaryKey": false, 844 + "notNull": false 845 + }, 846 + "name": { 847 + "name": "name", 848 + "type": "text", 849 + "primaryKey": false, 850 + "notNull": true 851 + }, 852 + "display_name": { 853 + "name": "display_name", 854 + "type": "text", 855 + "primaryKey": false, 856 + "notNull": false 857 + }, 858 + "uri": { 859 + "name": "uri", 860 + "type": "text", 861 + "primaryKey": false, 862 + "notNull": false 863 + }, 864 + "cid": { 865 + "name": "cid", 866 + "type": "text", 867 + "primaryKey": false, 868 + "notNull": false 869 + }, 870 + "repo": { 871 + "name": "repo", 872 + "type": "text", 873 + "primaryKey": false, 874 + "notNull": false 875 + }, 876 + "provider": { 877 + "name": "provider", 878 + "type": "text", 879 + "primaryKey": false, 880 + "notNull": true, 881 + "default": "'cloudflare'" 882 + }, 883 + "description": { 884 + "name": "description", 885 + "type": "text", 886 + "primaryKey": false, 887 + "notNull": false 888 + }, 889 + "topics": { 890 + "name": "topics", 891 + "type": "text[]", 892 + "primaryKey": false, 893 + "notNull": false 894 + }, 895 + "logo": { 896 + "name": "logo", 897 + "type": "text", 898 + "primaryKey": false, 899 + "notNull": false 900 + }, 901 + "readme": { 902 + "name": "readme", 903 + "type": "text", 904 + "primaryKey": false, 905 + "notNull": false 906 + }, 907 + "public_key": { 908 + "name": "public_key", 909 + "type": "text", 910 + "primaryKey": false, 911 + "notNull": true 912 + }, 913 + "user_id": { 914 + "name": "user_id", 915 + "type": "text", 916 + "primaryKey": false, 917 + "notNull": false 918 + }, 919 + "instance_type": { 920 + "name": "instance_type", 921 + "type": "text", 922 + "primaryKey": false, 923 + "notNull": false 924 + }, 925 + "vcpus": { 926 + "name": "vcpus", 927 + "type": "integer", 928 + "primaryKey": false, 929 + "notNull": false 930 + }, 931 + "memory": { 932 + "name": "memory", 933 + "type": "integer", 934 + "primaryKey": false, 935 + "notNull": false 936 + }, 937 + "disk": { 938 + "name": "disk", 939 + "type": "integer", 940 + "primaryKey": false, 941 + "notNull": false 942 + }, 943 + "status": { 944 + "name": "status", 945 + "type": "text", 946 + "primaryKey": false, 947 + "notNull": true 948 + }, 949 + "keep_alive": { 950 + "name": "keep_alive", 951 + "type": "boolean", 952 + "primaryKey": false, 953 + "notNull": true, 954 + "default": false 955 + }, 956 + "sleep_after": { 957 + "name": "sleep_after", 958 + "type": "text", 959 + "primaryKey": false, 960 + "notNull": false 961 + }, 962 + "sandbox_id": { 963 + "name": "sandbox_id", 964 + "type": "text", 965 + "primaryKey": false, 966 + "notNull": false 967 + }, 968 + "installs": { 969 + "name": "installs", 970 + "type": "integer", 971 + "primaryKey": false, 972 + "notNull": true, 973 + "default": 0 974 + }, 975 + "started_at": { 976 + "name": "started_at", 977 + "type": "timestamp", 978 + "primaryKey": false, 979 + "notNull": false 980 + }, 981 + "created_at": { 982 + "name": "created_at", 983 + "type": "timestamp", 984 + "primaryKey": false, 985 + "notNull": true, 986 + "default": "now()" 987 + }, 988 + "updated_at": { 989 + "name": "updated_at", 990 + "type": "timestamp", 991 + "primaryKey": false, 992 + "notNull": true, 993 + "default": "now()" 994 + } 995 + }, 996 + "indexes": {}, 997 + "foreignKeys": { 998 + "sandboxes_user_id_users_id_fk": { 999 + "name": "sandboxes_user_id_users_id_fk", 1000 + "tableFrom": "sandboxes", 1001 + "tableTo": "users", 1002 + "columnsFrom": [ 1003 + "user_id" 1004 + ], 1005 + "columnsTo": [ 1006 + "id" 1007 + ], 1008 + "onDelete": "no action", 1009 + "onUpdate": "no action" 1010 + } 1011 + }, 1012 + "compositePrimaryKeys": {}, 1013 + "uniqueConstraints": { 1014 + "sandboxes_name_unique": { 1015 + "name": "sandboxes_name_unique", 1016 + "nullsNotDistinct": false, 1017 + "columns": [ 1018 + "name" 1019 + ] 1020 + }, 1021 + "sandboxes_uri_unique": { 1022 + "name": "sandboxes_uri_unique", 1023 + "nullsNotDistinct": false, 1024 + "columns": [ 1025 + "uri" 1026 + ] 1027 + }, 1028 + "sandboxes_cid_unique": { 1029 + "name": "sandboxes_cid_unique", 1030 + "nullsNotDistinct": false, 1031 + "columns": [ 1032 + "cid" 1033 + ] 1034 + } 1035 + }, 1036 + "policies": {}, 1037 + "checkConstraints": {}, 1038 + "isRLSEnabled": false 1039 + }, 1040 + "public.secrets": { 1041 + "name": "secrets", 1042 + "schema": "", 1043 + "columns": { 1044 + "id": { 1045 + "name": "id", 1046 + "type": "text", 1047 + "primaryKey": true, 1048 + "notNull": true, 1049 + "default": "secret_id()" 1050 + }, 1051 + "name": { 1052 + "name": "name", 1053 + "type": "text", 1054 + "primaryKey": false, 1055 + "notNull": true 1056 + }, 1057 + "value": { 1058 + "name": "value", 1059 + "type": "text", 1060 + "primaryKey": false, 1061 + "notNull": true 1062 + }, 1063 + "redacted": { 1064 + "name": "redacted", 1065 + "type": "text", 1066 + "primaryKey": false, 1067 + "notNull": false 1068 + }, 1069 + "created_at": { 1070 + "name": "created_at", 1071 + "type": "timestamp", 1072 + "primaryKey": false, 1073 + "notNull": true, 1074 + "default": "now()" 1075 + } 1076 + }, 1077 + "indexes": {}, 1078 + "foreignKeys": {}, 1079 + "compositePrimaryKeys": {}, 1080 + "uniqueConstraints": {}, 1081 + "policies": {}, 1082 + "checkConstraints": {}, 1083 + "isRLSEnabled": false 1084 + }, 1085 + "public.services": { 1086 + "name": "services", 1087 + "schema": "", 1088 + "columns": { 1089 + "id": { 1090 + "name": "id", 1091 + "type": "text", 1092 + "primaryKey": true, 1093 + "notNull": true, 1094 + "default": "xata_id()" 1095 + }, 1096 + "sandbox_id": { 1097 + "name": "sandbox_id", 1098 + "type": "text", 1099 + "primaryKey": false, 1100 + "notNull": true 1101 + }, 1102 + "name": { 1103 + "name": "name", 1104 + "type": "text", 1105 + "primaryKey": false, 1106 + "notNull": true 1107 + }, 1108 + "command": { 1109 + "name": "command", 1110 + "type": "text", 1111 + "primaryKey": false, 1112 + "notNull": true 1113 + }, 1114 + "description": { 1115 + "name": "description", 1116 + "type": "text", 1117 + "primaryKey": false, 1118 + "notNull": false 1119 + }, 1120 + "service_id": { 1121 + "name": "service_id", 1122 + "type": "text", 1123 + "primaryKey": false, 1124 + "notNull": false 1125 + }, 1126 + "status": { 1127 + "name": "status", 1128 + "type": "text", 1129 + "primaryKey": false, 1130 + "notNull": true, 1131 + "default": "'STOPPED'" 1132 + }, 1133 + "created_at": { 1134 + "name": "created_at", 1135 + "type": "timestamp", 1136 + "primaryKey": false, 1137 + "notNull": true, 1138 + "default": "now()" 1139 + }, 1140 + "updated_at": { 1141 + "name": "updated_at", 1142 + "type": "timestamp", 1143 + "primaryKey": false, 1144 + "notNull": true, 1145 + "default": "now()" 1146 + } 1147 + }, 1148 + "indexes": { 1149 + "unique_sandbox_service": { 1150 + "name": "unique_sandbox_service", 1151 + "columns": [ 1152 + { 1153 + "expression": "name", 1154 + "isExpression": false, 1155 + "asc": true, 1156 + "nulls": "last" 1157 + }, 1158 + { 1159 + "expression": "sandbox_id", 1160 + "isExpression": false, 1161 + "asc": true, 1162 + "nulls": "last" 1163 + } 1164 + ], 1165 + "isUnique": true, 1166 + "concurrently": false, 1167 + "method": "btree", 1168 + "with": {} 1169 + } 1170 + }, 1171 + "foreignKeys": { 1172 + "services_sandbox_id_sandboxes_id_fk": { 1173 + "name": "services_sandbox_id_sandboxes_id_fk", 1174 + "tableFrom": "services", 1175 + "tableTo": "sandboxes", 1176 + "columnsFrom": [ 1177 + "sandbox_id" 1178 + ], 1179 + "columnsTo": [ 1180 + "id" 1181 + ], 1182 + "onDelete": "cascade", 1183 + "onUpdate": "no action" 1184 + } 1185 + }, 1186 + "compositePrimaryKeys": {}, 1187 + "uniqueConstraints": {}, 1188 + "policies": {}, 1189 + "checkConstraints": {}, 1190 + "isRLSEnabled": false 1191 + }, 1192 + "public.snapshots": { 1193 + "name": "snapshots", 1194 + "schema": "", 1195 + "columns": { 1196 + "id": { 1197 + "name": "id", 1198 + "type": "text", 1199 + "primaryKey": true, 1200 + "notNull": true, 1201 + "default": "snapshot_id()" 1202 + }, 1203 + "slug": { 1204 + "name": "slug", 1205 + "type": "text", 1206 + "primaryKey": false, 1207 + "notNull": true 1208 + }, 1209 + "created_at": { 1210 + "name": "created_at", 1211 + "type": "timestamp", 1212 + "primaryKey": false, 1213 + "notNull": true, 1214 + "default": "now()" 1215 + } 1216 + }, 1217 + "indexes": {}, 1218 + "foreignKeys": {}, 1219 + "compositePrimaryKeys": {}, 1220 + "uniqueConstraints": { 1221 + "snapshots_slug_unique": { 1222 + "name": "snapshots_slug_unique", 1223 + "nullsNotDistinct": false, 1224 + "columns": [ 1225 + "slug" 1226 + ] 1227 + } 1228 + }, 1229 + "policies": {}, 1230 + "checkConstraints": {}, 1231 + "isRLSEnabled": false 1232 + }, 1233 + "public.sprite_auth": { 1234 + "name": "sprite_auth", 1235 + "schema": "", 1236 + "columns": { 1237 + "id": { 1238 + "name": "id", 1239 + "type": "text", 1240 + "primaryKey": true, 1241 + "notNull": true, 1242 + "default": "xata_id()" 1243 + }, 1244 + "sandbox_id": { 1245 + "name": "sandbox_id", 1246 + "type": "text", 1247 + "primaryKey": false, 1248 + "notNull": true 1249 + }, 1250 + "user_id": { 1251 + "name": "user_id", 1252 + "type": "text", 1253 + "primaryKey": false, 1254 + "notNull": true 1255 + }, 1256 + "sprite_token": { 1257 + "name": "sprite_token", 1258 + "type": "text", 1259 + "primaryKey": false, 1260 + "notNull": true 1261 + }, 1262 + "redacted_sprite_token": { 1263 + "name": "redacted_sprite_token", 1264 + "type": "text", 1265 + "primaryKey": false, 1266 + "notNull": true 1267 + }, 1268 + "created_at": { 1269 + "name": "created_at", 1270 + "type": "timestamp", 1271 + "primaryKey": false, 1272 + "notNull": true, 1273 + "default": "now()" 1274 + } 1275 + }, 1276 + "indexes": { 1277 + "unique_sprite_auth": { 1278 + "name": "unique_sprite_auth", 1279 + "columns": [ 1280 + { 1281 + "expression": "sandbox_id", 1282 + "isExpression": false, 1283 + "asc": true, 1284 + "nulls": "last" 1285 + }, 1286 + { 1287 + "expression": "user_id", 1288 + "isExpression": false, 1289 + "asc": true, 1290 + "nulls": "last" 1291 + } 1292 + ], 1293 + "isUnique": true, 1294 + "concurrently": false, 1295 + "method": "btree", 1296 + "with": {} 1297 + } 1298 + }, 1299 + "foreignKeys": { 1300 + "sprite_auth_sandbox_id_sandboxes_id_fk": { 1301 + "name": "sprite_auth_sandbox_id_sandboxes_id_fk", 1302 + "tableFrom": "sprite_auth", 1303 + "tableTo": "sandboxes", 1304 + "columnsFrom": [ 1305 + "sandbox_id" 1306 + ], 1307 + "columnsTo": [ 1308 + "id" 1309 + ], 1310 + "onDelete": "cascade", 1311 + "onUpdate": "no action" 1312 + }, 1313 + "sprite_auth_user_id_users_id_fk": { 1314 + "name": "sprite_auth_user_id_users_id_fk", 1315 + "tableFrom": "sprite_auth", 1316 + "tableTo": "users", 1317 + "columnsFrom": [ 1318 + "user_id" 1319 + ], 1320 + "columnsTo": [ 1321 + "id" 1322 + ], 1323 + "onDelete": "no action", 1324 + "onUpdate": "no action" 1325 + } 1326 + }, 1327 + "compositePrimaryKeys": {}, 1328 + "uniqueConstraints": {}, 1329 + "policies": {}, 1330 + "checkConstraints": {}, 1331 + "isRLSEnabled": false 1332 + }, 1333 + "public.ssh_keys": { 1334 + "name": "ssh_keys", 1335 + "schema": "", 1336 + "columns": { 1337 + "id": { 1338 + "name": "id", 1339 + "type": "text", 1340 + "primaryKey": true, 1341 + "notNull": true, 1342 + "default": "xata_id()" 1343 + }, 1344 + "sandbox_id": { 1345 + "name": "sandbox_id", 1346 + "type": "text", 1347 + "primaryKey": false, 1348 + "notNull": true 1349 + }, 1350 + "public_key": { 1351 + "name": "public_key", 1352 + "type": "text", 1353 + "primaryKey": false, 1354 + "notNull": true 1355 + }, 1356 + "private_key": { 1357 + "name": "private_key", 1358 + "type": "text", 1359 + "primaryKey": false, 1360 + "notNull": true 1361 + }, 1362 + "redacted": { 1363 + "name": "redacted", 1364 + "type": "text", 1365 + "primaryKey": false, 1366 + "notNull": false 1367 + }, 1368 + "created_at": { 1369 + "name": "created_at", 1370 + "type": "timestamp", 1371 + "primaryKey": false, 1372 + "notNull": true, 1373 + "default": "now()" 1374 + } 1375 + }, 1376 + "indexes": { 1377 + "unique_sandbox_ssh_key": { 1378 + "name": "unique_sandbox_ssh_key", 1379 + "columns": [ 1380 + { 1381 + "expression": "public_key", 1382 + "isExpression": false, 1383 + "asc": true, 1384 + "nulls": "last" 1385 + }, 1386 + { 1387 + "expression": "sandbox_id", 1388 + "isExpression": false, 1389 + "asc": true, 1390 + "nulls": "last" 1391 + } 1392 + ], 1393 + "isUnique": true, 1394 + "concurrently": false, 1395 + "method": "btree", 1396 + "with": {} 1397 + } 1398 + }, 1399 + "foreignKeys": { 1400 + "ssh_keys_sandbox_id_sandboxes_id_fk": { 1401 + "name": "ssh_keys_sandbox_id_sandboxes_id_fk", 1402 + "tableFrom": "ssh_keys", 1403 + "tableTo": "sandboxes", 1404 + "columnsFrom": [ 1405 + "sandbox_id" 1406 + ], 1407 + "columnsTo": [ 1408 + "id" 1409 + ], 1410 + "onDelete": "cascade", 1411 + "onUpdate": "no action" 1412 + } 1413 + }, 1414 + "compositePrimaryKeys": {}, 1415 + "uniqueConstraints": {}, 1416 + "policies": {}, 1417 + "checkConstraints": {}, 1418 + "isRLSEnabled": false 1419 + }, 1420 + "public.tailscale_auth_keys": { 1421 + "name": "tailscale_auth_keys", 1422 + "schema": "", 1423 + "columns": { 1424 + "id": { 1425 + "name": "id", 1426 + "type": "text", 1427 + "primaryKey": true, 1428 + "notNull": true, 1429 + "default": "xata_id()" 1430 + }, 1431 + "sandbox_id": { 1432 + "name": "sandbox_id", 1433 + "type": "text", 1434 + "primaryKey": false, 1435 + "notNull": true 1436 + }, 1437 + "auth_key": { 1438 + "name": "auth_key", 1439 + "type": "text", 1440 + "primaryKey": false, 1441 + "notNull": true 1442 + }, 1443 + "redacted": { 1444 + "name": "redacted", 1445 + "type": "text", 1446 + "primaryKey": false, 1447 + "notNull": true 1448 + }, 1449 + "created_at": { 1450 + "name": "created_at", 1451 + "type": "timestamp", 1452 + "primaryKey": false, 1453 + "notNull": true, 1454 + "default": "now()" 1455 + } 1456 + }, 1457 + "indexes": {}, 1458 + "foreignKeys": { 1459 + "tailscale_auth_keys_sandbox_id_sandboxes_id_fk": { 1460 + "name": "tailscale_auth_keys_sandbox_id_sandboxes_id_fk", 1461 + "tableFrom": "tailscale_auth_keys", 1462 + "tableTo": "sandboxes", 1463 + "columnsFrom": [ 1464 + "sandbox_id" 1465 + ], 1466 + "columnsTo": [ 1467 + "id" 1468 + ], 1469 + "onDelete": "cascade", 1470 + "onUpdate": "no action" 1471 + } 1472 + }, 1473 + "compositePrimaryKeys": {}, 1474 + "uniqueConstraints": {}, 1475 + "policies": {}, 1476 + "checkConstraints": {}, 1477 + "isRLSEnabled": false 1478 + }, 1479 + "public.users": { 1480 + "name": "users", 1481 + "schema": "", 1482 + "columns": { 1483 + "id": { 1484 + "name": "id", 1485 + "type": "text", 1486 + "primaryKey": true, 1487 + "notNull": true, 1488 + "default": "xata_id()" 1489 + }, 1490 + "did": { 1491 + "name": "did", 1492 + "type": "text", 1493 + "primaryKey": false, 1494 + "notNull": true 1495 + }, 1496 + "display_name": { 1497 + "name": "display_name", 1498 + "type": "text", 1499 + "primaryKey": false, 1500 + "notNull": false 1501 + }, 1502 + "handle": { 1503 + "name": "handle", 1504 + "type": "text", 1505 + "primaryKey": false, 1506 + "notNull": true 1507 + }, 1508 + "avatar": { 1509 + "name": "avatar", 1510 + "type": "text", 1511 + "primaryKey": false, 1512 + "notNull": false 1513 + }, 1514 + "created_at": { 1515 + "name": "created_at", 1516 + "type": "timestamp", 1517 + "primaryKey": false, 1518 + "notNull": true, 1519 + "default": "now()" 1520 + }, 1521 + "updated_at": { 1522 + "name": "updated_at", 1523 + "type": "timestamp", 1524 + "primaryKey": false, 1525 + "notNull": true, 1526 + "default": "now()" 1527 + } 1528 + }, 1529 + "indexes": {}, 1530 + "foreignKeys": {}, 1531 + "compositePrimaryKeys": {}, 1532 + "uniqueConstraints": { 1533 + "users_did_unique": { 1534 + "name": "users_did_unique", 1535 + "nullsNotDistinct": false, 1536 + "columns": [ 1537 + "did" 1538 + ] 1539 + }, 1540 + "users_handle_unique": { 1541 + "name": "users_handle_unique", 1542 + "nullsNotDistinct": false, 1543 + "columns": [ 1544 + "handle" 1545 + ] 1546 + } 1547 + }, 1548 + "policies": {}, 1549 + "checkConstraints": {}, 1550 + "isRLSEnabled": false 1551 + }, 1552 + "public.variables": { 1553 + "name": "variables", 1554 + "schema": "", 1555 + "columns": { 1556 + "id": { 1557 + "name": "id", 1558 + "type": "text", 1559 + "primaryKey": true, 1560 + "notNull": true, 1561 + "default": "variable_id()" 1562 + }, 1563 + "name": { 1564 + "name": "name", 1565 + "type": "text", 1566 + "primaryKey": false, 1567 + "notNull": true 1568 + }, 1569 + "value": { 1570 + "name": "value", 1571 + "type": "text", 1572 + "primaryKey": false, 1573 + "notNull": true 1574 + }, 1575 + "created_at": { 1576 + "name": "created_at", 1577 + "type": "timestamp", 1578 + "primaryKey": false, 1579 + "notNull": true, 1580 + "default": "now()" 1581 + }, 1582 + "updated_at": { 1583 + "name": "updated_at", 1584 + "type": "timestamp", 1585 + "primaryKey": false, 1586 + "notNull": true, 1587 + "default": "now()" 1588 + } 1589 + }, 1590 + "indexes": {}, 1591 + "foreignKeys": {}, 1592 + "compositePrimaryKeys": {}, 1593 + "uniqueConstraints": {}, 1594 + "policies": {}, 1595 + "checkConstraints": {}, 1596 + "isRLSEnabled": false 1597 + }, 1598 + "public.vercel_auth": { 1599 + "name": "vercel_auth", 1600 + "schema": "", 1601 + "columns": { 1602 + "id": { 1603 + "name": "id", 1604 + "type": "text", 1605 + "primaryKey": true, 1606 + "notNull": true, 1607 + "default": "xata_id()" 1608 + }, 1609 + "sandbox_id": { 1610 + "name": "sandbox_id", 1611 + "type": "text", 1612 + "primaryKey": false, 1613 + "notNull": true 1614 + }, 1615 + "user_id": { 1616 + "name": "user_id", 1617 + "type": "text", 1618 + "primaryKey": false, 1619 + "notNull": true 1620 + }, 1621 + "vercel_token": { 1622 + "name": "vercel_token", 1623 + "type": "text", 1624 + "primaryKey": false, 1625 + "notNull": true 1626 + }, 1627 + "redacted_vercel_token": { 1628 + "name": "redacted_vercel_token", 1629 + "type": "text", 1630 + "primaryKey": false, 1631 + "notNull": true 1632 + }, 1633 + "project_id": { 1634 + "name": "project_id", 1635 + "type": "text", 1636 + "primaryKey": false, 1637 + "notNull": true 1638 + }, 1639 + "team_id": { 1640 + "name": "team_id", 1641 + "type": "text", 1642 + "primaryKey": false, 1643 + "notNull": true 1644 + }, 1645 + "created_at": { 1646 + "name": "created_at", 1647 + "type": "timestamp", 1648 + "primaryKey": false, 1649 + "notNull": true, 1650 + "default": "now()" 1651 + } 1652 + }, 1653 + "indexes": { 1654 + "unique_vercel_auth": { 1655 + "name": "unique_vercel_auth", 1656 + "columns": [ 1657 + { 1658 + "expression": "sandbox_id", 1659 + "isExpression": false, 1660 + "asc": true, 1661 + "nulls": "last" 1662 + }, 1663 + { 1664 + "expression": "user_id", 1665 + "isExpression": false, 1666 + "asc": true, 1667 + "nulls": "last" 1668 + } 1669 + ], 1670 + "isUnique": true, 1671 + "concurrently": false, 1672 + "method": "btree", 1673 + "with": {} 1674 + } 1675 + }, 1676 + "foreignKeys": { 1677 + "vercel_auth_sandbox_id_sandboxes_id_fk": { 1678 + "name": "vercel_auth_sandbox_id_sandboxes_id_fk", 1679 + "tableFrom": "vercel_auth", 1680 + "tableTo": "sandboxes", 1681 + "columnsFrom": [ 1682 + "sandbox_id" 1683 + ], 1684 + "columnsTo": [ 1685 + "id" 1686 + ], 1687 + "onDelete": "cascade", 1688 + "onUpdate": "no action" 1689 + }, 1690 + "vercel_auth_user_id_users_id_fk": { 1691 + "name": "vercel_auth_user_id_users_id_fk", 1692 + "tableFrom": "vercel_auth", 1693 + "tableTo": "users", 1694 + "columnsFrom": [ 1695 + "user_id" 1696 + ], 1697 + "columnsTo": [ 1698 + "id" 1699 + ], 1700 + "onDelete": "no action", 1701 + "onUpdate": "no action" 1702 + } 1703 + }, 1704 + "compositePrimaryKeys": {}, 1705 + "uniqueConstraints": {}, 1706 + "policies": {}, 1707 + "checkConstraints": {}, 1708 + "isRLSEnabled": false 1709 + }, 1710 + "public.volumes": { 1711 + "name": "volumes", 1712 + "schema": "", 1713 + "columns": { 1714 + "id": { 1715 + "name": "id", 1716 + "type": "text", 1717 + "primaryKey": true, 1718 + "notNull": true, 1719 + "default": "volume_id()" 1720 + }, 1721 + "slug": { 1722 + "name": "slug", 1723 + "type": "text", 1724 + "primaryKey": false, 1725 + "notNull": true 1726 + }, 1727 + "size": { 1728 + "name": "size", 1729 + "type": "integer", 1730 + "primaryKey": false, 1731 + "notNull": true 1732 + }, 1733 + "size_unit": { 1734 + "name": "size_unit", 1735 + "type": "text", 1736 + "primaryKey": false, 1737 + "notNull": true 1738 + }, 1739 + "created_at": { 1740 + "name": "created_at", 1741 + "type": "timestamp", 1742 + "primaryKey": false, 1743 + "notNull": true, 1744 + "default": "now()" 1745 + }, 1746 + "updated_at": { 1747 + "name": "updated_at", 1748 + "type": "timestamp", 1749 + "primaryKey": false, 1750 + "notNull": true, 1751 + "default": "now()" 1752 + } 1753 + }, 1754 + "indexes": {}, 1755 + "foreignKeys": {}, 1756 + "compositePrimaryKeys": {}, 1757 + "uniqueConstraints": { 1758 + "volumes_slug_unique": { 1759 + "name": "volumes_slug_unique", 1760 + "nullsNotDistinct": false, 1761 + "columns": [ 1762 + "slug" 1763 + ] 1764 + } 1765 + }, 1766 + "policies": {}, 1767 + "checkConstraints": {}, 1768 + "isRLSEnabled": false 1769 + }, 1770 + "public.integrations": { 1771 + "name": "integrations", 1772 + "schema": "", 1773 + "columns": { 1774 + "id": { 1775 + "name": "id", 1776 + "type": "text", 1777 + "primaryKey": true, 1778 + "notNull": true, 1779 + "default": "xata_id()" 1780 + }, 1781 + "sandbox_id": { 1782 + "name": "sandbox_id", 1783 + "type": "text", 1784 + "primaryKey": false, 1785 + "notNull": true 1786 + }, 1787 + "name": { 1788 + "name": "name", 1789 + "type": "text", 1790 + "primaryKey": false, 1791 + "notNull": true 1792 + }, 1793 + "description": { 1794 + "name": "description", 1795 + "type": "text", 1796 + "primaryKey": false, 1797 + "notNull": false 1798 + }, 1799 + "webhook_url": { 1800 + "name": "webhook_url", 1801 + "type": "text", 1802 + "primaryKey": false, 1803 + "notNull": true 1804 + }, 1805 + "created_at": { 1806 + "name": "created_at", 1807 + "type": "timestamp", 1808 + "primaryKey": false, 1809 + "notNull": true, 1810 + "default": "now()" 1811 + } 1812 + }, 1813 + "indexes": { 1814 + "unique_sandbox_integration": { 1815 + "name": "unique_sandbox_integration", 1816 + "columns": [ 1817 + { 1818 + "expression": "sandbox_id", 1819 + "isExpression": false, 1820 + "asc": true, 1821 + "nulls": "last" 1822 + }, 1823 + { 1824 + "expression": "name", 1825 + "isExpression": false, 1826 + "asc": true, 1827 + "nulls": "last" 1828 + } 1829 + ], 1830 + "isUnique": true, 1831 + "concurrently": false, 1832 + "method": "btree", 1833 + "with": {} 1834 + } 1835 + }, 1836 + "foreignKeys": { 1837 + "integrations_sandbox_id_sandboxes_id_fk": { 1838 + "name": "integrations_sandbox_id_sandboxes_id_fk", 1839 + "tableFrom": "integrations", 1840 + "tableTo": "sandboxes", 1841 + "columnsFrom": [ 1842 + "sandbox_id" 1843 + ], 1844 + "columnsTo": [ 1845 + "id" 1846 + ], 1847 + "onDelete": "cascade", 1848 + "onUpdate": "no action" 1849 + } 1850 + }, 1851 + "compositePrimaryKeys": {}, 1852 + "uniqueConstraints": {}, 1853 + "policies": {}, 1854 + "checkConstraints": {}, 1855 + "isRLSEnabled": false 1856 + } 1857 + }, 1858 + "enums": {}, 1859 + "schemas": {}, 1860 + "sequences": {}, 1861 + "roles": {}, 1862 + "policies": {}, 1863 + "views": {}, 1864 + "_meta": { 1865 + "columns": {}, 1866 + "schemas": {}, 1867 + "tables": {} 1868 + } 1869 + }
+7
apps/cf-sandbox/drizzle/meta/_journal.json
··· 274 274 "when": 1774809740001, 275 275 "tag": "0038_massive_captain_midlands", 276 276 "breakpoints": true 277 + }, 278 + { 279 + "idx": 39, 280 + "version": "7", 281 + "when": 1774884985913, 282 + "tag": "0039_tense_micromax", 283 + "breakpoints": true 277 284 } 278 285 ] 279 286 }
+2
apps/cf-sandbox/src/schema/vercel-auth.ts
··· 17 17 .references(() => users.id), 18 18 vercelToken: text("vercel_token").notNull(), 19 19 redactedVercelToken: text("redacted_vercel_token").notNull(), 20 + projectId: text("project_id").notNull(), 21 + teamId: text("team_id").notNull(), 20 22 createdAt: timestamp("created_at").defaultNow().notNull(), 21 23 }, 22 24 (t) => [uniqueIndex("unique_vercel_auth").on(t.sandboxId, t.userId)],
+16
apps/cli/src/cmd/create.ts
··· 77 77 providerOptions.redactedDenoDeployToken = redact(denoDeployToken); 78 78 } 79 79 80 + if (provider === "vercel") { 81 + const vercelApiToken = process.env.VERCEL_API_TOKEN; 82 + const vercelProjectId = process.env.VERCEL_PROJECT_ID; 83 + const vercelTeamId = process.env.VERCEL_TEAM_ID; 84 + if (!vercelApiToken || !vercelProjectId || !vercelTeamId) { 85 + consola.error( 86 + "VERCEL_API_TOKEN, VERCEL_PROJECT_ID and VERCEL_TEAM_ID environment variables are required for Vercel provider.", 87 + ); 88 + process.exit(1); 89 + } 90 + providerOptions.vercelApiToken = await encrypt(vercelApiToken); 91 + providerOptions.redactedVercelApiToken = redact(vercelApiToken); 92 + providerOptions.vercelProjectId = vercelProjectId; 93 + providerOptions.vercelTeamId = vercelTeamId; 94 + } 95 + 80 96 try { 81 97 const sandbox = await client.post<Sandbox>( 82 98 "/xrpc/io.pocketenv.sandbox.createSandbox",
+253 -120
apps/sandbox/src/index.ts
··· 16 16 variables, 17 17 spriteAuth, 18 18 denoAuth, 19 + vercelAuth, 19 20 } from "./schema/mod.ts"; 20 21 import { 21 22 adjectives, ··· 203 204 daytonaApiKey: decrypt(params.daytonaApiKey), 204 205 organizationId: params.daytonaOrganizationId, 205 206 denoDeployToken: decrypt(params.denoDeployToken), 207 + vercelApiToken: decrypt(params.vercelApiKey), 208 + vercelProjectId: params.vercelProjectId, 209 + vercelTeamId: params.vercelTeamId, 206 210 }); 207 211 const sandboxId = await sandbox.id(); 208 212 ··· 263 267 const body = await c.req.json<StartSandboxInput>(); 264 268 const { repo } = StartSandboxInputSchema.parse(body); 265 269 266 - const [[spriteAuthParams], [daytonaAuthParams], [denoAuthParams]] = 267 - await Promise.all([ 268 - c.var.db 269 - .select() 270 - .from(spriteAuth) 271 - .where(eq(spriteAuth.sandboxId, record.id)) 272 - .execute(), 273 - c.var.db 274 - .select() 275 - .from(daytonaAuth) 276 - .where(eq(daytonaAuth.sandboxId, record.id)) 277 - .execute(), 278 - c.var.db 279 - .select() 280 - .from(denoAuth) 281 - .where(eq(denoAuth.sandboxId, record.id)) 282 - .execute(), 283 - ]); 270 + const [ 271 + [spriteAuthParams], 272 + [daytonaAuthParams], 273 + [denoAuthParams], 274 + [vercelAuthParams], 275 + ] = await Promise.all([ 276 + c.var.db 277 + .select() 278 + .from(spriteAuth) 279 + .where(eq(spriteAuth.sandboxId, record.id)) 280 + .execute(), 281 + c.var.db 282 + .select() 283 + .from(daytonaAuth) 284 + .where(eq(daytonaAuth.sandboxId, record.id)) 285 + .execute(), 286 + c.var.db 287 + .select() 288 + .from(denoAuth) 289 + .where(eq(denoAuth.sandboxId, record.id)) 290 + .execute(), 291 + c.var.db 292 + .select() 293 + .from(vercelAuth) 294 + .where(eq(vercelAuth.sandboxId, record.id)) 295 + .execute(), 296 + ]); 284 297 285 298 if (!record.sandboxId) { 286 299 sandbox = await createSandbox(record.provider as Provider, { ··· 302 315 sandbox = await getSandboxById( 303 316 record.provider as Provider, 304 317 record.sandboxId!, 305 - decrypt( 306 - spriteAuthParams?.spriteToken || 307 - daytonaAuthParams?.apiKey || 308 - denoAuthParams?.deployToken, 309 - ), 310 - daytonaAuthParams?.organizationId, 318 + { 319 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 320 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 321 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 322 + organizationId: daytonaAuthParams?.organizationId, 323 + vercelApiToken: decrypt(vercelAuthParams?.vercelToken), 324 + vercelProjectId: vercelAuthParams?.projectId, 325 + vercelTeamId: vercelAuthParams?.teamId, 326 + }, 311 327 ); 312 328 313 329 if (!sandbox) { ··· 430 446 return c.json({ error: "Sandbox provider not supported" }, 400); 431 447 } 432 448 433 - const [[spriteAuthParams], [daytonaAuthParams], [denoAuthParams]] = 434 - await Promise.all([ 435 - c.var.db 436 - .select() 437 - .from(spriteAuth) 438 - .where(eq(spriteAuth.sandboxId, record.id)) 439 - .execute(), 440 - c.var.db 441 - .select() 442 - .from(daytonaAuth) 443 - .where(eq(daytonaAuth.sandboxId, record.id)) 444 - .execute(), 445 - c.var.db 446 - .select() 447 - .from(denoAuth) 448 - .where(eq(denoAuth.sandboxId, record.id)) 449 - .execute(), 450 - ]); 449 + const [ 450 + [spriteAuthParams], 451 + [daytonaAuthParams], 452 + [denoAuthParams], 453 + [vercelAuthParams], 454 + ] = await Promise.all([ 455 + c.var.db 456 + .select() 457 + .from(spriteAuth) 458 + .where(eq(spriteAuth.sandboxId, record.id)) 459 + .execute(), 460 + c.var.db 461 + .select() 462 + .from(daytonaAuth) 463 + .where(eq(daytonaAuth.sandboxId, record.id)) 464 + .execute(), 465 + c.var.db 466 + .select() 467 + .from(denoAuth) 468 + .where(eq(denoAuth.sandboxId, record.id)) 469 + .execute(), 470 + c.var.db 471 + .select() 472 + .from(vercelAuth) 473 + .where(eq(vercelAuth.sandboxId, record.id)) 474 + .execute(), 475 + ]); 476 + 477 + if (!record.sandboxId) { 478 + sandbox = await createSandbox(record.provider as Provider, { 479 + id: record.id, 480 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 481 + organizationId: daytonaAuthParams?.organizationId, 482 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 483 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 484 + }); 485 + const sandboxId = await sandbox.id(); 486 + await c.var.db 487 + .update(sandboxes) 488 + .set({ sandboxId }) 489 + .where(eq(sandboxes.id, record.id)) 490 + .execute(); 491 + record.sandboxId = sandboxId; 492 + } 451 493 452 494 sandbox = await getSandboxById( 453 495 record.provider as Provider, 454 496 record.sandboxId!, 455 - decrypt( 456 - spriteAuthParams?.spriteToken || 457 - daytonaAuthParams?.apiKey || 458 - denoAuthParams?.deployToken, 459 - ), 460 - daytonaAuthParams?.organizationId, 497 + { 498 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 499 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 500 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 501 + organizationId: daytonaAuthParams?.organizationId, 502 + vercelApiToken: decrypt(vercelAuthParams?.vercelToken), 503 + vercelProjectId: vercelAuthParams?.projectId, 504 + vercelTeamId: vercelAuthParams?.teamId, 505 + }, 461 506 ); 462 507 463 508 if (!sandbox) { ··· 486 531 return c.json({ error: "Sandbox provider not supported" }, 400); 487 532 } 488 533 489 - const [[spriteAuthParams], [daytonaAuthParams], [denoAuthParams]] = 490 - await Promise.all([ 491 - c.var.db 492 - .select() 493 - .from(spriteAuth) 494 - .where(eq(spriteAuth.sandboxId, record.id)) 495 - .execute(), 496 - c.var.db 497 - .select() 498 - .from(daytonaAuth) 499 - .where(eq(daytonaAuth.sandboxId, record.id)) 500 - .execute(), 501 - c.var.db 502 - .select() 503 - .from(denoAuth) 504 - .where(eq(denoAuth.sandboxId, record.id)) 505 - .execute(), 506 - ]); 534 + const [ 535 + [spriteAuthParams], 536 + [daytonaAuthParams], 537 + [denoAuthParams], 538 + [vercelAuthParams], 539 + ] = await Promise.all([ 540 + c.var.db 541 + .select() 542 + .from(spriteAuth) 543 + .where(eq(spriteAuth.sandboxId, record.id)) 544 + .execute(), 545 + c.var.db 546 + .select() 547 + .from(daytonaAuth) 548 + .where(eq(daytonaAuth.sandboxId, record.id)) 549 + .execute(), 550 + c.var.db 551 + .select() 552 + .from(denoAuth) 553 + .where(eq(denoAuth.sandboxId, record.id)) 554 + .execute(), 555 + c.var.db 556 + .select() 557 + .from(vercelAuth) 558 + .where(eq(vercelAuth.sandboxId, record.id)) 559 + .execute(), 560 + ]); 561 + 562 + if (!record.sandboxId) { 563 + sandbox = await createSandbox(record.provider as Provider, { 564 + id: record.id, 565 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 566 + organizationId: daytonaAuthParams?.organizationId, 567 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 568 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 569 + }); 570 + const sandboxId = await sandbox.id(); 571 + await c.var.db 572 + .update(sandboxes) 573 + .set({ sandboxId }) 574 + .where(eq(sandboxes.id, record.id)) 575 + .execute(); 576 + record.sandboxId = sandboxId; 577 + } 507 578 508 579 sandbox = await getSandboxById( 509 580 record.provider as Provider, 510 581 record.sandboxId!, 511 - decrypt( 512 - spriteAuthParams?.spriteToken || 513 - daytonaAuthParams?.apiKey || 514 - denoAuthParams?.deployToken, 515 - ), 516 - daytonaAuthParams?.organizationId, 582 + { 583 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 584 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 585 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 586 + organizationId: daytonaAuthParams?.organizationId, 587 + vercelApiToken: decrypt(vercelAuthParams?.vercelToken), 588 + vercelProjectId: vercelAuthParams?.projectId, 589 + vercelTeamId: vercelAuthParams?.teamId, 590 + }, 517 591 ); 518 592 519 593 if (!sandbox) { ··· 538 612 return c.json({ error: "Sandbox provider not supported" }, 400); 539 613 } 540 614 541 - const [[spriteAuthParams], [daytonaAuthParams], [denoAuthParams]] = 542 - await Promise.all([ 543 - c.var.db 544 - .select() 545 - .from(spriteAuth) 546 - .where(eq(spriteAuth.sandboxId, record.id)) 547 - .execute(), 548 - c.var.db 549 - .select() 550 - .from(daytonaAuth) 551 - .where(eq(daytonaAuth.sandboxId, record.id)) 552 - .execute(), 553 - c.var.db 554 - .select() 555 - .from(denoAuth) 556 - .where(eq(denoAuth.sandboxId, record.id)) 557 - .execute(), 558 - ]); 615 + const [ 616 + [spriteAuthParams], 617 + [daytonaAuthParams], 618 + [denoAuthParams], 619 + [vercelAuthParams], 620 + ] = await Promise.all([ 621 + c.var.db 622 + .select() 623 + .from(spriteAuth) 624 + .where(eq(spriteAuth.sandboxId, record.id)) 625 + .execute(), 626 + c.var.db 627 + .select() 628 + .from(daytonaAuth) 629 + .where(eq(daytonaAuth.sandboxId, record.id)) 630 + .execute(), 631 + c.var.db 632 + .select() 633 + .from(denoAuth) 634 + .where(eq(denoAuth.sandboxId, record.id)) 635 + .execute(), 636 + c.var.db 637 + .select() 638 + .from(vercelAuth) 639 + .where(eq(vercelAuth.sandboxId, record.id)) 640 + .execute(), 641 + ]); 642 + 643 + if (!record.sandboxId) { 644 + sandbox = await createSandbox(record.provider as Provider, { 645 + id: record.id, 646 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 647 + organizationId: daytonaAuthParams?.organizationId, 648 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 649 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 650 + }); 651 + const sandboxId = await sandbox.id(); 652 + await c.var.db 653 + .update(sandboxes) 654 + .set({ sandboxId }) 655 + .where(eq(sandboxes.id, record.id)) 656 + .execute(); 657 + record.sandboxId = sandboxId; 658 + } 659 + 559 660 sandbox = await getSandboxById( 560 661 record.provider as Provider, 561 662 record.sandboxId!, 562 - decrypt( 563 - spriteAuthParams?.spriteToken || 564 - daytonaAuthParams?.apiKey || 565 - denoAuthParams?.deployToken, 566 - ), 567 - daytonaAuthParams?.organizationId, 663 + { 664 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 665 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 666 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 667 + organizationId: daytonaAuthParams?.organizationId, 668 + vercelApiToken: decrypt(vercelAuthParams?.vercelToken), 669 + vercelProjectId: vercelAuthParams?.projectId, 670 + vercelTeamId: vercelAuthParams?.teamId, 671 + }, 568 672 ); 569 673 570 674 if (!sandbox) { ··· 594 698 return c.json({ error: "Sandbox provider not supported" }, 400); 595 699 } 596 700 597 - const [[spriteAuthParams], [daytonaAuthParams], [denoAuthParams]] = 598 - await Promise.all([ 599 - c.var.db 600 - .select() 601 - .from(spriteAuth) 602 - .where(eq(spriteAuth.sandboxId, record.id)) 603 - .execute(), 604 - c.var.db 605 - .select() 606 - .from(daytonaAuth) 607 - .where(eq(daytonaAuth.sandboxId, record.id)) 608 - .execute(), 609 - c.var.db 610 - .select() 611 - .from(denoAuth) 612 - .where(eq(denoAuth.sandboxId, record.id)) 613 - .execute(), 614 - ]); 701 + const [ 702 + [spriteAuthParams], 703 + [daytonaAuthParams], 704 + [denoAuthParams], 705 + [vercelAuthParams], 706 + ] = await Promise.all([ 707 + c.var.db 708 + .select() 709 + .from(spriteAuth) 710 + .where(eq(spriteAuth.sandboxId, record.id)) 711 + .execute(), 712 + c.var.db 713 + .select() 714 + .from(daytonaAuth) 715 + .where(eq(daytonaAuth.sandboxId, record.id)) 716 + .execute(), 717 + c.var.db 718 + .select() 719 + .from(denoAuth) 720 + .where(eq(denoAuth.sandboxId, record.id)) 721 + .execute(), 722 + c.var.db 723 + .select() 724 + .from(vercelAuth) 725 + .where(eq(vercelAuth.sandboxId, record.id)) 726 + .execute(), 727 + ]); 728 + 729 + if (!record.sandboxId) { 730 + sandbox = await createSandbox(record.provider as Provider, { 731 + id: record.id, 732 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 733 + organizationId: daytonaAuthParams?.organizationId, 734 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 735 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 736 + }); 737 + const sandboxId = await sandbox.id(); 738 + await c.var.db 739 + .update(sandboxes) 740 + .set({ sandboxId }) 741 + .where(eq(sandboxes.id, record.id)) 742 + .execute(); 743 + record.sandboxId = sandboxId; 744 + } 615 745 616 746 sandbox = await getSandboxById( 617 747 record.provider as Provider, 618 748 record.sandboxId!, 619 - decrypt( 620 - spriteAuthParams?.spriteToken || 621 - daytonaAuthParams?.apiKey || 622 - denoAuthParams?.deployToken, 623 - ), 624 - daytonaAuthParams?.organizationId, 749 + { 750 + daytonaApiKey: decrypt(daytonaAuthParams?.apiKey), 751 + spriteToken: decrypt(spriteAuthParams?.spriteToken), 752 + denoDeployToken: decrypt(denoAuthParams?.deployToken), 753 + organizationId: daytonaAuthParams?.organizationId, 754 + vercelApiToken: decrypt(vercelAuthParams?.vercelToken), 755 + vercelProjectId: vercelAuthParams?.projectId, 756 + vercelTeamId: vercelAuthParams?.teamId, 757 + }, 625 758 ); 626 759 627 760 if (!sandbox) {
+15 -9
apps/sandbox/src/providers/mod.ts
··· 48 48 daytonaApiKey?: string; 49 49 organizationId?: string; 50 50 denoDeployToken?: string; 51 + vercelApiToken?: string; 52 + vercelProjectId?: string; 53 + vercelTeamId?: string; 51 54 [key: string]: any; 52 55 } 53 56 ··· 81 84 export async function getSandboxById( 82 85 provider: Provider, 83 86 id: string, 84 - token?: string, 85 - organizationId?: string, 87 + options?: SandboxOptions, 86 88 ): Promise<BaseSandbox> { 87 89 switch (provider) { 88 90 case "daytona": { 89 91 const module = await import("./daytona/mod.ts"); 90 92 try { 91 - return new module.default().get(id, token, organizationId); 93 + return new module.default().get( 94 + id, 95 + options?.daytonaApiKey, 96 + options?.organizationId, 97 + ); 92 98 } catch (err) { 93 99 console.error(`Error getting Daytona sandbox with ID ${id}:`, err); 94 100 return createSandbox("daytona", { 95 101 id, 96 - daytonaApiKey: token, 97 - organizationId, 102 + daytonaApiKey: options?.daytonaApiKey, 103 + organizationId: options?.organizationId, 98 104 }); 99 105 } 100 106 } 101 107 case "deno": { 102 108 const module = await import("./deno/mod.ts"); 103 109 try { 104 - return await new module.default().get(id, token); 110 + return await new module.default().get(id, options?.denoDeployToken); 105 111 } catch (err) { 106 112 console.error(`Error getting Deno sandbox with ID ${id}:`, err); 107 113 return createSandbox("deno", { 108 114 id, 109 - denoDeployToken: token, 115 + denoDeployToken: options?.denoDeployToken, 110 116 // snapshotRoot: process.env.DENO_SNAPSHOT_ROOT, 111 117 }); 112 118 } 113 119 } 114 120 case "vercel": 115 121 return import("./vercel/mod.ts").then((module) => 116 - new module.default().get(id), 122 + new module.default().get(id, options!), 117 123 ); 118 124 case "sprites": 119 125 return import("./sprites/mod.ts").then((module) => 120 - new module.default().get(id, token), 126 + new module.default().get(id, options?.spriteToken), 121 127 ); 122 128 default: 123 129 consola.error(`Provider ${provider} is not supported yet.`);
+7 -8
apps/sandbox/src/providers/vercel/mod.ts
··· 1 1 import BaseProvider, { BaseSandbox, SandboxOptions } from "../mod.ts"; 2 2 import { Sandbox } from "@vercel/sandbox"; 3 - import process, { env } from "node:process"; 4 3 import consola from "consola"; 5 4 import path from "node:path"; 6 5 import { Buffer } from "node:buffer"; ··· 121 120 class VercelProvider implements BaseProvider { 122 121 async create(options: SandboxOptions): Promise<BaseSandbox> { 123 122 const credentials = { 124 - token: process.env.VERCEL_API_TOKEN, 125 - projectId: process.env.VERCEL_PROJECT_ID, 126 - teamId: process.env.VERCEL_TEAM_ID, 123 + token: options.vercelApiToken, 124 + projectId: options.vercelProjectId, 125 + teamId: options.vercelTeamId, 127 126 }; 128 127 const ports = { 129 128 ports: options.ports, ··· 144 143 return new VercelSandbox(sandbox); 145 144 } 146 145 147 - async get(id: string): Promise<BaseSandbox> { 146 + async get(id: string, options: SandboxOptions): Promise<BaseSandbox> { 148 147 const sandbox = await Sandbox.get({ 149 148 sandboxId: id, 150 - token: process.env.VERCEL_API_TOKEN, 151 - projectId: process.env.VERCEL_PROJECT_ID, 152 - teamId: process.env.VERCEL_TEAM_ID, 149 + token: options.vercelApiToken, 150 + projectId: options.vercelProjectId, 151 + teamId: options.vercelTeamId, 153 152 }); 154 153 return new VercelSandbox(sandbox); 155 154 }
+2
apps/sandbox/src/schema/vercel-auth.ts
··· 17 17 .references(() => users.id), 18 18 vercelToken: text("vercel_token").notNull(), 19 19 redactedVercelToken: text("redacted_vercel_token").notNull(), 20 + projectId: text("project_id").notNull(), 21 + teamId: text("team_id").notNull(), 20 22 createdAt: timestamp("created_at").defaultNow().notNull(), 21 23 }, 22 24 (t) => [uniqueIndex("unique_vercel_auth").on(t.sandboxId, t.userId)],
+30 -3
apps/sandbox/src/types/sandbox.ts
··· 44 44 redactedDenoDeployToken: z.string().optional(), 45 45 redactedDaytonaApiKey: z.string().optional(), 46 46 daytonaApiKey: z.string().optional(), 47 - vercelToken: z.string().optional(), 48 - redactedVercelToken: z.string().optional(), 47 + vercelApiKey: z.string().optional(), 48 + redactedVercelApiKey: z.string().optional(), 49 + vercelProjectId: z.string().optional(), 50 + vercelTeamId: z.string().optional(), 49 51 vcpus: z.number().optional().default(2), 50 52 memory: z.number().optional().default(4), 51 53 disk: z.number().optional().default(3), ··· 140 142 }); 141 143 } 142 144 } 143 - }); 145 + 146 + if (data.provider === "vercel") { 147 + if (!data.vercelApiKey) { 148 + ctx.addIssue({ 149 + code: z.ZodIssueCode.custom, 150 + message: "vercelApiKey is required when provider is 'vercel'", 151 + path: ["vercelApiKey"], 152 + }); 153 + } 154 + if (!data.redactedVercelApiKey) { 155 + ctx.addIssue({ 156 + code: z.ZodIssueCode.custom, 157 + message: 158 + "redactedVercelApiKey is required when provider is 'vercel'", 159 + path: ["redactedVercelApiKey"], 160 + }); 161 + } 162 + if (!data.vercelProjectId) { 163 + ctx.addIssue({ 164 + code: z.ZodIssueCode.custom, 165 + message: 166 + "vercelProjectId is required when provider is 'vercel'", 167 + path: ["vercelProjectId"], 168 + }); 169 + } 170 + } 144 171 145 172 export const StartSandboxInputSchema = z.object({ 146 173 repo: z.string().optional(),
+63 -3
apps/web/src/pages/settings/provider/Provider.tsx
··· 18 18 19 19 const LABELS = { 20 20 daytona: "Daytona API Key", 21 - vercel: "Vercel Access Token", 21 + vercel: "Vercel API Token", 22 22 deno: "Deno Deploy Token", 23 23 sprites: "Sprites API Key", 24 24 } as const; ··· 30 30 provider: z.enum(["cloudflare", "daytona", "vercel", "deno", "sprites"]), 31 31 apiKey: z.string().optional(), 32 32 organizationId: z.string().optional(), 33 + vercelProjectId: z.string().optional(), 34 + vercelTeamId: z.string().optional(), 33 35 }) 34 36 .superRefine((data, ctx) => { 35 37 if (data.provider !== "cloudflare" && !data.apiKey?.trim()) { ··· 46 48 path: ["organizationId"], 47 49 }); 48 50 } 51 + if (data.provider === "vercel" && !data.vercelProjectId?.trim()) { 52 + ctx.addIssue({ 53 + code: z.ZodIssueCode.custom, 54 + message: "Vercel Project ID is required", 55 + path: ["vercelProjectId"], 56 + }); 57 + } 58 + if (data.provider === "vercel" && !data.vercelTeamId?.trim()) { 59 + ctx.addIssue({ 60 + code: z.ZodIssueCode.custom, 61 + message: "Vercel Team ID is required", 62 + path: ["vercelTeamId"], 63 + }); 64 + } 49 65 }); 50 66 51 67 type FormValues = z.infer<typeof schema>; ··· 72 88 formState: { errors }, 73 89 } = useForm<FormValues>({ 74 90 resolver: zodResolver(schema), 75 - defaultValues: { provider: "cloudflare", apiKey: "" }, 91 + defaultValues: { 92 + provider: "cloudflare", 93 + apiKey: "", 94 + vercelProjectId: "", 95 + vercelTeamId: "", 96 + }, 76 97 }); 77 98 78 99 useEffect(() => { ··· 85 106 setValue("provider", providerPref.name as FormValues["provider"]); 86 107 setValue("apiKey", providerPref.redactedApiKey ?? ""); 87 108 setValue("organizationId", providerPref.organizationId ?? ""); 109 + setValue("vercelProjectId", providerPref.vercelProjectId ?? ""); 110 + setValue("vercelTeamId", providerPref.vercelTeamId ?? ""); 88 111 } 89 112 }, [preferences, setValue]); 90 113 ··· 103 126 pref.organizationId = values.organizationId.trim(); 104 127 } 105 128 129 + if (values.provider === "vercel") { 130 + pref.vercelProjectId = values.vercelProjectId?.trim(); 131 + pref.vercelTeamId = values.vercelTeamId?.trim(); 132 + } 133 + 106 134 if (values.apiKey?.includes("**") && values.provider !== "cloudflare") { 107 - if (values.provider !== "daytona") { 135 + if (values.provider !== "daytona" && values.provider !== "vercel") { 108 136 return; 109 137 } 110 138 } ··· 166 194 onProviderChange(e); 167 195 setValue("apiKey", ""); 168 196 setValue("organizationId", ""); 197 + setValue("vercelProjectId", ""); 198 + setValue("vercelTeamId", ""); 169 199 }} 170 200 className="select select-lg font-medium text-[15px]" 171 201 > ··· 209 239 {errors.organizationId.message} 210 240 </p> 211 241 )} 242 + </div> 243 + )} 244 + {provider === "vercel" && ( 245 + <div className="flex flex-row mt-4 gap-6"> 246 + <div className="w-96"> 247 + <label className="label-text">Vercel Project ID</label> 248 + <input 249 + {...register("vercelProjectId")} 250 + type="text" 251 + className="input input-lg font-medium text-[15px]" 252 + /> 253 + {errors.vercelProjectId && ( 254 + <p className="text-error text-sm mt-1"> 255 + {errors.vercelProjectId.message} 256 + </p> 257 + )} 258 + </div> 259 + <div className="w-96"> 260 + <label className="label-text">Vercel Team ID</label> 261 + <input 262 + {...register("vercelTeamId")} 263 + type="text" 264 + className="input input-lg font-medium text-[15px]" 265 + /> 266 + {errors.vercelTeamId && ( 267 + <p className="text-error text-sm mt-1"> 268 + {errors.vercelTeamId.message} 269 + </p> 270 + )} 271 + </div> 212 272 </div> 213 273 )} 214 274 </div>
+2
apps/web/src/types/preferences.ts
··· 11 11 apiKey?: string; 12 12 redactedApiKey?: string; 13 13 organizationId?: string; 14 + vercelProjectId?: string; 15 + vercelTeamId?: string; 14 16 $type: "io.pocketenv.sandbox.defs#sandboxProviderPref"; 15 17 }; 16 18