> ## Documentation Index
> Fetch the complete documentation index at: https://namespace.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Started with the Devbox SDK

> Install the TypeScript SDK, create a client, and create and operate Devboxes programmatically.

export const KeepTabPosition = () => {
  useEffect(() => {
    const stateKey = "__namespaceKeepTabPosition";
    const releaseState = state => {
      state.instances -= 1;
      if (state.instances > 0) return;
      state.removeListeners();
      if (window[stateKey] === state) delete window[stateKey];
    };
    const existingState = window[stateKey];
    if (existingState) {
      existingState.instances += 1;
      return () => releaseState(existingState);
    }
    const maxSettleTime = 250;
    const requiredStableFrames = 3;
    const positionTolerance = 0.5;
    let animationFrame;
    let observer;
    let settleDeadline;
    let stableFrames = 0;
    let tabListToKeep;
    let tabListTop;
    const findTab = event => {
      if (!(event.target instanceof Element)) return null;
      return event.target.closest(".tab-container [role='tab']");
    };
    const findScrollContainer = element => {
      for (let parent = element.parentElement; parent; parent = parent.parentElement) {
        const {overflowY} = getComputedStyle(parent);
        if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
          return parent;
        }
      }
      return null;
    };
    const stopKeepingPosition = () => {
      if (animationFrame) cancelAnimationFrame(animationFrame);
      animationFrame = undefined;
      observer?.disconnect();
      tabListToKeep = undefined;
    };
    const restoreTabListPosition = () => {
      if (!tabListToKeep?.isConnected) return 0;
      const offset = tabListToKeep.getBoundingClientRect().top - tabListTop;
      if (Math.abs(offset) < positionTolerance) return offset;
      const scrollOptions = {
        top: offset,
        behavior: "instant"
      };
      const scrollContainer = findScrollContainer(tabListToKeep);
      if (scrollContainer) {
        scrollContainer.scrollBy(scrollOptions);
      } else {
        window.scrollBy(scrollOptions);
      }
      return offset;
    };
    const settlePosition = () => {
      const offset = restoreTabListPosition();
      stableFrames = Math.abs(offset) < positionTolerance ? stableFrames + 1 : 0;
      if (stableFrames >= requiredStableFrames || performance.now() >= settleDeadline) {
        stopKeepingPosition();
        return;
      }
      animationFrame = requestAnimationFrame(settlePosition);
    };
    observer = new MutationObserver(() => {
      stableFrames = 0;
      restoreTabListPosition();
    });
    const keepTabListInPlace = tab => {
      stopKeepingPosition();
      tabListToKeep = tab.closest("[role='tablist']");
      if (!tabListToKeep) return;
      tabListTop = tabListToKeep.getBoundingClientRect().top;
      settleDeadline = performance.now() + maxSettleTime;
      stableFrames = 0;
      observer.observe(document.getElementById("content") ?? document.documentElement, {
        attributes: true,
        attributeFilter: ["aria-selected", "class"],
        childList: true,
        subtree: true
      });
      animationFrame = requestAnimationFrame(settlePosition);
    };
    const selectWithoutNavigation = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      if (!tab) return;
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tab.click();
      restoreTabListPosition();
    };
    const selectWithKeyboard = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      const tabList = tab?.closest("[role='tablist']");
      if (!tab || !tabList) return;
      const tabs = Array.from(tabList.querySelectorAll(":scope > [role='tab']"));
      const currentIndex = tabs.indexOf(tab);
      let nextIndex;
      switch (event.key) {
        case "ArrowLeft":
          nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
          break;
        case "ArrowRight":
          nextIndex = (currentIndex + 1) % tabs.length;
          break;
        case "Home":
          nextIndex = 0;
          break;
        case "End":
          nextIndex = tabs.length - 1;
          break;
        case "Enter":
        case " ":
          nextIndex = currentIndex;
          break;
        default:
          return;
      }
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tabs[nextIndex]?.click();
      tabs[nextIndex]?.focus({
        preventScroll: true
      });
      restoreTabListPosition();
    };
    document.addEventListener("click", selectWithoutNavigation, true);
    document.addEventListener("keydown", selectWithKeyboard, true);
    const state = {
      instances: 1,
      removeListeners: () => {
        stopKeepingPosition();
        document.removeEventListener("click", selectWithoutNavigation, true);
        document.removeEventListener("keydown", selectWithKeyboard, true);
      }
    };
    window[stateKey] = state;
    return () => releaseState(state);
  }, []);
  return null;
};

