npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

toruk-embed

v0.3.0

Published

TypeScript SDK for TORUK — typed client for prediction APIs, plus an embeddable chat widget

Readme

toruk-embed

The official TORUK SDK and embeddable chat widget for the web.

Starting in 0.2.x, toruk-embed ships two surfaces from the same package:

  1. TorukClient — a typed TypeScript SDK for TORUK's prediction APIs. Unified employees.* namespace for chatflows and agentflows (execute, stream, feedback, attach, vectorUpsert, getConfig, isStreamAvailable), explicit AuthConfig, response-envelope normalization, error mapping. First-class React bindings via toruk-embed/react.
  2. Embeddable chat widget (preserved from 0.1.x)registerWebComponents, init, initFull, etc. The SolidJS-based Web Component. Existing 0.1.x code keeps working without modification.

Table of Contents


Installation

npm install toruk-embed
# or
pnpm add toruk-embed
# or
yarn add toruk-embed

SDK — TorukClient

0.3.0 — rename: workflows.*employees.*. The typed TypeScript client for TORUK's prediction APIs. Old names (workflows, predict, workflowId, WORKFLOW_NOT_FOUND, …) remain as deprecated aliases for one minor cycle and emit a one-time console.warn on first use. See Migration from 0.2.x and MIGRATION.md for the full mapping.

The SDK is a client for TORUK-CORE's prediction endpoint family. It handles authentication, header construction, response envelope normalization, and error mapping, so consumers don't have to wire raw HTTP calls.

Migration from 0.2.x

- await toruk.workflows.predict({ workflowId: 'chatflow_abc', message: 'Hi' });
+ await toruk.employees.execute({ taskId: 'chatflow_abc', message: 'Hi' });

Quick reference:

| 0.2.x | 0.3.0 | | ----------------------- | ---------------------- | | toruk.workflows.* | toruk.employees.* | | .predict(…) | .execute(…) | | workflowId | taskId | | WORKFLOW_NOT_FOUND | TASK_NOT_FOUND | | WORKFLOW_BUILD_FAILED | TASK_BUILD_FAILED | | WorkflowPredictInput | EmployeeExecuteInput |

Wire body and URL paths are unchanged — question, chatId, /api/v1/prediction/:id, etc. all stay the same. Only SDK names changed.

SDK Quick Start

import { TorukClient } from 'toruk-embed';

const toruk = new TorukClient({
  baseUrl: 'https://toruk.company.com',
  auth: { type: 'apiKey', apiKey: process.env.TORUK_API_KEY! },
});

// Run a chatflow OR an agentflow — same call, same shape
const result = await toruk.employees.execute({
  taskId: 'chatflow_abc', // or 'agentflow_xyz'
  message: 'Summarize this document',
  chatId: 'chat_xyz', // optional — auto-generated if omitted
});

if (result.success) {
  console.log(result.data.text); // bot reply
  console.log(result.data.chatId); // session id
  console.log(result.data.messageId); // optional message id
} else {
  console.error(result.error.code, result.message);
}

In Node-only contexts (server-to-server, CLIs, scripts), import from the dedicated entry to skip the browser-only widget code:

import { TorukClient, fromEnv } from 'toruk-embed/node';

const toruk = fromEnv();
// Reads TORUK_BASE_URL + TORUK_API_KEY (or TORUK_JWT) from process.env

Authentication

The SDK supports the three authentication modes that TORUK-CORE accepts on its prediction routes:

| Mode | When to use | Wire behavior | | -------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | apiKey | Browser apps (task-bound key) or server-to-server (org-scoped key). The primary path. | Sends x-api-key. On GET requests, also appends ?apikey= as a query-param fallback so browser clients survive proxies that strip custom headers during CORS preflight. | | jwt | Apps where the user is already logged into TORUK and the host has an access token. | Sends Authorization: Bearer <token>. Calls a host-supplied refresh callback once on a 401 and retries. | | none | Calls a public task that doesn't require credentials. | Sends no auth headers. |

// API key (primary)
new TorukClient({
  baseUrl: 'https://toruk.company.com',
  auth: { type: 'apiKey', apiKey: 'tk_live_…' },
});

// JWT bearer with token rotation
new TorukClient({
  baseUrl: 'https://toruk.company.com',
  auth: {
    type: 'jwt',
    token: accessToken,
    refresh: async () => await refreshAccessToken(), // called once on 401
  },
});

