the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import { Sandbox } from '@pocketenv/sdk';
2import consola from 'consola';
3import z from 'zod';
4import { c } from '../theme';
5import process from "node:process";
6import Table from "cli-table3";
7import dayjs from "dayjs";
8import relativeTime from "dayjs/plugin/relativeTime";
9
10dayjs.extend(relativeTime);
11
12const ttlSchema = z
13 .string()
14 .regex(/^\d+(m|h|d)$/, 'Invalid TTL format (e.g. 10m, 2h, 7d)')
15 .transform((value) => {
16 const amount = parseInt(value.slice(0, -1), 10);
17 const unit = value.slice(-1);
18
19 switch (unit) {
20 case 'm':
21 return amount * 60;
22 case 'h':
23 return amount * 60 * 60;
24 case 'd':
25 return amount * 60 * 60 * 24;
26 default:
27 throw new Error('Invalid TTL unit');
28 }
29 });
30
31const backupOptionsSchema = z.object({
32 description: z.string().optional(),
33 ttl: ttlSchema.optional(),
34});
35
36export type BackupOptions = z.infer<typeof backupOptionsSchema>;
37
38export async function createBackup(sandboxId: string, directory: string, options: BackupOptions) {
39 try {
40 const { data, error } = backupOptionsSchema.safeParse(options);
41 if (error) {
42 consola.error(`Invalid backup options: ${error.issues[0]?.message}`);
43 process.exit(1);
44 }
45
46 const sandbox = await Sandbox.get(sandboxId);
47
48 if (sandbox.data.provider !== "cloudflare") {
49 consola.error(`Backups are only supported for sandboxes running on Cloudflare Workers`);
50 process.exit(1);
51 }
52
53 const { description, ttl } = data;
54
55 await sandbox.backup.create(
56 directory,
57 description,
58 ttl,
59 );
60 consola.success(`Backup request for sandbox ${c.primary(sandboxId)} at directory ${c.primary(directory)} created successfully`);
61 consola.log(` This may take a few moments to complete.\n Run ${c.primary(`pocketenv backup ls ${sandboxId}`)} to check the status of your backup.`);
62 } catch (e) {
63 consola.error(`Failed to create backup for sandbox`, e);
64 process.exit(1);
65 }
66}
67
68export async function restoreBackup(backupId: string) {
69 try {
70 await Sandbox.restoreBackup(backupId);
71 consola.success(`Backup ${c.primary(backupId)} restored successfully`);
72 } catch {
73 consola.error(`Failed to restore backup ${c.primary(backupId)}`);
74 process.exit(1);
75 }
76}
77
78export async function listBackups(sandboxId: string) {
79 const sandbox = await Sandbox.get(sandboxId);
80 try {
81 const { backups } = await sandbox.backup.list();
82
83 const table = new Table({
84 head: [
85 c.primary("BACKUP ID"),
86 c.primary("DIRECTORY"),
87 c.primary("CREATED AT"),
88 c.primary("EXPIRES AT"),
89 ],
90 chars: {
91 top: "",
92 "top-mid": "",
93 "top-left": "",
94 "top-right": "",
95 bottom: "",
96 "bottom-mid": "",
97 "bottom-left": "",
98 "bottom-right": "",
99 left: "",
100 "left-mid": "",
101 mid: "",
102 "mid-mid": "",
103 right: "",
104 "right-mid": "",
105 middle: " ",
106 },
107 style: {
108 border: [],
109 head: [],
110 },
111 });
112
113 for (const backup of backups) {
114 table.push([
115 c.secondary(backup.id),
116 backup.directory,
117 dayjs(backup.createdAt).fromNow(),
118 backup.expiresAt ? dayjs(backup.expiresAt).fromNow() : "Never",
119 ]);
120 }
121
122 consola.log(table.toString());
123 } catch {
124 consola.error(`Failed to list backups for sandbox ${c.primary(sandboxId)}`);
125 process.exit(1);
126 }
127}