@pillar-ai/react 0.1.7 → 0.1.11

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.
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { Pillar } from '@pillar-ai/sdk';
2
+ import { scanPageDirect, Pillar } from '@pillar-ai/sdk';
3
3
  import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
4
4
  import { createRoot } from 'react-dom/client';
5
5
 
@@ -10,13 +10,22 @@ const PillarContext = createContext(null);
10
10
  // ============================================================================
11
11
  // Provider Component
12
12
  // ============================================================================
13
- function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children, }) {
13
+ function PillarProvider({ productKey, helpCenter, config, onTask, cards, domScanning, children, }) {
14
14
  const [pillar, setPillar] = useState(null);
15
15
  const [state, setState] = useState("uninitialized");
16
16
  const [isPanelOpen, setIsPanelOpen] = useState(false);
17
+ const [isDOMScanningEnabled, setIsDOMScanningEnabledState] = useState(domScanning ?? false);
18
+ // Support both productKey (new) and helpCenter (deprecated)
19
+ const resolvedKey = productKey ?? helpCenter;
17
20
  // Keep a ref to the latest onTask callback to avoid re-subscribing
18
21
  const onTaskRef = useRef(onTask);
19
22
  onTaskRef.current = onTask;
23
+ // Warn about deprecated helpCenter usage
24
+ useEffect(() => {
25
+ if (helpCenter && !productKey) {
26
+ console.warn('[Pillar React] "helpCenter" prop is deprecated. Use "productKey" instead.');
27
+ }
28
+ }, []);
20
29
  // Keep a ref to cards to access latest versions
21
30
  const cardsRef = useRef(cards);
22
31
  cardsRef.current = cards;
@@ -32,6 +41,10 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
32
41
  if (mounted) {
33
42
  setPillar(existingInstance);
34
43
  setState(existingInstance.state);
44
+ // Apply DOM scanning settings to existing instance
45
+ if (domScanning !== undefined) {
46
+ existingInstance.setDOMScanningEnabled(domScanning);
47
+ }
35
48
  // Re-subscribe to events
36
49
  existingInstance.on("panel:open", () => {
37
50
  setIsPanelOpen(true);
@@ -44,9 +57,13 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
44
57
  }
45
58
  // Initialize new instance
46
59
  const instance = await Pillar.init({
47
- helpCenter,
48
- publicKey,
60
+ productKey: resolvedKey,
49
61
  ...config,
62
+ domScanning: {
63
+ ...config?.domScanning,
64
+ // Explicit prop overrides config value
65
+ enabled: domScanning ?? config?.domScanning?.enabled ?? false,
66
+ },
50
67
  });
51
68
  if (mounted) {
52
69
  setPillar(instance);
@@ -76,7 +93,7 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
76
93
  // Note: We intentionally don't call Pillar.destroy() here
77
94
  // The singleton persists to maintain state across route changes
78
95
  };
79
- }, [helpCenter, publicKey]); // Re-initialize if credentials change
96
+ }, [resolvedKey]); // Re-initialize if productKey changes
80
97
  // Update state when SDK state changes
81
98
  useEffect(() => {
82
99
  if (pillar) {
@@ -101,6 +118,15 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
101
118
  return unsubscribe;
102
119
  }
103
120
  }, [pillar]);
121
+ // Sync DOM scanning props with SDK when they change
122
+ useEffect(() => {
123
+ if (pillar) {
124
+ if (domScanning !== undefined) {
125
+ pillar.setDOMScanningEnabled(domScanning);
126
+ setIsDOMScanningEnabledState(domScanning);
127
+ }
128
+ }
129
+ }, [pillar, domScanning]);
104
130
  // Register custom card renderers
105
131
  useEffect(() => {
106
132
  if (!pillar || !cards)
@@ -162,6 +188,15 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
162
188
  const setTextSelectionEnabled = useCallback((enabled) => {
163
189
  pillar?.setTextSelectionEnabled(enabled);
164
190
  }, [pillar]);
191
+ const setDOMScanningEnabled = useCallback((enabled) => {
192
+ pillar?.setDOMScanningEnabled(enabled);
193
+ setIsDOMScanningEnabledState(enabled);
194
+ }, [pillar]);
195
+ const scanPage = useCallback((options) => {
196
+ if (!pillar)
197
+ return null;
198
+ return scanPageDirect(options);
199
+ }, [pillar]);
165
200
  const on = useCallback((event, callback) => {
166
201
  return pillar?.on(event, callback) ?? (() => { });
167
202
  }, [pillar]);
@@ -180,6 +215,9 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
180
215
  navigate,
181
216
  setTheme,
182
217
  setTextSelectionEnabled,
218
+ setDOMScanningEnabled,
219
+ isDOMScanningEnabled,
220
+ scanPage,
183
221
  on,
184
222
  }), [
185
223
  pillar,
@@ -194,6 +232,9 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
194
232
  navigate,
195
233
  setTheme,
196
234
  setTextSelectionEnabled,
235
+ setDOMScanningEnabled,
236
+ isDOMScanningEnabled,
237
+ scanPage,
197
238
  on,
198
239
  ]);
199
240
  return (jsx(PillarContext.Provider, { value: value, children: children }));
