Copilot Widget SDK
Cimulate's Copilot Widget SDK is a React component library which allows you to embed our Copilot Widget into your app.
Installation
Requires Node >= 22.15.0 and npm >= 10.9.2
npm install @cimulate/copilot-widget
NPM hosts must provide react and react-dom (^18.3 || ^19). The published ESM artifacts import those peers from the host (they are not bundled). CDN/UMD <script> installs are unchanged and still bundle React.
Bundle size snapshots
Use npm run measure:bundle-sizes to capture before/after gzip sizes for an optimization. Run everything from this directory after a fresh npm run build so dist/ matches the code under test.
One session = one timestamp folder under .tmp/bundle-sizes/<DD-MM-YYYY-HHmmss>/ with bundle-sizes.before.json and bundle-sizes.after.json. .tmp/ is gitignored — paste the --diff table into the PR if you need a durable record.
# 1. Baseline (before the change)
npm run build
npm run measure:bundle-sizes -- --before
# 2. Apply the change, rebuild, capture after
npm run build
npm run measure:bundle-sizes -- --after
# 3. Diff the latest session
npm run measure:bundle-sizes -- --diff
- Build before every capture; a stale
dist/produces wrong numbers. - One change per session — run a new
--beforefor each optimization/PR. - Don’t start another
--beforeuntil you’ve finished--after/--difffor the current pair;--afterand bare--diffalways use the latest timestamp directory. - To diff an older pair explicitly:
npm run measure:bundle-sizes -- --diff \
.tmp/bundle-sizes/<timestamp>/bundle-sizes.before.json \
.tmp/bundle-sizes/<timestamp>/bundle-sizes.after.json
Playgrounds
React App Playground
npm start
Simulates a React environment. Faster for local development because Vite serves source via hot-module replacement — no full build is required, and changes reflect instantly in the browser. This is the most appropriate testing playground for customers who will integrate the widget in a React-based storefront.
Settings are in src/App.tsx. Example configurations you can modify:
// Theme definitions
const THEMES: Record<string, ThemeConfig> = {
sfra: {
primaryColor: "#0070D2",
fontFamily: '"Helvetica Neue", Arial, sans-serif',
backgroundColor: "#F7F7F7",
// ...
},
"storefront-next": {
primaryColor: "#000000",
fontFamily: '"Inter", sans-serif',
backgroundColor: "#FFFFFF",
// ...
},
};
// Copilot mode props
<CopilotWidget
apiKey={apiKey}
theme={currentTheme}
logoUrl="https://example.com/logo.png"
headerText="Cimulate Copilot"
isDevelopment={true}
/>
// Agentforce/Messaging mode props
<AgentForceWidget
headerText="Commerce Client"
disclaimerMarkdown="This is AI, *be careful*."
isDevelopment={true}
theme={currentTheme}
searchConfig={{
placeholder: "Find me cutting-edge outdoor gear",
buttonType: "icon",
buttonIcon: "arrow",
}}
messagingConfig={{
scrt2Url: "",
orgId: "",
esDeveloperName: "AgentForce",
}}
/>
Messaging mode: append
?ccDevMode=trueto the page URL to forceisDevelopmenton at runtime (staging endpoints + verbose logging), with no redeploy required.
HTML / CDN Integration Playground
npm run preview
The most common integration method because it is framework-agnostic. Any changes to the widget should be tested on this playground. It is slower for local development because it must produce a full build of all three artifacts (main bundle, copilot sub-bundle, messaging sub-bundle) and requires a rebuild on every change.
Settings are in public/index.html. Example configurations you can modify:
<script>
// Widget injection with configuration
injectCopilotWidget({
elementId: "root",
apiKey: "INSERT_YOUR_API_KEY_HERE",
componentConfig: { isOpen: true, options: { dialogPosition: "bottom-right" } },
isDevelopment: true,
mode: "copilot",
theme: {
primaryColor: "#000000",
fontFamily: '"Inter", sans-serif',
backgroundColor: "#FFFFFF",
},
headerText: "Commerce Client",
searchConfig: {
placeholder: "Find me cutting-edge outdoor gear",
buttonType: "icon",
buttonIcon: "arrow",
},
});
// Event handlers for programmatic control
const { eventHandlers } = window["cimulate-copilot-widget"];
eventHandlers.search.updateSearchQuery("running shoes");
eventHandlers.components.setWidgetType("modal"); // "chat" | "dialog" | "modal"
eventHandlers.components.toggleWidgetOpen(true);
eventHandlers.pdp.setPdpContext({ productId: "product-123" });
eventHandlers.browse.setBrowseContext({ categoryId: "outdoor-gear" });
// Messaging mode: submit a query from a storefront search bar or embedded question
const { sendMessage, eventHandlers: messagingHandlers } = window.CimulateMessaging;
sendMessage("running shoes");
messagingHandlers.components.toggleWidgetOpen(true);
</script>
Messaging Page Context
Messaging integrations provide normalized Product Detail Page (PDP) or Search Results Page (SRP) storefront state through the generic page-context API:
import { eventHandlers } from "@cimulate/copilot-widget/messaging";
eventHandlers.pageContext.set({
type: "pdp",
id: "product-123",
display: {
name: "Deep Cleansing Foam",
imageUrl: "https://store.example.com/products/foam.jpg",
},
});
eventHandlers.pageContext.set({
type: "srp",
query: "running shoes",
refinements: [{ name: "color", values: ["blue"] }],
minPrice: 50,
maxPrice: 150,
});
const current = eventHandlers.pageContext.get();
eventHandlers.pageContext.clear();
refinements, minPrice, and maxPrice are optional for SRP context. The widget omits empty refinements and undefined price bounds. PDP and SRP context are mutually exclusive.
To seed page context once at mount (same shape as eventHandlers.pageContext.set), pass messagingConfig.initialPageContext with enablePageContext: true. Later runtime updates still go through the event handler.
<AgentForceWidget
mode="messaging"
messagingConfig={{
enablePageContext: true,
initialPageContext: {
type: "srp",
query: "running shoes",
refinements: [{ name: "color", values: ["blue"] }],
minPrice: 50,
maxPrice: 150,
},
// existing SCRT2 fields...
}}
/>
With messagingConfig.enablePageContext, the widget snapshots the page context when a shopper sends a text message and reuses it for retries and session recovery. The first text message after each PDP or SRP context switch gets a divider above its user bubble. Changing pages never sends a message on its own. File uploads do not carry page context or create dividers.
The optional PDP display fields set the product chip and divider's name and image. SRP context shows a removable chip with a search icon and Search: "query"; its divider uses the same label. Only one context chip is shown at a time. Removing either chip clears page context, so the next message has no context or divider. The chip remains visible for later messages on the same product or search query. Setting SRP context replaces the PDP chip, and setting PDP context replaces the SRP chip.
Messaging Cart
Messaging integrations push the shopper's current basket with eventHandlers.cart.update. Call it from the messaging entry only: window.CimulateMessaging.eventHandlers.cart.update(cart) on the CDN, or the NPM import below. The Copilot entry does not expose it.
import { eventHandlers } from "@cimulate/copilot-widget/messaging";
eventHandlers.cart.update({
id: "cart-1",
currencyCode: "USD",
subtotal: 90,
total: 90,
items: [
{
itemId: "line-1",
productId: "SKU-1",
name: "Men's Rovereto Running Shoes",
imageUrl: "https://store.example.com/products/shoes.jpg",
quantity: 1,
itemSubtotal: 90,
originalSubtotal: 90,
variants: [{ lbl: "Color", type: "color", value: "Black / White" }],
},
],
});
The object is the full basket (CartDetails), not a quantity stub. An empty items array means an empty cart. A payload without an items array is ignored. A call before the widget is mounted is ignored.
Call it after the widget is mounted, on page load, after the shopper changes the basket outside the widget, and again after a restored conversation. The latest cart message in the chat updates to that basket. Older cart messages stay as they were. The call does not emit cimulate:cart-update. The next agent cart result is compared with that basket.
Linting and pre-commit hooks
Lint and format locally with the npm scripts:
npm run lint # prettier --check + eslint
npm run lint:fix # prettier --write + eslint --fix
ESLint enforces the commenting guardrails from AGENTS.md via two local rules (local/banner-comments, local/redundant-identifier-comment in eslint-rules/).
A pre-commit hook (.pre-commit-config.yaml) runs prettier + eslint on the copilot-widget files you're committing and fails the commit on formatting/lint errors. It uses the same pre-commit framework as the Python subprojects (see docs/development/development-workflow.md).
npm run start and npm run preview auto-install this hook for you (via scripts/ensure-precommit.sh) as long as the pre-commit tool is on your PATH. If you see a pre-commit' is NOT installed warning when starting the dev server, install the tool and it will wire itself up on the next start:
brew install pre-commit # macOS (recommended — avoids pyenv/venv shim issues)
pip install pre-commit # any platform
To set the hook up manually (or if you don't use the dev server), run once from this directory:
cd javascript/copilot-widget
brew install pre-commit # or: pip install pre-commit
pre-commit install
The hook only lints staged files, so pre-existing issues elsewhere won't block your commit.
Publish Flow
This project follows trunk-based development — all changes merge directly to master. The package uses semantic versioning; bump the version in package.json before merging. Be mindful of backwards-compatibility since customers have existing implementations relying on the public API.
Publishing is handled automatically by the Build and Publish Javascript Packages GitHub Actions workflow (.github/workflows/build-and-publish-js.yaml).
Trigger: Push to master with changes under javascript/ (only runs for copilot-widget).
Steps:
- Install dependencies
- Run tests (
npm run test) - Build (
npm run build) - Publish to npm (
npm publish --access public) - Upload UMD bundles and CSS to a Google Cloud Storage bucket for CDN delivery, versioned by the package version:
<bucket>/copilot-widget/<version>/*.umd.js <bucket>/copilot-widget/<version>/*.css
Creating Symlinks
To develop against local versions of the SDK dependencies, use npm link:
copilot-sdk
# In javascript/copilot-sdk
npm install
npm run build
npm link
# In javascript/copilot-widget
npm link @cimulate/copilot-sdk
agentforce-sdk
# In javascript/agentforce-sdk
npm install
npm run build
npm link
# In javascript/copilot-widget
npm link @cimulate/agentforce-sdk
To unlink and restore the registry version:
npm unlink @cimulate/copilot-sdk
npm unlink @cimulate/agentforce-sdk
npm install