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.

Validate sandbox and add Tailscale/SSH handling

Exit early when a sandbox is not found in CLI commands.

Add Tailscale auth key put/get with validation, redaction, and
encryption. Add SSH keys get/put handling with improved output and
redaction.

+157 -3
+6
apps/cli/src/cmd/env.ts
··· 21 21 }, 22 22 }, 23 23 ); 24 + 25 + if (!data.sandbox) { 26 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 27 + process.exit(1); 28 + } 29 + 24 30 const response = await client.get<{ variables: Variable[] }>( 25 31 "/xrpc/io.pocketenv.variable.getVariables", 26 32 {
+5
apps/cli/src/cmd/secret.ts
··· 88 88 }, 89 89 }); 90 90 91 + if (!data.sandbox) { 92 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 93 + process.exit(1); 94 + } 95 + 91 96 await client.post( 92 97 "/xrpc/io.pocketenv.secret.addSecret", 93 98 {
+49 -2
apps/cli/src/cmd/sshkeys.ts
··· 7 7 import { client } from "../client"; 8 8 import type { Sandbox } from "../types/sandbox"; 9 9 import { env } from "../lib/env"; 10 + import type { SshKeys } from "../types/sshkeys"; 11 + import chalk from "chalk"; 10 12 11 13 export async function getSshKey(sandbox: string) { 12 14 const token = await getAccessToken(); 15 + 16 + const { data } = await client.get<{ sandbox: Sandbox }>( 17 + "/xrpc/io.pocketenv.sandbox.getSandbox", 18 + { 19 + params: { 20 + id: sandbox, 21 + }, 22 + headers: { 23 + Authorization: `Bearer ${token}`, 24 + }, 25 + }, 26 + ); 27 + 28 + if (!data.sandbox) { 29 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 30 + process.exit(1); 31 + } 32 + 33 + try { 34 + const { data: sshKeys } = await client.get<SshKeys>( 35 + "/xrpc/io.pocketenv.sandbox.getSshKeys", 36 + { 37 + params: { 38 + id: data.sandbox.id, 39 + }, 40 + headers: { 41 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 42 + }, 43 + }, 44 + ); 45 + 46 + consola.log("\nPrivate Key:"); 47 + consola.log(sshKeys.privateKey.replace(/\\n/g, "\n")); 48 + consola.log("\nPublic Key:"); 49 + consola.log(sshKeys.publicKey, "\n"); 50 + } catch (error) { 51 + consola.info( 52 + `No SSH keys found for this sandbox.\n Create one with ${chalk.greenBright(`pocketenv sshkeys put ${sandbox} --generate`)}.`, 53 + ); 54 + } 13 55 } 14 56 15 57 export async function putKeys( ··· 78 120 }, 79 121 ); 80 122 123 + if (!data.sandbox) { 124 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 125 + process.exit(1); 126 + } 127 + 81 128 const encryptedPrivateKey = await encrypt(privateKey); 82 129 83 130 const redacted = (() => { ··· 120 167 }, 121 168 ); 122 169 123 - consola.log("\nPrivate Key:\n"); 170 + consola.log("\nPrivate Key:"); 124 171 consola.log(redacted.replace(/\\n/g, "\n")); 125 - consola.log("\nPublic Key:\n"); 172 + consola.log("\nPublic Key:"); 126 173 consola.log(publicKey, "\n"); 127 174 128 175 consola.success("SSH keys saved successfully!");
+97 -1
apps/cli/src/cmd/tailscale.ts
··· 1 - import { input } from "@inquirer/prompts"; 1 + import { password } from "@inquirer/prompts"; 2 2 import getAccessToken from "../lib/getAccessToken"; 3 + import { client } from "../client"; 4 + import type { Sandbox } from "../types/sandbox"; 5 + import consola from "consola"; 6 + import chalk from "chalk"; 7 + import type { TailscaleAuthKey } from "../types/tailscale-auth-key"; 8 + import { env } from "../lib/env"; 9 + import encrypt from "../lib/sodium"; 3 10 4 11 export async function putAuthKey(sandbox: string) { 5 12 const token = await getAccessToken(); 13 + 14 + const authKey = ( 15 + await password({ message: "Enter Tailscale Auth Key" }) 16 + ).trim(); 17 + 18 + if (!authKey.startsWith("tskey-auth-")) { 19 + consola.error("Invalid Tailscale Auth Key"); 20 + process.exit(1); 21 + } 22 + 23 + const { data } = await client.get<{ sandbox: Sandbox }>( 24 + "/xrpc/io.pocketenv.sandbox.getSandbox", 25 + { 26 + params: { 27 + id: sandbox, 28 + }, 29 + headers: { 30 + Authorization: `Bearer ${token}`, 31 + }, 32 + }, 33 + ); 34 + 35 + if (!data.sandbox) { 36 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 37 + process.exit(1); 38 + } 39 + 40 + const redacted = 41 + authKey.length > 14 42 + ? authKey.slice(0, 11) + 43 + "*".repeat(authKey.length - 14) + 44 + authKey.slice(-3) 45 + : authKey; 46 + 47 + await client.post( 48 + "/xrpc/io.pocketenv.sandbox.putTailscaleAuthKey", 49 + { 50 + id: data.sandbox.id, 51 + authKey: await encrypt(authKey), 52 + redacted, 53 + }, 54 + { 55 + headers: { 56 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 57 + }, 58 + }, 59 + ); 60 + 61 + consola.success(redacted); 62 + consola.success( 63 + `Tailscale auth key saved for sandbox: ${chalk.greenBright(sandbox)}`, 64 + ); 6 65 } 7 66 8 67 export async function getTailscaleAuthKey(sandbox: string) { 9 68 const token = await getAccessToken(); 69 + 70 + const { data } = await client.get<{ sandbox: Sandbox }>( 71 + "/xrpc/io.pocketenv.sandbox.getSandbox", 72 + { 73 + params: { 74 + id: sandbox, 75 + }, 76 + headers: { 77 + Authorization: `Bearer ${token}`, 78 + }, 79 + }, 80 + ); 81 + 82 + if (!data.sandbox) { 83 + consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`); 84 + process.exit(1); 85 + } 86 + 87 + try { 88 + const { data: tailscale } = await client.get<TailscaleAuthKey>( 89 + "/xrpc/io.pocketenv.sandbox.getTailscaleAuthKey", 90 + { 91 + params: { 92 + id: data.sandbox.id, 93 + }, 94 + headers: { 95 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 96 + }, 97 + }, 98 + ); 99 + consola.info(`Tailscale auth key: ${chalk.greenBright(tailscale.authKey)}`); 100 + } catch { 101 + consola.error( 102 + `No Tailscale Auth Key found for sandbox: ${chalk.greenBright(sandbox)}`, 103 + ); 104 + process.exit(1); 105 + } 10 106 }