@@ -220,8 +261,7 @@ function usePillarContext() {
220
261
  * @example
221
262
  * ```tsx
222
263
  * <PillarProvider
223
- * helpCenter="my-help"
224
- * publicKey="pk_xxx"
264
+ * productKey="my-product-key"
225
265
  * config={{ panel: { container: 'manual' } }}
226
266
  * >
227
267
  * <div className="my-layout">
@@ -356,5 +396,107 @@ function usePillar() {
356
396
  };
357
397
  }
358
398
 
359
- export { PillarPanel, PillarProvider, useHelpPanel, usePillar, usePillarContext };
399
+ /**
400
+ * usePillarTool Hook
401
+ *
402
+ * Register one or more tools with co-located metadata and handlers.
403
+ * Tools are registered on mount and unregistered on unmount.
404
+ *
405
+ * @example Single tool
406
+ * ```tsx
407
+ * import { usePillarTool } from '@pillar-ai/react';
408
+ *
409
+ * function CartButton() {
410
+ * usePillarTool({
411
+ * name: 'add_to_cart',
412
+ * description: 'Add a product to the shopping cart',
413
+ * inputSchema: {
414
+ * type: 'object',
415
+ * properties: {
416
+ * productId: { type: 'string', description: 'Product ID' },
417
+ * quantity: { type: 'number', description: 'Quantity to add' },
418
+ * },
419
+ * required: ['productId', 'quantity'],
420
+ * },
421
+ * execute: async ({ productId, quantity }) => {
422
+ * await cartApi.add(productId, quantity);
423
+ * return { content: [{ type: 'text', text: 'Added to cart' }] };
424
+ * },
425
+ * });
426
+ *
427
+ * return <button>Cart</button>;
428
+ * }
429
+ * ```
430
+ *
431
+ * @example Multiple tools
432
+ * ```tsx
433
+ * import { usePillarTool } from '@pillar-ai/react';
434
+ *
435
+ * function BillingPage() {
436
+ * usePillarTool([
437
+ * {
438
+ * name: 'get_current_plan',
439
+ * description: 'Get the current billing plan',
440
+ * execute: async () => ({ plan: 'pro', price: 29 }),
441
+ * },
442
+ * {
443
+ * name: 'upgrade_plan',
444
+ * description: 'Upgrade to a higher plan',
445
+ * inputSchema: {
446
+ * type: 'object',
447
+ * properties: { planId: { type: 'string' } },
448
+ * required: ['planId'],
449
+ * },
450
+ * execute: async ({ planId }) => {
451
+ * await billingApi.upgrade(planId);
452
+ * return { content: [{ type: 'text', text: 'Upgraded!' }] };
453
+ * },
454
+ * },
455
+ * ]);
456
+ *
457
+ * return <div>Billing Content</div>;
458
+ * }
459
+ * ```
460
+ */
461
+ /**
462
+ * Register one or more Pillar tools with co-located metadata and handlers.
463
+ *
464
+ * The tools are registered when the component mounts and automatically
465
+ * unregistered when it unmounts. The `execute` functions always capture
466
+ * the latest React state and props via refs, so you don't need to worry
467
+ * about stale closures.
468
+ *
469
+ * @param schemaOrSchemas - Single tool schema or array of tool schemas
470
+ */
471
+ function usePillarTool(schemaOrSchemas) {
472
+ const { pillar } = usePillarContext();
473
+ // Normalize to array for consistent handling
474
+ const schemas = useMemo(() => (Array.isArray(schemaOrSchemas) ? schemaOrSchemas : [schemaOrSchemas]), [schemaOrSchemas]);
475
+ // Keep refs to latest schemas so handlers capture current state/props
476
+ const schemasRef = useRef(schemas);
477
+ schemasRef.current = schemas;
478
+ // Stable dependency key for the effect (tool names joined)
479
+ const toolNamesKey = useMemo(() => schemas.map((s) => s.name).join(','), [schemas]);
480
+ useEffect(() => {
481
+ if (!pillar)
482
+ return;
483
+ // Register all tools and collect unsubscribe functions
484
+ const unsubscribes = schemasRef.current.map((schema, index) => {
485
+ return pillar.defineTool({
486
+ ...schema,
487
+ // Wrap execute to always use the latest ref version
488
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
489
+ execute: (input) => schemasRef.current[index].execute(input),
490
+ });
491
+ });
492
+ // Cleanup: unregister all tools
493
+ return () => {
494
+ unsubscribes.forEach((unsub) => unsub());
495
+ };
496
+ }, [pillar, toolNamesKey]);
497
+ }
498
+ /** @deprecated Use usePillarTool instead */
499
+ const usePillarAction = usePillarTool;
500
+
501
+ export { PillarPanel, PillarProvider, useHelpPanel, usePillar, usePillarAction, usePillarContext, usePillarTool };
360
502
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/PillarProvider.tsx","../src/PillarPanel.tsx","../src/hooks/useHelpPanel.ts","../src/hooks/usePillar.ts"],"sourcesContent":["/**\n * PillarProvider\n * Context provider that initializes and manages the Pillar SDK\n */\n\nimport {\n Pillar,\n type CardCallbacks,\n type PillarConfig,\n type PillarEvents,\n type PillarState,\n type TaskExecutePayload,\n type ThemeConfig,\n} from \"@pillar-ai/sdk\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type ReactNode,\n} from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\n// ============================================================================\n// Card Types\n// ============================================================================\n\n/**\n * Props passed to custom card components.\n */\nexport interface CardComponentProps<T = Record<string, unknown>> {\n /** Data extracted by the AI for this action */\n data: T;\n /** Called when user confirms the action */\n onConfirm: (modifiedData?: Record<string, unknown>) => void;\n /** Called when user cancels the action */\n onCancel: () => void;\n /** Called to report state changes (loading, success, error) */\n onStateChange?: (\n state: \"loading\" | \"success\" | \"error\",\n message?: string\n ) => void;\n}\n\n/**\n * A React component that can be used as a custom card renderer.\n */\nexport type CardComponent<T = Record<string, unknown>> = ComponentType<\n CardComponentProps<T>\n>;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PillarContextValue {\n /** The Pillar SDK instance */\n pillar: Pillar | null;\n\n /** Current SDK state */\n state: PillarState;\n\n /** Whether the SDK is ready */\n isReady: boolean;\n\n /** Whether the panel is currently open */\n isPanelOpen: boolean;\n\n /** Open the help panel */\n open: (options?: {\n view?: string;\n article?: string;\n search?: string;\n focusInput?: boolean;\n }) => void;\n\n /** Close the help panel */\n close: () => void;\n\n /** Toggle the help panel */\n toggle: () => void;\n\n /** Open a specific article */\n openArticle: (slug: string) => void;\n\n /** Open a specific category */\n openCategory: (slug: string) => Promise<void>;\n\n /** Perform a search */\n search: (query: string) => void;\n\n /** Navigate to a specific view */\n navigate: (view: string, params?: Record<string, string>) => void;\n\n /** Update the panel theme at runtime */\n setTheme: (theme: Partial<ThemeConfig>) => void;\n\n /** Enable or disable the text selection \"Ask AI\" popover */\n setTextSelectionEnabled: (enabled: boolean) => void;\n\n /** Subscribe to SDK events */\n on: <K extends keyof PillarEvents>(\n event: K,\n callback: (data: PillarEvents[K]) => void\n ) => () => void;\n}\n\nexport interface PillarProviderProps {\n /** Help center subdomain or identifier */\n helpCenter: string;\n\n /** Public API key */\n publicKey: string;\n\n /**\n * Additional SDK configuration\n *\n * Notable options:\n * - `panel.useShadowDOM`: Whether to isolate styles in Shadow DOM (default: false).\n * Set to false to let custom cards inherit your app's CSS (Tailwind, etc.)\n */\n config?: Omit<PillarConfig, \"helpCenter\" | \"publicKey\">;\n\n /**\n * Handler called when a task action is triggered from the chat.\n * Use this to handle AI-suggested actions like opening modals, navigating, etc.\n *\n * @example\n * ```tsx\n * <PillarProvider\n * helpCenter=\"my-app\"\n * publicKey=\"pk_...\"\n * onTask={(task) => {\n * switch (task.name) {\n * case 'invite_team_member':\n * openInviteModal(task.data);\n * break;\n * case 'open_settings':\n * router.push('/settings');\n * break;\n * }\n * }}\n * >\n * ```\n */\n onTask?: (task: TaskExecutePayload) => void;\n\n /**\n * Custom card components to render for inline_ui type actions.\n * Map card type names to React components that will render the inline UI.\n *\n * @example\n * ```tsx\n * import { InviteMembersCard } from './cards/InviteMembersCard';\n *\n * <PillarProvider\n * helpCenter=\"my-app\"\n * publicKey=\"pk_...\"\n * cards={{\n * invite_members: InviteMembersCard,\n * confirm_delete: ConfirmDeleteCard,\n * }}\n * >\n * ```\n */\n cards?: Record<string, CardComponent>;\n\n /** Children components */\n children: ReactNode;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst PillarContext = createContext<PillarContextValue | null>(null);\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\nexport function PillarProvider({\n helpCenter,\n publicKey,\n config,\n onTask,\n cards,\n children,\n}: PillarProviderProps): React.ReactElement {\n const [pillar, setPillar] = useState<Pillar | null>(null);\n const [state, setState] = useState<PillarState>(\"uninitialized\");\n const [isPanelOpen, setIsPanelOpen] = useState(false);\n\n // Keep a ref to the latest onTask callback to avoid re-subscribing\n const onTaskRef = useRef(onTask);\n onTaskRef.current = onTask;\n\n // Keep a ref to cards to access latest versions\n const cardsRef = useRef(cards);\n cardsRef.current = cards;\n\n // Initialize SDK\n useEffect(() => {\n let mounted = true;\n\n const initPillar = async () => {\n try {\n // Pillar is a singleton - check if already initialized\n const existingInstance = Pillar.getInstance();\n if (existingInstance) {\n // Reuse existing instance (preserves chat history, panel state, etc.)\n if (mounted) {\n setPillar(existingInstance);\n setState(existingInstance.state);\n\n // Re-subscribe to events\n existingInstance.on(\"panel:open\", () => {\n setIsPanelOpen(true);\n });\n\n existingInstance.on(\"panel:close\", () => {\n setIsPanelOpen(false);\n });\n }\n return;\n }\n\n // Initialize new instance\n const instance = await Pillar.init({\n helpCenter,\n publicKey,\n ...config,\n });\n\n if (mounted) {\n setPillar(instance);\n setState(instance.state);\n\n // Listen for panel open/close\n instance.on(\"panel:open\", () => {\n setIsPanelOpen(true);\n });\n\n instance.on(\"panel:close\", () => {\n setIsPanelOpen(false);\n });\n }\n } catch (error) {\n console.error(\"[Pillar React] Failed to initialize:\", error);\n if (mounted) {\n setState(\"error\");\n }\n }\n };\n\n initPillar();\n\n // Cleanup - DON'T destroy the singleton on unmount\n // This preserves conversation history and panel state across navigation\n // Pillar.destroy() should only be called explicitly when the app unmounts completely\n return () => {\n mounted = false;\n // Note: We intentionally don't call Pillar.destroy() here\n // The singleton persists to maintain state across route changes\n };\n }, [helpCenter, publicKey]); // Re-initialize if credentials change\n\n // Update state when SDK state changes\n useEffect(() => {\n if (pillar) {\n const unsubscribe = pillar.on(\"ready\", () => {\n setState(\"ready\");\n });\n\n const unsubscribeError = pillar.on(\"error\", () => {\n setState(\"error\");\n });\n\n return () => {\n unsubscribe();\n unsubscribeError();\n };\n }\n }, [pillar]);\n\n // Register task handler\n useEffect(() => {\n if (pillar) {\n const unsubscribe = pillar.on(\"task:execute\", (task) => {\n onTaskRef.current?.(task);\n });\n\n return unsubscribe;\n }\n }, [pillar]);\n\n // Register custom card renderers\n useEffect(() => {\n if (!pillar || !cards) return;\n\n const unsubscribers: Array<() => void> = [];\n const roots: Map<HTMLElement, Root> = new Map();\n\n // Register each card component as a vanilla renderer\n Object.entries(cards).forEach(([cardType, Component]) => {\n const unsubscribe = pillar.registerCard(\n cardType,\n (container, data, callbacks: CardCallbacks) => {\n // Create a React root for this container\n const root = createRoot(container);\n roots.set(container, root);\n\n // Render the React component\n root.render(\n <Component\n data={data}\n onConfirm={callbacks.onConfirm}\n onCancel={callbacks.onCancel}\n onStateChange={callbacks.onStateChange}\n />\n );\n\n // Return cleanup function\n return () => {\n const existingRoot = roots.get(container);\n if (existingRoot) {\n existingRoot.unmount();\n roots.delete(container);\n }\n };\n }\n );\n\n unsubscribers.push(unsubscribe);\n });\n\n return () => {\n // Cleanup all registrations\n unsubscribers.forEach((unsub) => unsub());\n // Unmount all React roots\n roots.forEach((root) => root.unmount());\n roots.clear();\n };\n }, [pillar, cards]);\n\n // Actions\n const open = useCallback(\n (options?: {\n view?: string;\n article?: string;\n search?: string;\n focusInput?: boolean;\n }) => {\n pillar?.open(options);\n },\n [pillar]\n );\n\n const close = useCallback(() => {\n pillar?.close();\n }, [pillar]);\n\n const toggle = useCallback(() => {\n pillar?.toggle();\n }, [pillar]);\n\n const openArticle = useCallback(\n (slug: string) => {\n pillar?.open({ article: slug });\n },\n [pillar]\n );\n\n const openCategory = useCallback(\n async (slug: string) => {\n pillar?.navigate(\"category\", { slug });\n },\n [pillar]\n );\n\n const search = useCallback(\n (query: string) => {\n pillar?.open({ search: query });\n },\n [pillar]\n );\n\n const navigate = useCallback(\n (view: string, params?: Record<string, string>) => {\n pillar?.navigate(view, params);\n },\n [pillar]\n );\n\n const setTheme = useCallback(\n (theme: Partial<ThemeConfig>) => {\n pillar?.setTheme(theme);\n },\n [pillar]\n );\n\n const setTextSelectionEnabled = useCallback(\n (enabled: boolean) => {\n pillar?.setTextSelectionEnabled(enabled);\n },\n [pillar]\n );\n\n const on = useCallback(\n <K extends keyof PillarEvents>(\n event: K,\n callback: (data: PillarEvents[K]) => void\n ) => {\n return pillar?.on(event, callback) ?? (() => {});\n },\n [pillar]\n );\n\n // Context value\n const value = useMemo<PillarContextValue>(\n () => ({\n pillar,\n state,\n isReady: state === \"ready\",\n isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n search,\n navigate,\n setTheme,\n setTextSelectionEnabled,\n on,\n }),\n [\n pillar,\n state,\n isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n search,\n navigate,\n setTheme,\n setTextSelectionEnabled,\n on,\n ]\n );\n\n return (\n <PillarContext.Provider value={value}>{children}</PillarContext.Provider>\n );\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function usePillarContext(): PillarContextValue {\n const context = useContext(PillarContext);\n\n if (!context) {\n throw new Error(\"usePillarContext must be used within a PillarProvider\");\n }\n\n return context;\n}\n","/**\n * PillarPanel Component\n * Renders the Pillar help panel at a custom location in the DOM\n */\n\nimport React, { useRef, useEffect, type CSSProperties, type HTMLAttributes } from 'react';\nimport { usePillarContext } from './PillarProvider';\n\nexport interface PillarPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** Custom class name for the container */\n className?: string;\n \n /** Custom inline styles for the container */\n style?: CSSProperties;\n}\n\n/**\n * Renders the Pillar help panel at a custom location in the DOM.\n * Use this when you want to control where the panel is rendered instead of\n * having it automatically appended to document.body.\n * \n * **Important**: When using this component, set `panel.container: 'manual'` in your\n * PillarProvider config to prevent automatic mounting.\n * \n * @example\n * ```tsx\n * <PillarProvider \n * helpCenter=\"my-help\" \n * publicKey=\"pk_xxx\"\n * config={{ panel: { container: 'manual' } }}\n * >\n * <div className=\"my-layout\">\n * <Sidebar />\n * <PillarPanel className=\"help-panel-container\" />\n * <MainContent />\n * </div>\n * </PillarProvider>\n * ```\n */\nexport function PillarPanel({ \n className, \n style,\n ...props \n}: PillarPanelProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null);\n const { pillar, isReady } = usePillarContext();\n const hasMounted = useRef(false);\n\n useEffect(() => {\n // Only mount once when SDK is ready and we have a container\n if (!isReady || !pillar || !containerRef.current || hasMounted.current) {\n return;\n }\n\n // Mount the panel into our container\n pillar.mountPanelTo(containerRef.current);\n hasMounted.current = true;\n\n // Cleanup is handled by Pillar.destroy() in the provider\n }, [isReady, pillar]);\n\n return (\n <div \n ref={containerRef} \n className={className}\n style={style}\n data-pillar-panel-container=\"\"\n {...props}\n />\n );\n}\n\n","/**\n * useHelpPanel Hook\n * Panel-specific controls and state\n */\n\nimport { useCallback } from 'react';\nimport { usePillarContext } from '../PillarProvider';\n\nexport interface UseHelpPanelResult {\n /** Whether the panel is currently open */\n isOpen: boolean;\n \n /** Open the panel */\n open: (options?: { view?: string; article?: string; search?: string }) => void;\n \n /** Close the panel */\n close: () => void;\n \n /** Toggle the panel */\n toggle: () => void;\n \n /** Open a specific article in the panel */\n openArticle: (slug: string) => void;\n \n /** Open a specific category in the panel */\n openCategory: (slug: string) => Promise<void>;\n \n /** Open search with a query */\n openSearch: (query?: string) => void;\n \n /** Open the AI chat */\n openChat: () => void;\n}\n\n/**\n * Hook for panel-specific controls\n * \n * @example\n * ```tsx\n * function HelpButton() {\n * const { isOpen, toggle, openChat } = useHelpPanel();\n * \n * return (\n * <div>\n * <button onClick={toggle}>\n * {isOpen ? 'Close' : 'Help'}\n * </button>\n * <button onClick={openChat}>\n * Ask AI\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useHelpPanel(): UseHelpPanelResult {\n const { isPanelOpen, open, close, toggle, openArticle, openCategory, search, navigate } = usePillarContext();\n\n const openSearch = useCallback(\n (query?: string) => {\n if (query) {\n search(query);\n } else {\n open({ view: 'search' });\n }\n },\n [search, open]\n );\n\n const openChat = useCallback(() => {\n navigate('chat');\n }, [navigate]);\n\n return {\n isOpen: isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n openSearch,\n openChat,\n };\n}\n\n","/**\n * usePillar Hook\n * Access Pillar SDK instance and state with optional type-safe onTask\n */\n\nimport type {\n SyncActionDefinitions,\n ActionDefinitions,\n ActionDataType,\n ActionNames,\n} from '@pillar-ai/sdk';\nimport { useCallback } from 'react';\nimport { usePillarContext, type PillarContextValue } from '../PillarProvider';\n\nexport type UsePillarResult = PillarContextValue;\n\n/**\n * Extended result with type-safe onTask method.\n *\n * @template TActions - The action definitions for type inference\n */\nexport interface TypedUsePillarResult<\n TActions extends SyncActionDefinitions | ActionDefinitions,\n> extends Omit<PillarContextValue, 'pillar'> {\n pillar: PillarContextValue['pillar'];\n /**\n * Type-safe task handler registration.\n *\n * @param taskName - The action name (autocompleted from your actions)\n * @param handler - Handler function with typed data parameter\n * @returns Unsubscribe function\n */\n onTask: <TName extends ActionNames<TActions>>(\n taskName: TName,\n handler: (data: ActionDataType<TActions, TName>) => void\n ) => () => void;\n}\n\n/**\n * Hook to access the Pillar SDK instance and state\n *\n * @example Basic usage (untyped)\n * ```tsx\n * function MyComponent() {\n * const { isReady, open, close, isPanelOpen } = usePillar();\n *\n * if (!isReady) return <div>Loading...</div>;\n *\n * return (\n * <button onClick={() => open()}>\n * {isPanelOpen ? 'Close Help' : 'Get Help'}\n * </button>\n * );\n * }\n * ```\n *\n * @example Type-safe onTask with action definitions\n * ```tsx\n * import { actions } from '@/lib/pillar/actions';\n *\n * function MyComponent() {\n * const { pillar, onTask } = usePillar<typeof actions>();\n *\n * useEffect(() => {\n * // TypeScript knows data has { type, url, name }\n * const unsub = onTask('add_new_source', (data) => {\n * console.log(data.url); // ✓ Typed!\n * });\n * return unsub;\n * }, [onTask]);\n * }\n * ```\n */\nexport function usePillar<\n TActions extends SyncActionDefinitions | ActionDefinitions = SyncActionDefinitions,\n>(): TypedUsePillarResult<TActions> {\n const context = usePillarContext();\n\n // Create a type-safe wrapper around pillar.onTask\n const onTask = useCallback(\n <TName extends ActionNames<TActions>>(\n taskName: TName,\n handler: (data: ActionDataType<TActions, TName>) => void\n ): (() => void) => {\n if (!context.pillar) {\n // Return no-op if pillar not ready\n return () => {};\n }\n // Cast handler to match the SDK's expected type\n // The runtime behavior is the same, this is just for type narrowing\n return context.pillar.onTask(\n taskName as string,\n handler as (data: Record<string, unknown>) => void\n );\n },\n [context.pillar]\n );\n\n return {\n ...context,\n onTask,\n };\n}\n\n"],"names":["_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/PillarProvider.tsx","../src/PillarPanel.tsx","../src/hooks/useHelpPanel.ts","../src/hooks/usePillar.ts","../src/hooks/usePillarTool.ts"],"sourcesContent":["/**\n * PillarProvider\n * Context provider that initializes and manages the Pillar SDK\n */\n\nimport {\n Pillar,\n scanPageDirect,\n type CardCallbacks,\n type CompactScanResult,\n type PillarConfig,\n type PillarEvents,\n type PillarState,\n type ScanOptions,\n type TaskExecutePayload,\n type ThemeConfig,\n} from \"@pillar-ai/sdk\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type ReactNode,\n} from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\n// ============================================================================\n// Card Types\n// ============================================================================\n\n/**\n * Props passed to custom card components.\n */\nexport interface CardComponentProps<T = Record<string, unknown>> {\n /** Data extracted by the AI for this action */\n data: T;\n /** Called when user confirms the action */\n onConfirm: (modifiedData?: Record<string, unknown>) => void;\n /** Called when user cancels the action */\n onCancel: () => void;\n /** Called to report state changes (loading, success, error) */\n onStateChange?: (\n state: \"loading\" | \"success\" | \"error\",\n message?: string\n ) => void;\n}\n\n/**\n * A React component that can be used as a custom card renderer.\n */\nexport type CardComponent<T = Record<string, unknown>> = ComponentType<\n CardComponentProps<T>\n>;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PillarContextValue {\n /** The Pillar SDK instance */\n pillar: Pillar | null;\n\n /** Current SDK state */\n state: PillarState;\n\n /** Whether the SDK is ready */\n isReady: boolean;\n\n /** Whether the panel is currently open */\n isPanelOpen: boolean;\n\n /** Open the help panel */\n open: (options?: {\n view?: string;\n article?: string;\n search?: string;\n focusInput?: boolean;\n }) => void;\n\n /** Close the help panel */\n close: () => void;\n\n /** Toggle the help panel */\n toggle: () => void;\n\n /** Open a specific article */\n openArticle: (slug: string) => void;\n\n /** Open a specific category */\n openCategory: (slug: string) => Promise<void>;\n\n /** Perform a search */\n search: (query: string) => void;\n\n /** Navigate to a specific view */\n navigate: (view: string, params?: Record<string, string>) => void;\n\n /** Update the panel theme at runtime */\n setTheme: (theme: Partial<ThemeConfig>) => void;\n\n /** Enable or disable the text selection \"Ask AI\" popover */\n setTextSelectionEnabled: (enabled: boolean) => void;\n\n /** Enable or disable DOM scanning */\n setDOMScanningEnabled: (enabled: boolean) => void;\n\n /** Whether DOM scanning is enabled */\n isDOMScanningEnabled: boolean;\n\n /** Manually scan the page and get the compact result */\n scanPage: (options?: ScanOptions) => CompactScanResult | null;\n\n /** Subscribe to SDK events */\n on: <K extends keyof PillarEvents>(\n event: K,\n callback: (data: PillarEvents[K]) => void\n ) => () => void;\n}\n\nexport interface PillarProviderProps {\n /**\n * Your product key from the Pillar app.\n * Get it at app.trypillar.com\n */\n productKey?: string;\n\n /**\n * @deprecated Use `productKey` instead. Will be removed in v1.0.\n */\n helpCenter?: string;\n\n /**\n * Additional SDK configuration\n *\n * Notable options:\n * - `panel.useShadowDOM`: Whether to isolate styles in Shadow DOM (default: false).\n * Set to false to let custom cards inherit your app's CSS (Tailwind, etc.)\n */\n config?: Omit<PillarConfig, \"productKey\" | \"helpCenter\">;\n\n /**\n * Handler called when a task action is triggered from the chat.\n * Use this to handle AI-suggested actions like opening modals, navigating, etc.\n *\n * @example\n * ```tsx\n * <PillarProvider\n * productKey=\"my-product-key\"\n * onTask={(task) => {\n * switch (task.name) {\n * case 'invite_team_member':\n * openInviteModal(task.data);\n * break;\n * case 'open_settings':\n * router.push('/settings');\n * break;\n * }\n * }}\n * >\n * ```\n */\n onTask?: (task: TaskExecutePayload) => void;\n\n /**\n * Custom card components to render for inline_ui type actions.\n * Map card type names to React components that will render the inline UI.\n *\n * @example\n * ```tsx\n * import { InviteMembersCard } from './cards/InviteMembersCard';\n *\n * <PillarProvider\n * productKey=\"my-product-key\"\n * cards={{\n * invite_members: InviteMembersCard,\n * confirm_delete: ConfirmDeleteCard,\n * }}\n * >\n * ```\n */\n cards?: Record<string, CardComponent>;\n\n /**\n * Enable DOM scanning to send page context with messages.\n * When enabled, interactable elements and text content are captured and sent to the LLM.\n * @default false\n */\n domScanning?: boolean;\n\n /**\n * Enable DOM scanning dev mode to preview the scanned page before sending.\n * Shows a modal with the AST tree visualization before each message is sent.\n * Useful for debugging what context will be sent to the LLM.\n /** Children components */\n children: ReactNode;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst PillarContext = createContext<PillarContextValue | null>(null);\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\nexport function PillarProvider({\n productKey,\n helpCenter,\n config,\n onTask,\n cards,\n domScanning,\n children,\n}: PillarProviderProps): React.ReactElement {\n const [pillar, setPillar] = useState<Pillar | null>(null);\n const [state, setState] = useState<PillarState>(\"uninitialized\");\n const [isPanelOpen, setIsPanelOpen] = useState(false);\n const [isDOMScanningEnabled, setIsDOMScanningEnabledState] = useState(domScanning ?? false);\n\n // Support both productKey (new) and helpCenter (deprecated)\n const resolvedKey = productKey ?? helpCenter;\n\n // Keep a ref to the latest onTask callback to avoid re-subscribing\n const onTaskRef = useRef(onTask);\n onTaskRef.current = onTask;\n\n // Warn about deprecated helpCenter usage\n useEffect(() => {\n if (helpCenter && !productKey) {\n console.warn(\n '[Pillar React] \"helpCenter\" prop is deprecated. Use \"productKey\" instead.'\n );\n }\n }, []);\n\n // Keep a ref to cards to access latest versions\n const cardsRef = useRef(cards);\n cardsRef.current = cards;\n\n // Initialize SDK\n useEffect(() => {\n let mounted = true;\n\n const initPillar = async () => {\n try {\n // Pillar is a singleton - check if already initialized\n const existingInstance = Pillar.getInstance();\n if (existingInstance) {\n // Reuse existing instance (preserves chat history, panel state, etc.)\n if (mounted) {\n setPillar(existingInstance);\n setState(existingInstance.state);\n\n // Apply DOM scanning settings to existing instance\n if (domScanning !== undefined) {\n existingInstance.setDOMScanningEnabled(domScanning);\n }\n\n // Re-subscribe to events\n existingInstance.on(\"panel:open\", () => {\n setIsPanelOpen(true);\n });\n\n existingInstance.on(\"panel:close\", () => {\n setIsPanelOpen(false);\n });\n }\n return;\n }\n\n // Initialize new instance\n const instance = await Pillar.init({\n productKey: resolvedKey,\n ...config,\n domScanning: {\n ...config?.domScanning,\n // Explicit prop overrides config value\n enabled: domScanning ?? config?.domScanning?.enabled ?? false,\n },\n });\n\n if (mounted) {\n setPillar(instance);\n setState(instance.state);\n\n // Listen for panel open/close\n instance.on(\"panel:open\", () => {\n setIsPanelOpen(true);\n });\n\n instance.on(\"panel:close\", () => {\n setIsPanelOpen(false);\n });\n }\n } catch (error) {\n console.error(\"[Pillar React] Failed to initialize:\", error);\n if (mounted) {\n setState(\"error\");\n }\n }\n };\n\n initPillar();\n\n // Cleanup - DON'T destroy the singleton on unmount\n // This preserves conversation history and panel state across navigation\n // Pillar.destroy() should only be called explicitly when the app unmounts completely\n return () => {\n mounted = false;\n // Note: We intentionally don't call Pillar.destroy() here\n // The singleton persists to maintain state across route changes\n };\n }, [resolvedKey]); // Re-initialize if productKey changes\n\n // Update state when SDK state changes\n useEffect(() => {\n if (pillar) {\n const unsubscribe = pillar.on(\"ready\", () => {\n setState(\"ready\");\n });\n\n const unsubscribeError = pillar.on(\"error\", () => {\n setState(\"error\");\n });\n\n return () => {\n unsubscribe();\n unsubscribeError();\n };\n }\n }, [pillar]);\n\n // Register task handler\n useEffect(() => {\n if (pillar) {\n const unsubscribe = pillar.on(\"task:execute\", (task) => {\n onTaskRef.current?.(task);\n });\n\n return unsubscribe;\n }\n }, [pillar]);\n\n // Sync DOM scanning props with SDK when they change\n useEffect(() => {\n if (pillar) {\n if (domScanning !== undefined) {\n pillar.setDOMScanningEnabled(domScanning);\n setIsDOMScanningEnabledState(domScanning);\n }\n }\n }, [pillar, domScanning]);\n\n // Register custom card renderers\n useEffect(() => {\n if (!pillar || !cards) return;\n\n const unsubscribers: Array<() => void> = [];\n const roots: Map<HTMLElement, Root> = new Map();\n\n // Register each card component as a vanilla renderer\n Object.entries(cards).forEach(([cardType, Component]) => {\n const unsubscribe = pillar.registerCard(\n cardType,\n (container, data, callbacks: CardCallbacks) => {\n // Create a React root for this container\n const root = createRoot(container);\n roots.set(container, root);\n\n // Render the React component\n root.render(\n <Component\n data={data}\n onConfirm={callbacks.onConfirm}\n onCancel={callbacks.onCancel}\n onStateChange={callbacks.onStateChange}\n />\n );\n\n // Return cleanup function\n return () => {\n const existingRoot = roots.get(container);\n if (existingRoot) {\n existingRoot.unmount();\n roots.delete(container);\n }\n };\n }\n );\n\n unsubscribers.push(unsubscribe);\n });\n\n return () => {\n // Cleanup all registrations\n unsubscribers.forEach((unsub) => unsub());\n // Unmount all React roots\n roots.forEach((root) => root.unmount());\n roots.clear();\n };\n }, [pillar, cards]);\n\n // Actions\n const open = useCallback(\n (options?: {\n view?: string;\n article?: string;\n search?: string;\n focusInput?: boolean;\n }) => {\n pillar?.open(options);\n },\n [pillar]\n );\n\n const close = useCallback(() => {\n pillar?.close();\n }, [pillar]);\n\n const toggle = useCallback(() => {\n pillar?.toggle();\n }, [pillar]);\n\n const openArticle = useCallback(\n (slug: string) => {\n pillar?.open({ article: slug });\n },\n [pillar]\n );\n\n const openCategory = useCallback(\n async (slug: string) => {\n pillar?.navigate(\"category\", { slug });\n },\n [pillar]\n );\n\n const search = useCallback(\n (query: string) => {\n pillar?.open({ search: query });\n },\n [pillar]\n );\n\n const navigate = useCallback(\n (view: string, params?: Record<string, string>) => {\n pillar?.navigate(view, params);\n },\n [pillar]\n );\n\n const setTheme = useCallback(\n (theme: Partial<ThemeConfig>) => {\n pillar?.setTheme(theme);\n },\n [pillar]\n );\n\n const setTextSelectionEnabled = useCallback(\n (enabled: boolean) => {\n pillar?.setTextSelectionEnabled(enabled);\n },\n [pillar]\n );\n\n const setDOMScanningEnabled = useCallback(\n (enabled: boolean) => {\n pillar?.setDOMScanningEnabled(enabled);\n setIsDOMScanningEnabledState(enabled);\n },\n [pillar]\n );\n\n const scanPage = useCallback(\n (options?: ScanOptions): CompactScanResult | null => {\n if (!pillar) return null;\n return scanPageDirect(options);\n },\n [pillar]\n );\n\n const on = useCallback(\n <K extends keyof PillarEvents>(\n event: K,\n callback: (data: PillarEvents[K]) => void\n ) => {\n return pillar?.on(event, callback) ?? (() => {});\n },\n [pillar]\n );\n\n // Context value\n const value = useMemo<PillarContextValue>(\n () => ({\n pillar,\n state,\n isReady: state === \"ready\",\n isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n search,\n navigate,\n setTheme,\n setTextSelectionEnabled,\n setDOMScanningEnabled,\n isDOMScanningEnabled,\n scanPage,\n on,\n }),\n [\n pillar,\n state,\n isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n search,\n navigate,\n setTheme,\n setTextSelectionEnabled,\n setDOMScanningEnabled,\n isDOMScanningEnabled,\n scanPage,\n on,\n ]\n );\n\n return (\n <PillarContext.Provider value={value}>{children}</PillarContext.Provider>\n );\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function usePillarContext(): PillarContextValue {\n const context = useContext(PillarContext);\n\n if (!context) {\n throw new Error(\"usePillarContext must be used within a PillarProvider\");\n }\n\n return context;\n}\n","/**\n * PillarPanel Component\n * Renders the Pillar help panel at a custom location in the DOM\n */\n\nimport React, { useRef, useEffect, type CSSProperties, type HTMLAttributes } from 'react';\nimport { usePillarContext } from './PillarProvider';\n\nexport interface PillarPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** Custom class name for the container */\n className?: string;\n \n /** Custom inline styles for the container */\n style?: CSSProperties;\n}\n\n/**\n * Renders the Pillar help panel at a custom location in the DOM.\n * Use this when you want to control where the panel is rendered instead of\n * having it automatically appended to document.body.\n * \n * **Important**: When using this component, set `panel.container: 'manual'` in your\n * PillarProvider config to prevent automatic mounting.\n * \n * @example\n * ```tsx\n * <PillarProvider \n * productKey=\"my-product-key\"\n * config={{ panel: { container: 'manual' } }}\n * >\n * <div className=\"my-layout\">\n * <Sidebar />\n * <PillarPanel className=\"help-panel-container\" />\n * <MainContent />\n * </div>\n * </PillarProvider>\n * ```\n */\nexport function PillarPanel({ \n className, \n style,\n ...props \n}: PillarPanelProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null);\n const { pillar, isReady } = usePillarContext();\n const hasMounted = useRef(false);\n\n useEffect(() => {\n // Only mount once when SDK is ready and we have a container\n if (!isReady || !pillar || !containerRef.current || hasMounted.current) {\n return;\n }\n\n // Mount the panel into our container\n pillar.mountPanelTo(containerRef.current);\n hasMounted.current = true;\n\n // Cleanup is handled by Pillar.destroy() in the provider\n }, [isReady, pillar]);\n\n return (\n <div \n ref={containerRef} \n className={className}\n style={style}\n data-pillar-panel-container=\"\"\n {...props}\n />\n );\n}\n\n","/**\n * useHelpPanel Hook\n * Panel-specific controls and state\n */\n\nimport { useCallback } from 'react';\nimport { usePillarContext } from '../PillarProvider';\n\nexport interface UseHelpPanelResult {\n /** Whether the panel is currently open */\n isOpen: boolean;\n \n /** Open the panel */\n open: (options?: { view?: string; article?: string; search?: string }) => void;\n \n /** Close the panel */\n close: () => void;\n \n /** Toggle the panel */\n toggle: () => void;\n \n /** Open a specific article in the panel */\n openArticle: (slug: string) => void;\n \n /** Open a specific category in the panel */\n openCategory: (slug: string) => Promise<void>;\n \n /** Open search with a query */\n openSearch: (query?: string) => void;\n \n /** Open the AI chat */\n openChat: () => void;\n}\n\n/**\n * Hook for panel-specific controls\n * \n * @example\n * ```tsx\n * function HelpButton() {\n * const { isOpen, toggle, openChat } = useHelpPanel();\n * \n * return (\n * <div>\n * <button onClick={toggle}>\n * {isOpen ? 'Close' : 'Help'}\n * </button>\n * <button onClick={openChat}>\n * Ask AI\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useHelpPanel(): UseHelpPanelResult {\n const { isPanelOpen, open, close, toggle, openArticle, openCategory, search, navigate } = usePillarContext();\n\n const openSearch = useCallback(\n (query?: string) => {\n if (query) {\n search(query);\n } else {\n open({ view: 'search' });\n }\n },\n [search, open]\n );\n\n const openChat = useCallback(() => {\n navigate('chat');\n }, [navigate]);\n\n return {\n isOpen: isPanelOpen,\n open,\n close,\n toggle,\n openArticle,\n openCategory,\n openSearch,\n openChat,\n };\n}\n\n","/**\n * usePillar Hook\n * Access Pillar SDK instance and state with optional type-safe onTask\n */\n\nimport type {\n SyncActionDefinitions,\n ActionDefinitions,\n ActionDataType,\n ActionNames,\n} from '@pillar-ai/sdk';\nimport { useCallback } from 'react';\nimport { usePillarContext, type PillarContextValue } from '../PillarProvider';\n\nexport type UsePillarResult = PillarContextValue;\n\n/**\n * Extended result with type-safe onTask method.\n *\n * @template TActions - The action definitions for type inference\n */\nexport interface TypedUsePillarResult<\n TActions extends SyncActionDefinitions | ActionDefinitions,\n> extends Omit<PillarContextValue, 'pillar'> {\n pillar: PillarContextValue['pillar'];\n /**\n * Type-safe task handler registration.\n *\n * @param taskName - The action name (autocompleted from your actions)\n * @param handler - Handler function with typed data parameter\n * @returns Unsubscribe function\n */\n onTask: <TName extends ActionNames<TActions>>(\n taskName: TName,\n handler: (data: ActionDataType<TActions, TName>) => void\n ) => () => void;\n}\n\n/**\n * Hook to access the Pillar SDK instance and state\n *\n * @example Basic usage (untyped)\n * ```tsx\n * function MyComponent() {\n * const { isReady, open, close, isPanelOpen } = usePillar();\n *\n * if (!isReady) return <div>Loading...</div>;\n *\n * return (\n * <button onClick={() => open()}>\n * {isPanelOpen ? 'Close Help' : 'Get Help'}\n * </button>\n * );\n * }\n * ```\n *\n * @example Type-safe onTask with action definitions\n * ```tsx\n * import { actions } from '@/lib/pillar/actions';\n *\n * function MyComponent() {\n * const { pillar, onTask } = usePillar<typeof actions>();\n *\n * useEffect(() => {\n * // TypeScript knows data has { type, url, name }\n * const unsub = onTask('add_new_source', (data) => {\n * console.log(data.url); // ✓ Typed!\n * });\n * return unsub;\n * }, [onTask]);\n * }\n * ```\n */\nexport function usePillar<\n TActions extends SyncActionDefinitions | ActionDefinitions = SyncActionDefinitions,\n>(): TypedUsePillarResult<TActions> {\n const context = usePillarContext();\n\n // Create a type-safe wrapper around pillar.onTask\n const onTask = useCallback(\n <TName extends ActionNames<TActions>>(\n taskName: TName,\n handler: (data: ActionDataType<TActions, TName>) => void\n ): (() => void) => {\n if (!context.pillar) {\n // Return no-op if pillar not ready\n return () => {};\n }\n // Cast handler to match the SDK's expected type\n // The runtime behavior is the same, this is just for type narrowing\n return context.pillar.onTask(\n taskName as string,\n handler as (data: Record<string, unknown>) => void\n );\n },\n [context.pillar]\n );\n\n return {\n ...context,\n onTask,\n };\n}\n\n","/**\n * usePillarTool Hook\n *\n * Register one or more tools with co-located metadata and handlers.\n * Tools are registered on mount and unregistered on unmount.\n *\n * @example Single tool\n * ```tsx\n * import { usePillarTool } from '@pillar-ai/react';\n *\n * function CartButton() {\n * usePillarTool({\n * name: 'add_to_cart',\n * description: 'Add a product to the shopping cart',\n * inputSchema: {\n * type: 'object',\n * properties: {\n * productId: { type: 'string', description: 'Product ID' },\n * quantity: { type: 'number', description: 'Quantity to add' },\n * },\n * required: ['productId', 'quantity'],\n * },\n * execute: async ({ productId, quantity }) => {\n * await cartApi.add(productId, quantity);\n * return { content: [{ type: 'text', text: 'Added to cart' }] };\n * },\n * });\n *\n * return <button>Cart</button>;\n * }\n * ```\n *\n * @example Multiple tools\n * ```tsx\n * import { usePillarTool } from '@pillar-ai/react';\n *\n * function BillingPage() {\n * usePillarTool([\n * {\n * name: 'get_current_plan',\n * description: 'Get the current billing plan',\n * execute: async () => ({ plan: 'pro', price: 29 }),\n * },\n * {\n * name: 'upgrade_plan',\n * description: 'Upgrade to a higher plan',\n * inputSchema: {\n * type: 'object',\n * properties: { planId: { type: 'string' } },\n * required: ['planId'],\n * },\n * execute: async ({ planId }) => {\n * await billingApi.upgrade(planId);\n * return { content: [{ type: 'text', text: 'Upgraded!' }] };\n * },\n * },\n * ]);\n *\n * return <div>Billing Content</div>;\n * }\n * ```\n */\n\nimport { useEffect, useRef, useMemo } from 'react';\nimport type { ToolSchema } from '@pillar-ai/sdk';\nimport { usePillarContext } from '../PillarProvider';\n\n/**\n * Register a single Pillar tool with co-located metadata and handler.\n */\nexport function usePillarTool<TInput = Record<string, unknown>>(\n schema: ToolSchema<TInput>\n): void;\n\n/**\n * Register multiple Pillar tools with co-located metadata and handlers.\n */\nexport function usePillarTool(schemas: ToolSchema[]): void;\n\n/**\n * Register one or more Pillar tools with co-located metadata and handlers.\n *\n * The tools are registered when the component mounts and automatically\n * unregistered when it unmounts. The `execute` functions always capture\n * the latest React state and props via refs, so you don't need to worry\n * about stale closures.\n *\n * @param schemaOrSchemas - Single tool schema or array of tool schemas\n */\nexport function usePillarTool<TInput = Record<string, unknown>>(\n schemaOrSchemas: ToolSchema<TInput> | ToolSchema[]\n): void {\n const { pillar } = usePillarContext();\n\n // Normalize to array for consistent handling\n const schemas = useMemo(\n () => (Array.isArray(schemaOrSchemas) ? schemaOrSchemas : [schemaOrSchemas]),\n [schemaOrSchemas]\n );\n\n // Keep refs to latest schemas so handlers capture current state/props\n const schemasRef = useRef(schemas);\n schemasRef.current = schemas;\n\n // Stable dependency key for the effect (tool names joined)\n const toolNamesKey = useMemo(\n () => schemas.map((s) => s.name).join(','),\n [schemas]\n );\n\n useEffect(() => {\n if (!pillar) return;\n\n // Register all tools and collect unsubscribe functions\n const unsubscribes = schemasRef.current.map((schema, index) => {\n return pillar.defineTool({\n ...schema,\n // Wrap execute to always use the latest ref version\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execute: (input: any) => schemasRef.current[index].execute(input),\n } as ToolSchema);\n });\n\n // Cleanup: unregister all tools\n return () => {\n unsubscribes.forEach((unsub) => unsub());\n };\n }, [pillar, toolNamesKey]);\n}\n\n/** @deprecated Use usePillarTool instead */\nexport const usePillarAction = usePillarTool;\n"],"names":["_jsx"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -12,13 +12,22 @@ const PillarContext = react.createContext(null);
12
12
  // ============================================================================
13
13
  // Provider Component
14
14
  // ============================================================================
15
- function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children, }) {
15
+ function PillarProvider({ productKey, helpCenter, config, onTask, cards, domScanning, children, }) {
16
16
  const [pillar, setPillar] = react.useState(null);
17
17
  const [state, setState] = react.useState("uninitialized");
18
18
  const [isPanelOpen, setIsPanelOpen] = react.useState(false);
19
+ const [isDOMScanningEnabled, setIsDOMScanningEnabledState] = react.useState(domScanning ?? false);
20
+ // Support both productKey (new) and helpCenter (deprecated)
21
+ const resolvedKey = productKey ?? helpCenter;
19
22
  // Keep a ref to the latest onTask callback to avoid re-subscribing
20
23
  const onTaskRef = react.useRef(onTask);
21
24
  onTaskRef.current = onTask;
25
+ // Warn about deprecated helpCenter usage
26
+ react.useEffect(() => {
27
+ if (helpCenter && !productKey) {
28
+ console.warn('[Pillar React] "helpCenter" prop is deprecated. Use "productKey" instead.');
29
+ }
30
+ }, []);
22
31
  // Keep a ref to cards to access latest versions
23
32
  const cardsRef = react.useRef(cards);
24
33
  cardsRef.current = cards;
@@ -34,6 +43,10 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
34
43
  if (mounted) {
35
44
  setPillar(existingInstance);
36
45
  setState(existingInstance.state);
46
+ // Apply DOM scanning settings to existing instance
47
+ if (domScanning !== undefined) {
48
+ existingInstance.setDOMScanningEnabled(domScanning);
49
+ }
37
50
  // Re-subscribe to events
38
51
  existingInstance.on("panel:open", () => {
39
52
  setIsPanelOpen(true);
@@ -46,9 +59,13 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
46
59
  }
47
60
  // Initialize new instance
48
61
  const instance = await sdk.Pillar.init({
49
- helpCenter,
50
- publicKey,
62
+ productKey: resolvedKey,
51
63
  ...config,
64
+ domScanning: {
65
+ ...config?.domScanning,
66
+ // Explicit prop overrides config value
67
+ enabled: domScanning ?? config?.domScanning?.enabled ?? false,
68
+ },
52
69
  });
53
70
  if (mounted) {
54
71
  setPillar(instance);
@@ -78,7 +95,7 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
78
95
  // Note: We intentionally don't call Pillar.destroy() here
79
96
  // The singleton persists to maintain state across route changes
80
97
  };
81
- }, [helpCenter, publicKey]); // Re-initialize if credentials change
98
+ }, [resolvedKey]); // Re-initialize if productKey changes
82
99
  // Update state when SDK state changes