// Public task (no credentials)
new TorukClient({
  baseUrl: 'https://toruk.company.com',
  auth: { type: 'none' },
});

Note on organization scoping. The SDK does not send x-organization-id headers. TORUK-CORE derives the organization server-side from the API key or JWT membership — the client never needs to supply it.

Browser-distributed API keys

API keys can be used in the browser (TORUK-CORE accepts them via header and query-param fallback). When distributing keys to browsers:

  • Use task-bound keys, not organization-scoped — a leaked key can only access that one task.
  • Set a short expiresAt on the key and rotate via a host-side endpoint.

employees.execute

Run a chatflow or an agentflow without streaming. TORUK-CORE serves both entity types through the same endpoint, so the SDK accepts either UUID through the same method.

const result = await toruk.employees.execute({
  taskId: 'chatflow_abc', // or 'agentflow_xyz'
  message: 'Explain the uploaded document',
  chatId: 'chat_xyz', // optional; UUID auto-generated if omitted
  overrideConfig: { variables: { region: 'eu-west-1' } }, // optional passthrough
  history: [{ role: 'user', content: 'Earlier turn' }], // optional
  signal: controller.signal, // optional AbortSignal
});

if (result.success) {
  result.data.chatId; // string — always present
  result.data.text; // string | undefined — bot reply
  result.data.messageId; // string | undefined
  result.data.sourceDocuments; // unknown[] | undefined
  result.data.usedTools; // unknown[] | undefined
  result.data.agentReasoning; // unknown | undefined
} else {
  result.error.code; // 'TASK_NOT_FOUND' | 'FORBIDDEN' | ...
  result.error.details; // unknown — backend-supplied detail payload
  result.message; // human-readable
}

Input fields

