Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,34 @@ export BUDDY_PROJECT="your-project"
export BUDDY_REGION="US" # Optional: US (default), EU, or AP
```

## Apps

Sandboxes can run multiple apps simultaneously. Each app is a long-running process defined by a command string.

```typescript
const sandbox = await Sandbox.create({
identifier: "my-sandbox",
name: "My Sandbox",
os: "ubuntu:24.04",
first_boot_commands: "apt-get update && apt-get install -y curl",
apps: ["node server.js", "python worker.py"],
});

// List apps
for (const app of sandbox.data.apps ?? []) {
console.log(`${app.id}: "${app.command}" -> ${app.app_status}`);
}

// Control individual apps
const appId = sandbox.data.apps![0].id!;

await sandbox.stopApp(appId);
await sandbox.startApp(appId);

const { logs } = await sandbox.getAppLogs(appId);
console.log(logs);
```

## Regions

Configure the API region:
Expand Down
3 changes: 2 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"example:lifecycle": "NODE_OPTIONS='--enable-source-maps' dotenvx run -q -- tsx src/lifecycle.ts",
"example:streaming": "NODE_OPTIONS='--enable-source-maps' dotenvx run -q -- tsx src/streaming.ts",
"example:filesystem": "NODE_OPTIONS='--enable-source-maps' dotenvx run -q -- tsx src/filesystem.ts",
"example:apps": "NODE_OPTIONS='--enable-source-maps' dotenvx run -q -- tsx src/apps.ts",
"example:error-handling": "NODE_OPTIONS='--enable-source-maps' dotenvx run -q -- tsx src/error-handling.ts",
"example:all": "concurrently \"pnpm example:ping\" \"pnpm example:list\" \"pnpm example:lifecycle\" \"pnpm example:streaming\" \"pnpm example:filesystem\" \"pnpm example:error-handling\""
"example:all": "concurrently \"pnpm example:ping\" \"pnpm example:list\" \"pnpm example:lifecycle\" \"pnpm example:streaming\" \"pnpm example:filesystem\" \"pnpm example:apps\" \"pnpm example:error-handling\""
},
"keywords": [],
"author": "",
Expand Down
70 changes: 70 additions & 0 deletions examples/src/apps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Sandbox } from "@buddy-works/sandbox-sdk";
import { log } from "@/shared/logger";

log("Sandbox Apps Example\n");

const identifier = "apps-demo-sandbox";

let sandbox: Sandbox;

try {
sandbox = await Sandbox.getByIdentifier(identifier);
log(`Found existing sandbox with identifier: ${identifier}, deleting...`);
await sandbox.destroy();
} catch {
// Sandbox doesn't exist, nothing to delete
}

log(`Creating sandbox with 2 apps...`);
sandbox = await Sandbox.create({
identifier,
name: "Apps Demo Sandbox",
os: "ubuntu:24.04",
first_boot_commands: "apt-get update && apt-get install -y curl",
apps: [
'while true; do echo "[app1] ping $(date)"; sleep 2; done',
'while true; do echo "[app2] pong $(date)"; sleep 3; done',
],
});
log(`Created sandbox: ${sandbox.data.identifier} (${sandbox.data.html_url})`);
log(`Status: ${sandbox.data.status}, Setup: ${sandbox.data.setup_status}\n`);

log("Apps:");
for (const app of sandbox.data.apps ?? []) {
log(` ${app.id}: "${app.command}" -> ${app.app_status}`);
}

const firstApp = sandbox.data.apps?.[0];
const secondApp = sandbox.data.apps?.[1];

if (!firstApp?.id || !secondApp?.id) {
throw new Error("Expected 2 apps in sandbox");
}

log(`\nStopping the first app (${firstApp.id})...`);
await sandbox.stopApp(firstApp.id);
log("Apps after stopping the first one:");
for (const app of sandbox.data.apps ?? []) {
log(` ${app.id}: ${app.app_status}`);
}

log(`\nStarting the first app (${firstApp.id})...`);
await sandbox.startApp(firstApp.id);
log("Apps after starting the first one:");
for (const app of sandbox.data.apps ?? []) {
log(` ${app.id}: ${app.app_status}`);
}

log("\nWaiting a few seconds for log output...");
await new Promise((resolve) => setTimeout(resolve, 5000));

log(`Getting logs for the second app (${secondApp.id})...`);
const logs = await sandbox.getAppLogs(secondApp.id);
log(`Logs (${logs.logs?.length ?? 0} entries):`);
for (const entry of logs.logs ?? []) {
log(` ${entry}`);
}

log("\nCleaning up...");
await sandbox.destroy();
log("Sandbox deleted successfully");
67 changes: 67 additions & 0 deletions src/api/openapi/transformers.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@ import type {
AddSandboxResponse,
AddSandboxSnapshotResponse,
CreateSandboxDirectoryResponse,
ExecuteSandboxCommandResponse,
GetIntegrationResponse,
GetIntegrationsResponse,
GetProjectSnapshotsResponse,
GetSandboxCommandResponse,
GetSandboxCommandsResponse,
GetSandboxContentResponse,
GetSandboxResponse,
GetSandboxSnapshotResponse,
GetSandboxSnapshotsResponse,
GetWorkspaceMemberProjectsResponse,
GetWorkspaceResponse,
RestartSandboxResponse,
StartSandboxAppResponse,
StartSandboxResponse,
StopSandboxAppResponse,
StopSandboxResponse,
TerminateSandboxCommandResponse,
UpdateIntegrationResponse,
UpdateSandboxByYamlResponse,
UpdateSandboxResponse,
Expand Down Expand Up @@ -164,6 +170,67 @@ export const updateSandboxResponseTransformer = async (
return data;
};

export const startSandboxAppResponseTransformer = async (
data: any,
): Promise<StartSandboxAppResponse> => {
data = sandboxResponseSchemaResponseTransformer(data);
return data;
};

export const stopSandboxAppResponseTransformer = async (
data: any,
): Promise<StopSandboxAppResponse> => {
data = sandboxResponseSchemaResponseTransformer(data);
return data;
};

const sandboxCommandViewSchemaResponseTransformer = (data: any) => {
if (data.start_date) {
data.start_date = new Date(data.start_date);
}
if (data.finish_date) {
data.finish_date = new Date(data.finish_date);
}
return data;
};

const sandboxCommandsViewSchemaResponseTransformer = (data: any) => {
if (data.commands) {
data.commands = data.commands.map((item: any) =>
sandboxCommandViewSchemaResponseTransformer(item),
);
}
return data;
};

export const getSandboxCommandsResponseTransformer = async (
data: any,
): Promise<GetSandboxCommandsResponse> => {
data = sandboxCommandsViewSchemaResponseTransformer(data);
return data;
};

export const executeSandboxCommandResponseTransformer = async (
data: any,
): Promise<ExecuteSandboxCommandResponse> => {
data = sandboxCommandViewSchemaResponseTransformer(data);
return data;
};

export const getSandboxCommandResponseTransformer = async (
data: any,
): Promise<GetSandboxCommandResponse> => {
data = sandboxCommandViewSchemaResponseTransformer(data);
return data;
};

export const terminateSandboxCommandResponseTransformer = async (
data: any,
): Promise<TerminateSandboxCommandResponse> => {
data = sandboxCommandViewSchemaResponseTransformer(data);
return data;
};

const sandboxContentItemSchemaResponseTransformer = (data: any) => {
if (data.size) {
data.size = BigInt(data.size.toString());
Expand Down
Loading