@timely-sandbox/client
v0.1.1
Published
믿을 수 없는 코드를 격리된 환경에서 실행하는 클라이언트. 샌드박스를 띄우고 명령을 실행하고 파일을 주고받습니다. 의존성 없음, Node 20+.
Readme
@timely-sandbox/client
믿을 수 없는 코드를 격리된 환경에서 돌려야 할 때 쓰는 클라이언트입니다. 샌드박스를 띄우고, 명령을 실행하고, 파일을 주고받습니다.
AI 에이전트가 생성한 코드를 실행하거나, 사용자가 올린 스크립트를 처리하거나, 빌드·변환 작업을 서버 밖에서 돌리고 싶을 때 쓰기 좋습니다.
의존성이 없고 Node 20 이상이면 바로 동작합니다.
설치
npm i @timely-sandbox/client접속 정보는 운영자에게 요청하세요. 아래 세 가지를 받게 됩니다.
TSBX_URL=https://sbx.timelyai.io
TSBX_KEY_ID=...
TSBX_SECRET=...빠른 시작
import { ControllerClient, sandboxClientFor } from "@timely-sandbox/client";
const tsbx = new ControllerClient({
baseUrl: process.env.TSBX_URL!,
keyId: process.env.TSBX_KEY_ID!,
secret: process.env.TSBX_SECRET!,
});
// 샌드박스를 하나 만듭니다. labels 는 나중에 이 샌드박스를 다시 찾는 열쇠가 되니
// 서비스 쪽 식별자를 그대로 넣어 주세요.
const info = await tsbx.create({
image: "python:3.12-slim",
labels: { user: "u-42" },
});
// 준비될 때까지 기다립니다. 예비 풀에서 잡히면 즉시 반환되고,
// 새로 뜨는 경우에만 실제로 기다립니다.
const sbx = sandboxClientFor(info.url ? info : await tsbx.waitUntilReachable(info.id));
await sbx.writeFile("/workspace/hello.py", "print('hi')\n");
const run = await sbx.exec("python3 /workspace/hello.py");
console.log(run.stdout); // hi
console.log(run.exitCode); // 0
await tsbx.delete(info.id);알아 두면 좋은 것 두 가지
컨트롤러와 샌드박스는 서로 다른 곳입니다
샌드박스를 만들고 지우는 일은 컨트롤러(baseUrl)에 요청하고, 명령 실행과 파일
입출력은 샌드박스(info.url)에 직접 요청합니다. 큰 파일이 컨트롤러를 거치지 않게
하려는 구조라서, 두 요청은 서로 다른 키로 서명됩니다.
sandboxClientFor(info) 를 쓰면 이 부분은 알아서 처리되니 크게 신경 쓰지 않아도 됩니다.
다만 샌드박스 키(info.key)는 그 샌드박스 하나만 열 수 있는 키이고, 워크스페이스 키는
여러분의 모든 샌드박스를 다룰 수 있는 키입니다. 워크스페이스 키를 샌드박스 안으로
넘기는 일은 피해 주세요.
파일은 샌드박스보다 오래 남습니다
샌드박스는 일정 시간 요청이 없으면 자동으로 회수되지만, 작업 공간(볼륨)은 며칠 더 남아 있습니다. 사용자가 나중에 돌아왔을 때 파일이 그대로여야 한다면 라벨로 볼륨을 찾아서 다시 붙여 주세요.
async function open(userId: string) {
const labels = { user: userId };
// 아직 살아 있는 샌드박스가 있으면 그대로 씁니다.
const alive = (await tsbx.list(labels)).find((s) => s.state !== "terminating");
if (alive) return alive;
// 없으면 같은 라벨의 볼륨을 찾아 이어 붙입니다.
const [vol] = await tsbx.listVolumes(labels);
const info = await tsbx.create({
image: "python:3.12-slim",
labels,
...(vol ? { volume: vol.name } : {}),
});
return info.url ? info : tsbx.waitUntilReachable(info.id);
}볼륨 이름을 따로 저장해 둘 필요는 없습니다. 라벨만 있으면 언제든 찾을 수 있어요.
tsbx.delete(id) 는 볼륨을 남겨 둡니다. 사용자가 "전부 지워 달라"고 했을 때만
tsbx.delete(id, { purgeVolume: true }) 를 쓰세요.
주요 API
ControllerClient
| 메서드 | 설명 |
|---|---|
| create(spec) | 샌드박스 생성 (image, labels, volume, idleTtlSec, cpu, memory …) |
| list(labels?) | 내 워크스페이스의 샌드박스 목록 |
| get(id) / find(id) | 하나 조회 (find 는 없으면 null) |
| waitUntilReachable(id) | 파드가 뜰 때까지 대기 |
| patch(id, spec) | 유휴 시간 등 변경 |
| delete(id, opts?) | 삭제 ({ purgeVolume: true } 면 볼륨까지) |
| listVolumes(labels?) | 작업 공간 목록 |
| deleteVolume(name) | 작업 공간 삭제 |
SandboxClient (sandboxClientFor(info) 로 만듭니다)
| 메서드 | 설명 |
|---|---|
| exec(cmd, opts?) | 명령 실행 후 { stdout, stderr, exitCode } |
| execStream(cmd, opts?) | 출력을 실시간으로 받는 async iterator |
| writeFile(path, data) / readFile(path) / readText(path) | 파일 입출력 |
| list(path, opts?) / stat(path) / exists(path) | 파일 조회 |
| makeDir(path) / remove(path, opts?) | 디렉터리 만들기·지우기 |
| startProcess(opts) / stopProcess(name) | 오래 도는 프로세스 |
| fetchPort(port, path, init?) | 샌드박스 안에서 열린 포트로 HTTP 요청 |
| health() | 마지막 활동 시각, 메모리 감시 기록 |
CLI
패키지에 CLI 도 들어 있습니다. 환경 변수는 위와 같습니다.
npx tsbx ls # 샌드박스 목록
npx tsbx create python:3.12-slim --label user=u-42
npx tsbx exec <id> echo hello
npx tsbx rm <id> # 볼륨은 유지
npx tsbx rm <id> --purge-volume
npx tsbx --helpTypeScript
응답 타입은 모두 함께 내보냅니다. 별도 @types 패키지는 필요하지 않습니다.
import type { SandboxInfo, VolumeInfo, CreateSandboxRequest } from "@timely-sandbox/client";다른 언어를 쓰신다면 요청 서명 방식만 옮기면 됩니다. HMAC-SHA256 한 번이고, 온보딩 문서에 파이썬과 curl 예제가 있습니다.
더 읽을 것
라이선스
MIT