<KeepTabPosition />

Use the SDK when a program needs to create Devboxes and run work in them: test runners that fan out across machines, agent harnesses that need an isolated sandbox per task, or internal tooling that provisions environments for your team.

This page takes you from an empty project to a script that creates a Devbox and runs a command in it.

## Getting Started

<Steps titleSize="h3">
  <Step title="Authenticate with Namespace">
    The SDK has no login of its own. It reads the credential written by the Devbox CLI, so [install the Devbox CLI](/docs/devbox/cli#getting-started) and log in once:

    ```bash theme={null}
    devbox login
    ```

    For automated workloads, use [workload identity federation](/docs/reference/typescript-sdk/authentication#workload-identity-federation) instead. Applications running inside a Namespace workload already receive a credential and need no setup.
  </Step>

  <Step title="Install the SDK">
    Add the SDK to your project:

    <CodeGroup>
      ```bash npm theme={null}
      npm install @namespacelabs/sdk
      ```

      ```bash pnpm theme={null}
      pnpm add @namespacelabs/sdk
      ```

      ```bash Yarn theme={null}
      yarn add @namespacelabs/sdk
      ```
    </CodeGroup>

    The Devbox API lives at `@namespacelabs/sdk/devbox`. The package root re-exports the same API.
  </Step>

  <Step title="Create a client">
    The client holds the connection to Namespace. `createDevboxClient()` resolves the credential from step 1 automatically when `tokenSource` is omitted.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      import { createDevboxClient } from "@namespacelabs/sdk/devbox";

      const client = createDevboxClient();
      ```
    </CodeGroup>

    See [Create a client](/docs/reference/typescript-sdk/client) for configuration options.
  </Step>

  <Step title="Create a Devbox">
    `devboxes.create()` provisions a Devbox and returns a handle you operate on. Devboxes start by default, and the call waits for the Devbox to become ready.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const devbox = await client.devboxes.create({
        name: "my-devbox",
        imageName: "builtin:agents",
        // repository: "github.com/your-org/your-repo",
      });

      console.log(`Created Devbox: ${devbox.name}`);
      ```
    </CodeGroup>

    Setting `repository` checks out that repository in the Devbox, which requires a [connected GitHub organization](/docs/devbox#getting-started).

    <Tip>
      Use `devboxes.get()` to get a handle for a Devbox that already exists:

      <CodeGroup>
        ```typescript TypeScript theme={null}
        const devbox = await client.devboxes.get("my-devbox");
        ```
      </CodeGroup>
    </Tip>
  </Step>

  <Step title="Run a command">
    `devbox.exec()` runs a command in the Devbox and resolves once it finishes, giving you its output and exit code.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const result = await devbox.exec(["node", "--version"]);

      process.stdout.write(result.stdout);
      process.exitCode = result.exitCode;
      ```
    </CodeGroup>

    A non-zero exit code resolves normally rather than throwing, so check `exitCode` yourself.
  </Step>

  <Step title="Close the client">
    Close the client when you are finished with it to release its cached connections.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      client.close();
      ```
    </CodeGroup>

    Closing the client does not delete the Devbox. Call `devbox.delete()` to remove it, or create it with `ephemeral: true` so its instance and storage are discarded when it stops.
  </Step>
</Steps>

## The complete script

The steps above assemble into one file:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createDevboxClient } from "@namespacelabs/sdk/devbox";

  const client = createDevboxClient();

  const devbox = await client.devboxes.create({
    name: "my-devbox",
    imageName: "builtin:agents",
  });

  const result = await devbox.exec(["node", "--version"]);
  process.stdout.write(result.stdout);

  client.close();
  ```
</CodeGroup>

## Other ways to run commands

`exec()` covers most cases. Use one of these when it does not.

<Tabs>
  <Tab title="shell">
    `devbox.shell()` runs a script through the Devbox's configured shell. Use it for pipes, redirects, variable expansion, or chained commands.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const result = await devbox.shell("node --version | tee version.txt");

      process.stdout.write(result.stdout);
      process.exitCode = result.exitCode;
      ```
    </CodeGroup>
  </Tab>

  <Tab title="terminal">
    `devbox.terminal.open()` opens an interactive session. Use it for continuous input, REPLs, or programs that require a TTY.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const terminal = await devbox.terminal.open({ columns: 120, rows: 40 });
      terminal.onData((data) => process.stdout.write(data));
      terminal.write("node --version\n");

      terminal.close();
      ```
    </CodeGroup>
  </Tab>

  <Tab title="terminal (attached)">
    Attach your process's stdin and stdout to the session so the user types directly into the Devbox shell. The session runs until the remote shell ends, such as when the user types `exit`.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const terminal = await devbox.terminal.open({ columns: 120, rows: 40 });

      const forwardInput = (data: Buffer) => terminal.write(data);

      try {
        terminal.onData((data) => process.stdout.write(data));

        process.stdin.setRawMode(true);
        process.stdin.resume();
        process.stdin.on("data", forwardInput);

        const result = await terminal.wait();

        process.exitCode = result.exitCode ?? 1;
      } finally {
        process.stdin.off("data", forwardInput);
        process.stdin.pause();
        terminal.close();
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

See [Run commands](/docs/reference/typescript-sdk/commands) for when to choose `exec()` over `shell()`, and [Use interactive terminals](/docs/reference/typescript-sdk/terminals) for the full terminal interface.

## TypeScript SDK reference

The reference documents the complete SDK surface, starting with [installation and a first Devbox](/docs/reference/typescript-sdk).

<Columns cols={3}>
  <Card title="Devboxes" icon="container" href="/docs/reference/typescript-sdk/devboxes">
    Creation options and lifecycle operations.
  </Card>

  <Card title="Commands" icon="play" href="/docs/reference/typescript-sdk/commands">
    `exec()`, `shell()`, their options, and their results.
  </Card>

  <Card title="Authentication" icon="key" href="/docs/reference/typescript-sdk/authentication">
    Workload identity federation, local credentials, and token sources.
  </Card>
</Columns>

## Set this up with a coding agent

Hand your agent the prompt below instead of following the steps above. It installs the [Namespace devboxes agent skill](https://github.com/namespacelabs/agent-skills), which covers the CLI setup, then points the agent at the SDK reference so it writes against the current API.

You will need to complete the login in your browser when the agent reaches that step.

```text wrap Agent Prompt theme={null}
Install the Namespace devboxes agent skill, then use it to get me set up:

  npx skills add namespacelabs/agent-skills

Following that skill:

1. Install the Devbox CLI for my operating system, then confirm it works.
2. Log me in to Devbox, then verify that the login succeeded.
3. Read https://namespace.so/docs/reference/typescript-sdk.md and install
   @namespacelabs/sdk with this project's package manager.
4. Read https://namespace.so/docs/reference/typescript-sdk/devboxes.md and
   https://namespace.so/docs/reference/typescript-sdk/commands.md, then write a
   script that creates a Devbox named "first-devbox" from the "builtin:agents"
   image, runs `node --version` in it, prints the output, and closes the client.
5. Run the script and show me the output.

If a documentation link returns a 404, find the current page in
https://namespace.so/docs/llms.txt, an index of every documentation page.

Do not delete or stop any Devbox that already exists in my workspace.
```

To install the skill yourself, or to install it globally rather than per project, see [Agent Skills](/docs/devbox/agent-skills).

## Next steps

<Columns cols={3}>
  <Card title="Devbox CLI" icon="terminal" href="/docs/devbox/cli">
    Create and connect to Devboxes from your terminal.
  </Card>

  <Card title="Agent Skills" icon="bot" href="/docs/devbox/agent-skills">
    Let a coding agent create and drive Devboxes for you.
  </Card>

  <Card title="Blueprints" icon="layers" href="/docs/devbox/blueprint">
    Reusable Devbox configurations.
  </Card>
</Columns>
