DeepBotDownload

The bridge: window.deepbot

Every HTML page DeepBot serves for an app (cards and pages) gets two things injected at the top of <head>: the bridge script (which defines window.deepbot) and the design kit stylesheet. You do not include them yourself, and you do not import anything.

The page runs in a locked frame (sandbox="allow-scripts allow-downloads allow-forms", no same-origin). That means:

  • no cookies, no localStorage / sessionStorage / IndexedDB (they throw): use deepbot.storage or files;
  • no alert, confirm, prompt: draw your own confirmations ("Delete for good?" on a second click);
  • no popups (window.open): use deepbot.open(url);
  • no direct access to the person's files or DeepBot's API: everything goes through the bridge;
  • downloads work (<a download> with a Blob URL), forms work, ES modules and relative imports work.

Every method returns a Promise. A refused call (missing permission, a path outside your folders, a host not listed) rejects with an Error whose message says why. Calls time out after 120 s.

#Start

const init = await deepbot.ready;
// init = { args, mode: "card" | "page", app: { id, name, version }, theme: {...}, dark: true|false }
deepbot.args   // the card's call arguments, or the page's params (openPage), {} when none
deepbot.mode   // "card" or "page"
deepbot.dark   // DeepBot is in dark mode (the <html> element also has class "dark")

Always await deepbot.ready before reading deepbot.args.

#Events

const off = deepbot.on("args", (args) => { /* the Bot is still streaming the call: new args */ });
deepbot.on("theme", ({ dark, theme }) => { /* the person switched light/dark; CSS vars already updated */ });
deepbot.on("display", ({ full }) => { /* a card went in or out of full screen; <html> has class "full" */ });
off(); // stop listening

#Files: the shared workspace

The Bot and the app share the workspace (the Bot's working folder). The app may touch only the folders in permissions.files. Paths are relative to the workspace: "recipes/pancakes.json".

const { items } = await deepbot.files.list("recipes");
// items: [{ name: "pancakes.json", path: "recipes/pancakes.json", dir: false, size: 812, modified: 1760000000000 }]
// a folder that does not exist yet: { items: [] }

const text  = await deepbot.files.read("recipes/pancakes.md");          // string (default "text")
const data  = await deepbot.files.read("recipes/pancakes.json", "json"); // parsed JSON
const b64   = await deepbot.files.read("recipes/photo.jpg", "base64");   // base64 string
// limits: text and json up to 8 MB, base64 up to 25 MB; a missing file rejects ("No such file.")

await deepbot.files.write("recipes/pancakes.json", JSON.stringify(recipe, null, 2)); // text, up to 4 MB
await deepbot.files.write("recipes/photo.jpg", b64, { encoding: "base64" });        // binary, up to 40 MB
// folders are created as needed; an existing file is replaced

await deepbot.files.delete("recipes/old.json"); // a file, or a folder inside your folders (not the folder itself)

img.src = deepbot.files.url("recipes/photo.jpg"); // an address for <img>, <video>, <audio>, CSS url(); supports seeking

#Storage: the app's own small data, per person

Not in the workspace, not visible to the Bot. For settings, pins, the last tab, a draft. JSON values, up to 2 MB per app and person.

const prefs = (await deepbot.storage.get("prefs")) ?? { units: "metric" }; // null when never set
await deepbot.storage.set("prefs", { ...prefs, units: "imperial" });

#Talking to the Bot

Needs "ask": true.

// In a card: a message from the person to the Bot, in this conversation.
await deepbot.ask("Scale the pancakes recipe to 8 servings.");

// With extra instructions for the Bot for that turn (not shown as the person's words):
await deepbot.ask("Make it vegetarian.", {
  instructions: "Rewrite recipes/pancakes.json in place, then call showRecipeBoxRecipe with slug pancakes.",
});

// In a page (no conversation open): starts a new conversation with this text. instructions are dropped.
await deepbot.ask("Plan dinners for next week using my recipe box.");

#Telling the Bot whether the card worked (cards)

The Bot waits up to 10 seconds for this after it calls a card.

deepbot.done();                                   // the Bot hears the card's `confirmation`
deepbot.done("Showing 3 of 12 recipes.");          // ... plus your note: give the Bot what it needs to answer
deepbot.fail("There is no recipes/pancakes.json"); // the Bot hears: "It did not work, the card says: ...
                                                   //  Fix that and show it again" and tries again

If the card reports nothing, the Bot hears the confirmation anyway. An uncaught error in the page is reported as a failure automatically. Always end a card with one of the two: it is what stops a Bot from saying "here it is" over an empty card.

#Network (needs permissions.network)

The frame has no network of its own for APIs: DeepBot makes the request, only to the listed hosts, and returns the answer. This also means no CORS problems.

const res = await deepbot.net.fetch("https://api.example.com/v1/items?q=tea", {
  method: "GET",                  // default GET
  headers: { accept: "application/json" },
  as: "json",                     // "text" (default) | "json" | "base64"
});
// res = { status: 200, ok: true, headers: {...}, body: <parsed JSON>, url, redirected, ms, firstByte, bytes }

await deepbot.net.fetch("https://api.example.com/v1/items", {
  method: "POST",
  headers: { "content-type": "application/json", authorization: "Bearer {{secret:api-key}}" },
  body: JSON.stringify({ name: "tea" }),   // a string; for binary: body: b64, bodyEncoding: "base64"
  as: "json",
});

Limits: 120 s, answers up to 25 MB, set-cookie is dropped, redirect: "manual" is available. A non-2xx answer resolves with ok: false (it does not reject); a refused host or network error rejects.

#Secrets: keys and tokens

Kept by DeepBot, encrypted, per app and per person. The app can never read a value back. It can only use one inside net.fetch (URL, headers or a text body) as {{secret:name}}, which DeepBot fills in on the way out.

const { secrets } = await deepbot.secrets.list();     // [{ name: "api-key", updated: "2026-..." }]
await deepbot.secrets.set("api-key", input.value);     // names: letters, digits, . _ - (1-64)
await deepbot.secrets.remove("api-key");

Typical settings UI: a password field, Save calls secrets.set, then show "Key saved" with a Remove button (never the key).

await deepbot.openPage("box", { slug: "pancakes" }); // one of your pages, opened with deepbot.args = { slug: "pancakes" }
await deepbot.open("https://example.com/help");      // http(s) only, in the person's browser
await deepbot.fullscreen();                           // cards: in or out of full screen

#Low level

deepbot.call(method, params) sends any bridge request by name ("files.list", ...). You do not need it; it is what the helpers above use.