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 anything — a Gmail account, a Postgres

database, a GitHub repo, a spreadsheet, a robot, a parts catalog — 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 happens to hit the parts catalog because it's keyless; once you

bring a token, the very same hook calls any provider your scene has.)

import { DaslabProvider, useDaslabCall } from "@daslab/sdk/react";

function Bom() {
  const { data, loading } = useDaslabCall("parts_configure", {
    use_case: "machine-monitoring",
    scale: 12,
    market: "th",
  });
  if (loading) return <p>configuring…</p>;
  return (
    <h1>
      {data.total_display} <small>{data.per_unit_display}</small>
      <a href={data.share_url}>open configurator ↗</a>
    </h1>
  );
}

export default function Page() {
  return (
    <DaslabProvider>
      <Bom />
    </DaslabProvider>
  );
}

Paste it, npm run dev, and you have live data on your page in sixty seconds

with zero keys. Add a token and the same pattern reads your inbox, queries your

database, 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.

HookWhat 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.
ComponentWhat 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 — the view half of a cell, 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, a hundred-plus 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 (Parts Catalog,
ChEMBL, …) — providers that 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 useScene sees 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

  1. Read a public sceneuseScene("acme/showroom"), no key.
  2. Call a keyless tool — the BOM example above, no key.
  3. Bring a token — your scenes, every provider you've connected, same hooks.
  4. Let the agent write it — this SDK ships llms.txt; paste it into your
agent and describe the page you want.

Honest changelog

Named, designed, not yet shipped — watch this page:

  • useScene(ref, { at }) — scenes are versioned like git; time-travel reads
are reserved in the types today and land with scene history.
  • useSceneHistory(ref) — the commit list behind { at }.
  • useJob(id) — headless job following. Today useDaslabChat follows jobs and
the raw client covers the rest; this hook ships when a real page demands it.
  • React hooks section in the generated llms.txt.

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-07-23