| Field | Type | Notes | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | taskId | string | UUID of the chatflow or agentflow. Sent as the URL path parameter, not a body field. | | message | string | The user message. Mapped to wire field question (TORUK-CORE's name for it). | | chatId | string | Optional. Auto-generated as a UUID if omitted. | | overrideConfig | Record<string, unknown> | Optional engine-side passthrough (variables, session overrides). | | history | ChatHistoryItem[] | Optional turn history. | | uploads | EmployeeUpload[] | Optional JSON uploads (URL or data-URI). For binary file uploads, use employees.attach. | | leadEmail | string | Optional lead-capture email. | | action | EmployeeAction | Optional humanInput continuation payload. | | signal | AbortSignal | Optional. Cancels the request. | | headers | Record<string, string> | Optional per-call custom headers. |


employees.stream

Stream tokens as they are generated via Server-Sent Events. Returns a Promise that resolves with { chatId, messageId } when the stream ends.

const controller = new AbortController();

const result = await toruk.employees.stream({
  taskId: 'chatflow_abc',
  message: 'Tell me a story',
  chatId: 'chat_xyz', // optional — resume a session
  signal: controller.signal, // optional — cancel mid-stream

  onStart: ({ chatId, messageId }) => {
    console.log('Stream started', chatId, messageId);
  },
  onToken: (token) => {
    process.stdout.write(token); // called for each text chunk
  },
  onDone: ({ chatId, messageId }) => {
    console.log('\nDone', chatId, messageId);
  },
  onError: (err) => {
    console.error(err.code, err.message);
  },
  onEvent: (ev) => {
    // catch-all — fires for every SSE frame: 'start', 'token', 'end'
  },
});

console.log(result.chatId, result.messageId);

// Cancel at any time:
controller.abort();

Non-streaming fallback. If the backend returns JSON instead of an SSE stream (task has isStreaming: false), employees.stream() automatically synthesizes onStart, onToken, and onDone from the JSON response — no change to the calling code needed.

All errors are surfaced as TorukSdkError. See Error Handling for the full code table.


employees.getConfig

Fetch the public chatbot config for a task (theme, welcome message, etc.). Maps to GET /api/v1/public-chatbotConfig/:id.

const result = await toruk.employees.getConfig('chatflow_abc');

if (result.success) {
  console.log(result.data); // arbitrary config object
}

employees.isStreamAvailable

Pre-flight check for whether a task supports streaming. Maps to GET /api/v1/chatflows-streaming/:id.

const result = await toruk.employees.isStreamAvailable('chatflow_abc');

if (result.success && result.data.isStreaming) {
  // safe to call employees.stream
}

employees.feedback

Submit thumbs-up / thumbs-down (with optional free-text content) for a specific message. Maps to POST /api/v1/feedback/:id.

const result = await toruk.employees.feedback({
  taskId: 'chatflow_abc',
  chatId: 'chat_xyz',
  messageId: 'msg_123',
  rating: 'thumbsUp', // or 'thumbsDown'
  content: 'Nailed the cite.', // optional free-text
});

if (result.success) {
  result.data.id; // backend-assigned feedback id
}

employees.attach

Upload one or more files into a chat session (multipart). Maps to POST /api/v1/attachments/:id/:chatId.

Pass File (browser) or Blob (Node 18+) instances. The optional filenames array overrides per-position when passing raw Blobs without a name.

const result = await toruk.employees.attach({
  taskId: 'chatflow_abc',
  chatId: 'chat_xyz',
  files: [pdfFile, screenshotBlob],
  filenames: ['contract.pdf', 'screenshot.png'], // optional
});

employees.vectorUpsert

Upsert one or more documents into the task's vector store for RAG (multipart). Maps to POST /api/v1/vector/upsert/:id.

Engine-specific fields (e.g. splitter, chunkSize) are passed through via fields.

const result = await toruk.employees.vectorUpsert({
  taskId: 'chatflow_abc',
  files: [docFile],
  fields: { splitter: 'recursive', chunkSize: '1000' }, // optional
});

Error Handling

The SDK distinguishes predictable errors (returned in the response envelope) from structural failures (thrown).

Predictable errors — response envelope

Returned without a throw. The result.success === false branch carries a typed error code.

const result = await toruk.employees.execute({ taskId: 'missing', message: 'hi' });

if (!result.success) {
  switch (result.error.code) {
    case 'TASK_NOT_FOUND':
      // 404 — task ID is wrong
      break;
    case 'UNAUTHORIZED':
      // 401 — bad/missing credentials
      break;
    case 'FORBIDDEN':
      // 403 — auth OK but task not accessible
      break;
    case 'RATE_LIMITED':
      // 429 — slow down
      break;
    // ... see below for the full list
  }
}

Known codes:

| Code | When | | ----------------------- | ------------------------------------------- | | TASK_NOT_FOUND | 404 — task does not exist | | UNAUTHORIZED | 401 — auth failed | | FORBIDDEN | 403 — auth OK but task not accessible | | BAD_REQUEST | 400 — malformed request body | | TASK_BUILD_FAILED | 500 — backend build error | | STREAMING_UNAVAILABLE | 503 — streaming requested but not available | | RATE_LIMITED | 429 — too many requests | | UNKNOWN | other 4xx/5xx without a known backend code |

The legacy codes WORKFLOW_NOT_FOUND and WORKFLOW_BUILD_FAILED remain in the TorukSdkErrorCode union as deprecated string aliases so existing consumer switch statements still compile — but the SDK emits only the new codes. Update your switches before the next major release.

Structural failures — thrown

A TorukSdkError is thrown when the failure is structural and can't be expressed in the envelope: network down, request aborted, response parse failure.

import { TorukSdkError } from 'toruk-embed';

try {
  await toruk.employees.execute({ taskId: 'x', message: 'hi' });
} catch (err) {
  if (err instanceof TorukSdkError) {
    err.code; // 'NETWORK' | 'ABORTED' | 'TIMEOUT' | 'MALFORMED_RESPONSE' | ...
    err.status; // HTTP status if applicable
    err.details; // backend-supplied detail payload if applicable
  }
}

Response Envelope

Every SDK method returns a discriminated union:

type TorukApiResponse<T> = { success: true; data: T } | { success: false; message: string; error: { code: string; details?: unknown } };

Success responses from TORUK-CORE arrive unwrapped (just the bare execution result); the SDK wraps them into { success: true, data }. Error responses already carry the envelope shape; the SDK normalizes the error.code against the table above.


Subpath Entries

| Entry | Use when | What's exported | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | toruk-embed | Browser-bundler consumers (Vite, Next.js, Webpack). | TorukClient, TorukSdkError, types, AND the legacy widget surface (registerWebComponents, init, …). | | toruk-embed/browser | Browser consumers who only want the SDK — no widget transitive imports. Smaller bundle. | TorukClient, TorukSdkError, types. | | toruk-embed/node | Pure-Node consumers (server-to-server, CLIs, tests). Skips the widget code that loads solid-element at module top-level. | Same as /browser + the fromEnv() helper. | | toruk-embed/previewer | Embed the TORUK chat widget with typed lifecycle controls. Prefer over legacy init() for new projects. | createTorukPreviewer, TorukPreviewerInstance, PreviewerMode, PreviewerMountOptions | | toruk-embed/react | React apps — context provider + hook + <TorukPreviewer /> component. SSR-safe (no-ops on the server). | TorukProvider, useTorukClient, TorukPreviewer |


fromEnv helper (Node)

In Node-only contexts you can construct a client from environment variables:

import { fromEnv } from 'toruk-embed/node';

// Reads:
//   TORUK_BASE_URL  (required)
//   TORUK_API_KEY   (preferred for server-to-server) OR
//   TORUK_JWT       (alternative)
const toruk = fromEnv();

Throws a plain Error with a clear message if either variable is missing — misconfiguration fails loudly at boot instead of silently at the first request.


SDK ↔ Backend wire mapping

The SDK uses developer-friendly field names; TORUK-CORE uses different names on the wire. The translation happens at the boundary.

| SDK input field | Wire body field | | ------------------------------------------------------------- | -------------------------------------------------- | | taskId | (path :id — not a body field) | | message | question | | stream (set by employees.stream) | streaming | | chatId | chatId (passthrough; auto-generated if absent) | | overrideConfig, history, uploads, leadEmail, action | (passthrough verbatim) |

| Wire response field | SDK envelope path | | ------------------------------------------------------ | --------------------------------------------------- | | chatId | result.data.chatId | | text | result.data.text | | chatMessageId | result.data.messageId (renamed at the boundary) | | sourceDocuments, usedTools, agentReasoning, etc. | result.data.<same name> (passthrough) |

This mapping is the spec — it does not change in patch releases.


PreviewerAPI — createTorukPreviewer

New in 0.2.3. Prefer this over the legacy init() / initFull() helpers for new projects.

createTorukPreviewer mounts the TORUK chat widget with typed lifecycle controls, without requiring you to call registerWebComponents() manually.

import { createTorukPreviewer } from 'toruk-embed/previewer';

const previewer = createTorukPreviewer({
  baseUrl: 'https://toruk.company.com',
  auth: { type: 'apiKey', apiKey: 'tk_live_…' },
});

previewer.mount(target, options) — returns Promise<void>

Awaiting the returned Promise ensures the widget element is in the DOM before you inspect or interact with it.

// Floating bubble (default) — appended to document.body
await previewer.mount(null, {
  taskId: 'chatflow_abc',
  mode: 'floating', // default; 'inline' | 'modal' | 'floating'
});

// Full-page inline inside a container
await previewer.mount('#chat-host', {
  taskId: 'chatflow_abc',
  mode: 'inline',
  variant: 'neptune', // 'luna' | 'erebus' | 'neptune'
  withSidebar: true,
});

// Modal overlay with backdrop
await previewer.mount(null, {
  taskId: 'chatflow_abc',
  mode: 'modal',
});

Lifecycle methods

previewer.unmount(); // remove widget from DOM
previewer.open(); // show widget (floating/modal)
previewer.close(); // hide widget (floating/modal)
previewer.updateConfig({ taskId: 'xyz' }); // hot-update props in place
const el = previewer.element; // mounted DOM element, or undefined

Mode reference

| mode | DOM element | target | | ------------ | -------------------------------------------- | -------------------------------------------- | | 'floating' | <toruk-chatbot> (bubble) | ignored — mounted on document.body | | 'inline' | <toruk-fullchatbot> | CSS selector or Element | | 'modal' | <toruk-fullchatbot> inside a fixed overlay | ignored — overlay mounted on document.body |

Mount options

| field | type | description | | ------------- | ------------------------------------- | -------------------------------------------- | | taskId | string | Chatflow or agentflow UUID | | mode | 'floating' \| 'inline' \| 'modal' | Defaults to 'floating' | | variant | 'luna' \| 'erebus' \| 'neptune' | Full-page visual variant (inline/modal only) | | withSidebar | boolean | Show sidebar in full-page modes | | theme | Record<string, unknown> | Theme override passed to the widget | | taskConfig | Record<string, unknown> | Per-task engine config | | onRequest | (req: RequestInit) => Promise<void> | Extra request hook (composes with auth) |


React Bindings — toruk-embed/react

First-class React surface: a context provider for sharing a TorukClient, a hook to access it, and a <TorukPreviewer /> component that wraps the imperative previewer. SSR-safe (no-ops on the server; mounts in useEffect after hydration).

react and react-dom are optional peer dependencies. Only consumers who import toruk-embed/react need them installed.

TorukProvider

Wrap your tree once. Pass either config (the provider constructs the client) or client (you manage it):

import { TorukProvider } from 'toruk-embed/react';

export function App() {
  return (
    <TorukProvider config={{ baseUrl: 'https://toruk.company.com', auth: { type: 'apiKey', apiKey: 'tk_live_…' } }}>
      <Page />
    </TorukProvider>
  );
}

useTorukClient

Access the shared client anywhere below the provider. Throws if used outside a TorukProvider.

import { useTorukClient } from 'toruk-embed/react';

function SendButton() {
  const toruk = useTorukClient();
  const onClick = async () => {
    const r = await toruk.employees.execute({ taskId: 'chatflow_abc', message: 'Hi' });
    // ...
  };
  return <button onClick={onClick}>Send</button>;
}

<TorukPreviewer />

Drop-in component that mounts the chat widget. Sources auth from the surrounding provider.

import { TorukPreviewer } from 'toruk-embed/react';

// Floating bubble — appended to <body>
<TorukPreviewer taskId="chatflow_abc" />

// Inline, full-page
<TorukPreviewer
  taskId="chatflow_abc"
  mode="inline"
  variant="neptune"
  withSidebar
  className="chat-host"
  style={{ height: 600 }}
  fallback={<Skeleton />}
/>

| Prop | Type | Description | | ------------- | ------------------------------------- | ----------------------------------------------------------- | | taskId | string | Required — chatflow or agentflow UUID. | | mode | 'floating' \| 'inline' \| 'modal' | Defaults to 'floating'. | | variant | 'luna' \| 'erebus' \| 'neptune' | Full-page variant for inline/modal. | | withSidebar | boolean | Show sidebar in full-page modes. | | className | string | Class forwarded onto the host <div> (inline/modal only). | | style | CSSProperties | Style forwarded onto the host <div> (inline/modal only). | | fallback | ReactNode | Rendered while the widget mounts (skeleton, spinner, etc.). | | theme | Record<string, unknown> | Theme override passed to the widget. | | taskConfig | Record<string, unknown> | Per-task engine config. | | onRequest | (req: RequestInit) => Promise<void> | Extra request hook (composes with auth). |


Widget vs Headless

| | TorukClient (headless) | createTorukPreviewer / init (widget) | | ------------------ | -------------------------------------------- | -------------------------------------------------------------------- | | DOM dependency | None — Node.js, edge, workers | Browser only | | Auth | Typed AuthConfig — no localStorage | Reads from onRequest hook or localStorage | | UI | You own the UI | Ships the TORUK SolidJS chat UI | | Use case | Custom UI, server-side calls, data pipelines | Drop-in embeds for web pages | | Entry point | toruk-embed or toruk-embed/node | toruk-embed/previewer (preferred) or toruk-embed (legacy init) | | Mount control | n/a | mount() / unmount() / open() / close() | | Streaming | employees.stream() with callbacks | Handled internally by the widget UI |

Use TorukClient when you need to call prediction APIs directly — from a Node.js server, a custom React/Vue UI, or a data pipeline. Use createTorukPreviewer when you want to drop the full chat UI into a web page with minimal code.


Legacy Widget API

Preserved unchanged from [email protected]. The widget renders a fully-featured chat UI as a Web Component, built with SolidJS and compiled to native Custom Elements. Existing widget code works in 0.2.x without modification.

Usage — Script Tag (CDN)

Embed the chatbot on any page with a single script tag. The script registers the Web Components and exposes window.Chatbot.

<script type="module">
  import Chatbot from 'https://unpkg.com/toruk-embed/dist/web.js';

  Chatbot.init({
    chatflowid: 'YOUR_CHATFLOW_ID',
    apiHost: 'https://your-toruk-api.com',
  });
</script>

Or with UMD (no ES modules required):

<script src="https://unpkg.com/toruk-embed/dist/web.umd.js"></script>
<script>
  window.TorukEmbed.init({
    chatflowid: 'YOUR_CHATFLOW_ID',
    apiHost: 'https://your-toruk-api.com',
  });
</script>

Usage — npm Module

Import and use inside any JS/TS bundler project (Vite, Next.js, webpack, etc.).

import { registerWebComponents, init } from 'toruk-embed';

// Register custom elements once — SSR-safe (no-op on server)
registerWebComponents();

// Mount the bubble chatbot
init({
  chatflowid: 'YOUR_CHATFLOW_ID',
  apiHost: 'https://your-toruk-api.com',
  theme: {
    button: { backgroundColor: '#3385FF' },
    chatWindow: {
      title: 'TORUK Assistant',
      welcomeMessage: 'Hello! How can I help?',
    },
  },
});

Next.js (SSR) — use dynamic import:

// app/components/ChatWidget.tsx
'use client';

import { useEffect } from 'react';

export default function ChatWidget() {
  useEffect(() => {
    import('toruk-embed').then(({ registerWebComponents, init }) => {
      registerWebComponents();
      init({
        chatflowid: 'YOUR_CHATFLOW_ID',
        apiHost: 'https://your-toruk-api.com',
      });
    });
  }, []);

  return null;
}

Templates

Three display modes are available:

| Template | Description | | -------- | -------------------------------------------- | | bubble | Floating chat bubble in the corner (default) | | full | Full-page embedded chat with sidebar | | popup | Popup overlay triggered by user action |

Use init() for bubble, initFull() for full-page, or initFromConfig() for flexible config-driven setup.


API Reference

init()

Mounts the bubble chatbot. Removes any existing widget first.

init(props: BotProps): void
import { registerWebComponents, init } from 'toruk-embed';

registerWebComponents();
init({
  chatflowid: 'abc-123',
  apiHost: 'https://api.example.com',
});

initFull()

Mounts the full-page chatbot.

initFull(props: BotProps & {
  id?: string;
  variant?: 'luna' | 'erebus' | 'neptune';
  fullPageStyle?: 'luna' | 'erebus' | 'neptune';
  withSidebar?: boolean;
}): void
import { registerWebComponents, initFull } from 'toruk-embed';

registerWebComponents();
initFull({
  chatflowid: 'abc-123',
  apiHost: 'https://api.example.com',
  variant: 'neptune',
  withSidebar: true,
});

initFromConfig()

Flexible initializer — accepts a TemplateConfig object. Used for dynamic/server-driven configuration.

initFromConfig(config: {
  template: 'bubble' | 'chatbubble' | 'full' | 'fullPage';
  flowId: string;
  apiHost?: string;
  theme?: BubbleTheme;
  variant?: 'luna' | 'erebus' | 'neptune';
  fullPageStyle?: 'luna' | 'erebus' | 'neptune';
  withSidebar?: boolean;
  chatflowConfig?: Record<string, unknown>;
  observersConfig?: observersConfigType;
  onRequest?: (request: RequestInit) => Promise<void>;
}): void
import { registerWebComponents, initFromConfig } from 'toruk-embed';

registerWebComponents();
initFromConfig({
  template: 'fullPage',
  flowId: 'abc-123',
  apiHost: 'https://api.example.com',
  variant: 'luna',
  withSidebar: true,
});

destroy()

Removes the mounted widget from the DOM.

destroy(): void
import { destroy } from 'toruk-embed';

destroy();

registerWebComponents()

Registers the <toruk-chatbot> and <toruk-fullchatbot> custom elements (plus the deprecated <flowise-chatbot> / <flowise-fullchatbot> aliases for backward compatibility). Must be called once before init(), initFull(), or initFromConfig().

  • Safe to call on SSR/Node — is a no-op when window is undefined.
  • Safe to call multiple times — custom elements self-skip re-registration.
import { registerWebComponents } from 'toruk-embed';

registerWebComponents();

BotProps

All properties accepted by init() and initFull().

| Prop | Type | Required | Description | | ---------------------------------------------- | ------------------------------------- | -------- | -------------------------------------------------------------- | | chatflowid | string | ✅ | The chatflow UUID from your TORUK instance | | apiHost | string | — | Base URL of the TORUK API (e.g. https://api.example.com) | | onRequest | (req: RequestInit) => Promise<void> | — | Hook to modify every outgoing request (add auth headers, etc.) | | chatflowConfig | Record<string, unknown> | — | Extra config passed to the chatflow | | observersConfig | observersConfigType | — | Reactive callbacks for messages, loading state | | theme.button.backgroundColor | string | — | Bubble button background color (hex) | | theme.button.iconColor | string | — | Bubble button icon color (hex) | | theme.chatWindow.title | string | — | Chat window header title | | theme.chatWindow.titleBackgroundColor | string | — | Header background color | | theme.chatWindow.titleTextColor | string | — | Header text color | | theme.chatWindow.titleAvatarSrc | string | — | URL for the avatar shown in the header | | theme.chatWindow.welcomeMessage | string | — | First message shown before user types | | theme.chatWindow.heroGreeting | string | — | Large greeting text in the hero section | | theme.chatWindow.heroHeadline | string | — | Subtitle under the greeting | | theme.chatWindow.backgroundColor | string | — | Chat window background color | | theme.chatWindow.fontSize | number | — | Base font size (px) | | theme.chatWindow.userMessage.backgroundColor | string | — | User bubble background | | theme.chatWindow.userMessage.textColor | string | — | User bubble text color | | theme.chatWindow.botMessage.backgroundColor | string | — | Bot bubble background | | theme.chatWindow.botMessage.textColor | string | — | Bot bubble text color | | theme.chatWindow.textInput.backgroundColor | string | — | Input area background | | theme.chatWindow.textInput.textColor | string | — | Input text color | | theme.chatWindow.textInput.sendButtonColor | string | — | Send button color | | theme.chatWindow.textInput.placeholder | string | — | Input placeholder text | | theme.chatWindow.showTitle | boolean | — | Show/hide the title bar | | theme.chatWindow.showAgentMessages | boolean | — | Show/hide agent reasoning messages | | theme.chatWindow.starterPrompts | string[] | — | Suggested prompts shown before first message | | theme.chatWindow.clearChatOnReload | boolean | — | Clear chat history on page reload | | theme.chatWindow.renderHTML | boolean | — | Allow HTML rendering in bot messages | | theme.customCSS | string | — | Raw CSS injected into the widget shadow scope | | isFullPage | boolean | — | Internal flag; set automatically by initFull() |


Theme Variants

Full-page mode (initFull / initFromConfig with template: 'fullPage') supports three visual variants:

| Variant | Description | | --------- | ---------------------------------------------------------------------- | | luna | Clean light/dark default layout | | erebus | Dark gradient with glowing accents | | neptune | Deep-space dark with animated WebGL laser beam (Three.js; lazy-loaded) |

initFull({
  chatflowid: 'abc-123',
  apiHost: 'https://api.example.com',
  variant: 'neptune',
  withSidebar: true,
});

Note: The Neptune variant loads Three.js dynamically only when rendered. Users of the luna and erebus variants do not download Three.js.


observersConfig

Subscribe to reactive state changes inside the widget.

import { registerWebComponents, init } from 'toruk-embed';

registerWebComponents();
init({
  chatflowid: 'abc-123',
  apiHost: 'https://api.example.com',
  observersConfig: {
    observeUserInput: (input) => {
      console.log('User typed:', input);
    },
    observeLoading: (isLoading) => {
      console.log('Loading:', isLoading);
    },
    observeMessages: (messages) => {
      console.log('Messages updated:', messages);
    },
  },
});

| Observer | Payload type | Fires when | | ------------------ | --------------- | ----------------------------------------- | | observeUserInput | string | User types in the input box | | observeLoading | boolean | Bot starts or stops generating a response | | observeMessages | MessageType[] | Any message is added or updated |


Demo Server

A local proxy server for development (keeps your API key out of the browser) lives in demo/.

cd demo
cp .env.example .env
# Fill in API_HOST and TORUK_API_KEY in .env
npm install
npm start
# Opens http://localhost:3001

License

MIT — see LICENSE.