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.

Use CLI theme colors, support piped secrets, fix user join

Replace scattered chalk usage with the centralized theme.c helpers and
add
an 'error' color. Allow secret values to be read from piped stdin in
putSecret (trim trailing newline) and add improved success/error
logging.
Fix SQL leftJoin to use sandboxes.userId when joining users.

+52 -40
+1 -1
apps/api/src/xrpc/io/pocketenv/secret/deleteSecret.ts
··· 22 22 .from(sandboxSecrets) 23 23 .leftJoin(secrets, eq(sandboxSecrets.secretId, secrets.id)) 24 24 .leftJoin(sandboxes, eq(sandboxSecrets.sandboxId, sandboxes.id)) 25 - .leftJoin(users, eq(sandboxSecrets.sandboxId, users.id)) 25 + .leftJoin(users, eq(sandboxes.userId, users.id)) 26 26 .where( 27 27 and( 28 28 or(
+3 -3
apps/cli/src/cmd/create.ts
··· 2 2 import { client } from "../client"; 3 3 import getAccessToken from "../lib/getAccessToken"; 4 4 import type { Sandbox } from "../types/sandbox"; 5 - import chalk from "chalk"; 6 5 import connectToSandbox from "./ssh"; 6 + import { c } from "../theme"; 7 7 8 8 async function createSandbox( 9 9 name: string, ··· 21 21 22 22 if (["deno", "vercel", "daytona"].includes(provider || "")) { 23 23 consola.error( 24 - `This Sandbox Runtime is temporarily disabled. ${chalk.greenBright(provider ?? "")}`, 24 + `This Sandbox Runtime is temporarily disabled. ${c.primary(provider ?? "")}`, 25 25 ); 26 26 process.exit(1); 27 27 } ··· 43 43 ); 44 44 if (!ssh) { 45 45 consola.success( 46 - `Sandbox created successfully: ${chalk.greenBright(sandbox.data.name)}`, 46 + `Sandbox created successfully: ${c.primary(sandbox.data.name)}`, 47 47 ); 48 48 return; 49 49 }
+8 -8
apps/cli/src/cmd/env.ts
··· 2 2 import getAccessToken from "../lib/getAccessToken"; 3 3 import type { Sandbox } from "../types/sandbox"; 4 4 import type { Variable } from "../types/variable"; 5 - import chalk from "chalk"; 6 5 import dayjs from "dayjs"; 7 6 import consola from "consola"; 8 7 import Table from "cli-table3"; 9 8 import { env } from "../lib/env"; 9 + import { c } from "../theme"; 10 10 11 11 export async function listEnvs(sandbox: string) { 12 12 const token = await getAccessToken(); ··· 23 23 ); 24 24 25 25 if (!data.sandbox) { 26 - consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 26 + consola.error(`Sandbox not found: ${c.primary(sandbox)}`); 27 27 process.exit(1); 28 28 } 29 29 ··· 43 43 44 44 const table = new Table({ 45 45 head: [ 46 - chalk.cyan("ID"), 47 - chalk.cyan("NAME"), 48 - chalk.cyan("VALUE"), 49 - chalk.cyan("CREATED AT"), 46 + c.primary("ID"), 47 + c.primary("NAME"), 48 + c.primary("VALUE"), 49 + c.primary("CREATED AT"), 50 50 ], 51 51 chars: { 52 52 top: "", ··· 73 73 74 74 for (const variable of response.data.variables) { 75 75 table.push([ 76 - chalk.greenBright(variable.id), 77 - chalk.greenBright(variable.name), 76 + c.secondary(variable.id), 77 + c.highlight(variable.name), 78 78 variable.value, 79 79 dayjs(variable.createdAt).fromNow(), 80 80 ]);
+3 -6
apps/cli/src/cmd/file.ts
··· 1 - import chalk from "chalk"; 2 1 import consola from "consola"; 3 2 import getAccessToken from "../lib/getAccessToken"; 4 3 import dayjs from "dayjs"; ··· 32 31 try { 33 32 await fs.access(resolvedPath); 34 33 } catch (err) { 35 - consola.error(`No such file: ${chalk.redBright(localPath)}`); 34 + consola.error(`No such file: ${c.error(localPath)}`); 36 35 process.exit(1); 37 36 } 38 37 content = await fs.readFile(resolvedPath, "utf-8"); ··· 63 62 ); 64 63 65 64 consola.success( 66 - `File ${chalk.rgb(0, 232, 198)(remotePath)} successfully created in sandbox ${chalk.rgb(0, 232, 198)(sandbox)}`, 65 + `File ${c.primary(remotePath)} successfully created in sandbox ${c.primary(sandbox)}`, 67 66 ); 68 67 } catch (error) { 69 68 consola.error(`Failed to create file: ${error}`); ··· 133 132 Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 134 133 }, 135 134 }); 136 - consola.success( 137 - `File ${chalk.rgb(0, 232, 198)(id)} successfully deleted from sandbox`, 138 - ); 135 + consola.success(`File ${c.primary(id)} successfully deleted from sandbox`); 139 136 } catch (error) { 140 137 consola.error(`Failed to delete file: ${error}`); 141 138 }
+30 -16
apps/cli/src/cmd/secret.ts
··· 79 79 80 80 export async function putSecret(sandbox: string, key: string) { 81 81 const token = await getAccessToken(); 82 - const value = await password({ message: "Enter secret value" }); 82 + const isStdinPiped = !process.stdin.isTTY; 83 + const value = isStdinPiped 84 + ? await new Promise<string>((resolve) => { 85 + let data = ""; 86 + process.stdin.setEncoding("utf8"); 87 + process.stdin.on("data", (chunk) => (data += chunk)); 88 + process.stdin.on("end", () => resolve(data.trimEnd())); 89 + }) 90 + : await password({ message: "Enter secret value" }); 83 91 84 92 const { data } = await client.get("/xrpc/io.pocketenv.sandbox.getSandbox", { 85 93 params: { ··· 95 103 process.exit(1); 96 104 } 97 105 98 - await client.post( 99 - "/xrpc/io.pocketenv.secret.addSecret", 100 - { 101 - secret: { 102 - sandboxId: data.sandbox.id, 103 - name: key, 104 - value: await encrypt(value), 106 + try { 107 + await client.post( 108 + "/xrpc/io.pocketenv.secret.addSecret", 109 + { 110 + secret: { 111 + sandboxId: data.sandbox.id, 112 + name: key, 113 + value: await encrypt(value), 114 + }, 105 115 }, 106 - }, 107 - { 108 - headers: { 109 - Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 116 + { 117 + headers: { 118 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 119 + }, 110 120 }, 111 - }, 112 - ); 121 + ); 122 + 123 + consola.success("Secret added successfully"); 124 + } catch (error) { 125 + consola.error("Failed to add secret:", error); 126 + } 113 127 } 114 128 115 129 export async function deleteSecret(id: string) { ··· 126 140 }); 127 141 128 142 consola.success("Secret deleted successfully"); 129 - } catch { 130 - consola.error("Failed to delete secret"); 143 + } catch (error) { 144 + consola.error("Failed to delete secret:", error); 131 145 } 132 146 }
+6 -6
apps/cli/src/cmd/tailscale.ts
··· 3 3 import { client } from "../client"; 4 4 import type { Sandbox } from "../types/sandbox"; 5 5 import consola from "consola"; 6 - import chalk from "chalk"; 7 6 import type { TailscaleAuthKey } from "../types/tailscale-auth-key"; 8 7 import { env } from "../lib/env"; 9 8 import encrypt from "../lib/sodium"; 9 + import { c } from "../theme"; 10 10 11 11 export async function putAuthKey(sandbox: string) { 12 12 const token = await getAccessToken(); ··· 33 33 ); 34 34 35 35 if (!data.sandbox) { 36 - consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 36 + consola.error(`Sandbox not found: ${c.primary(sandbox)}`); 37 37 process.exit(1); 38 38 } 39 39 ··· 60 60 61 61 consola.success(redacted); 62 62 consola.success( 63 - `Tailscale auth key saved for sandbox: ${chalk.greenBright(sandbox)}`, 63 + `Tailscale auth key saved for sandbox: ${c.primary(sandbox)}`, 64 64 ); 65 65 } 66 66 ··· 80 80 ); 81 81 82 82 if (!data.sandbox) { 83 - consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 83 + consola.error(`Sandbox not found: ${c.primary(sandbox)}`); 84 84 process.exit(1); 85 85 } 86 86 ··· 96 96 }, 97 97 }, 98 98 ); 99 - consola.info(`Tailscale auth key: ${chalk.greenBright(tailscale.authKey)}`); 99 + consola.info(`Tailscale auth key: ${c.primary(tailscale.authKey)}`); 100 100 } catch { 101 101 consola.error( 102 - `No Tailscale Auth Key found for sandbox: ${chalk.greenBright(sandbox)}`, 102 + `No Tailscale Auth Key found for sandbox: ${c.primary(sandbox)}`, 103 103 ); 104 104 process.exit(1); 105 105 }
+1
apps/cli/src/theme.ts
··· 8 8 muted: (s: string | number) => chalk.rgb(200, 210, 220)(s), 9 9 link: (s: string | number) => chalk.rgb(255, 160, 100)(s), 10 10 sky: (s: string | number) => chalk.rgb(0, 210, 255)(s), 11 + error: (s: string | number) => chalk.rgb(255, 100, 100)(s), 11 12 };