TypeScript client
Use pols from Node.js with the typed TypeScript client, generated from the same OpenAPI spec as the API.
@zuschaua/pols-client is a typed client for the pols API. Its types are generated from the API’s OpenAPI spec, so they match the API exactly; on top of them it adds helpers for waiting, streaming and computer use.
Install
The package is published on GitHub Packages. Point the @zuschaua scope at it in your project’s .npmrc, then install it:
@zuschaua:registry=https://npm.pkg.github.com
npm install @zuschaua/pols-client
GitHub Packages asks npm to authenticate even for reading; log in with a GitHub token that may read packages (npm login --scope=@zuschaua --registry=https://npm.pkg.github.com). If npm still cannot find the package, write to team@peweo.com for access.
Create, run, clean up
import { createPolsClient } from "@zuschaua/pols-client";
const pols = createPolsClient({ apiKey: process.env.POLS_API_KEY! });
let sb = await pols.sandboxes.create({
name: "ci-run",
size: "large",
env: { NODE_ENV: "test" },
secrets: ["GITHUB_TOKEN"], // vault entries, set as environment variables
});
sb = await pols.sandboxes.waitFor(sb); // until it runs
for await (const ev of pols.execStream(sb.id, { command: ["bash", "-lc", "npm test"] })) {
if (ev.type === "stdout") process.stdout.write(ev.data ?? "");
if (ev.type === "stderr") process.stderr.write(ev.data ?? "");
if (ev.type === "exit") console.log("exit code", ev.exit_code);
}
await pols.files.write(sb.id, "/home/user/notes.md", "# notes");
const bytes = await pols.files.read(sb.id, "/home/user/notes.md");
await pols.sandboxes.waitFor(await pols.sandboxes.stop(sb.id));
Lifecycle calls return as soon as the API accepted them; waitFor polls until the sandbox reached its desired state.
What the client covers
| Area | Calls |
|---|---|
| Sandboxes | pols.sandboxes.list, .get, .create, .fork, .stop, .resume, .delete, .stats, .waitFor |
| Commands | pols.exec (collected output), pols.execStream (streamed events) |
| Files | pols.files.read, pols.files.write |
| Templates | pols.templates.list, .get, .create, .delete |
| Vault | pols.vault.list |
| Org | pols.org, pols.orgStats, pols.usage, pols.apiKeys.list |
| Computer use | pols.computer.screenshot, .click, .doubleClick, .drag, .type, .key, .scroll, .action; pols.browser.cdp |
| Access | pols.ports.list, .publish, .unpublish, .link; pols.desktop.link; pols.sshKeys.list, .create, .delete; pols.ssh |
For anything without a helper, pols.raw is the typed openapi-fetch client for every endpoint.
createPolsClient takes the apiKey, and optionally a baseUrl (default https://api.pols.so) and a custom fetch. It refuses a plain http:// base URL other than localhost unless you set allowInsecureHttp. waitFor gives up after 5 minutes unless you pass timeoutMs, and throws when the sandbox ends in error.
Errors
Failed calls throw a PolsError with the HTTP status and the API’s stable error code, such as quota_exceeded or not_found. See Errors for what each code means.
Computer use and the browser
import { chromium } from "playwright";
const png = await pols.computer.screenshot(sb.id); // Uint8Array, a PNG
await pols.computer.click(sb.id, 960, 540);
await pols.computer.type(sb.id, "hello");
const { websocket_url } = await pols.browser.cdp(sb.id); // connect within 5 minutes
const browser = await chromium.connectOverCDP(websocket_url);
See Computer use.