Daslab scenes as React hooks
A scene holds assets; tools act on them; hooks watch them. That one sentence is the whole SDK. A scene can hold a Gmail account, a Postgres database, a GitHub repo, a spreadsheet, or a robot, and every tool of every connected provider becomes a typed React hook. One import, every integration.
Your first page needs no account and no API key: keyless providers answer anonymous calls, and public scenes serve their assets to anyone. (The example below searches flights because flight search is keyless; once you bring a token, the very same hook calls any provider your scene has.)
import { DaslabProvider, useDaslabCall } from "@daslab/sdk/react";
function Flights() {
const { data, loading } = useDaslabCall("daslab_search_flights", {
from: "BER",
to: "BKK",
date: "2026-09-12",
});
if (loading) return <p>searching…</p>;
return (
<ul>
{data.results.slice(0, 3).map((f) => (
<li key={`${f.departure}-${f.price}`}>
${Math.round(f.price)} · {f.airlines.join(", ")} ·{" "}
{f.stops === 0 ? "nonstop" : `${f.stops} stops`}
</li>
))}
</ul>
);
}
export default function Page() {
return (
<DaslabProvider>
<Flights />
</DaslabProvider>
);
}
Paste it, npm run dev, and you have live prices on your page in sixty seconds with zero keys. Add a token and the same pattern reads your inbox, queries your database, and files your issues. Nothing about the code changes but the tool name.
One vocabulary, two runtimes
The hook names you use in the browser are the hook names scenes use on the server. A reactive scene definition and your React page speak the same language:
// In a scene definition (runs on Daslab):
const mail = await gmail.useInbox(ctx, { query: "is:unread" });
// In your React app (runs in the browser):
const d = useDaslab();
const mail = d.gmail().useInbox({ query: "is:unread" });
Learn the vocabulary once; write scenes, dashboards, public sites, or agent harnesses with it.
The surface
Five hooks, two components. That's the whole thing.
| Hook | What it gives you |
|---|---|
useScene(ref) | A public scene's identity + assets, as live data. Ref is a handle (acme/showroom), slug, or id. |
useAsset(ref) | One asset from that scene. fields carries a note's body, a widget's data. |
useDaslabCall(tool, input) | Any provider tool, SWR-style: cache, dedup, refetch, enabled. MCP text results are parsed to JSON for you. |
useDaslabChat(opts) | The full conversation surface: streaming, resume, recovery. Every chat flow is "follow the job". |
useDaslab() | The raw typed client. Every generated provider hook hangs off it. |
| Component | What it renders |
|---|---|
<Chat/> | The Daslab chat surface, themable via chat.css. |
<Widget data={…}/> | Any widget payload (metric, table, list, record, key-value) as a presentable block. Hooks return the data; <Widget/> renders it, so you never start from a blank div. |
import { useScene, useAsset, Widget } from "@daslab/sdk/react";
function Showroom() {
const { scene, assets, loading } = useScene("acme/showroom");
if (loading) return null;
return (
<main>
<h1>{scene.name}</h1>
{assets.map(a => <Widget key={a.id} data={a.fields?.data ?? a.fields} />)}
</main>
);
}
Every provider, one grammar
Every tool of every provider your scene has an account for is a typed hook on the client: Gmail, Sheets, GitHub, Postgres, Stripe, Slack, S3, SAP, search, robots, and 100+ more. The grammar never changes:
function OpsDashboard() {
const d = useDaslab();
const leads = d.sheets("Lead-Tracker").useRead({ maxRows: 200 });
const mention = d.brave().useWebSearch({ query: "daslab.run" });
const scrape = d.firecrawl().useScrape({ url: "https://competitor.example" });
// …every d.<provider>(name).use<Verb>(input) is a hook; render as you like
}
Property access is not a hook; only the final use* call is, so the Rules of Hooks hold. Autocomplete is the reference: the input and output types are generated from the same schemas the server enforces.
Public by design
- Anonymous tool calls run against keyless providers only (flight search, PubMed, ChEMBL, and so on), which hold no credentials and read no scene state, so the answer is the same for everyone. Everything credentialed requires your Bearer token, exactly as before.
- Public scenes opt in explicitly: a scene declares a public role in its blueprint, and
useScenesees only the assets that role's layers expose. Private by default, layer-scoped when public.
<DaslabProvider> {/* public mode, no token */}
<DaslabProvider token={key} scene={id}> {/* your scenes, your tools */}
The escalation ladder
- Read a public scene:
useScene("acme/showroom"), no key. - Call a keyless tool: the flight search above, no key.
- Bring a token: your scenes, every provider you've connected, same hooks.
- Let the agent write it: this SDK ships
llms.txt; paste it into your agent and describe the page you want.
Install
npm install @daslab/sdk
import { DaslabProvider, useScene, useAsset, useDaslabCall, Widget } from "@daslab/sdk/react";
Server-side (Node/Bun) usage of the same client is documented in the package README and llms.txt; the CLI/MCP surface is at /docs/cli.
Updated 2026-08-06