83
100
  react.useEffect(() => {
84
101
  if (pillar) {
@@ -103,6 +120,15 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
103
120
  return unsubscribe;
104
121
  }
105
122
  }, [pillar]);
123
+ // Sync DOM scanning props with SDK when they change
124
+ react.useEffect(() => {
125
+ if (pillar) {
126
+ if (domScanning !== undefined) {
127
+ pillar.setDOMScanningEnabled(domScanning);
128
+ setIsDOMScanningEnabledState(domScanning);
129
+ }
130
+ }
131
+ }, [pillar, domScanning]);
106
132
  // Register custom card renderers
107
133
  react.useEffect(() => {
108
134
  if (!pillar || !cards)
@@ -164,6 +190,15 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
164
190
  const setTextSelectionEnabled = react.useCallback((enabled) => {
165
191
  pillar?.setTextSelectionEnabled(enabled);
166
192
  }, [pillar]);
193
+ const setDOMScanningEnabled = react.useCallback((enabled) => {
194
+ pillar?.setDOMScanningEnabled(enabled);
195
+ setIsDOMScanningEnabledState(enabled);
196
+ }, [pillar]);
197
+ const scanPage = react.useCallback((options) => {
198
+ if (!pillar)
199
+ return null;
200
+ return sdk.scanPageDirect(options);
201
+ }, [pillar]);
167
202
  const on = react.useCallback((event, callback) => {
168
203
  return pillar?.on(event, callback) ?? (() => { });
169
204
  }, [pillar]);
@@ -182,6 +217,9 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
182
217
  navigate,
183
218
  setTheme,
184
219
  setTextSelectionEnabled,
220
+ setDOMScanningEnabled,
221
+ isDOMScanningEnabled,
222
+ scanPage,
185
223
  on,
186
224
  }), [
187
225
  pillar,
@@ -196,6 +234,9 @@ function PillarProvider({ helpCenter, publicKey, config, onTask, cards, children
196
234
  navigate,
197
235
  setTheme,
198
236
  setTextSelectionEnabled,
237
+ setDOMScanningEnabled,
238
+ isDOMScanningEnabled,
239
+ scanPage,
199
240
  on,
200
241
  ]);
201
242
  return (jsxRuntime.jsx(PillarContext.Provider, { value: value, children: children }));
@@ -222,8 +263,7 @@ function usePillarContext() {
222
263
  * @example
223
264
  * ```tsx
224
265
  * <PillarProvider
225
- * helpCenter="my-help"
226
- * publicKey="pk_xxx"
266
+ * productKey="my-product-key"
227
267
  * config={{ panel: { container: 'manual' } }}
228
268
  * >
229
269
  * <div className="my-layout">
@@ -358,9 +398,113 @@ function usePillar() {
358
398
  };
359
399
  }
360
400
 
401
+ /**
402
+ * usePillarTool Hook
403
+ *
404
+ * Register one or more tools with co-located metadata and handlers.
405
+ * Tools are registered on mount and unregistered on unmount.
406
+ *
407
+ * @example Single tool
408
+ * ```tsx
409
+ * import { usePillarTool } from '@pillar-ai/react';
410
+ *
411
+ * function CartButton() {
412
+ * usePillarTool({
413
+ * name: 'add_to_cart',
414
+ * description: 'Add a product to the shopping cart',
415
+ * inputSchema: {
416
+ * type: 'object',
417
+ * properties: {
418
+ * productId: { type: 'string', description: 'Product ID' },
419
+ * quantity: { type: 'number', description: 'Quantity to add' },
420
+ * },
421
+ * required: ['productId', 'quantity'],
422
+ * },
423
+ * execute: async ({ productId, quantity }) => {
424
+ * await cartApi.add(productId, quantity);
425
+ * return { content: [{ type: 'text', text: 'Added to cart' }] };
426
+ * },
427
+ * });
428
+ *
429
+ * return <button>Cart</button>;
430
+ * }
431
+ * ```
432
+ *
433
+ * @example Multiple tools
434
+ * ```tsx
435
+ * import { usePillarTool } from '@pillar-ai/react';
436
+ *
437
+ * function BillingPage() {
438
+ * usePillarTool([
439
+ * {
440
+ * name: 'get_current_plan',
441
+ * description: 'Get the current billing plan',
442
+ * execute: async () => ({ plan: 'pro', price: 29 }),
443
+ * },
444
+ * {
445
+ * name: 'upgrade_plan',
446
+ * description: 'Upgrade to a higher plan',
447
+ * inputSchema: {
448
+ * type: 'object',
449
+ * properties: { planId: { type: 'string' } },
450
+ * required: ['planId'],
451
+ * },
452
+ * execute: async ({ planId }) => {
453
+ * await billingApi.upgrade(planId);
454
+ * return { content: [{ type: 'text', text: 'Upgraded!' }] };
455
+ * },
456
+ * },
457
+ * ]);
458
+ *
459
+ * return <div>Billing Content</div>;
460
+ * }
461
+ * ```
462
+ */
463
+ /**
464
+ * Register one or more Pillar tools with co-located metadata and handlers.
465
+ *
466
+ * The tools are registered when the component mounts and automatically
467
+ * unregistered when it unmounts. The `execute` functions always capture
468
+ * the latest React state and props via refs, so you don't need to worry
469
+ * about stale closures.
470
+ *
471
+ * @param schemaOrSchemas - Single tool schema or array of tool schemas
472
+ */
473
+ function usePillarTool(schemaOrSchemas) {
474
+ const { pillar } = usePillarContext();
475
+ // Normalize to array for consistent handling
476
+ const schemas = react.useMemo(() => (Array.isArray(schemaOrSchemas) ? schemaOrSchemas : [schemaOrSchemas]), [schemaOrSchemas]);
477
+ // Keep refs to latest schemas so handlers capture current state/props
478
+ const schemasRef = react.useRef(schemas);
479
+ schemasRef.current = schemas;
480
+ // Stable dependency key for the effect (tool names joined)
481
+ const toolNamesKey = react.useMemo(() => schemas.map((s) => s.name).join(','), [schemas]);
482
+ react.useEffect(() => {
483
+ if (!pillar)
484
+ return;
485
+ // Register all tools and collect unsubscribe functions
486
+ const unsubscribes = schemasRef.current.map((schema, index) => {
487
+ return pillar.defineTool({
488
+ ...schema,
489
+ // Wrap execute to always use the latest ref version
490
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
491
+ execute: (input) => schemasRef.current[index].execute(input),
492
+ });
493
+ });
494
+ // Cleanup: unregister all tools
495
+ return () => {
496
+ unsubscribes.forEach((unsub) => unsub());
497
+ };
498
+ }, [pillar, toolNamesKey]);
499
+ }
500
+ /** @deprecated Use usePillarTool instead */
501
+ const usePillarAction = usePillarTool;
502
+
361
503
  exports.PillarPanel = PillarPanel;
362
504
  exports.PillarProvider = PillarProvider;
363
505
  exports.useHelpPanel = useHelpPanel;
364
506
  exports.usePillar = usePillar;
507
+ exports.usePillarAction = usePillarAction;
365
508
  exports.usePillarContext = usePillarContext;
509
+ exports.usePillarTool = usePillarTool;
366
510
  //# sourceMappingURL=index.js.map