SDK reference

The VibeOS SDK

Sandboxed apps reach the operating system through one global object: window.vos. It gives you a permission-scoped filesystem, hosted AI, a proxied network client and app metadata — no keys, no imports, no build step.

window.vos4 namespaces11 methodsfs.watch · fs.rename · fs.deleteDir

What the SDK is

When VibeOS opens a .vibe app, your HTML document runs inside a sandboxed iframe with no direct access to the desktop, other apps, or the network. Before your scripts run, the host injects a single global object — window.vos — which acts as the only door out of the sandbox.

  • Nothing to install. No npm package, no <script src>. The object simply exists at runtime.
  • Everything is a Promise. Each call is a message to the host and back, so results are always awaited.
  • Everything is permission-scoped. What your app may touch is decided by the permissions in your manifest and approved by the user at install time.

Booting your app

The SDK is injected asynchronously, and VibeOS fires a one-shot vos-ready event on window once window.vos is available. Because your script may run before or after that moment, always use the defensive pattern below rather than a bare event listener.

html
<script>
  // window.vos is injected asynchronously. Two things can be true when your
  // script runs: the SDK is already there, or it isn't yet.
  // This pattern handles both — always use it.
  function start() {
    // Safe to call window.vos.* from here on.
    window.vos.app.getInfo().then(function (info) {
      document.title = info.name;
    });
  }

  if (window.vos) start();
  else window.addEventListener("vos-ready", start, { once: true });
</script>

A bare vos-ready listener can miss the event

vos-ready fires once and does not replay. If your listener is attached after it fired — which happens routinely — your app never starts. Check window.vos first, then fall back to the event.

javascript
// Every method returns a Promise, so async/await works normally.
async function start() {
  const info = await window.vos.app.getInfo();
  const files = await window.vos.fs.readDir(info.sandboxPath);
  console.log(info.name, "has", files.length, "files");
}
javascript
// Calls reject with a normal Error. Always handle rejection —
// a missing file, a denied path and an offline API all arrive this way.
try {
  const text = await window.vos.fs.readFile("home/Documents", "note.md");
} catch (err) {
  console.warn("read failed:", err.message);
}

API surface

Four namespaces, eleven methods. Each namespace has its own reference page with full signatures, return shapes, recipes and failure modes.

NamespaceWhat it doesRequires
vos.fsRead, write, delete, rename and watch files in the user's Home directory or your app's private sandbox.read
vos.aiHosted chat completions through the VibeOS AI gateway, plus an entitlement pre-check.ai: true
vos.netOutbound HTTPS requests proxied by the host, CORS-free and restricted to declared origins.network
vos.appMetadata about the running instance: name, id, sandbox path, granted permissions.Nothing

How calls work

Every method is a remote procedure call. The SDK posts a message to the host window, the host validates and performs the operation, then posts the result back and your Promise settles. Three consequences matter when you build:

  • Calls have a 30-second timeout. If the host does not answer within 30s the Promise rejects. Keep operations small and batch thoughtfully.
  • Permissions are re-checked every single call. The host reads the grant the user approved at install time from its own storage — never from the manifest inside your HTML. An app can never widen its own scope at runtime.
  • Errors arrive as plain messages. Rejections are a normal Error whose message describes the failure. See errors & limits for the full catalogue and the caveat about error codes.

Test everything in the browser IDE

The VibeOS IDE runs your app against a live SDK implementation with a JavaScript console and a file explorer, so you can call window.vos.* by hand and watch the filesystem change before you ever submit.

What's new

The SDK is versioned with the VibeOS desktop, and the host is backwards compatible — nothing has been renamed or removed. The most recent additions are all in the filesystem namespace:

AdditionWhy it matters
fs.deleteDir(path)Recursively delete a folder in one call instead of walking and deleting each file.
fs.rename(from, to) / fs.move(from, to)Native rename and move. Same-volume moves are metadata-only, so large files move instantly.
fs.onChange(cb)Subscribe to every filesystem change so your UI stays in sync when the user edits files outside your app.
fs.watch(path, cb, opts)Debounced, path-filtered watching — the recommended way to keep a document view live.

Migrating older apps

If your app implemented rename by read-write-delete, or polled for external edits, replace those with fs.rename and fs.watch. The old patterns still work, so migration is optional and can be done incrementally.

TypeScript types

Apps ship as a single inline HTML document, so most are plain JavaScript. If you author in TypeScript and compile to one file, these declarations describe the whole runtime surface.

typescript
// Optional: drop this into your app for editor autocomplete.
// The runtime object is injected by VibeOS — you never import anything.
type VosEntry = { name: string; type: string };

interface Vos {
  fs: {
    readDir(path: string): Promise<VosEntry[]>;
    readFile(path: string, name: string): Promise<string | null>;
    writeFile(path: string, name: string, content: string): Promise<boolean>;
    deleteFile(path: string, name: string): Promise<boolean>;
    deleteDir(path: string): Promise<boolean>;
    rename(fromPath: string, toPath: string): Promise<boolean>;
    move(fromPath: string, toPath: string): Promise<boolean>;
    onChange(cb: (d: { affectedPath?: string }) => void): () => void;
    watch(
      path: string,
      cb: (d: { affectedPath?: string }) => void,
      opts?: { debounceMs?: number },
    ): () => void;
  };
  ai: {
    chat(messages: { role: "system" | "user" | "assistant"; content: string }[]): Promise<string>;
    canUse(category?: string): Promise<{ ok: boolean; code?: string; message?: string }>;
  };
  net: {
    fetch(url: string, init?: { method?: string; headers?: Record<string, string>; body?: string }):
      Promise<{ status: number; statusText: string; headers: Record<string, string>; body?: string; bodyBase64?: string }>;
  };
  app: {
    getInfo(): Promise<{
      name: string;
      id: string;
      closeBehavior: "close" | "minimize";
      sandboxPath: string;
      permissions: { hasRead: boolean; hasWrite: boolean; hasAi: boolean; hasNet: boolean; network: string[] };
    }>;
  };
}

declare global {
  interface Window { vos: Vos }
}

Next steps