Examples
Build a To-Do app
A complete, working .vibe app you can study, submit, and fork. It focuses on the two things every app needs: a correct manifest and clean VibeOS SDK integration for saving data.
This walkthrough builds a small To-Do list that persists tasks across sessions using the VibeOS filesystem SDK — no servers, no API keys, one JSON file. Grab the finished template first, then read how each piece works.
Use it as a template
to-do.vibe is a ready-to-submit manifest. Open it in any editor, change the name, iconEmoji, description and the inline html, then paste it into the submit form to create your own draft.
What you'll build
- A single-window app with an add form and a checkable task list.
- Persistence to the app's private sandbox via
window.vos.fs. - A debounced save so rapid edits don't thrash the filesystem.
- A manifest that requests the minimum permissions it actually needs.
Step 1 — The manifest
Everything ships in one JSON object. The metadata fields describe the listing; the html field holds the entire app as a single inline document. Here's the shell (the real html is the full document from the download):
{
"vibe": 1,
"name": "To-Do",
"iconEmoji": "✅",
"iconBg": "linear-gradient(135deg,#10b981,#0d9488)",
"description": "A fast, minimal to-do list that saves your tasks to your app sandbox.",
"version": "1.0.0",
"author": "VibeOS",
"genre": "productivity",
"width": 460,
"height": 560,
"closeBehavior": "close",
"permissions": { "read": [], "write": [] },
"html": "<!doctype html>…your inline app…"
}See the .vibe manifest reference for every field. The only part that needs real thought here is permissions.
Step 2 — Request the right permissions
The To-Do app only ever reads and writes its own data file — it never touches the user's Documents or Pictures. So it declares the filesystem capability with empty arrays, which scopes it to the app's private sandbox and skips the directory-approval prompt entirely.
// Empty arrays declare the filesystem capability but grant ONLY
// the app's private sandbox at home/.apps/<appId>/.
// No user prompt for shared directories, nothing else is reachable.
"permissions": { "read": [], "write": [] }Least privilege wins reviews
["home/Documents"]) if your app genuinely needs them. Sandbox-only apps install with no friction and pass review faster. This app also omits ai because it makes no model calls.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.
Wait for the SDK
The SDK is injected asynchronously. Start your app from the vos-ready event, falling back to running immediately if window.vos already exists.
<script>
(function () {
// Wait for the SDK. If window.vos already exists, start now;
// otherwise listen for the one-shot "vos-ready" event.
if (window.vos) init();
else window.addEventListener("vos-ready", init);
})();
</script>Find the sandbox and load saved tasks
window.vos.app.getInfo() returns the app's sandboxPath — the private directory backing the empty-array permissions. Read your data file from there on boot; a missing file just means it's the first run.
var FILE = "todos.json";
var sandboxPath = null;
function init() {
if (!window.vos) { setStatus("SDK unavailable"); return; }
// getInfo() returns the app's private sandbox path. No permission needed.
window.vos.app.getInfo()
.then(function (info) {
sandboxPath = info.sandboxPath;
if (!sandboxPath) { setStatus("No sandbox"); return; }
// Try to read previously saved tasks from the sandbox.
return window.vos.fs.readFile(sandboxPath, FILE);
})
.then(function (content) {
if (typeof content === "string" && content) {
var parsed = JSON.parse(content);
if (Array.isArray(parsed.todos)) todos = parsed.todos;
}
loaded = true;
setStatus("Ready");
render();
})
.catch(function () {
// File doesn't exist on first run — start with an empty list.
loaded = true;
setStatus("Ready");
render();
});
}Save on every change (debounced)
When tasks change, schedule a write instead of saving immediately. The debounce coalesces quick edits into a single writeFile call.
var dirty = false;
var saveTimer = null;
// Debounce writes so rapid edits don't hammer the filesystem.
function scheduleSave() {
dirty = true;
setStatus("Saving…");
clearTimeout(saveTimer);
saveTimer = setTimeout(save, 250);
}
function save() {
if (!sandboxPath || !dirty) return;
var payload = JSON.stringify({ version: 1, todos: todos }, null, 2);
window.vos.fs.writeFile(sandboxPath, FILE, payload)
.then(function () { dirty = false; setStatus("Saved"); })
.catch(function (err) { setStatus("Save failed: " + err.message); });
}Always handle the missing-file case
readFile rejects. Catch it and start with empty state rather than showing an error — see the .catch() in step 2.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.