Examples

Build an AI chatbot

A complete, working .vibe app that talks to a language model through the VibeOS AI gateway and remembers every conversation. It focuses on the two VibeOS integrations that make it possible: window.vos.ai for inference and window.vos.fs for persistence.

This walkthrough builds a polished chat UI that sends messages to a model with window.vos.ai.chat() — no API keys, no backend — and saves the full thread to the app's private sandbox so it survives restarts. Grab the finished template first, then read how each VibeOS piece works.

Use it as a template

SimpleAIChatbot.vibe is a ready-to-submit manifest. Open it in any editor, change the name, iconEmoji, the SYSTEM prompt and the inline html, then paste it into the submit form to create your own draft.

What you'll build

  • A single-window chat app with a composer, message stream and typing indicator.
  • Model replies via the VibeOS AI gateway — window.vos.ai.chat().
  • Persistent history saved to the app's private sandbox via window.vos.fs.
  • A manifest that requests exactly two capabilities: AI and sandbox storage.

Step 1 — The manifest

Everything ships in one JSON object. The metadata describes the listing; the html field holds the entire app inline. Note the iconImage base64 data URI — it renders the custom logo in App Center and on the VibeOS desktop. Here's the shell (the real html is the full document from the download):

json
{
  "vibe": 1,
  "name": "Simple AI Chatbot",
  "iconEmoji": "✨",
  "iconBg": "linear-gradient(135deg,#111827,#4e5d73)",
  "iconImage": "data:image/png;base64,iVBOR…",
  "description": "An elegant AI chatbot with persistent conversation history.",
  "version": "1.1.0",
  "width": 760,
  "height": 720,
  "closeBehavior": "close",
  "permissions": { "read": [], "write": [], "ai": true },
  "html": "<!doctype html>…your inline app…"
}

See the .vibe manifest reference for every field. The part that matters most for this app is permissions.

Step 2 — Request AI + sandbox storage

Unlike the To-Do app, the chatbot needs one extra capability: AI. Setting "ai": true is what makes window.vos.ai available — without it the host blocks every model call. It still only stores its own history file, so the filesystem stays scoped to the sandbox with empty arrays.

json
// "ai": true unlocks window.vos.ai (the AI gateway).
// Empty read/write arrays scope the filesystem to the app's
// private sandbox (home/.apps/<appId>/) — just enough to
// persist chat history. No shared directories, no prompts.
"permissions": { "read": [], "write": [], "ai": true }

Why no API key?

Apps never ship model credentials. window.vos.ai proxies requests through the VibeOS AI gateway, which handles the provider, auth and rate limits for you. Declaring "ai": true is the entire opt-in.

Step 3 — Wire up the SDK

Inside the inline HTML, the app talks to VibeOS through the global window.vos object. Every method returns a Promise.

1

Wait for the SDK, then restore history

The SDK is injected asynchronously, so start from the vos-ready event (falling back to running immediately if it already exists).

javascript
// Run as soon as the SDK is ready. If window.vos already
// exists, start now; otherwise wait for the one-shot event.
window.addEventListener("vos-ready", loadHistory);
if (window.vos) loadHistory();

On boot, resolve the sandbox path and read the saved thread. A missing file just means it's the first run — catch the error and start with an empty list.

javascript
const HISTORY_FILE = "history.json";
let sandboxPath = "home/.apps"; // replaced by getInfo()
let messages = [];              // user/assistant turns only

async function loadHistory() {
  try {
    // getInfo() returns the private sandbox path — no prompt.
    const info = await window.vos.app.getInfo();
    if (info && info.sandboxPath) sandboxPath = info.sandboxPath;

    const raw = await window.vos.fs.readFile(sandboxPath, HISTORY_FILE);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) {
        messages = parsed.filter(
          (m) => m && (m.role === "user" || m.role === "assistant")
            && typeof m.content === "string"
        );
      }
    }
  } catch (e) {
    /* first run — no file yet, start empty */
  }
  renderAll();
}
2

Send a message through the AI gateway

This is the heart of the app. Prepend a system prompt, hand the full message array to window.vos.ai.chat(), and render the reply. Always wrap the call in try/catch so a failed request shows a friendly message instead of breaking the UI.

javascript
// The system prompt steers tone; it is NEVER persisted.
const SYSTEM = {
  role: "system",
  content: "You are Simple AI Chatbot, a thoughtful, concise assistant.",
};

async function send(text) {
  messages.push({ role: "user", content: text });

  // Show a typing indicator while the model thinks.
  const typingBubble = bubble("assistant");
  typingBubble.innerHTML = '<div class="typing"><span></span><span></span><span></span></div>';

  try {
    // window.vos.ai.chat() takes the full message array and
    // returns the assistant's reply. No API key, no endpoint —
    // the host routes it through the VibeOS AI gateway.
    const payload = [SYSTEM, ...messages];
    const reply = await window.vos.ai.chat(payload);
    const out = (typeof reply === "string" ? reply : JSON.stringify(reply)).trim();

    messages.push({ role: "assistant", content: out || "(no response)" });
    typingBubble.innerHTML = renderMarkdown(out);
    persist();
  } catch (err) {
    typingBubble.innerHTML =
      '<span style="color:#FCA5A5">Something went wrong: ' + err.message + "</span>";
  }
}
3

Persist the conversation (debounced)

After each turn, schedule a write instead of saving immediately. The debounce coalesces rapid updates into a single writeFile call to the sandbox.

javascript
let saveTimer = null;

// Debounce writes so each reply doesn't block on the filesystem.
function persist() {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(async () => {
    try {
      await window.vos.fs.writeFile(
        sandboxPath,
        HISTORY_FILE,
        JSON.stringify(messages)
      );
    } catch (e) { /* ignore transient write errors */ }
  }, 250);
}

Keep the system prompt out of storage

The SYSTEM message is added only when calling the model — it's never pushed into messages and never written to disk. Persist user and assistant turns only, so changing the prompt later doesn't corrupt saved history.

Step 4 — Submit it

Paste your manifest into the submit form as a draft, then follow submission & review. For the full SDK surface — filesystem and AI — see the SDK reference. For a simpler, AI-free starting point, see the To-Do example.