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 chalk from "chalk";
2import { version } from "../package.json" assert { type: "json" };
3import { Command } from "commander";
4import start from "./cmd/start";
5import login from "./cmd/login";
6import whoami from "./cmd/whoami";
7import ssh from "./cmd/ssh";
8import listSandboxes from "./cmd/list";
9import stop from "./cmd/stop";
10import createSandbox from "./cmd/create";
11import logout from "./cmd/logout";
12import deleteSandbox from "./cmd/rm";
13import { deleteSecret, listSecrets, putSecret } from "./cmd/secret";
14import { deleteEnv, listEnvs, putEnv } from "./cmd/env";
15import { getSshKey, putKeys } from "./cmd/sshkeys";
16import { getTailscaleAuthKey, putAuthKey } from "./cmd/tailscale";
17import { exposePort } from "./cmd/expose";
18import { unexposePort } from "./cmd/unexpose";
19import { createVolume, deleteVolume, listVolumes } from "./cmd/volume";
20import { deleteFile, listFiles, putFile } from "./cmd/file";
21import consola from "consola";
22import { listPorts } from "./cmd/ports";
23import { c } from "./theme";
24import { exposeVscode } from "./cmd/vscode";
25import { exec } from "./cmd/exec";
26import {
27 createService,
28 deleteService,
29 listServices,
30 restartService,
31 startService,
32 stopService,
33} from "./cmd/service";
34import copy from "./cmd/copy";
35import ps from "./cmd/ps";
36import { createBackup, listBackups, restoreBackup } from "./cmd/backup";
37
38const program = new Command();
39
40program
41 .name("pocketenv")
42 .description(
43 `${chalk.bold.rgb(0, 232, 198)(`pocketenv v${version}`)} ${c.muted("─")} ${c.muted("Open, interoperable sandbox platform for agents and humans")}`,
44 )
45 .version(version);
46
47program.configureHelp({
48 styleTitle: (str) => chalk.bold.rgb(0, 210, 255)(str),
49 styleCommandText: (str) => c.secondary(str),
50 styleDescriptionText: (str) => c.muted(str),
51 styleOptionText: (str) => c.highlight(str),
52 styleArgumentText: (str) => c.accent(str),
53 styleSubcommandText: (str) => c.secondary(str),
54});
55
56program.addHelpText(
57 "after",
58 `
59${chalk.bold.rgb(0, 210, 255)("─".repeat(90))}
60 ${chalk.bold.rgb(0, 232, 198)("Learn more:")} ${c.link("https://docs.pocketenv.io")}
61 ${chalk.bold.rgb(0, 232, 198)("Discord:")} ${c.link("https://discord.gg/9ada4pFUFS")}
62 ${chalk.bold.rgb(0, 232, 198)("Report bugs:")} ${c.link("https://github.com/pocketenv-io/pocketenv/issues")}
63${chalk.bold.rgb(0, 210, 255)("─".repeat(90))}
64`,
65);
66
67program
68 .command("login")
69 .argument("<handle>", "your AT Proto handle (e.g., <username>.bsky.social)")
70 .description("login with your AT Proto account and get a session token")
71 .action(login);
72
73program
74 .command("whoami")
75 .description("get the current logged-in user")
76 .action(whoami);
77
78program
79 .command("console")
80 .aliases(["shell", "ssh", "s"])
81 .argument("[sandbox]", "the sandbox to connect to")
82 .description("open an interactive shell for the given sandbox")
83 .action(ssh);
84
85program.command("ls").description("list sandboxes").action(listSandboxes);
86
87program
88 .command("copy")
89 .aliases(["cp"])
90 .argument("<source>", "the source file or directory")
91 .argument("<destination>", "the destination file or directory")
92 .description("copy files from or to a running sandbox")
93 .addHelpText(
94 "after",
95 `
96${chalk.bold("Examples:")}
97 ${c.primary("pocketenv cp ./local-file.txt my-sandbox:/remote-file.txt")} Copy a local file to a sandbox
98 ${c.primary("pocketenv cp my-sandbox:/remote-file.txt ./local-file.txt")} Copy a file from a sandbox to local
99 ${c.primary("pocketenv cp my-sandbox:/remote-dir ./local-dir")} Copy a directory from a sandbox to local
100 ${c.primary("pocketenv cp ./local-dir my-sandbox:/remote-dir")} Copy a local directory to a sandbox
101`,
102 )
103 .action(copy);
104
105program.command("ps").description("list running Sandboxes").action(ps);
106
107program
108 .command("start")
109 .argument("<sandbox>", "the sandbox to start")
110 .option("--ssh, -s", "connect to the Sandbox and automatically open a shell")
111 .option(
112 "--repo, -r <repo>",
113 "the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)",
114 )
115 .option(
116 "--keep-alive, -k",
117 "keep the sandbox alive, ignoring inactivity timeout",
118 )
119 .description("start the given sandbox")
120 .action(start);
121
122program
123 .command("stop")
124 .argument("<sandbox>", "the sandbox to stop")
125 .description("stop the given sandbox")
126 .action(stop);
127
128program
129 .command("create")
130 .aliases(["new"])
131 .option("--provider, -p <provider>", "the provider to use for the sandbox")
132 .option(
133 "--base, -b <base>",
134 "the base sandbox to use for the sandbox, e.g. openclaw, claude-code, codex, copilot ...",
135 )
136 .option("--ssh, -s", "connect to the Sandbox and automatically open a shell")
137 .option(
138 "--repo, -r <repo>",
139 "the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)",
140 )
141 .argument("[name]", "the name of the sandbox to create")
142 .description("create a new sandbox")
143 .action(createSandbox);
144
145program
146 .command("logout")
147 .description("logout (removes session token)")
148 .action(logout);
149
150program
151 .command("rm")
152 .aliases(["delete", "remove"])
153 .argument("<sandbox>", "the sandbox to delete")
154 .description("delete the given sandbox")
155 .action(deleteSandbox);
156
157program
158 .command("vscode")
159 .aliases(["code", "code-server"])
160 .argument("<sandbox>", "the sandbox to expose VS Code for")
161 .description("expose a visual studio code server to the internet")
162 .action(exposeVscode);
163
164program
165 .enablePositionalOptions()
166 .command("exec")
167 .argument("<sandbox>", "the sandbox to execute the command in")
168 .argument("<command...>", "the command to execute")
169 .description("execute a command in the given sandbox")
170 .passThroughOptions()
171 .action(exec);
172
173program
174 .command("expose")
175 .argument("<sandbox>", "the sandbox to expose a port for")
176 .argument("<port>", "the port to expose", (val) => {
177 const port = parseInt(val, 10);
178 if (isNaN(port)) {
179 consola.error(`port must be a number, got: ${val}`);
180 process.exit(1);
181 }
182 return port;
183 })
184 .argument("[description]", "an optional description for the exposed port")
185 .description("expose a port from the given sandbox to the internet")
186 .action(exposePort);
187
188program
189 .command("unexpose")
190 .argument("<sandbox>", "the sandbox to unexpose a port for")
191 .argument("<port>", "the port to unexpose", (val) => {
192 const port = parseInt(val, 10);
193 if (isNaN(port)) {
194 consola.error(`port must be a number, got: ${val}`);
195 process.exit(1);
196 }
197 return port;
198 })
199 .description("unexpose a port from the given sandbox")
200 .action(unexposePort);
201
202const backup = program.command("backup").description("manage sandbox backups");
203
204backup
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)", "3d")
210 .description("create a backup for the given sandbox")
211 .action(createBackup);
212
213backup
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
219backup
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);
225
226const volume = program.command("volume").description("manage volumes");
227
228volume
229 .command("put")
230 .argument("<sandbox>", "the sandbox to put the volume in")
231 .argument("<name>", "the name of the volume")
232 .argument("<path>", "the path to mount the volume at")
233 .description("put a volume in the given sandbox")
234 .action(createVolume);
235
236volume
237 .command("list")
238 .aliases(["ls"])
239 .argument("<sandbox>", "the sandbox to list volumes for")
240 .description("list volumes in the given sandbox")
241 .action(listVolumes);
242
243volume
244 .command("delete")
245 .aliases(["rm", "remove"])
246 .argument("<id>", "the ID of the volume to delete")
247 .description("delete a volume")
248 .action(deleteVolume);
249
250const file = program.command("file").description("manage files");
251
252file
253 .command("put")
254 .argument("<sandbox>", "the sandbox to put the file in")
255 .argument("<path>", "the remote path to upload the file to")
256 .argument("[localPath]", "the local path of the file to upload")
257 .description("upload a file to the given sandbox")
258 .action(putFile);
259
260file
261 .command("list")
262 .aliases(["ls"])
263 .argument("<sandbox>", "the sandbox to list files for")
264 .description("list files in the given sandbox")
265 .action(listFiles);
266
267file
268 .command("delete")
269 .aliases(["rm", "remove"])
270 .argument("<id>", "the ID of the file to delete")
271 .description("delete a file")
272 .action(deleteFile);
273
274program
275 .command("ports")
276 .argument("<sandbox>", "the sandbox to list exposed ports for")
277 .description("list exposed ports for a sandbox")
278 .action(listPorts);
279
280const secret = program.command("secret").description("manage secrets");
281
282secret
283 .command("put")
284 .argument("<sandbox>", "the sandbox to put the secret in")
285 .argument("<key>", "the key of the secret")
286 .description("put a secret in the given sandbox")
287 .action(putSecret);
288
289secret
290 .command("list")
291 .aliases(["ls"])
292 .argument("<sandbox>", "the sandbox to list secrets for")
293 .description("list secrets in the given sandbox")
294 .action(listSecrets);
295
296secret
297 .command("delete")
298 .aliases(["rm", "remove"])
299 .argument("<secret_id>", "the ID of the secret to delete")
300 .description("delete a secret")
301 .action(deleteSecret);
302
303const env = program.command("env").description("manage environment variables");
304
305env
306 .command("put")
307 .argument("<sandbox>", "the sandbox to put the environment variable in")
308 .argument("<key>", "the key of the environment variable")
309 .argument("<value>", "the value of the environment variable")
310 .description("put an environment variable in the given sandbox")
311 .action(putEnv);
312
313env
314 .command("list")
315 .aliases(["ls"])
316 .argument("<sandbox>", "the sandbox to list environment variables for")
317 .description("list environment variables in the given sandbox")
318 .action(listEnvs);
319
320env
321 .command("delete")
322 .aliases(["rm", "remove"])
323 .argument("<variable_id>", "the ID of the environment variable to delete")
324 .description("delete an environment variable")
325 .action(deleteEnv);
326
327const sshkeys = program.command("sshkeys").description("manage SSH keys");
328
329sshkeys
330 .command("put")
331 .argument("<sandbox>", "the sandbox to put the SSH key in")
332 .option("--private-key <path>", "the path to the SSH private key")
333 .option("--public-key <path>", "the path to the SSH public key")
334 .option("--generate, -g", "generate a new SSH key pair")
335 .description("put an SSH key in the given sandbox")
336 .action(putKeys);
337
338sshkeys
339 .command("get")
340 .argument("<sandbox>", "the sandbox to get the SSH key from")
341 .description("get an SSH key (public key only) from the given sandbox")
342 .action(getSshKey);
343
344const tailscale = program.command("tailscale").description("manage Tailscale");
345
346tailscale
347 .command("put")
348 .argument("<sandbox>", "the sandbox to put the Tailscale Auth Key in")
349 .description("put a Tailscale Auth Key in the given sandbox")
350 .action(putAuthKey);
351
352tailscale
353 .command("get")
354 .argument("<sandbox>", "the sandbox to get the Tailscale Auth Key from")
355 .description("get a Tailscale Auth Key (redacted) from the given sandbox")
356 .action(getTailscaleAuthKey);
357
358const service = program.command("service").description("manage services");
359
360service
361 .command("create")
362 .argument("<sandbox>", "the sandbox to create the service in")
363 .argument("<name>", "the name of the service")
364 .argument("<command...>", "the command to run for the service")
365 .option("--description, -d <description>", "a description for the service")
366 .option("--ports, -p <ports...>", "a list of ports to expose for the service")
367 .description("create a new service in the given sandbox")
368 .action(createService);
369
370service
371 .command("list")
372 .aliases(["ls"])
373 .argument("<sandbox>", "the sandbox to list services for")
374 .description("list services in the given sandbox")
375 .action(listServices);
376
377service
378 .command("delete")
379 .aliases(["rm", "remove"])
380 .argument("<service_id>", "the ID of the service to delete")
381 .description("delete a service")
382 .action(deleteService);
383
384service
385 .command("start")
386 .argument("<service_id>", "the ID of the service to start")
387 .description("start a service")
388 .action(startService);
389
390service
391 .command("stop")
392 .argument("<service_id>", "the ID of the service to stop")
393 .description("stop a service")
394 .action(stopService);
395
396service
397 .command("restart")
398 .argument("<service_id>", "the ID of the service to restart")
399 .description("restart a service")
400 .action(restartService);
401
402if (process.argv.length <= 2) {
403 program.help();
404}
405
406program.parse(process.argv);