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 consola from "consola";
3import getAccessToken from "../../lib/getAccessToken";
4import { client } from "../../client";
5import { env } from "../../lib/env";
6import type { Sandbox } from "../../types/sandbox";
7import type { Profile } from "../../types/profile";
8import cloudflare from "./cloudflare";
9import tty from "./tty";
10import terminal from "./terminal";
11
12async function ssh(sandboxName: string | undefined) {
13 const token = await getAccessToken();
14 const authToken = env.POCKETENV_TOKEN || token;
15
16 let sandbox: Sandbox;
17
18 if (!sandboxName) {
19 // No name provided – list the user's sandboxes and pick the first running one.
20 const profile = await client.get<Profile>(
21 "/xrpc/io.pocketenv.actor.getProfile",
22 { headers: { Authorization: `Bearer ${authToken}` } },
23 );
24
25 const response = await client.get<{ sandboxes: Sandbox[] }>(
26 "/xrpc/io.pocketenv.actor.getActorSandboxes",
27 {
28 params: { did: profile.data.did, offset: 0, limit: 100 },
29 headers: { Authorization: `Bearer ${authToken}` },
30 },
31 );
32
33 const runningSandboxes = response.data.sandboxes.filter(
34 (s) => s.status === "RUNNING",
35 );
36
37 if (runningSandboxes.length === 0) {
38 consola.error(
39 `No running sandboxes found. ` +
40 `Start one with ${chalk.greenBright("pocketenv start <sandbox>")} first.`,
41 );
42 process.exit(1);
43 }
44
45 sandbox = runningSandboxes[0] as Sandbox;
46 consola.info(`Connecting to sandbox ${chalk.greenBright(sandbox.name)}…`);
47 } else {
48 // Look up the named sandbox.
49 const response = await client.get<{ sandbox: Sandbox | null }>(
50 "/xrpc/io.pocketenv.sandbox.getSandbox",
51 {
52 params: { id: sandboxName },
53 headers: { Authorization: `Bearer ${authToken}` },
54 },
55 );
56
57 if (!response.data.sandbox) {
58 consola.error(`Sandbox ${chalk.yellowBright(sandboxName)} not found.`);
59 process.exit(1);
60 }
61
62 sandbox = response.data.sandbox;
63 }
64
65 if (sandbox.status !== "RUNNING") {
66 consola.error(
67 `Sandbox ${chalk.yellowBright(sandbox.name)} is not running. ` +
68 `Start it with ${chalk.greenBright(`pocketenv start ${sandbox.name}`)}.`,
69 );
70 process.exit(1);
71 }
72
73 // export type Provider = "daytona" | "deno" | "cloudflare" | "vercel" | "sprites" | "modal" | "e2b" | "runloop" | "hopx" | "blaxel";
74 switch (sandbox.provider) {
75 case "cloudflare":
76 await cloudflare(sandbox);
77 break;
78 case "daytona":
79 await terminal(sandbox);
80 break;
81 case "deno":
82 await terminal(sandbox);
83 break;
84 case "vercel":
85 await tty(sandbox, false); // pty
86 break;
87 case "sprites":
88 await tty(sandbox, true);
89 break;
90 case "modal":
91 await tty(sandbox, false); // pty
92 break;
93 case "e2b":
94 await tty(sandbox, false); // pty
95 break;
96 case "hopx":
97 await tty(sandbox, false); // pty
98 break;
99 case "runloop":
100 await tty(sandbox, false); // pty
101 break;
102 case "blaxel":
103 await tty(sandbox, false); // pty
104 break;
105 default:
106 consola.error(
107 `Sandbox ${chalk.yellowBright(sandbox.name)} uses provider ` +
108 `${chalk.cyan(sandbox.provider)}, but this command only supports ` +
109 `${chalk.cyan("cloudflare")}, ${chalk.cyan("daytona")}, ${chalk.cyan("deno")}, ${chalk.cyan("vercel")}, ${chalk.cyan("sprites")}, ${chalk.cyan("modal")}, ${chalk.cyan("hopx")}, ${chalk.cyan("runloop")}, ${chalk.cyan("blaxel")} or ${chalk.cyan("e2b")} sandboxes.`,
110 );
111 process.exit(1);
112 }
113}
114
115export default ssh;