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 port/volume/file commands and name lookup

+312 -22
+13 -4
apps/api/src/xrpc/io/pocketenv/volume/getVolumes.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 - import { eq, and, count } from "drizzle-orm"; 3 + import { eq, and, count, or } from "drizzle-orm"; 4 4 import type { Server } from "lexicon"; 5 5 import type { 6 6 QueryParams, ··· 71 71 params.sandboxId 72 72 ? and( 73 73 eq(users.did, auth.credentials.did), 74 - eq(sandboxVolumes.sandboxId, params.sandboxId), 74 + or( 75 + eq(sandboxVolumes.sandboxId, params.sandboxId), 76 + eq(sandboxes.name, params.sandboxId), 77 + ), 75 78 ) 76 79 : eq(users.did, auth.credentials.did), 77 80 ) ··· 93 96 params.sandboxId 94 97 ? and( 95 98 eq(users.did, auth.credentials.did), 96 - eq(sandboxVolumes.sandboxId, params.sandboxId), 99 + or( 100 + eq(sandboxVolumes.sandboxId, params.sandboxId), 101 + eq(sandboxes.name, params.sandboxId), 102 + ), 97 103 ) 98 104 : eq(users.did, auth.credentials.did), 99 105 ) ··· 115 121 params.sandboxId 116 122 ? and( 117 123 eq(users.did, auth.credentials.did), 118 - eq(sandboxVolumes.sandboxId, params.sandboxId), 124 + or( 125 + eq(sandboxVolumes.sandboxId, params.sandboxId), 126 + eq(sandboxes.name, params.sandboxId), 127 + ), 119 128 ) 120 129 : eq(users.did, auth.credentials.did), 121 130 )
+1 -1
apps/cli/package.json
··· 4 4 "bin": { 5 5 "pocketenv": "dist/index.js" 6 6 }, 7 - "version": "0.2.5", 7 + "version": "0.3.0", 8 8 "type": "module", 9 9 "keywords": [ 10 10 "sandbox",
+11
apps/cli/src/cmd/expose.ts
··· 1 + import chalk from "chalk"; 2 + import consola from "consola"; 3 + import getAccessToken from "../lib/getAccessToken"; 4 + 5 + export async function exposePort(sandbox: string, port: number) { 6 + const token = await getAccessToken(); 7 + 8 + consola.success( 9 + `Port ${chalk.rgb(0, 232, 198)(port)} exposed for sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 10 + ); 11 + }
+23
apps/cli/src/cmd/file.ts
··· 1 + import chalk from "chalk"; 2 + import consola from "consola"; 3 + import getAccessToken from "../lib/getAccessToken"; 4 + 5 + export async function putFile(sandbox: string, path: string) { 6 + const token = await getAccessToken(); 7 + 8 + consola.success( 9 + `File ${chalk.rgb(0, 232, 198)(path)} successfully created in sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 10 + ); 11 + } 12 + 13 + export async function listFiles(sandbox: string) { 14 + const token = await getAccessToken(); 15 + } 16 + 17 + export async function deleteFile(sandbox: string, id: string) { 18 + const token = await getAccessToken(); 19 + 20 + consola.success( 21 + `File ${chalk.rgb(0, 232, 198)(id)} successfully deleted from sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 22 + ); 23 + }
+7 -7
apps/cli/src/cmd/list.ts
··· 1 1 import { client } from "../client"; 2 - import chalk from "chalk"; 3 2 import consola from "consola"; 4 3 import { env } from "../lib/env"; 5 4 import getAccessToken from "../lib/getAccessToken"; ··· 8 7 import dayjs from "dayjs"; 9 8 import relativeTime from "dayjs/plugin/relativeTime"; 10 9 import type { Profile } from "../types/profile"; 10 + import { c } from "../theme"; 11 11 dayjs.extend(relativeTime); 12 12 13 13 async function listSandboxes() { ··· 35 35 36 36 const table = new Table({ 37 37 head: [ 38 - chalk.cyan("NAME"), 39 - chalk.cyan("BASE"), 40 - chalk.cyan("STATUS"), 41 - chalk.cyan("CREATED AT"), 38 + c.primary("NAME"), 39 + c.primary("BASE"), 40 + c.primary("STATUS"), 41 + c.primary("CREATED AT"), 42 42 ], 43 43 chars: { 44 44 top: "", ··· 65 65 66 66 for (const sandbox of response.data.sandboxes) { 67 67 table.push([ 68 - chalk.greenBright(sandbox.name), 68 + c.secondary(sandbox.name), 69 69 sandbox.baseSandbox, 70 70 sandbox.status === "RUNNING" 71 - ? chalk.greenBright(sandbox.status) 71 + ? c.highlight(sandbox.status) 72 72 : sandbox.status, 73 73 dayjs(sandbox.createdAt).fromNow(), 74 74 ]);
+63
apps/cli/src/cmd/ports.ts
··· 1 + import chalk from "chalk"; 2 + import { client } from "../client"; 3 + import { env } from "../lib/env"; 4 + import getAccessToken from "../lib/getAccessToken"; 5 + import type { Port } from "../types/port"; 6 + import CliTable3 from "cli-table3"; 7 + import consola from "consola"; 8 + import { c } from "../theme"; 9 + 10 + export async function listPorts(sandbox: string) { 11 + const token = await getAccessToken(); 12 + 13 + const response = await client.get<{ ports: Port[] }>( 14 + "/xrpc/io.pocketenv.sandbox.getExposedPorts", 15 + { 16 + params: { 17 + id: sandbox, 18 + }, 19 + headers: { 20 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 21 + }, 22 + }, 23 + ); 24 + 25 + const table = new CliTable3({ 26 + head: [ 27 + c.primary("PORT"), 28 + c.primary("DESCRIPTION"), 29 + c.primary("PREVIEW URL"), 30 + ], 31 + chars: { 32 + top: "", 33 + "top-mid": "", 34 + "top-left": "", 35 + "top-right": "", 36 + bottom: "", 37 + "bottom-mid": "", 38 + "bottom-left": "", 39 + "bottom-right": "", 40 + left: "", 41 + "left-mid": "", 42 + mid: "", 43 + "mid-mid": "", 44 + right: "", 45 + "right-mid": "", 46 + middle: " ", 47 + }, 48 + style: { 49 + border: [], 50 + head: [], 51 + }, 52 + }); 53 + 54 + for (const port of response.data.ports) { 55 + table.push([ 56 + c.secondary(port.port), 57 + port.description || "-", 58 + c.link(port.previewUrl || "-"), 59 + ]); 60 + } 61 + 62 + consola.log(table.toString()); 63 + }
+11
apps/cli/src/cmd/unexpose.ts
··· 1 + import chalk from "chalk"; 2 + import consola from "consola"; 3 + import getAccessToken from "../lib/getAccessToken"; 4 + 5 + export async function unexposePort(sandbox: string, port: number) { 6 + const token = await getAccessToken(); 7 + 8 + consola.success( 9 + `Port ${chalk.rgb(0, 232, 198)(port)} unexposed for sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 10 + ); 11 + }
+75
apps/cli/src/cmd/volume.ts
··· 1 + import chalk from "chalk"; 2 + import consola from "consola"; 3 + import getAccessToken from "../lib/getAccessToken"; 4 + import { client } from "../client"; 5 + import { env } from "../lib/env"; 6 + import type { Volume } from "../types/volume"; 7 + import CliTable3 from "cli-table3"; 8 + import { c } from "../theme"; 9 + 10 + export async function listVolumes(sandboxId: string) { 11 + const token = await getAccessToken(); 12 + 13 + const response = await client.get<{ volumes: Volume[] }>( 14 + "/xrpc/io.pocketenv.volume.getVolumes", 15 + { 16 + params: { 17 + sandboxId, 18 + }, 19 + headers: { 20 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 21 + }, 22 + }, 23 + ); 24 + 25 + const table = new CliTable3({ 26 + head: [c.primary("NAME"), c.primary("PATH")], 27 + chars: { 28 + top: "", 29 + "top-mid": "", 30 + "top-left": "", 31 + "top-right": "", 32 + bottom: "", 33 + "bottom-mid": "", 34 + "bottom-left": "", 35 + "bottom-right": "", 36 + left: "", 37 + "left-mid": "", 38 + mid: "", 39 + "mid-mid": "", 40 + right: "", 41 + "right-mid": "", 42 + middle: " ", 43 + }, 44 + style: { 45 + border: [], 46 + head: [], 47 + }, 48 + }); 49 + 50 + for (const volume of response.data.volumes) { 51 + table.push([c.secondary(volume.name), volume.path]); 52 + } 53 + 54 + consola.log(table.toString()); 55 + } 56 + 57 + export async function createVolume( 58 + sandbox: string, 59 + name: string, 60 + path: string, 61 + ) { 62 + const token = await getAccessToken(); 63 + 64 + consola.success( 65 + `Volume ${chalk.rgb(0, 232, 198)(name)} successfully mounted in sandbox ${chalk.rgb(0, 232, 198)(sandbox)} at path ${chalk.rgb(0, 232, 198)(path)}`, 66 + ); 67 + } 68 + 69 + export async function deleteVolume(sandbox: string, name: string) { 70 + const token = await getAccessToken(); 71 + 72 + consola.success( 73 + `Volume ${chalk.rgb(0, 232, 198)(name)} successfully deleted from sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 74 + ); 75 + }
+92 -10
apps/cli/src/index.ts
··· 14 14 import { deleteEnv, listEnvs, putEnv } from "./cmd/env"; 15 15 import { getSshKey, putKeys } from "./cmd/sshkeys"; 16 16 import { getTailscaleAuthKey, putAuthKey } from "./cmd/tailscale"; 17 - 18 - const c = { 19 - primary: (s: string) => chalk.rgb(0, 232, 198)(s), 20 - secondary: (s: string) => chalk.rgb(0, 198, 232)(s), 21 - accent: (s: string) => chalk.rgb(130, 100, 255)(s), 22 - highlight: (s: string) => chalk.rgb(100, 232, 130)(s), 23 - muted: (s: string) => chalk.rgb(200, 210, 220)(s), 24 - link: (s: string) => chalk.rgb(255, 160, 100)(s), 25 - sky: (s: string) => chalk.rgb(0, 210, 255)(s), 26 - }; 17 + import { exposePort } from "./cmd/expose"; 18 + import { unexposePort } from "./cmd/unexpose"; 19 + import { createVolume, deleteVolume, listVolumes } from "./cmd/volume"; 20 + import { deleteFile, listFiles, putFile } from "./cmd/file"; 21 + import consola from "consola"; 22 + import { listPorts } from "./cmd/ports"; 23 + import { c } from "./theme"; 27 24 28 25 const program = new Command(); 29 26 ··· 111 108 .argument("<sandbox>", "the sandbox to delete") 112 109 .description("delete the given sandbox") 113 110 .action(deleteSandbox); 111 + 112 + program 113 + .command("expose") 114 + .argument("<sandbox>", "the sandbox to expose a port for") 115 + .argument("<port>", "the port to expose", (val) => { 116 + const port = parseInt(val, 10); 117 + if (isNaN(port)) { 118 + consola.error(`port must be a number, got: ${val}`); 119 + process.exit(1); 120 + } 121 + return port; 122 + }) 123 + .description("expose a port from the given sandbox to the internet") 124 + .action(exposePort); 125 + 126 + program 127 + .command("unexpose") 128 + .argument("<sandbox>", "the sandbox to unexpose a port for") 129 + .argument("<port>", "the port to unexpose", (val) => { 130 + const port = parseInt(val, 10); 131 + if (isNaN(port)) { 132 + consola.error(`port must be a number, got: ${val}`); 133 + process.exit(1); 134 + } 135 + return port; 136 + }) 137 + .description("unexpose a port from the given sandbox") 138 + .action(unexposePort); 139 + 140 + const volume = program.command("volume").description("manage volumes"); 141 + 142 + volume 143 + .command("put") 144 + .argument("<sandbox>", "the sandbox to put the volume in") 145 + .argument("<name>", "the name of the volume") 146 + .argument("<path>", "the path to mount the volume at") 147 + .description("put a volume in the given sandbox") 148 + .action(createVolume); 149 + 150 + volume 151 + .command("list") 152 + .aliases(["ls"]) 153 + .argument("<sandbox>", "the sandbox to list volumes for") 154 + .description("list volumes in the given sandbox") 155 + .action(listVolumes); 156 + 157 + volume 158 + .command("delete") 159 + .aliases(["rm", "remove"]) 160 + .argument("<id>", "the ID of the volume to delete") 161 + .description("delete a volume") 162 + .action(deleteVolume); 163 + 164 + const file = program.command("file").description("manage files"); 165 + 166 + file 167 + .command("put") 168 + .argument("<sandbox>", "the sandbox to put the file in") 169 + .argument("<path>", "the remote path to upload the file to") 170 + .option( 171 + "--local-path, -f <localPath>", 172 + "the local path of the file to upload", 173 + ) 174 + .description("upload a file to the given sandbox") 175 + .action(putFile); 176 + 177 + file 178 + .command("list") 179 + .aliases(["ls"]) 180 + .argument("<sandbox>", "the sandbox to list files for") 181 + .description("list files in the given sandbox") 182 + .action(listFiles); 183 + 184 + file 185 + .command("delete") 186 + .aliases(["rm", "remove"]) 187 + .argument("<id>", "the ID of the file to delete") 188 + .description("delete a file") 189 + .action(deleteFile); 190 + 191 + program 192 + .command("ports") 193 + .argument("<sandbox>", "the sandbox to list exposed ports for") 194 + .description("list exposed ports for a sandbox") 195 + .action(listPorts); 114 196 115 197 const secret = program.command("secret").description("manage secrets"); 116 198
+11
apps/cli/src/theme.ts
··· 1 + import chalk from "chalk"; 2 + 3 + export const c = { 4 + primary: (s: string | number) => chalk.rgb(0, 232, 198)(s), 5 + secondary: (s: string | number) => chalk.rgb(0, 198, 232)(s), 6 + accent: (s: string | number) => chalk.rgb(130, 100, 255)(s), 7 + highlight: (s: string | number) => chalk.rgb(100, 232, 130)(s), 8 + muted: (s: string | number) => chalk.rgb(200, 210, 220)(s), 9 + link: (s: string | number) => chalk.rgb(255, 160, 100)(s), 10 + sky: (s: string | number) => chalk.rgb(0, 210, 255)(s), 11 + };
+5
apps/cli/src/types/port.ts
··· 1 + export type Port = { 2 + port: number; 3 + description?: string; 4 + previewUrl?: string; 5 + };