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 CLI backup commands and update restore API

Remove the sandbox id query param from restoreBackup lexicons/types and
use input.backupId instead. Update the XRPC handler to query the
backups table (left-joining sandboxes) and adjust the not-found check.
Add CLI backup command stubs (create, restore, list) and wire them into
the CLI. Minor schema/formatting tidy-up for backups id field.

+43 -46
-12
apps/api/lexicons/sandbox/restoreBackup.json
··· 5 5 "main": { 6 6 "type": "procedure", 7 7 "description": "Restore a backup of a sandbox", 8 - "parameters": { 9 - "type": "params", 10 - "required": [ 11 - "id" 12 - ], 13 - "properties": { 14 - "id": { 15 - "type": "string", 16 - "description": "The sandbox ID." 17 - } 18 - } 19 - }, 20 8 "input": { 21 9 "encoding": "application/json", 22 10 "schema": {
-10
apps/api/pkl/defs/sandbox/restoreBackup.pkl
··· 6 6 ["main"] { 7 7 type = "procedure" 8 8 description = "Restore a backup of a sandbox" 9 - parameters { 10 - type = "params" 11 - required = List("id") 12 - properties { 13 - ["id"] = new StringType { 14 - type = "string" 15 - description = "The sandbox ID." 16 - } 17 - } 18 - } 19 9 input { 20 10 encoding = "application/json" 21 11 schema {
-10
apps/api/src/lexicon/lexicons.ts
··· 1854 1854 main: { 1855 1855 type: "procedure", 1856 1856 description: "Restore a backup of a sandbox", 1857 - parameters: { 1858 - type: "params", 1859 - required: ["id"], 1860 - properties: { 1861 - id: { 1862 - type: "string", 1863 - description: "The sandbox ID.", 1864 - }, 1865 - }, 1866 - }, 1867 1857 input: { 1868 1858 encoding: "application/json", 1869 1859 schema: {
+1 -4
apps/api/src/lexicon/types/io/pocketenv/sandbox/restoreBackup.ts
··· 8 8 import { CID } from "multiformats/cid"; 9 9 import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 - export interface QueryParams { 12 - /** The sandbox ID. */ 13 - id: string; 14 - } 11 + export type QueryParams = {}; 15 12 16 13 export interface InputSchema { 17 14 /** The backup ID to restore. */
+1 -3
apps/api/src/schema/backups.ts
··· 3 3 import sandboxes from "./sandboxes"; 4 4 5 5 const backups = pgTable("backups", { 6 - id: text("id") 7 - .primaryKey() 8 - .default(sql`xata_id()`), 6 + id: text("id").primaryKey().default(sql`xata_id()`), 9 7 sandboxId: text("sandbox_id") 10 8 .notNull() 11 9 .references(() => sandboxes.id, { onDelete: "cascade" }),
+7 -7
apps/api/src/xrpc/io/pocketenv/sandbox/restoreBackup.ts
··· 22 22 23 23 const [record] = await ctx.db 24 24 .select() 25 - .from(schema.sandboxes) 25 + .from(schema.backups) 26 + .leftJoin( 27 + schema.sandboxes, 28 + eq(schema.backups.sandboxId, schema.sandboxes.id), 29 + ) 26 30 .leftJoin(schema.users, eq(schema.sandboxes.userId, schema.users.id)) 27 31 .where( 28 32 and( 29 - or( 30 - eq(schema.sandboxes.id, params.id), 31 - eq(schema.sandboxes.uri, params.id), 32 - eq(schema.sandboxes.name, params.id), 33 - ), 33 + eq(schema.backups.backupId, input.backupId), 34 34 eq(schema.users.did, auth.credentials.did), 35 35 ), 36 36 ) 37 37 .execute(); 38 38 39 - if (!record) { 39 + if (!record?.sandboxes) { 40 40 throw new XRPCError(404, "Sandbox not found"); 41 41 } 42 42
+9
apps/cli/src/cmd/backup.ts
··· 1 + export function createBackup() { 2 + 3 + } 4 + 5 + export function restoreBackup() { 6 + } 7 + 8 + export function listBackups() { 9 + }
+25
apps/cli/src/index.ts
··· 33 33 } from "./cmd/service"; 34 34 import copy from "./cmd/copy"; 35 35 import ps from "./cmd/ps"; 36 + import { createBackup, listBackups, restoreBackup } from "./cmd/backup"; 36 37 37 38 const program = new Command(); 38 39 ··· 197 198 }) 198 199 .description("unexpose a port from the given sandbox") 199 200 .action(unexposePort); 201 + 202 + const backup = program.command("backup").description("manage sandbox backups"); 203 + 204 + backup 205 + .command("create") 206 + .argument("<sandbox>", "the sandbox to create a backup for") 207 + .argument("<directory>", "the directory to backup") 208 + .option("--description, -d <description>", "an optional description for the backup") 209 + .option("--ttl, -t <ttl>", "time to live for the backup (e.g., 24h, 7d)", "7d") 210 + .description("create a backup for the given sandbox") 211 + .action(createBackup); 212 + 213 + backup 214 + .command("restore") 215 + .argument("<backup_id>", "the ID of the backup to restore") 216 + .description("restore a backup to the given sandbox") 217 + .action(restoreBackup); 218 + 219 + backup 220 + .command("list") 221 + .aliases(["ls"]) 222 + .argument("<sandbox>", "the sandbox to list backups for") 223 + .description("list backups for the given sandbox") 224 + .action(listBackups); 200 225 201 226 const volume = program.command("volume").description("manage volumes"); 202 227