CrowNest
TypeScript SDK

Commands

Run, start, cancel, and stream logs for commands with client.commands in the TypeScript SDK.

client.commands runs processes inside a sandbox and tracks each one as a Command record with status, output, and logs. run waits for the process to exit; start returns immediately so you can stream logs while it runs.

[!NOTE] A non-zero exit code is not an error. run resolves normally; inspect command.exitCode and command.status. See Errors.

run

Execute a command and wait for it to exit. The resolved Command includes exitCode, stdout, and stderr.

run(
  sandboxId: `sbx_${string}`,
  command: string,
  input?: RunCommandOptions,
): Promise<Command>

RunCommandOptions:

FieldTypeDefaultDescription
cwdstring/workspaceWorking directory for the process.
envRecord<string, string>Extra environment variables. Keys with the reserved CROWNEST prefix are rejected (reserved_env_key). Values are ephemeral and never persisted.
timeoutMsnumber60000Maximum runtime in milliseconds (maximum 600000).
collect{ path: string; name?: string }[]Files to export as artifacts after the command finishes. Requires the artifact:create scope.
collectOn"success" | "always""success"When to collect: only on exit code 0, or always.
idempotencyKeystringSDK-generatedIdempotency key with a 24-hour retry window.
const result = await client.commands.run(sandbox.id, "pytest -q", {
  cwd: "/workspace/app",
  timeoutMs: 120_000,
  collect: [{ path: "report.xml", name: "test-report" }],
  collectOn: "always",
});

console.log(result.status, result.exitCode);
console.log(result.stdout);

When collect is set, the command's collectStatus reports the outcome (succeeded, partial, failed, skipped, pending, or not_requested), with per-file details in collectErrors.

Error codes: invalid_request, forbidden, not_found, sandbox_destroyed, quota_exceeded, command_timed_out.

start

Start a command without waiting for it to exit. The returned Command has status queued or starting; follow it with get or streamLogs. start doesn't accept collect or collectOn.

start(
  sandboxId: `sbx_${string}`,
  command: string,
  input?: Omit<RunCommandOptions, "collect" | "collectOn">,
): Promise<Command>
const cmd = await client.commands.start(sandbox.id, "python server.py");

get

Fetch the current state of a command.

get(commandId: `cmd_${string}`): Promise<Command>
const cmd = await client.commands.get("cmd_abc123");
console.log(cmd.status, cmd.exitCode);

cancel

Cancel a running command. Graceful mode sends SIGTERM; force mode sends SIGKILL. The operation is idempotent by state.

cancel(
  commandId: `cmd_${string}`,
  input?: { mode?: "graceful" | "force" },
): Promise<Command>
FieldTypeDefaultDescription
mode"graceful" | "force""graceful"How to terminate the process.
const canceled = await client.commands.cancel(cmd.id, { mode: "force" });
console.log(canceled.status); // "canceled"

logs

Fetch stored log chunks for a command.

logs(
  commandId: `cmd_${string}`,
  input?: { afterSeq?: number; limit?: number },
): Promise<readonly CommandLogChunk[]>
FieldTypeDefaultDescription
afterSeqnumberReturn chunks after this sequence number.
limitnumber50Maximum chunks to return (maximum 100).

Each CommandLogChunk is { commandId, createdAt, data, seq, stream: "stdout" | "stderr" }.

const chunks = await client.commands.logs(cmd.id, { limit: 100 });
for (const chunk of chunks) {
  process.stdout.write(chunk.data);
}

streamLogs

Stream logs live over server-sent events as an async iterable. The stream ends with a terminal event carrying the final command state.

streamLogs(
  commandId: `cmd_${string}`,
  input?: { afterSeq?: number },
): AsyncIterable<CommandLogStreamEvent>

CommandLogStreamEvent is a union:

EventShape
log{ type: "log", stream: "stdout" | "stderr", data, seq, createdAt }
heartbeat{ type: "heartbeat" }
terminal{ type: "terminal", command } — final Command state
error{ type: "error", code, message }

A typical streaming loop starts a command, prints log data as it arrives, and reads the final state from the terminal event:

const cmd = await client.commands.start(sandbox.id, "npm test");

for await (const event of client.commands.streamLogs(cmd.id)) {
  if (event.type === "log") process.stdout.write(event.data);
  if (event.type === "error") throw new Error(event.message);
  if (event.type === "terminal") {
    console.log("exit code:", event.command.exitCode);
    break;
  }
}

Pass afterSeq to resume after the last sequence number you've seen. If the requested sequence is outside the retention window, the stream emits an error event with code stream_gap.

Command object

The Command type includes id (cmd_ prefix), sandboxId, command, cwd, env, status (queued, starting, running, exited, failed, canceled, timed_out, killed), exitCode, stdout, stderr, stdoutTruncated, stderrTruncated, startedAt, finishedAt, durationMs, collectStatus, collectErrors, cancelMode, canceledAt, killedReason (sandbox_destroyed), and terminationSignal (SIGTERM or SIGKILL).

Next steps

  • Files — write inputs and read outputs.
  • Artifacts — keep command outputs after the sandbox is gone.
  • Errors — error codes and retry guidance.

On this page