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 start/stop service endpoints and status

Add lexicon definitions, handlers and generated types for service
start/stop. Add DB migration (services.status) and update cf-sandbox to
set status on start/stop. Update API schema and CLI to show service
status in listings.

+1975 -8
+21
apps/api/lexicons/service/startService.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.service.startService", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "parameters": { 8 + "type": "params", 9 + "required": [ 10 + "serviceId" 11 + ], 12 + "properties": { 13 + "serviceId": { 14 + "type": "string", 15 + "description": "The ID of the service to start." 16 + } 17 + } 18 + } 19 + } 20 + } 21 + }
+21
apps/api/lexicons/service/stopService.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.service.stopService", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "parameters": { 8 + "type": "params", 9 + "required": [ 10 + "serviceId" 11 + ], 12 + "properties": { 13 + "serviceId": { 14 + "type": "string", 15 + "description": "The ID of the service to stop." 16 + } 17 + } 18 + } 19 + } 20 + } 21 + }
+19
apps/api/pkl/defs/service/startService.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.service.startService" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + parameters { 9 + type = "params" 10 + required = List("serviceId") 11 + properties { 12 + ["serviceId"] = new StringType { 13 + type = "string" 14 + description = "The ID of the service to start." 15 + } 16 + } 17 + } 18 + } 19 + }
+19
apps/api/pkl/defs/service/stopService.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.service.stopService" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + parameters { 9 + type = "params" 10 + required = List("serviceId") 11 + properties { 12 + ["serviceId"] = new StringType { 13 + type = "string" 14 + description = "The ID of the service to stop." 15 + } 16 + } 17 + } 18 + } 19 + }
+24
apps/api/src/lexicon/index.ts
··· 49 49 import type * as IoPocketenvServiceDeleteService from "./types/io/pocketenv/service/deleteService"; 50 50 import type * as IoPocketenvServiceGetServices from "./types/io/pocketenv/service/getServices"; 51 51 import type * as IoPocketenvServiceRestartService from "./types/io/pocketenv/service/restartService"; 52 + import type * as IoPocketenvServiceStartService from "./types/io/pocketenv/service/startService"; 53 + import type * as IoPocketenvServiceStopService from "./types/io/pocketenv/service/stopService"; 52 54 import type * as IoPocketenvServiceUpdateService from "./types/io/pocketenv/service/updateService"; 53 55 import type * as IoPocketenvVariableAddVariable from "./types/io/pocketenv/variable/addVariable"; 54 56 import type * as IoPocketenvVariableDeleteVariable from "./types/io/pocketenv/variable/deleteVariable"; ··· 587 589 >, 588 590 ) { 589 591 const nsid = "io.pocketenv.service.restartService"; // @ts-ignore 592 + return this._server.xrpc.method(nsid, cfg); 593 + } 594 + 595 + startService<AV extends AuthVerifier>( 596 + cfg: ConfigOf< 597 + AV, 598 + IoPocketenvServiceStartService.Handler<ExtractAuth<AV>>, 599 + IoPocketenvServiceStartService.HandlerReqCtx<ExtractAuth<AV>> 600 + >, 601 + ) { 602 + const nsid = "io.pocketenv.service.startService"; // @ts-ignore 603 + return this._server.xrpc.method(nsid, cfg); 604 + } 605 + 606 + stopService<AV extends AuthVerifier>( 607 + cfg: ConfigOf< 608 + AV, 609 + IoPocketenvServiceStopService.Handler<ExtractAuth<AV>>, 610 + IoPocketenvServiceStopService.HandlerReqCtx<ExtractAuth<AV>> 611 + >, 612 + ) { 613 + const nsid = "io.pocketenv.service.stopService"; // @ts-ignore 590 614 return this._server.xrpc.method(nsid, cfg); 591 615 } 592 616
+40
apps/api/src/lexicon/lexicons.ts
··· 2269 2269 }, 2270 2270 }, 2271 2271 }, 2272 + IoPocketenvServiceStartService: { 2273 + lexicon: 1, 2274 + id: "io.pocketenv.service.startService", 2275 + defs: { 2276 + main: { 2277 + type: "procedure", 2278 + parameters: { 2279 + type: "params", 2280 + required: ["serviceId"], 2281 + properties: { 2282 + serviceId: { 2283 + type: "string", 2284 + description: "The ID of the service to start.", 2285 + }, 2286 + }, 2287 + }, 2288 + }, 2289 + }, 2290 + }, 2291 + IoPocketenvServiceStopService: { 2292 + lexicon: 1, 2293 + id: "io.pocketenv.service.stopService", 2294 + defs: { 2295 + main: { 2296 + type: "procedure", 2297 + parameters: { 2298 + type: "params", 2299 + required: ["serviceId"], 2300 + properties: { 2301 + serviceId: { 2302 + type: "string", 2303 + description: "The ID of the service to stop.", 2304 + }, 2305 + }, 2306 + }, 2307 + }, 2308 + }, 2309 + }, 2272 2310 IoPocketenvServiceUpdateService: { 2273 2311 lexicon: 1, 2274 2312 id: "io.pocketenv.service.updateService", ··· 2832 2870 IoPocketenvServiceDeleteService: "io.pocketenv.service.deleteService", 2833 2871 IoPocketenvServiceGetServices: "io.pocketenv.service.getServices", 2834 2872 IoPocketenvServiceRestartService: "io.pocketenv.service.restartService", 2873 + IoPocketenvServiceStartService: "io.pocketenv.service.startService", 2874 + IoPocketenvServiceStopService: "io.pocketenv.service.stopService", 2835 2875 IoPocketenvServiceUpdateService: "io.pocketenv.service.updateService", 2836 2876 IoPocketenvVariableAddVariable: "io.pocketenv.variable.addVariable", 2837 2877 IoPocketenvVariableDefs: "io.pocketenv.variable.defs",
+35
apps/api/src/lexicon/types/io/pocketenv/service/startService.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + 11 + export interface QueryParams { 12 + /** The ID of the service to start. */ 13 + serviceId: string; 14 + } 15 + 16 + export type InputSchema = undefined; 17 + export type HandlerInput = undefined; 18 + 19 + export interface HandlerError { 20 + status: number; 21 + message?: string; 22 + } 23 + 24 + export type HandlerOutput = HandlerError | void; 25 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 26 + auth: HA; 27 + params: QueryParams; 28 + input: HandlerInput; 29 + req: express.Request; 30 + res: express.Response; 31 + resetRouteRateLimits: () => Promise<void>; 32 + }; 33 + export type Handler<HA extends HandlerAuth = never> = ( 34 + ctx: HandlerReqCtx<HA>, 35 + ) => Promise<HandlerOutput> | HandlerOutput;
+35
apps/api/src/lexicon/types/io/pocketenv/service/stopService.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + 11 + export interface QueryParams { 12 + /** The ID of the service to stop. */ 13 + serviceId: string; 14 + } 15 + 16 + export type InputSchema = undefined; 17 + export type HandlerInput = undefined; 18 + 19 + export interface HandlerError { 20 + status: number; 21 + message?: string; 22 + } 23 + 24 + export type HandlerOutput = HandlerError | void; 25 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 26 + auth: HA; 27 + params: QueryParams; 28 + input: HandlerInput; 29 + req: express.Request; 30 + res: express.Response; 31 + resetRouteRateLimits: () => Promise<void>; 32 + }; 33 + export type Handler<HA extends HandlerAuth = never> = ( 34 + ctx: HandlerReqCtx<HA>, 35 + ) => Promise<HandlerOutput> | HandlerOutput;
+4 -1
apps/api/src/schema/services.ts
··· 5 5 const services = pgTable( 6 6 "services", 7 7 { 8 - id: text("id").primaryKey().default(sql`xata_id()`), 8 + id: text("id") 9 + .primaryKey() 10 + .default(sql`xata_id()`), 9 11 sandboxId: text("sandbox_id") 10 12 .notNull() 11 13 .references(() => sandboxes.id), ··· 13 15 command: text("command").notNull(), 14 16 description: text("description"), 15 17 serviceId: text("service_id"), 18 + status: text("status").notNull().default("STOPPED"), 16 19 createdAt: timestamp("created_at").defaultNow().notNull(), 17 20 updatedAt: timestamp("updated_at").defaultNow().notNull(), 18 21 },
+4
apps/api/src/xrpc/index.ts
··· 48 48 import getServices from "./io/pocketenv/service/getServices"; 49 49 import restartService from "./io/pocketenv/service/restartService"; 50 50 import updateService from "./io/pocketenv/service/updateService"; 51 + import startService from "./io/pocketenv/service/startService"; 52 + import stopService from "./io/pocketenv/service/stopService"; 51 53 52 54 export default function (server: Server, ctx: Context) { 53 55 // io.pocketenv ··· 102 104 getServices(server, ctx); 103 105 restartService(server, ctx); 104 106 updateService(server, ctx); 107 + startService(server, ctx); 108 + stopService(server, ctx); 105 109 106 110 return server; 107 111 }
+1
apps/api/src/xrpc/io/pocketenv/service/getServices.ts
··· 43 43 name: record.name, 44 44 description: record.description!, 45 45 command: record.command, 46 + createdAt: record.createdAt.toISOString(), 46 47 })), 47 48 } satisfies OutputSchema; 48 49 };
+68
apps/api/src/xrpc/io/pocketenv/service/startService.ts
··· 1 + import type { Server } from "lexicon"; 2 + import type { Context } from "context"; 3 + import type { QueryParams } from "lexicon/types/io/pocketenv/service/startService"; 4 + import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 5 + import schema from "schema"; 6 + import { and, eq } from "drizzle-orm"; 7 + import { Providers } from "consts"; 8 + import generateJwt from "lib/generateJwt"; 9 + import { consola } from "consola"; 10 + 11 + export default function (server: Server, ctx: Context) { 12 + const startService = async (params: QueryParams, auth: HandlerAuth) => { 13 + if (!auth.credentials) { 14 + throw new XRPCError(401, "Unauthorized"); 15 + } 16 + 17 + const records = await ctx.db 18 + .select() 19 + .from(schema.services) 20 + .leftJoin( 21 + schema.sandboxes, 22 + eq(schema.sandboxes.id, schema.services.sandboxId), 23 + ) 24 + .leftJoin(schema.users, eq(schema.users.id, schema.sandboxes.userId)) 25 + .where( 26 + and( 27 + eq(schema.services.id, params.serviceId), 28 + eq(schema.users.did, auth.credentials.did), 29 + ), 30 + ) 31 + .execute(); 32 + 33 + if (records.length === 0) { 34 + throw new XRPCError(404, "Service not found"); 35 + } 36 + 37 + const [record] = records; 38 + const sandbox = 39 + record!.sandboxes!.provider === Providers.CLOUDFLARE 40 + ? ctx.cfsandbox(record!.sandboxes!.base!) 41 + : ctx.sandbox(); 42 + 43 + if (record?.sandboxes?.status === "RUNNING") { 44 + consola.info("Service is already running, skipping start", { 45 + params, 46 + auth, 47 + }); 48 + return; 49 + } 50 + 51 + await sandbox.post( 52 + `/v1/sandboxes/${record!.sandboxes!.id}/services/${params.serviceId}`, 53 + {}, 54 + { 55 + headers: { 56 + Authorization: `Bearer ${await generateJwt(auth?.credentials?.did || "")}`, 57 + }, 58 + }, 59 + ); 60 + }; 61 + 62 + server.io.pocketenv.service.startService({ 63 + auth: ctx.authVerifier, 64 + handler: async ({ params, auth }) => { 65 + await startService(params, auth); 66 + }, 67 + }); 68 + }
+67
apps/api/src/xrpc/io/pocketenv/service/stopService.ts
··· 1 + import type { Server } from "lexicon"; 2 + import type { Context } from "context"; 3 + import type { QueryParams } from "lexicon/types/io/pocketenv/service/stopService"; 4 + import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 5 + import schema from "schema"; 6 + import { and, eq } from "drizzle-orm"; 7 + import { Providers } from "consts"; 8 + import generateJwt from "lib/generateJwt"; 9 + import { consola } from "consola"; 10 + 11 + export default function (server: Server, ctx: Context) { 12 + const stopService = async (params: QueryParams, auth: HandlerAuth) => { 13 + if (!auth.credentials) { 14 + throw new XRPCError(401, "Unauthorized"); 15 + } 16 + 17 + const records = await ctx.db 18 + .select() 19 + .from(schema.services) 20 + .leftJoin( 21 + schema.sandboxes, 22 + eq(schema.sandboxes.id, schema.services.sandboxId), 23 + ) 24 + .leftJoin(schema.users, eq(schema.users.id, schema.sandboxes.userId)) 25 + .where( 26 + and( 27 + eq(schema.services.id, params.serviceId), 28 + eq(schema.users.did, auth.credentials.did), 29 + ), 30 + ) 31 + .execute(); 32 + 33 + if (records.length === 0) { 34 + throw new XRPCError(404, "Service not found"); 35 + } 36 + 37 + const [record] = records; 38 + const sandbox = 39 + record!.sandboxes!.provider === Providers.CLOUDFLARE 40 + ? ctx.cfsandbox(record!.sandboxes!.base!) 41 + : ctx.sandbox(); 42 + 43 + if (record?.sandboxes?.status === "STOPPED") { 44 + consola.info("Service is already stopped, skipping stop", { 45 + params, 46 + auth, 47 + }); 48 + return; 49 + } 50 + 51 + await sandbox.delete( 52 + `/v1/sandboxes/${record!.sandboxes!.id}/services/${params.serviceId}`, 53 + { 54 + headers: { 55 + Authorization: `Bearer ${await generateJwt(auth?.credentials?.did || "")}`, 56 + }, 57 + }, 58 + ); 59 + }; 60 + 61 + server.io.pocketenv.service.stopService({ 62 + auth: ctx.authVerifier, 63 + handler: async ({ params, auth }) => { 64 + await stopService(params, auth); 65 + }, 66 + }); 67 + }
+1
apps/cf-sandbox/drizzle/0034_fearless_black_cat.sql
··· 1 + ALTER TABLE "services" ADD COLUMN "status" text DEFAULT 'STOPPED' NOT NULL;
+1451
apps/cf-sandbox/drizzle/meta/0034_snapshot.json
··· 1 + { 2 + "id": "7ad2ff94-171a-45fb-b544-3f14fb1a2f92", 3 + "prevId": "318e2171-8ade-4527-8007-b38c2ab497a3", 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.files": { 61 + "name": "files", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "xata_id()" 70 + }, 71 + "content": { 72 + "name": "content", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "created_at": { 78 + "name": "created_at", 79 + "type": "timestamp", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "default": "now()" 83 + }, 84 + "updated_at": { 85 + "name": "updated_at", 86 + "type": "timestamp", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "default": "now()" 90 + } 91 + }, 92 + "indexes": {}, 93 + "foreignKeys": {}, 94 + "compositePrimaryKeys": {}, 95 + "uniqueConstraints": {}, 96 + "policies": {}, 97 + "checkConstraints": {}, 98 + "isRLSEnabled": false 99 + }, 100 + "public.sandbox_files": { 101 + "name": "sandbox_files", 102 + "schema": "", 103 + "columns": { 104 + "id": { 105 + "name": "id", 106 + "type": "text", 107 + "primaryKey": true, 108 + "notNull": true, 109 + "default": "file_id()" 110 + }, 111 + "sandbox_id": { 112 + "name": "sandbox_id", 113 + "type": "text", 114 + "primaryKey": false, 115 + "notNull": true 116 + }, 117 + "file_id": { 118 + "name": "file_id", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true 122 + }, 123 + "path": { 124 + "name": "path", 125 + "type": "text", 126 + "primaryKey": false, 127 + "notNull": true 128 + }, 129 + "created_at": { 130 + "name": "created_at", 131 + "type": "timestamp", 132 + "primaryKey": false, 133 + "notNull": true, 134 + "default": "now()" 135 + }, 136 + "updated_at": { 137 + "name": "updated_at", 138 + "type": "timestamp", 139 + "primaryKey": false, 140 + "notNull": true, 141 + "default": "now()" 142 + } 143 + }, 144 + "indexes": { 145 + "unique_sandbox_file_path": { 146 + "name": "unique_sandbox_file_path", 147 + "columns": [ 148 + { 149 + "expression": "sandbox_id", 150 + "isExpression": false, 151 + "asc": true, 152 + "nulls": "last" 153 + }, 154 + { 155 + "expression": "path", 156 + "isExpression": false, 157 + "asc": true, 158 + "nulls": "last" 159 + } 160 + ], 161 + "isUnique": true, 162 + "concurrently": false, 163 + "method": "btree", 164 + "with": {} 165 + } 166 + }, 167 + "foreignKeys": { 168 + "sandbox_files_sandbox_id_sandboxes_id_fk": { 169 + "name": "sandbox_files_sandbox_id_sandboxes_id_fk", 170 + "tableFrom": "sandbox_files", 171 + "tableTo": "sandboxes", 172 + "columnsFrom": [ 173 + "sandbox_id" 174 + ], 175 + "columnsTo": [ 176 + "id" 177 + ], 178 + "onDelete": "no action", 179 + "onUpdate": "no action" 180 + }, 181 + "sandbox_files_file_id_files_id_fk": { 182 + "name": "sandbox_files_file_id_files_id_fk", 183 + "tableFrom": "sandbox_files", 184 + "tableTo": "files", 185 + "columnsFrom": [ 186 + "file_id" 187 + ], 188 + "columnsTo": [ 189 + "id" 190 + ], 191 + "onDelete": "no action", 192 + "onUpdate": "no action" 193 + } 194 + }, 195 + "compositePrimaryKeys": {}, 196 + "uniqueConstraints": {}, 197 + "policies": {}, 198 + "checkConstraints": {}, 199 + "isRLSEnabled": false 200 + }, 201 + "public.sandbox_ports": { 202 + "name": "sandbox_ports", 203 + "schema": "", 204 + "columns": { 205 + "id": { 206 + "name": "id", 207 + "type": "text", 208 + "primaryKey": true, 209 + "notNull": true, 210 + "default": "xata_id()" 211 + }, 212 + "sandbox_id": { 213 + "name": "sandbox_id", 214 + "type": "text", 215 + "primaryKey": false, 216 + "notNull": true 217 + }, 218 + "exposed_port": { 219 + "name": "exposed_port", 220 + "type": "integer", 221 + "primaryKey": false, 222 + "notNull": true 223 + }, 224 + "preview_url": { 225 + "name": "preview_url", 226 + "type": "text", 227 + "primaryKey": false, 228 + "notNull": false 229 + }, 230 + "description": { 231 + "name": "description", 232 + "type": "text", 233 + "primaryKey": false, 234 + "notNull": false 235 + }, 236 + "service_id": { 237 + "name": "service_id", 238 + "type": "text", 239 + "primaryKey": false, 240 + "notNull": false 241 + }, 242 + "created_at": { 243 + "name": "created_at", 244 + "type": "timestamp", 245 + "primaryKey": false, 246 + "notNull": true, 247 + "default": "now()" 248 + }, 249 + "updated_at": { 250 + "name": "updated_at", 251 + "type": "timestamp", 252 + "primaryKey": false, 253 + "notNull": true, 254 + "default": "now()" 255 + } 256 + }, 257 + "indexes": { 258 + "unique_sandbox_port": { 259 + "name": "unique_sandbox_port", 260 + "columns": [ 261 + { 262 + "expression": "sandbox_id", 263 + "isExpression": false, 264 + "asc": true, 265 + "nulls": "last" 266 + }, 267 + { 268 + "expression": "exposed_port", 269 + "isExpression": false, 270 + "asc": true, 271 + "nulls": "last" 272 + } 273 + ], 274 + "isUnique": true, 275 + "concurrently": false, 276 + "method": "btree", 277 + "with": {} 278 + } 279 + }, 280 + "foreignKeys": { 281 + "sandbox_ports_sandbox_id_sandboxes_id_fk": { 282 + "name": "sandbox_ports_sandbox_id_sandboxes_id_fk", 283 + "tableFrom": "sandbox_ports", 284 + "tableTo": "sandboxes", 285 + "columnsFrom": [ 286 + "sandbox_id" 287 + ], 288 + "columnsTo": [ 289 + "id" 290 + ], 291 + "onDelete": "no action", 292 + "onUpdate": "no action" 293 + }, 294 + "sandbox_ports_service_id_services_id_fk": { 295 + "name": "sandbox_ports_service_id_services_id_fk", 296 + "tableFrom": "sandbox_ports", 297 + "tableTo": "services", 298 + "columnsFrom": [ 299 + "service_id" 300 + ], 301 + "columnsTo": [ 302 + "id" 303 + ], 304 + "onDelete": "no action", 305 + "onUpdate": "no action" 306 + } 307 + }, 308 + "compositePrimaryKeys": {}, 309 + "uniqueConstraints": {}, 310 + "policies": {}, 311 + "checkConstraints": {}, 312 + "isRLSEnabled": false 313 + }, 314 + "public.sandbox_secrets": { 315 + "name": "sandbox_secrets", 316 + "schema": "", 317 + "columns": { 318 + "id": { 319 + "name": "id", 320 + "type": "text", 321 + "primaryKey": true, 322 + "notNull": true, 323 + "default": "xata_id()" 324 + }, 325 + "sandbox_id": { 326 + "name": "sandbox_id", 327 + "type": "text", 328 + "primaryKey": false, 329 + "notNull": true 330 + }, 331 + "secret_id": { 332 + "name": "secret_id", 333 + "type": "text", 334 + "primaryKey": false, 335 + "notNull": true 336 + }, 337 + "name": { 338 + "name": "name", 339 + "type": "text", 340 + "primaryKey": false, 341 + "notNull": false 342 + }, 343 + "created_at": { 344 + "name": "created_at", 345 + "type": "timestamp", 346 + "primaryKey": false, 347 + "notNull": true, 348 + "default": "now()" 349 + }, 350 + "updated_at": { 351 + "name": "updated_at", 352 + "type": "timestamp", 353 + "primaryKey": false, 354 + "notNull": true, 355 + "default": "now()" 356 + } 357 + }, 358 + "indexes": { 359 + "unique_sandbox_secret_by_name": { 360 + "name": "unique_sandbox_secret_by_name", 361 + "columns": [ 362 + { 363 + "expression": "sandbox_id", 364 + "isExpression": false, 365 + "asc": true, 366 + "nulls": "last" 367 + }, 368 + { 369 + "expression": "name", 370 + "isExpression": false, 371 + "asc": true, 372 + "nulls": "last" 373 + } 374 + ], 375 + "isUnique": true, 376 + "concurrently": false, 377 + "method": "btree", 378 + "with": {} 379 + } 380 + }, 381 + "foreignKeys": { 382 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 383 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 384 + "tableFrom": "sandbox_secrets", 385 + "tableTo": "sandboxes", 386 + "columnsFrom": [ 387 + "sandbox_id" 388 + ], 389 + "columnsTo": [ 390 + "id" 391 + ], 392 + "onDelete": "no action", 393 + "onUpdate": "no action" 394 + }, 395 + "sandbox_secrets_secret_id_secrets_id_fk": { 396 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 397 + "tableFrom": "sandbox_secrets", 398 + "tableTo": "secrets", 399 + "columnsFrom": [ 400 + "secret_id" 401 + ], 402 + "columnsTo": [ 403 + "id" 404 + ], 405 + "onDelete": "no action", 406 + "onUpdate": "no action" 407 + } 408 + }, 409 + "compositePrimaryKeys": {}, 410 + "uniqueConstraints": {}, 411 + "policies": {}, 412 + "checkConstraints": {}, 413 + "isRLSEnabled": false 414 + }, 415 + "public.sandbox_variables": { 416 + "name": "sandbox_variables", 417 + "schema": "", 418 + "columns": { 419 + "id": { 420 + "name": "id", 421 + "type": "text", 422 + "primaryKey": true, 423 + "notNull": true, 424 + "default": "xata_id()" 425 + }, 426 + "sandbox_id": { 427 + "name": "sandbox_id", 428 + "type": "text", 429 + "primaryKey": false, 430 + "notNull": true 431 + }, 432 + "variable_id": { 433 + "name": "variable_id", 434 + "type": "text", 435 + "primaryKey": false, 436 + "notNull": true 437 + }, 438 + "name": { 439 + "name": "name", 440 + "type": "text", 441 + "primaryKey": false, 442 + "notNull": true 443 + }, 444 + "created_at": { 445 + "name": "created_at", 446 + "type": "timestamp", 447 + "primaryKey": false, 448 + "notNull": true, 449 + "default": "now()" 450 + }, 451 + "updated_at": { 452 + "name": "updated_at", 453 + "type": "timestamp", 454 + "primaryKey": false, 455 + "notNull": true, 456 + "default": "now()" 457 + } 458 + }, 459 + "indexes": { 460 + "unique_sandbox_variables_by_name": { 461 + "name": "unique_sandbox_variables_by_name", 462 + "columns": [ 463 + { 464 + "expression": "sandbox_id", 465 + "isExpression": false, 466 + "asc": true, 467 + "nulls": "last" 468 + }, 469 + { 470 + "expression": "name", 471 + "isExpression": false, 472 + "asc": true, 473 + "nulls": "last" 474 + } 475 + ], 476 + "isUnique": true, 477 + "concurrently": false, 478 + "method": "btree", 479 + "with": {} 480 + } 481 + }, 482 + "foreignKeys": { 483 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 484 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 485 + "tableFrom": "sandbox_variables", 486 + "tableTo": "sandboxes", 487 + "columnsFrom": [ 488 + "sandbox_id" 489 + ], 490 + "columnsTo": [ 491 + "id" 492 + ], 493 + "onDelete": "no action", 494 + "onUpdate": "no action" 495 + }, 496 + "sandbox_variables_variable_id_variables_id_fk": { 497 + "name": "sandbox_variables_variable_id_variables_id_fk", 498 + "tableFrom": "sandbox_variables", 499 + "tableTo": "variables", 500 + "columnsFrom": [ 501 + "variable_id" 502 + ], 503 + "columnsTo": [ 504 + "id" 505 + ], 506 + "onDelete": "no action", 507 + "onUpdate": "no action" 508 + } 509 + }, 510 + "compositePrimaryKeys": {}, 511 + "uniqueConstraints": {}, 512 + "policies": {}, 513 + "checkConstraints": {}, 514 + "isRLSEnabled": false 515 + }, 516 + "public.sandbox_volumes": { 517 + "name": "sandbox_volumes", 518 + "schema": "", 519 + "columns": { 520 + "id": { 521 + "name": "id", 522 + "type": "text", 523 + "primaryKey": true, 524 + "notNull": true, 525 + "default": "volume_id()" 526 + }, 527 + "sandbox_id": { 528 + "name": "sandbox_id", 529 + "type": "text", 530 + "primaryKey": false, 531 + "notNull": true 532 + }, 533 + "volume_id": { 534 + "name": "volume_id", 535 + "type": "text", 536 + "primaryKey": false, 537 + "notNull": true 538 + }, 539 + "name": { 540 + "name": "name", 541 + "type": "text", 542 + "primaryKey": false, 543 + "notNull": false 544 + }, 545 + "path": { 546 + "name": "path", 547 + "type": "text", 548 + "primaryKey": false, 549 + "notNull": true 550 + }, 551 + "created_at": { 552 + "name": "created_at", 553 + "type": "timestamp", 554 + "primaryKey": false, 555 + "notNull": true, 556 + "default": "now()" 557 + }, 558 + "updated_at": { 559 + "name": "updated_at", 560 + "type": "timestamp", 561 + "primaryKey": false, 562 + "notNull": true, 563 + "default": "now()" 564 + } 565 + }, 566 + "indexes": { 567 + "unique_sandbox_volume_path": { 568 + "name": "unique_sandbox_volume_path", 569 + "columns": [ 570 + { 571 + "expression": "sandbox_id", 572 + "isExpression": false, 573 + "asc": true, 574 + "nulls": "last" 575 + }, 576 + { 577 + "expression": "path", 578 + "isExpression": false, 579 + "asc": true, 580 + "nulls": "last" 581 + } 582 + ], 583 + "isUnique": true, 584 + "concurrently": false, 585 + "method": "btree", 586 + "with": {} 587 + } 588 + }, 589 + "foreignKeys": { 590 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 591 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 592 + "tableFrom": "sandbox_volumes", 593 + "tableTo": "sandboxes", 594 + "columnsFrom": [ 595 + "sandbox_id" 596 + ], 597 + "columnsTo": [ 598 + "id" 599 + ], 600 + "onDelete": "no action", 601 + "onUpdate": "no action" 602 + }, 603 + "sandbox_volumes_volume_id_volumes_id_fk": { 604 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 605 + "tableFrom": "sandbox_volumes", 606 + "tableTo": "volumes", 607 + "columnsFrom": [ 608 + "volume_id" 609 + ], 610 + "columnsTo": [ 611 + "id" 612 + ], 613 + "onDelete": "no action", 614 + "onUpdate": "no action" 615 + } 616 + }, 617 + "compositePrimaryKeys": {}, 618 + "uniqueConstraints": {}, 619 + "policies": {}, 620 + "checkConstraints": {}, 621 + "isRLSEnabled": false 622 + }, 623 + "public.sandboxes": { 624 + "name": "sandboxes", 625 + "schema": "", 626 + "columns": { 627 + "id": { 628 + "name": "id", 629 + "type": "text", 630 + "primaryKey": true, 631 + "notNull": true, 632 + "default": "sandbox_id()" 633 + }, 634 + "base": { 635 + "name": "base", 636 + "type": "text", 637 + "primaryKey": false, 638 + "notNull": false 639 + }, 640 + "name": { 641 + "name": "name", 642 + "type": "text", 643 + "primaryKey": false, 644 + "notNull": true 645 + }, 646 + "display_name": { 647 + "name": "display_name", 648 + "type": "text", 649 + "primaryKey": false, 650 + "notNull": false 651 + }, 652 + "uri": { 653 + "name": "uri", 654 + "type": "text", 655 + "primaryKey": false, 656 + "notNull": false 657 + }, 658 + "cid": { 659 + "name": "cid", 660 + "type": "text", 661 + "primaryKey": false, 662 + "notNull": false 663 + }, 664 + "repo": { 665 + "name": "repo", 666 + "type": "text", 667 + "primaryKey": false, 668 + "notNull": false 669 + }, 670 + "provider": { 671 + "name": "provider", 672 + "type": "text", 673 + "primaryKey": false, 674 + "notNull": true, 675 + "default": "'cloudflare'" 676 + }, 677 + "description": { 678 + "name": "description", 679 + "type": "text", 680 + "primaryKey": false, 681 + "notNull": false 682 + }, 683 + "topics": { 684 + "name": "topics", 685 + "type": "text[]", 686 + "primaryKey": false, 687 + "notNull": false 688 + }, 689 + "logo": { 690 + "name": "logo", 691 + "type": "text", 692 + "primaryKey": false, 693 + "notNull": false 694 + }, 695 + "readme": { 696 + "name": "readme", 697 + "type": "text", 698 + "primaryKey": false, 699 + "notNull": false 700 + }, 701 + "public_key": { 702 + "name": "public_key", 703 + "type": "text", 704 + "primaryKey": false, 705 + "notNull": true 706 + }, 707 + "user_id": { 708 + "name": "user_id", 709 + "type": "text", 710 + "primaryKey": false, 711 + "notNull": false 712 + }, 713 + "instance_type": { 714 + "name": "instance_type", 715 + "type": "text", 716 + "primaryKey": false, 717 + "notNull": false 718 + }, 719 + "vcpus": { 720 + "name": "vcpus", 721 + "type": "integer", 722 + "primaryKey": false, 723 + "notNull": false 724 + }, 725 + "memory": { 726 + "name": "memory", 727 + "type": "integer", 728 + "primaryKey": false, 729 + "notNull": false 730 + }, 731 + "disk": { 732 + "name": "disk", 733 + "type": "integer", 734 + "primaryKey": false, 735 + "notNull": false 736 + }, 737 + "status": { 738 + "name": "status", 739 + "type": "text", 740 + "primaryKey": false, 741 + "notNull": true 742 + }, 743 + "keep_alive": { 744 + "name": "keep_alive", 745 + "type": "boolean", 746 + "primaryKey": false, 747 + "notNull": true, 748 + "default": false 749 + }, 750 + "sleep_after": { 751 + "name": "sleep_after", 752 + "type": "text", 753 + "primaryKey": false, 754 + "notNull": false 755 + }, 756 + "sandbox_id": { 757 + "name": "sandbox_id", 758 + "type": "text", 759 + "primaryKey": false, 760 + "notNull": false 761 + }, 762 + "installs": { 763 + "name": "installs", 764 + "type": "integer", 765 + "primaryKey": false, 766 + "notNull": true, 767 + "default": 0 768 + }, 769 + "started_at": { 770 + "name": "started_at", 771 + "type": "timestamp", 772 + "primaryKey": false, 773 + "notNull": false 774 + }, 775 + "created_at": { 776 + "name": "created_at", 777 + "type": "timestamp", 778 + "primaryKey": false, 779 + "notNull": true, 780 + "default": "now()" 781 + }, 782 + "updated_at": { 783 + "name": "updated_at", 784 + "type": "timestamp", 785 + "primaryKey": false, 786 + "notNull": true, 787 + "default": "now()" 788 + } 789 + }, 790 + "indexes": {}, 791 + "foreignKeys": { 792 + "sandboxes_user_id_users_id_fk": { 793 + "name": "sandboxes_user_id_users_id_fk", 794 + "tableFrom": "sandboxes", 795 + "tableTo": "users", 796 + "columnsFrom": [ 797 + "user_id" 798 + ], 799 + "columnsTo": [ 800 + "id" 801 + ], 802 + "onDelete": "no action", 803 + "onUpdate": "no action" 804 + } 805 + }, 806 + "compositePrimaryKeys": {}, 807 + "uniqueConstraints": { 808 + "sandboxes_name_unique": { 809 + "name": "sandboxes_name_unique", 810 + "nullsNotDistinct": false, 811 + "columns": [ 812 + "name" 813 + ] 814 + }, 815 + "sandboxes_uri_unique": { 816 + "name": "sandboxes_uri_unique", 817 + "nullsNotDistinct": false, 818 + "columns": [ 819 + "uri" 820 + ] 821 + }, 822 + "sandboxes_cid_unique": { 823 + "name": "sandboxes_cid_unique", 824 + "nullsNotDistinct": false, 825 + "columns": [ 826 + "cid" 827 + ] 828 + } 829 + }, 830 + "policies": {}, 831 + "checkConstraints": {}, 832 + "isRLSEnabled": false 833 + }, 834 + "public.secrets": { 835 + "name": "secrets", 836 + "schema": "", 837 + "columns": { 838 + "id": { 839 + "name": "id", 840 + "type": "text", 841 + "primaryKey": true, 842 + "notNull": true, 843 + "default": "secret_id()" 844 + }, 845 + "name": { 846 + "name": "name", 847 + "type": "text", 848 + "primaryKey": false, 849 + "notNull": true 850 + }, 851 + "value": { 852 + "name": "value", 853 + "type": "text", 854 + "primaryKey": false, 855 + "notNull": true 856 + }, 857 + "redacted": { 858 + "name": "redacted", 859 + "type": "text", 860 + "primaryKey": false, 861 + "notNull": false 862 + }, 863 + "created_at": { 864 + "name": "created_at", 865 + "type": "timestamp", 866 + "primaryKey": false, 867 + "notNull": true, 868 + "default": "now()" 869 + } 870 + }, 871 + "indexes": {}, 872 + "foreignKeys": {}, 873 + "compositePrimaryKeys": {}, 874 + "uniqueConstraints": {}, 875 + "policies": {}, 876 + "checkConstraints": {}, 877 + "isRLSEnabled": false 878 + }, 879 + "public.services": { 880 + "name": "services", 881 + "schema": "", 882 + "columns": { 883 + "id": { 884 + "name": "id", 885 + "type": "text", 886 + "primaryKey": true, 887 + "notNull": true, 888 + "default": "xata_id()" 889 + }, 890 + "sandbox_id": { 891 + "name": "sandbox_id", 892 + "type": "text", 893 + "primaryKey": false, 894 + "notNull": true 895 + }, 896 + "name": { 897 + "name": "name", 898 + "type": "text", 899 + "primaryKey": false, 900 + "notNull": true 901 + }, 902 + "command": { 903 + "name": "command", 904 + "type": "text", 905 + "primaryKey": false, 906 + "notNull": true 907 + }, 908 + "description": { 909 + "name": "description", 910 + "type": "text", 911 + "primaryKey": false, 912 + "notNull": false 913 + }, 914 + "service_id": { 915 + "name": "service_id", 916 + "type": "text", 917 + "primaryKey": false, 918 + "notNull": false 919 + }, 920 + "status": { 921 + "name": "status", 922 + "type": "text", 923 + "primaryKey": false, 924 + "notNull": true, 925 + "default": "'STOPPED'" 926 + }, 927 + "created_at": { 928 + "name": "created_at", 929 + "type": "timestamp", 930 + "primaryKey": false, 931 + "notNull": true, 932 + "default": "now()" 933 + }, 934 + "updated_at": { 935 + "name": "updated_at", 936 + "type": "timestamp", 937 + "primaryKey": false, 938 + "notNull": true, 939 + "default": "now()" 940 + } 941 + }, 942 + "indexes": { 943 + "unique_sandbox_service": { 944 + "name": "unique_sandbox_service", 945 + "columns": [ 946 + { 947 + "expression": "name", 948 + "isExpression": false, 949 + "asc": true, 950 + "nulls": "last" 951 + }, 952 + { 953 + "expression": "sandbox_id", 954 + "isExpression": false, 955 + "asc": true, 956 + "nulls": "last" 957 + } 958 + ], 959 + "isUnique": true, 960 + "concurrently": false, 961 + "method": "btree", 962 + "with": {} 963 + } 964 + }, 965 + "foreignKeys": { 966 + "services_sandbox_id_sandboxes_id_fk": { 967 + "name": "services_sandbox_id_sandboxes_id_fk", 968 + "tableFrom": "services", 969 + "tableTo": "sandboxes", 970 + "columnsFrom": [ 971 + "sandbox_id" 972 + ], 973 + "columnsTo": [ 974 + "id" 975 + ], 976 + "onDelete": "no action", 977 + "onUpdate": "no action" 978 + } 979 + }, 980 + "compositePrimaryKeys": {}, 981 + "uniqueConstraints": {}, 982 + "policies": {}, 983 + "checkConstraints": {}, 984 + "isRLSEnabled": false 985 + }, 986 + "public.snapshots": { 987 + "name": "snapshots", 988 + "schema": "", 989 + "columns": { 990 + "id": { 991 + "name": "id", 992 + "type": "text", 993 + "primaryKey": true, 994 + "notNull": true, 995 + "default": "snapshot_id()" 996 + }, 997 + "slug": { 998 + "name": "slug", 999 + "type": "text", 1000 + "primaryKey": false, 1001 + "notNull": true 1002 + }, 1003 + "created_at": { 1004 + "name": "created_at", 1005 + "type": "timestamp", 1006 + "primaryKey": false, 1007 + "notNull": true, 1008 + "default": "now()" 1009 + } 1010 + }, 1011 + "indexes": {}, 1012 + "foreignKeys": {}, 1013 + "compositePrimaryKeys": {}, 1014 + "uniqueConstraints": { 1015 + "snapshots_slug_unique": { 1016 + "name": "snapshots_slug_unique", 1017 + "nullsNotDistinct": false, 1018 + "columns": [ 1019 + "slug" 1020 + ] 1021 + } 1022 + }, 1023 + "policies": {}, 1024 + "checkConstraints": {}, 1025 + "isRLSEnabled": false 1026 + }, 1027 + "public.ssh_keys": { 1028 + "name": "ssh_keys", 1029 + "schema": "", 1030 + "columns": { 1031 + "id": { 1032 + "name": "id", 1033 + "type": "text", 1034 + "primaryKey": true, 1035 + "notNull": true, 1036 + "default": "xata_id()" 1037 + }, 1038 + "sandbox_id": { 1039 + "name": "sandbox_id", 1040 + "type": "text", 1041 + "primaryKey": false, 1042 + "notNull": true 1043 + }, 1044 + "public_key": { 1045 + "name": "public_key", 1046 + "type": "text", 1047 + "primaryKey": false, 1048 + "notNull": true 1049 + }, 1050 + "private_key": { 1051 + "name": "private_key", 1052 + "type": "text", 1053 + "primaryKey": false, 1054 + "notNull": true 1055 + }, 1056 + "redacted": { 1057 + "name": "redacted", 1058 + "type": "text", 1059 + "primaryKey": false, 1060 + "notNull": false 1061 + }, 1062 + "created_at": { 1063 + "name": "created_at", 1064 + "type": "timestamp", 1065 + "primaryKey": false, 1066 + "notNull": true, 1067 + "default": "now()" 1068 + } 1069 + }, 1070 + "indexes": { 1071 + "unique_sandbox_ssh_key": { 1072 + "name": "unique_sandbox_ssh_key", 1073 + "columns": [ 1074 + { 1075 + "expression": "public_key", 1076 + "isExpression": false, 1077 + "asc": true, 1078 + "nulls": "last" 1079 + }, 1080 + { 1081 + "expression": "sandbox_id", 1082 + "isExpression": false, 1083 + "asc": true, 1084 + "nulls": "last" 1085 + } 1086 + ], 1087 + "isUnique": true, 1088 + "concurrently": false, 1089 + "method": "btree", 1090 + "with": {} 1091 + } 1092 + }, 1093 + "foreignKeys": { 1094 + "ssh_keys_sandbox_id_sandboxes_id_fk": { 1095 + "name": "ssh_keys_sandbox_id_sandboxes_id_fk", 1096 + "tableFrom": "ssh_keys", 1097 + "tableTo": "sandboxes", 1098 + "columnsFrom": [ 1099 + "sandbox_id" 1100 + ], 1101 + "columnsTo": [ 1102 + "id" 1103 + ], 1104 + "onDelete": "no action", 1105 + "onUpdate": "no action" 1106 + } 1107 + }, 1108 + "compositePrimaryKeys": {}, 1109 + "uniqueConstraints": {}, 1110 + "policies": {}, 1111 + "checkConstraints": {}, 1112 + "isRLSEnabled": false 1113 + }, 1114 + "public.tailscale_auth_keys": { 1115 + "name": "tailscale_auth_keys", 1116 + "schema": "", 1117 + "columns": { 1118 + "id": { 1119 + "name": "id", 1120 + "type": "text", 1121 + "primaryKey": true, 1122 + "notNull": true, 1123 + "default": "xata_id()" 1124 + }, 1125 + "sandbox_id": { 1126 + "name": "sandbox_id", 1127 + "type": "text", 1128 + "primaryKey": false, 1129 + "notNull": true 1130 + }, 1131 + "auth_key": { 1132 + "name": "auth_key", 1133 + "type": "text", 1134 + "primaryKey": false, 1135 + "notNull": true 1136 + }, 1137 + "redacted": { 1138 + "name": "redacted", 1139 + "type": "text", 1140 + "primaryKey": false, 1141 + "notNull": true 1142 + }, 1143 + "created_at": { 1144 + "name": "created_at", 1145 + "type": "timestamp", 1146 + "primaryKey": false, 1147 + "notNull": true, 1148 + "default": "now()" 1149 + } 1150 + }, 1151 + "indexes": {}, 1152 + "foreignKeys": { 1153 + "tailscale_auth_keys_sandbox_id_sandboxes_id_fk": { 1154 + "name": "tailscale_auth_keys_sandbox_id_sandboxes_id_fk", 1155 + "tableFrom": "tailscale_auth_keys", 1156 + "tableTo": "sandboxes", 1157 + "columnsFrom": [ 1158 + "sandbox_id" 1159 + ], 1160 + "columnsTo": [ 1161 + "id" 1162 + ], 1163 + "onDelete": "no action", 1164 + "onUpdate": "no action" 1165 + } 1166 + }, 1167 + "compositePrimaryKeys": {}, 1168 + "uniqueConstraints": {}, 1169 + "policies": {}, 1170 + "checkConstraints": {}, 1171 + "isRLSEnabled": false 1172 + }, 1173 + "public.users": { 1174 + "name": "users", 1175 + "schema": "", 1176 + "columns": { 1177 + "id": { 1178 + "name": "id", 1179 + "type": "text", 1180 + "primaryKey": true, 1181 + "notNull": true, 1182 + "default": "xata_id()" 1183 + }, 1184 + "did": { 1185 + "name": "did", 1186 + "type": "text", 1187 + "primaryKey": false, 1188 + "notNull": true 1189 + }, 1190 + "display_name": { 1191 + "name": "display_name", 1192 + "type": "text", 1193 + "primaryKey": false, 1194 + "notNull": false 1195 + }, 1196 + "handle": { 1197 + "name": "handle", 1198 + "type": "text", 1199 + "primaryKey": false, 1200 + "notNull": true 1201 + }, 1202 + "avatar": { 1203 + "name": "avatar", 1204 + "type": "text", 1205 + "primaryKey": false, 1206 + "notNull": false 1207 + }, 1208 + "created_at": { 1209 + "name": "created_at", 1210 + "type": "timestamp", 1211 + "primaryKey": false, 1212 + "notNull": true, 1213 + "default": "now()" 1214 + }, 1215 + "updated_at": { 1216 + "name": "updated_at", 1217 + "type": "timestamp", 1218 + "primaryKey": false, 1219 + "notNull": true, 1220 + "default": "now()" 1221 + } 1222 + }, 1223 + "indexes": {}, 1224 + "foreignKeys": {}, 1225 + "compositePrimaryKeys": {}, 1226 + "uniqueConstraints": { 1227 + "users_did_unique": { 1228 + "name": "users_did_unique", 1229 + "nullsNotDistinct": false, 1230 + "columns": [ 1231 + "did" 1232 + ] 1233 + }, 1234 + "users_handle_unique": { 1235 + "name": "users_handle_unique", 1236 + "nullsNotDistinct": false, 1237 + "columns": [ 1238 + "handle" 1239 + ] 1240 + } 1241 + }, 1242 + "policies": {}, 1243 + "checkConstraints": {}, 1244 + "isRLSEnabled": false 1245 + }, 1246 + "public.variables": { 1247 + "name": "variables", 1248 + "schema": "", 1249 + "columns": { 1250 + "id": { 1251 + "name": "id", 1252 + "type": "text", 1253 + "primaryKey": true, 1254 + "notNull": true, 1255 + "default": "variable_id()" 1256 + }, 1257 + "name": { 1258 + "name": "name", 1259 + "type": "text", 1260 + "primaryKey": false, 1261 + "notNull": true 1262 + }, 1263 + "value": { 1264 + "name": "value", 1265 + "type": "text", 1266 + "primaryKey": false, 1267 + "notNull": true 1268 + }, 1269 + "created_at": { 1270 + "name": "created_at", 1271 + "type": "timestamp", 1272 + "primaryKey": false, 1273 + "notNull": true, 1274 + "default": "now()" 1275 + }, 1276 + "updated_at": { 1277 + "name": "updated_at", 1278 + "type": "timestamp", 1279 + "primaryKey": false, 1280 + "notNull": true, 1281 + "default": "now()" 1282 + } 1283 + }, 1284 + "indexes": {}, 1285 + "foreignKeys": {}, 1286 + "compositePrimaryKeys": {}, 1287 + "uniqueConstraints": {}, 1288 + "policies": {}, 1289 + "checkConstraints": {}, 1290 + "isRLSEnabled": false 1291 + }, 1292 + "public.volumes": { 1293 + "name": "volumes", 1294 + "schema": "", 1295 + "columns": { 1296 + "id": { 1297 + "name": "id", 1298 + "type": "text", 1299 + "primaryKey": true, 1300 + "notNull": true, 1301 + "default": "volume_id()" 1302 + }, 1303 + "slug": { 1304 + "name": "slug", 1305 + "type": "text", 1306 + "primaryKey": false, 1307 + "notNull": true 1308 + }, 1309 + "size": { 1310 + "name": "size", 1311 + "type": "integer", 1312 + "primaryKey": false, 1313 + "notNull": true 1314 + }, 1315 + "size_unit": { 1316 + "name": "size_unit", 1317 + "type": "text", 1318 + "primaryKey": false, 1319 + "notNull": true 1320 + }, 1321 + "created_at": { 1322 + "name": "created_at", 1323 + "type": "timestamp", 1324 + "primaryKey": false, 1325 + "notNull": true, 1326 + "default": "now()" 1327 + }, 1328 + "updated_at": { 1329 + "name": "updated_at", 1330 + "type": "timestamp", 1331 + "primaryKey": false, 1332 + "notNull": true, 1333 + "default": "now()" 1334 + } 1335 + }, 1336 + "indexes": {}, 1337 + "foreignKeys": {}, 1338 + "compositePrimaryKeys": {}, 1339 + "uniqueConstraints": { 1340 + "volumes_slug_unique": { 1341 + "name": "volumes_slug_unique", 1342 + "nullsNotDistinct": false, 1343 + "columns": [ 1344 + "slug" 1345 + ] 1346 + } 1347 + }, 1348 + "policies": {}, 1349 + "checkConstraints": {}, 1350 + "isRLSEnabled": false 1351 + }, 1352 + "public.integrations": { 1353 + "name": "integrations", 1354 + "schema": "", 1355 + "columns": { 1356 + "id": { 1357 + "name": "id", 1358 + "type": "text", 1359 + "primaryKey": true, 1360 + "notNull": true, 1361 + "default": "xata_id()" 1362 + }, 1363 + "sandbox_id": { 1364 + "name": "sandbox_id", 1365 + "type": "text", 1366 + "primaryKey": false, 1367 + "notNull": true 1368 + }, 1369 + "name": { 1370 + "name": "name", 1371 + "type": "text", 1372 + "primaryKey": false, 1373 + "notNull": true 1374 + }, 1375 + "description": { 1376 + "name": "description", 1377 + "type": "text", 1378 + "primaryKey": false, 1379 + "notNull": false 1380 + }, 1381 + "webhook_url": { 1382 + "name": "webhook_url", 1383 + "type": "text", 1384 + "primaryKey": false, 1385 + "notNull": true 1386 + }, 1387 + "created_at": { 1388 + "name": "created_at", 1389 + "type": "timestamp", 1390 + "primaryKey": false, 1391 + "notNull": true, 1392 + "default": "now()" 1393 + } 1394 + }, 1395 + "indexes": { 1396 + "unique_sandbox_integration": { 1397 + "name": "unique_sandbox_integration", 1398 + "columns": [ 1399 + { 1400 + "expression": "sandbox_id", 1401 + "isExpression": false, 1402 + "asc": true, 1403 + "nulls": "last" 1404 + }, 1405 + { 1406 + "expression": "name", 1407 + "isExpression": false, 1408 + "asc": true, 1409 + "nulls": "last" 1410 + } 1411 + ], 1412 + "isUnique": true, 1413 + "concurrently": false, 1414 + "method": "btree", 1415 + "with": {} 1416 + } 1417 + }, 1418 + "foreignKeys": { 1419 + "integrations_sandbox_id_sandboxes_id_fk": { 1420 + "name": "integrations_sandbox_id_sandboxes_id_fk", 1421 + "tableFrom": "integrations", 1422 + "tableTo": "sandboxes", 1423 + "columnsFrom": [ 1424 + "sandbox_id" 1425 + ], 1426 + "columnsTo": [ 1427 + "id" 1428 + ], 1429 + "onDelete": "no action", 1430 + "onUpdate": "no action" 1431 + } 1432 + }, 1433 + "compositePrimaryKeys": {}, 1434 + "uniqueConstraints": {}, 1435 + "policies": {}, 1436 + "checkConstraints": {}, 1437 + "isRLSEnabled": false 1438 + } 1439 + }, 1440 + "enums": {}, 1441 + "schemas": {}, 1442 + "sequences": {}, 1443 + "roles": {}, 1444 + "policies": {}, 1445 + "views": {}, 1446 + "_meta": { 1447 + "columns": {}, 1448 + "schemas": {}, 1449 + "tables": {} 1450 + } 1451 + }
+7
apps/cf-sandbox/drizzle/meta/_journal.json
··· 239 239 "when": 1774692682855, 240 240 "tag": "0033_ambiguous_masked_marvel", 241 241 "breakpoints": true 242 + }, 243 + { 244 + "idx": 34, 245 + "version": "7", 246 + "when": 1774713955840, 247 + "tag": "0034_fearless_black_cat", 248 + "breakpoints": true 242 249 } 243 250 ] 244 251 }
+7 -1
apps/cf-sandbox/src/index.ts
··· 853 853 854 854 await c.var.db 855 855 .update(services) 856 - .set({ serviceId }) 856 + .set({ serviceId, status: "RUNNING" }) 857 857 .where(eq(services.id, service.id)) 858 858 .execute(); 859 859 ··· 900 900 } 901 901 902 902 await sandbox.stopService(service.serviceId!); 903 + 904 + await c.var.db 905 + .update(services) 906 + .set({ status: "STOPPED" }) 907 + .where(eq(services.id, service.id)) 908 + .execute(); 903 909 904 910 return c.json({}); 905 911 } catch (err) {
+1
apps/cf-sandbox/src/schema/services.ts
··· 15 15 command: text("command").notNull(), 16 16 description: text("description"), 17 17 serviceId: text("service_id"), 18 + status: text("status").notNull().default("STOPPED"), 18 19 createdAt: timestamp("created_at").defaultNow().notNull(), 19 20 updatedAt: timestamp("updated_at").defaultNow().notNull(), 20 21 },
+138 -6
apps/cli/src/cmd/service.ts
··· 1 - export async function createService(sandbox: string) {} 1 + import consola from "consola"; 2 + import { client } from "../client"; 3 + import { env } from "../lib/env"; 4 + import getAccessToken from "../lib/getAccessToken"; 5 + import type { Service } from "../types/service"; 6 + import Table from "cli-table3"; 7 + import dayjs from "dayjs"; 8 + import relativeTime from "dayjs/plugin/relativeTime"; 9 + import { c } from "../theme"; 10 + import process from "node:process"; 2 11 3 - export async function listServices(sandbox: string) {} 12 + dayjs.extend(relativeTime); 4 13 5 - export async function restartService() {} 14 + type CreateServiceOptions = { 15 + ports?: number[]; 16 + description?: string; 17 + }; 6 18 7 - export async function startService() {} 19 + export async function createService( 20 + sandbox: string, 21 + name: string, 22 + command: string[], 23 + { ports, description }: CreateServiceOptions, 24 + ) { 25 + const token = await getAccessToken(); 8 26 9 - export async function stopService() {} 27 + console.log( 28 + `Creating service ${name} in sandbox ${sandbox} with command: ${command.join(" ")}`, 29 + ); 30 + console.log(`Ports: ${ports?.join(", ") || "None"}`); 31 + console.log(`Description: ${description || "None"}`); 10 32 11 - export async function deleteService() {} 33 + try { 34 + } catch (error) { 35 + consola.error("Failed to create service", error); 36 + process.exit(1); 37 + } 38 + } 39 + 40 + export async function listServices(sandboxId: string) { 41 + const token = await getAccessToken(); 42 + 43 + try { 44 + const { data } = await client.get<{ services: Service[] }>( 45 + "/xrpc/io.pocketenv.service.getServices", 46 + { 47 + params: { 48 + sandboxId, 49 + }, 50 + headers: { 51 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 52 + }, 53 + }, 54 + ); 55 + 56 + const table = new Table({ 57 + head: [ 58 + c.primary("ID"), 59 + c.primary("NAME"), 60 + c.primary("COMMAND"), 61 + c.primary("STATUS"), 62 + c.primary("CREATED AT"), 63 + ], 64 + chars: { 65 + top: "", 66 + "top-mid": "", 67 + "top-left": "", 68 + "top-right": "", 69 + bottom: "", 70 + "bottom-mid": "", 71 + "bottom-left": "", 72 + "bottom-right": "", 73 + left: "", 74 + "left-mid": "", 75 + mid: "", 76 + "mid-mid": "", 77 + right: "", 78 + "right-mid": "", 79 + middle: " ", 80 + }, 81 + style: { 82 + border: [], 83 + head: [], 84 + }, 85 + }); 86 + 87 + for (const service of data.services) { 88 + table.push([ 89 + c.secondary(service.id), 90 + service.name, 91 + service.command, 92 + service.status === "RUNNING" 93 + ? c.highlight(service.status) 94 + : service.status, 95 + dayjs(service.createdAt).fromNow(), 96 + ]); 97 + } 98 + 99 + consola.log(table.toString()); 100 + } catch (error) { 101 + consola.error("Failed to list services", error); 102 + process.exit(1); 103 + } 104 + } 105 + 106 + export async function restartService(id: string) { 107 + const token = await getAccessToken(); 108 + try { 109 + } catch (error) { 110 + consola.error(`Failed to restart service ${id}`, error); 111 + process.exit(1); 112 + } 113 + } 114 + 115 + export async function startService(id: string) { 116 + const token = await getAccessToken(); 117 + 118 + try { 119 + } catch (error) { 120 + consola.error(`Failed to start service ${id}`, error); 121 + process.exit(1); 122 + } 123 + } 124 + 125 + export async function stopService(id: string) { 126 + const token = await getAccessToken(); 127 + 128 + try { 129 + } catch (error) { 130 + consola.error(`Failed to stop service ${id}`, error); 131 + process.exit(1); 132 + } 133 + } 134 + 135 + export async function deleteService(id: string) { 136 + const token = await getAccessToken(); 137 + 138 + try { 139 + } catch (error) { 140 + consola.error(`Failed to delete service ${id}`, error); 141 + process.exit(1); 142 + } 143 + }
+2
apps/cli/src/index.ts
··· 315 315 .argument("<sandbox>", "the sandbox to create the service in") 316 316 .argument("<name>", "the name of the service") 317 317 .argument("<command...>", "the command to run for the service") 318 + .option("--description, -d <description>", "a description for the service") 319 + .option("--ports, -p <ports...>", "a list of ports to expose for the service") 318 320 .description("create a new service in the given sandbox") 319 321 .action(createService); 320 322
+9
apps/cli/src/types/service.ts
··· 1 + export type Service = { 2 + id: string; 3 + name: string; 4 + ports?: number[]; 5 + command: string; 6 + description?: string; 7 + status: "RUNNING" | "STOPPED"; 8 + createdAt: string; 9 + };
+1
apps/sandbox/src/schema/services.ts
··· 15 15 command: text("command").notNull(), 16 16 description: text("description"), 17 17 serviceId: text("service_id"), 18 + status: text("status").notNull().default("STOPPED"), 18 19 createdAt: timestamp("created_at").defaultNow().notNull(), 19 20 updatedAt: timestamp("updated_at").defaultNow().notNull(), 20 21 },