@sales-bot-llm/sdk 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +271 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @sales-bot-llm/sdk
2
+
3
+ Frontend SDK for the [Sales Bot](../sales_bot) chat API. A framework-agnostic core (~8 KB) plus thin adapters for React, Vue, a drop-in floating widget, and a plain `<script>` tag.
4
+
5
+ Sub-project #2 of 5 in the Sales Bot platform. Consumes the SSE wire format defined by sub-project #1 ([sales_bot/](../sales_bot)).
6
+
7
+ ## What you get
8
+
9
+ | Entry point | Use when |
10
+ | --- | --- |
11
+ | `@sales-bot-llm/sdk/core` | Custom UI, Node integration, or any framework not covered below. Returns a `SalesBotClient` with an async-iterable `ask()`. |
12
+ | `@sales-bot-llm/sdk/react` | React 18+ apps. `useSalesBot()` hook returns `{ ask, messages, isStreaming, error, conversationId, visitorToken, reset }`. |
13
+ | `@sales-bot-llm/sdk/vue` | Vue 3.4+ apps. Same shape as React, but state is exposed as `Ref<…>`. |
14
+ | `@sales-bot-llm/sdk/widget` | Drop-in floating launcher + chat panel rendered into a shadow DOM. `createWidget({ embedKey, theme }).mount()`. |
15
+ | `@sales-bot-llm/sdk/vanilla` | Plain HTML page. Load `dist/vanilla.global.js` via `<script>` → `window.SalesBot.default.widget({ … })`. |
16
+
17
+ All entry points talk to the same `SalesBotClient` and consume the same SSE event stream.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ pnpm add @sales-bot-llm/sdk
23
+ # or: npm install @sales-bot-llm/sdk / yarn add @sales-bot-llm/sdk
24
+ ```
25
+
26
+ React and Vue are optional peer deps — only install the one(s) you use.
27
+
28
+ ## Quickstart
29
+
30
+ ### Core (framework-agnostic)
31
+
32
+ ```ts
33
+ import { SalesBotClient } from '@sales-bot-llm/sdk/core'
34
+
35
+ const client = new SalesBotClient({
36
+ embedKey: 'pk_live_...', // from the Bot config in the admin UI
37
+ baseUrl: 'https://api.example.com', // your sales_bot deployment
38
+ })
39
+
40
+ for await (const event of client.ask('What does your trial include?')) {
41
+ if (event.event === 'delta') {
42
+ process.stdout.write(event.data.content)
43
+ } else if (event.event === 'done') {
44
+ break
45
+ }
46
+ }
47
+ ```
48
+
49
+ `client.ask()` is an async iterable over the typed SSE union (`turn_started`, `delta`, `tool_call_started`, `tool_call_finished`, `message_complete`, `usage`, `done`, `error`). Each event type maps to a strongly-typed payload — see [src/core/types.ts](src/core/types.ts).
50
+
51
+ The client also offers:
52
+
53
+ - `client.identify({ externalId, email, name, traits })` — merges into the next `ask()` call.
54
+ - `client.on(eventName, handler)` — event-bus subscription, returns an unsubscribe.
55
+ - `client.getConversationId()` / `setConversationId(id | null)` — persisted in storage so reloads continue the same conversation.
56
+ - `client.loadHistory()` — fetch the last 20 messages of the current conversation.
57
+ - `client.endConversation()` — close the conversation server-side and clear local state.
58
+ - `client.getBotConfig()` — public bot config (title, greeting, etc.), cached for the client's lifetime.
59
+
60
+ ### React
61
+
62
+ ```tsx
63
+ import { useSalesBot } from '@sales-bot-llm/sdk/react'
64
+
65
+ function Chat() {
66
+ const { ask, messages, isStreaming, error } = useSalesBot({
67
+ embedKey: 'pk_live_...',
68
+ baseUrl: 'https://api.example.com',
69
+ })
70
+
71
+ return (
72
+ <>
73
+ {messages.map(m => (
74
+ <div key={m.id} data-role={m.role}>{m.content}</div>
75
+ ))}
76
+ <button onClick={() => ask('Show me the pricing')} disabled={isStreaming}>
77
+ Ask
78
+ </button>
79
+ {error && <p role="alert">{error.message}</p>}
80
+ </>
81
+ )
82
+ }
83
+ ```
84
+
85
+ ### Vue
86
+
87
+ ```vue
88
+ <script setup lang="ts">
89
+ import { useSalesBot } from '@sales-bot-llm/sdk/vue'
90
+
91
+ const { ask, messages, isStreaming, error } = useSalesBot({
92
+ embedKey: 'pk_live_...',
93
+ baseUrl: 'https://api.example.com',
94
+ })
95
+ </script>
96
+
97
+ <template>
98
+ <div v-for="m in messages" :key="m.id" :data-role="m.role">{{ m.content }}</div>
99
+ <button @click="ask('Show me the pricing')" :disabled="isStreaming">Ask</button>
100
+ <p v-if="error" role="alert">{{ error.message }}</p>
101
+ </template>
102
+ ```
103
+
104
+ ### Floating widget
105
+
106
+ ```ts
107
+ import { createWidget } from '@sales-bot-llm/sdk/widget'
108
+
109
+ createWidget({
110
+ embedKey: 'pk_live_...',
111
+ baseUrl: 'https://api.example.com',
112
+ title: 'Ask us anything',
113
+ position: 'bottom-right',
114
+ theme: {
115
+ primary: '#3b82f6',
116
+ radius: '16px',
117
+ fontFamily: 'Inter, system-ui, sans-serif',
118
+ },
119
+ }).mount()
120
+ ```
121
+
122
+ The widget renders into a shadow root, so it can't be styled (or broken) by host-page CSS. Use the typed `theme` for common knobs and `customCss` for anything else. Every theme key maps to a `--sb-<kebab-case>` CSS variable on the shadow host — see [src/core/types.ts](src/core/types.ts) for the full list.
123
+
124
+ ### Vanilla `<script>` tag
125
+
126
+ ```html
127
+ <script src="https://your-cdn/sales-bot/vanilla.global.js"></script>
128
+ <script>
129
+ window.SalesBot.default.widget({
130
+ embedKey: 'pk_live_...',
131
+ baseUrl: 'https://api.example.com',
132
+ })
133
+ </script>
134
+ ```
135
+
136
+ Note the `.default.widget` indirection — the IIFE bundle exposes the module's exports wrapper, not the module itself. `window.SalesBot.default.widget(...)` is the recommended path; `window.SalesBot.SalesBot.widget(...)` also works.
137
+
138
+ ## Configuration
139
+
140
+ All entry points accept the same `SalesBotClientOptions`:
141
+
142
+ | Option | Required | Default | Purpose |
143
+ | --- | --- | --- | --- |
144
+ | `embedKey` | yes | — | Public bot key, `pk_live_…`. Created in the admin UI. |
145
+ | `baseUrl` | no | `http://localhost:3000` | Sales Bot backend URL. |
146
+ | `storage` | no | `localStorage` with `MemoryStorage` fallback | Custom `StorageAdapter` for the visitor token + conversation id. |
147
+ | `customHeaders` | no | — | Extra headers merged into every request. Useful in Node where the browser-style `Origin` header must be set manually. |
148
+
149
+ Widget-only extras (see [src/core/types.ts](src/core/types.ts) for the full `WidgetOptions` shape):
150
+
151
+ - `container?: HTMLElement` — mount target (default: `document.body`)
152
+ - `position?: 'bottom-right' | 'bottom-left'`
153
+ - `title?`, `placeholder?`
154
+ - `theme?: WidgetTheme` — typed CSS-variable overrides (colors, sizing, fonts, effects)
155
+ - `customCss?: string` — raw CSS appended inside the shadow root
156
+
157
+ ## Errors
158
+
159
+ Every error surfaces as a `SalesBotError` with a typed `code` you can switch on:
160
+
161
+ ```ts
162
+ import { SalesBotError } from '@sales-bot-llm/sdk/core'
163
+
164
+ try {
165
+ for await (const ev of client.ask('hi')) { /* … */ }
166
+ } catch (e) {
167
+ if (e instanceof SalesBotError) {
168
+ switch (e.code) {
169
+ case 'origin_not_allowed': /* domain not verified for this Bot */
170
+ case 'rate_limited': /* back off */
171
+ case 'out_of_credits': /* show top-up CTA */
172
+ case 'network_error': /* retry if e.retryable */
173
+ // …
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ Backend codes mirror the platform error catalog; SDK-only codes are `network_error` and `parse_error`.
180
+
181
+ ## Origin header — the most common gotcha
182
+
183
+ The backend validates `Origin` on every chat request. Browsers send it automatically, but the Bot's [Resource](../sales_bot/README.md) must list your origin as a verified domain (e.g. `localhost`, or your production host). A `403 origin_not_allowed` almost always means the Resource isn't configured for the host that's calling the SDK.
184
+
185
+ In Node (no browser, no automatic `Origin`), pass `customHeaders: { Origin: 'https://yourapp.example' }`.
186
+
187
+ ## Repo layout
188
+
189
+ ```
190
+ sales_bot_sdk/
191
+ ├── src/
192
+ │ ├── core/ framework-agnostic client, types, transport, SSE parser, storage
193
+ │ ├── react/ useSalesBot hook
194
+ │ ├── vue/ useSalesBot composable
195
+ │ ├── widget/ createWidget — shadow-DOM floating panel
196
+ │ └── vanilla/ IIFE entry, exposes window.SalesBot
197
+ ├── tests/ vitest specs incl. SSE contract, framework adapters, widget
198
+ ├── example/ Vite + React playground (see example/README.md)
199
+ ├── tsup.config.ts bundling: ESM + CJS for libs, IIFE for vanilla
200
+ ├── biome.json lint + format
201
+ └── package.json exports map driving the public surface
202
+ ```
203
+
204
+ ## Development
205
+
206
+ Node 20+ and pnpm 10+.
207
+
208
+ ```sh
209
+ pnpm install
210
+ pnpm build # tsup → dist/*.{js,cjs,d.ts} + vanilla.global.js (IIFE)
211
+ pnpm dev # tsup --watch
212
+ pnpm test # vitest run (unit + SSE-contract + adapter tests)
213
+ pnpm test:watch
214
+ pnpm test:coverage
215
+ pnpm typecheck # tsc --noEmit
216
+ pnpm lint # biome check .
217
+ pnpm lint:fix
218
+ pnpm size # size-limit budgets: core 8KB, react/vue 12KB, widget 25KB, vanilla 20KB
219
+ ```
220
+
221
+ The `tests/` tree mirrors the `src/` tree, plus a `contract/` folder that pins the SSE event taxonomy against the backend's `sse-events.contract.spec.ts`. **If the contract tests fail, do not loosen them — coordinate the change with the backend team.** The wire format is the integration boundary.
222
+
223
+ ### Run against a local backend
224
+
225
+ ```sh
226
+ # In ../sales_bot/:
227
+ pnpm start:dev # API on :3000
228
+
229
+ # In sales_bot_sdk/:
230
+ pnpm build
231
+ pnpm --filter @sales-bot/sdk-example dev # http://localhost:5173
232
+ ```
233
+
234
+ See [example/README.md](example/README.md) for the full set-up, including how to verify a `localhost` Resource and Bot in the admin UI.
235
+
236
+ ## SSE wire contract
237
+
238
+ The SDK is locked to the backend's SSE event taxonomy. The full list and payload shapes live in [src/core/types.ts](src/core/types.ts) — search for `SalesBotEvent`. Any change here is a breaking change for both sides and must be coordinated.
239
+
240
+ Event names: `turn_started`, `delta`, `tool_call_started`, `tool_call_finished`, `message_complete`, `usage`, `done`, `error`.
241
+
242
+ ## Bundle outputs
243
+
244
+ `pnpm build` produces (in `dist/`):
245
+
246
+ | File | Format | Purpose |
247
+ | --- | --- | --- |
248
+ | `core.{js,cjs,d.ts}` | ESM / CJS | Framework-agnostic client |
249
+ | `react.{js,cjs,d.ts}` | ESM / CJS | React adapter |
250
+ | `vue.{js,cjs,d.ts}` | ESM / CJS | Vue adapter |
251
+ | `widget.{js,cjs,d.ts}` | ESM / CJS | Floating widget |
252
+ | `vanilla.global.js` | IIFE, minified | `<script>`-loadable, exposes `window.SalesBot` |
253
+
254
+ All ESM/CJS bundles are side-effect-free and tree-shakable.
255
+
256
+ ## Publishing
257
+
258
+ The package is published as `@sales-bot-llm/sdk` on the public npm registry under `access=public`.
259
+
260
+ ```sh
261
+ pnpm build
262
+ pnpm test
263
+ pnpm size
264
+ npm publish # NPM_TOKEN must be set in the environment
265
+ ```
266
+
267
+ In CI, set `NPM_TOKEN` as a repo secret; npm picks it up automatically from the `.npmrc` directive.
268
+
269
+ ## License
270
+
271
+ MIT — see [package.json](package.json).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sales-bot-llm/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Frontend SDK for the Sales Bot chat API — framework-agnostic core with React, Vue, and vanilla adapters",
5
5
  "license": "MIT",
6
6
  "type": "module",