@pillar-ai/sdk 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pillar-ai/sdk
2
2
 
3
- Pillar Embedded Help SDK - Add contextual help, tooltips, and AI chat to your application.
3
+ Pillar Embedded Help SDK - Add contextual help and AI chat to your application.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,36 +10,11 @@ npm install @pillar-ai/sdk
10
10
 
11
11
  ## Quick Start
12
12
 
13
- ### Script Tag (CDN)
14
-
15
- ```html
16
- <script src="https://cdn.trypillar.com/sdk/pillar.min.js"></script>
17
- <script>
18
- Pillar.init({
19
- helpCenter: 'your-help-center',
20
- publicKey: 'pk_live_xxx',
21
- });
22
- </script>
23
- ```
24
-
25
- Or with auto-initialization via data attributes:
26
-
27
- ```html
28
- <script
29
- src="https://cdn.trypillar.com/sdk/pillar.min.js"
30
- data-help-center="your-help-center"
31
- data-public-key="pk_live_xxx"
32
- ></script>
33
- ```
34
-
35
- ### ES Modules
36
-
37
13
  ```javascript
38
- import { Pillar } from '@pillar-ai/sdk';
14
+ import { Pillar } from "@pillar-ai/sdk";
39
15
 
40
16
  await Pillar.init({
41
- helpCenter: 'your-help-center',
42
- publicKey: 'pk_live_xxx',
17
+ helpCenter: "your-help-center",
43
18
  });
44
19
  ```
45
20
 
@@ -48,45 +23,50 @@ await Pillar.init({
48
23
  ```javascript
49
24
  Pillar.init({
50
25
  // Required
51
- helpCenter: 'your-help-center',
52
- publicKey: 'pk_live_xxx',
26
+ helpCenter: "your-help-center",
53
27
 
54
28
  // Optional configuration
55
- config: {
56
- // Panel configuration
57
- panel: {
58
- position: 'right', // 'left' | 'right'
59
- mode: 'overlay', // 'overlay' | 'push'
60
- },
61
-
62
- // Floating button
63
- floatingButton: {
64
- enabled: true,
65
- position: 'bottom-right',
66
- },
29
+ panel: {
30
+ position: "right", // 'left' | 'right'
31
+ mode: "push", // 'overlay' | 'push'
32
+ },
67
33
 
68
- // Tooltips
69
- tooltips: {
70
- enabled: true,
71
- trigger: 'hover', // 'hover' | 'click'
72
- },
34
+ // Edge trigger (sidebar tab that opens the panel)
35
+ edgeTrigger: {
36
+ enabled: true, // Set to false to use your own custom button
37
+ },
73
38
 
74
- // Theme
75
- theme: {
76
- mode: 'system', // 'light' | 'dark' | 'system'
77
- colors: {
78
- primary: '#6366f1',
79
- },
39
+ // Theme
40
+ theme: {
41
+ mode: "auto", // 'light' | 'dark' | 'auto'
42
+ colors: {
43
+ primary: "#6366f1",
80
44
  },
81
45
  },
82
46
  });
83
47
  ```
84
48
 
49
+ ## Custom Trigger Button
50
+
51
+ To use your own button instead of the built-in edge trigger:
52
+
53
+ ```javascript
54
+ Pillar.init({
55
+ helpCenter: "your-help-center",
56
+ edgeTrigger: { enabled: false },
57
+ });
58
+
59
+ // Then control the panel programmatically
60
+ document.getElementById("my-help-button").addEventListener("click", () => {
61
+ Pillar.toggle();
62
+ });
63
+ ```
64
+
85
65
  ## Features
86
66
 
87
67
  - **AI Chat**: Embedded AI assistant that understands your product
68
+ - **Edge Trigger**: Built-in sidebar tab to open the help panel (or use your own button)
88
69
  - **Contextual Help**: Show relevant help based on user context
89
- - **Tooltips**: Attach interactive tooltips to any element
90
70
  - **Text Selection**: Allow users to ask questions about selected text
91
71
  - **Customizable UI**: Full control over positioning, theming, and behavior
92
72
 
@@ -35,6 +35,22 @@ export type ActionType = 'navigate' | 'open_modal' | 'fill_form' | 'trigger_acti
35
35
  * Supported platforms for action deployments.
36
36
  */
37
37
  export type Platform = 'web' | 'ios' | 'android' | 'desktop';
38
+ /**
39
+ * Schema property definition for a single field.
40
+ * Supports nested objects and arrays with items.
41
+ */
42
+ export interface ActionDataSchemaProperty {
43
+ type: 'string' | 'number' | 'boolean' | 'array' | 'object';
44
+ description?: string;
45
+ enum?: string[];
46
+ default?: unknown;
47
+ /** Items schema for array types */
48
+ items?: ActionDataSchemaProperty;
49
+ /** Nested properties for object types */
50
+ properties?: Record<string, ActionDataSchemaProperty>;
51
+ /** Required fields for nested object types */
52
+ required?: string[];
53
+ }
38
54
  /**
39
55
  * JSON Schema definition for action data.
40
56
  *
@@ -43,12 +59,7 @@ export type Platform = 'web' | 'ios' | 'android' | 'desktop';
43
59
  */
44
60
  export interface ActionDataSchema {
45
61
  type: 'object';
46
- properties: Record<string, {
47
- type: 'string' | 'number' | 'boolean' | 'array' | 'object';
48
- description?: string;
49
- enum?: string[];
50
- default?: unknown;
51
- }>;
62
+ properties: Record<string, ActionDataSchemaProperty>;
52
63
  required?: string[];
53
64
  }
54
65
  /**
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { TaskButtonData } from '../components/Panel/TaskButton';
6
6
  import type { ResolvedConfig } from '../core/config';
7
- import type { ProductContext, Suggestion, UserProfile } from '../core/context';
7
+ import type { Context, Suggestion, UserProfile } from '../core/context';
8
8
  import type { ExecutionPlan } from '../core/plan';
9
9
  import type { Workflow } from '../core/workflow';
10
10
  import type { UserContextItem } from '../types/user-context';
@@ -52,17 +52,6 @@ export interface ServerEmbedConfig {
52
52
  position?: string;
53
53
  label?: string;
54
54
  };
55
- features?: {
56
- aiChatEnabled?: boolean;
57
- searchEnabled?: boolean;
58
- tooltipsEnabled?: boolean;
59
- };
60
- sidebarTabs?: Array<{
61
- id: string;
62
- label: string;
63
- enabled: boolean;
64
- order: number;
65
- }>;
66
55
  theme?: {
67
56
  colors?: {
68
57
  primary?: string;
@@ -146,13 +135,13 @@ export declare class APIClient {
146
135
  * Get contextual help suggestions based on product context.
147
136
  * Returns relevant articles, videos, and actions.
148
137
  */
149
- getSuggestions(productContext: ProductContext, userProfile: UserProfile): Promise<Suggestion[]>;
138
+ getSuggestions(ctx: Context, userProfile: UserProfile): Promise<Suggestion[]>;
150
139
  /**
151
140
  * Chat with enhanced context.
152
141
  * Includes product context and user profile for better responses.
153
142
  *
154
143
  * Note: Context is passed to the MCP ask tool as additional arguments.
155
144
  */
156
- chatWithContext(message: string, history: ChatMessage[] | undefined, productContext: ProductContext, userProfile: UserProfile, onChunk?: (chunk: string) => void, existingConversationId?: string | null, onActions?: (actions: TaskButtonData[]) => void): Promise<ChatResponse>;
145
+ chatWithContext(message: string, history: ChatMessage[] | undefined, ctx: Context, userProfile: UserProfile, onChunk?: (chunk: string) => void, existingConversationId?: string | null, onActions?: (actions: TaskButtonData[]) => void): Promise<ChatResponse>;
157
146
  cancelAllRequests(): void;
158
147
  }
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env npx tsx
1
+ #!/usr/bin/env node
2
2
  export {};
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/cli/sync.ts
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { execSync } from "child_process";
8
+ import { pathToFileURL } from "url";
9
+ var DEFAULT_API_URL = "https://help-api.trypillar.com";
10
+ var LOCAL_API_URL = "http://localhost:8003";
11
+ function parseArgs(args) {
12
+ const result = {};
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i];
15
+ if (arg.startsWith("--")) {
16
+ const key = arg.slice(2);
17
+ const nextArg = args[i + 1];
18
+ if (nextArg && !nextArg.startsWith("--")) {
19
+ result[key] = nextArg;
20
+ i++;
21
+ } else {
22
+ result[key] = true;
23
+ }
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ function printUsage() {
29
+ console.log(`
30
+ Pillar Action Sync CLI
31
+
32
+ Usage:
33
+ npx pillar-sync --actions <path> [--local]
34
+
35
+ Arguments:
36
+ --actions <path> Path to your actions definition file (required)
37
+ Supports .ts, .js, .mjs files
38
+ --local Use localhost:8003 as the API URL (for local development)
39
+ --help Show this help message
40
+
41
+ Environment Variables:
42
+ PILLAR_SLUG Your help center slug (required)
43
+ PILLAR_SECRET Secret token for authentication (required)
44
+ PILLAR_API_URL API URL (default: https://help-api.trypillar.com)
45
+ PILLAR_PLATFORM Platform: web, ios, android, desktop (default: web)
46
+ PILLAR_VERSION App version (default: from package.json)
47
+ GIT_SHA Git commit SHA for traceability
48
+
49
+ Examples:
50
+ # Production
51
+ PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts
52
+
53
+ # Local development
54
+ PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts --local
55
+ `);
56
+ }
57
+ async function loadActions(actionsPath) {
58
+ const absolutePath = path.resolve(process.cwd(), actionsPath);
59
+ if (!fs.existsSync(absolutePath)) {
60
+ throw new Error(`Actions file not found: ${absolutePath}`);
61
+ }
62
+ const fileUrl = pathToFileURL(absolutePath).href;
63
+ try {
64
+ const module = await import(fileUrl);
65
+ const actions = module.default || module.actions;
66
+ if (!actions || typeof actions !== "object") {
67
+ throw new Error(
68
+ 'Actions file must export an actions object as default or named export "actions"'
69
+ );
70
+ }
71
+ return actions;
72
+ } catch (error) {
73
+ if (error instanceof Error && error.message.includes("Unknown file extension")) {
74
+ console.error("[pillar-sync] TypeScript files require tsx.");
75
+ console.error("[pillar-sync] Make sure tsx is installed: npm install -D tsx");
76
+ console.error("[pillar-sync] Then run: npx pillar-sync --actions ./actions.ts");
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ function buildManifest(actions, platform, version, gitSha) {
82
+ const entries = [];
83
+ for (const [name, definition] of Object.entries(actions)) {
84
+ const entry = {
85
+ name,
86
+ description: definition.description,
87
+ type: definition.type
88
+ };
89
+ if (definition.examples?.length)
90
+ entry.examples = definition.examples;
91
+ if (definition.path)
92
+ entry.path = definition.path;
93
+ if (definition.externalUrl)
94
+ entry.external_url = definition.externalUrl;
95
+ if (definition.autoRun)
96
+ entry.auto_run = definition.autoRun;
97
+ if (definition.autoComplete)
98
+ entry.auto_complete = definition.autoComplete;
99
+ if (definition.returns)
100
+ entry.returns_data = definition.returns;
101
+ if (definition.dataSchema)
102
+ entry.data_schema = definition.dataSchema;
103
+ if (definition.defaultData)
104
+ entry.default_data = definition.defaultData;
105
+ if (definition.requiredContext)
106
+ entry.required_context = definition.requiredContext;
107
+ entries.push(entry);
108
+ }
109
+ return {
110
+ platform,
111
+ version,
112
+ gitSha,
113
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
114
+ actions: entries
115
+ };
116
+ }
117
+ async function pollStatus(statusUrl, secret, maxWaitSeconds = 300) {
118
+ const startTime = Date.now();
119
+ let lastProgress = { processed: 0, total: 0 };
120
+ while (true) {
121
+ try {
122
+ const response = await fetch(statusUrl, {
123
+ headers: {
124
+ "X-Pillar-Secret": secret
125
+ }
126
+ });
127
+ if (!response.ok) {
128
+ throw new Error(`Status check failed: ${response.status} ${response.statusText}`);
129
+ }
130
+ const status = await response.json();
131
+ if (status.progress && (status.progress.processed !== lastProgress.processed || status.progress.total !== lastProgress.total)) {
132
+ const { processed, total, created, updated, deleted } = status.progress;
133
+ const percent = total > 0 ? Math.round(processed / total * 100) : 0;
134
+ console.log(
135
+ `[pillar-sync] Progress: ${processed}/${total} (${percent}%) - Created: ${created}, Updated: ${updated}, Deleted: ${deleted}`
136
+ );
137
+ lastProgress = { processed, total };
138
+ }
139
+ if (status.status === "completed" && status.is_complete) {
140
+ console.log(`[pillar-sync] \u2713 Sync completed successfully`);
141
+ if (status.deployment_id) {
142
+ console.log(`[pillar-sync] Deployment: ${status.deployment_id}`);
143
+ }
144
+ return;
145
+ }
146
+ if (status.status === "failed") {
147
+ throw new Error(status.error || "Sync job failed");
148
+ }
149
+ const elapsed = (Date.now() - startTime) / 1e3;
150
+ if (elapsed > maxWaitSeconds) {
151
+ throw new Error(`Timeout after ${maxWaitSeconds} seconds`);
152
+ }
153
+ await sleep(2e3);
154
+ } catch (error) {
155
+ if (error instanceof Error && error.message.includes("Timeout")) {
156
+ throw error;
157
+ }
158
+ console.error(`[pillar-sync] Poll error: ${error}`);
159
+ await sleep(2e3);
160
+ }
161
+ }
162
+ }
163
+ function sleep(ms) {
164
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
165
+ }
166
+ function getPackageVersion() {
167
+ try {
168
+ const pkgPath = path.join(process.cwd(), "package.json");
169
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
170
+ return pkg.version || "0.0.0";
171
+ } catch {
172
+ return "0.0.0";
173
+ }
174
+ }
175
+ function getGitSha() {
176
+ try {
177
+ return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim().slice(0, 7);
178
+ } catch {
179
+ return void 0;
180
+ }
181
+ }
182
+ async function main() {
183
+ const args = parseArgs(process.argv.slice(2));
184
+ if (args.help) {
185
+ printUsage();
186
+ process.exit(0);
187
+ }
188
+ const actionsPath = args.actions;
189
+ if (!actionsPath) {
190
+ console.error("[pillar-sync] Missing required --actions argument");
191
+ console.error("");
192
+ printUsage();
193
+ process.exit(1);
194
+ }
195
+ const isLocal = args.local === true;
196
+ const apiUrl = isLocal ? LOCAL_API_URL : process.env.PILLAR_API_URL || DEFAULT_API_URL;
197
+ const slug = process.env.PILLAR_SLUG;
198
+ const secret = process.env.PILLAR_SECRET;
199
+ if (isLocal) {
200
+ console.log(`[pillar-sync] Using local API: ${LOCAL_API_URL}`);
201
+ }
202
+ if (!slug || !secret) {
203
+ console.error("[pillar-sync] Missing required environment variables:");
204
+ if (!slug)
205
+ console.error(" - PILLAR_SLUG");
206
+ if (!secret)
207
+ console.error(" - PILLAR_SECRET");
208
+ console.error("");
209
+ console.error("Get these from the Pillar admin: Actions \u2192 Configure Sync");
210
+ process.exit(1);
211
+ }
212
+ console.log(`[pillar-sync] Loading actions from: ${actionsPath}`);
213
+ let actions;
214
+ try {
215
+ actions = await loadActions(actionsPath);
216
+ } catch (error) {
217
+ console.error(`[pillar-sync] Failed to load actions:`, error);
218
+ process.exit(1);
219
+ }
220
+ const actionCount = Object.keys(actions).length;
221
+ console.log(`[pillar-sync] Found ${actionCount} actions`);
222
+ if (actionCount === 0) {
223
+ console.warn("[pillar-sync] No actions found. Nothing to sync.");
224
+ process.exit(0);
225
+ }
226
+ const platform = process.env.PILLAR_PLATFORM || "web";
227
+ const version = process.env.PILLAR_VERSION || getPackageVersion();
228
+ const gitSha = process.env.GIT_SHA || getGitSha();
229
+ console.log(`[pillar-sync] Platform: ${platform}`);
230
+ console.log(`[pillar-sync] Version: ${version}`);
231
+ console.log(`[pillar-sync] Git SHA: ${gitSha || "not available"}`);
232
+ const manifest = buildManifest(actions, platform, version, gitSha);
233
+ if (process.env.PILLAR_DEBUG) {
234
+ const manifestPath = path.join(process.cwd(), "actions-manifest.json");
235
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
236
+ console.log(`[pillar-sync] Wrote manifest to ${manifestPath}`);
237
+ }
238
+ console.log(`[pillar-sync] Help Center: ${slug}`);
239
+ const requestBody = {
240
+ platform: manifest.platform,
241
+ version: manifest.version,
242
+ git_sha: gitSha,
243
+ actions: manifest.actions
244
+ };
245
+ const syncUrl = `${apiUrl}/api/admin/configs/${slug}/actions/sync/?async=true`;
246
+ console.log(`[pillar-sync] POST ${syncUrl}`);
247
+ try {
248
+ const response = await fetch(syncUrl, {
249
+ method: "POST",
250
+ headers: {
251
+ "Content-Type": "application/json",
252
+ "X-Pillar-Secret": secret
253
+ },
254
+ body: JSON.stringify(requestBody)
255
+ });
256
+ if (!response.ok) {
257
+ const errorText = await response.text();
258
+ console.error(`[pillar-sync] Sync failed: ${response.status} ${response.statusText}`);
259
+ console.error(`[pillar-sync] Response: ${errorText}`);
260
+ process.exit(1);
261
+ }
262
+ const result = await response.json();
263
+ if (result.status === "unchanged") {
264
+ console.log(`[pillar-sync] \u2713 Manifest unchanged (deployment ${result.deployment_id})`);
265
+ return;
266
+ }
267
+ if (result.status === "accepted" && result.job_id && result.status_url) {
268
+ console.log(`[pillar-sync] \u2713 Job accepted (job ${result.job_id})`);
269
+ console.log(`[pillar-sync] Polling for completion...`);
270
+ const statusUrl = result.status_url.startsWith("http") ? result.status_url : `${apiUrl}${result.status_url}`;
271
+ await pollStatus(statusUrl, secret);
272
+ return;
273
+ }
274
+ if (result.status === "created") {
275
+ console.log(`[pillar-sync] \u2713 Created deployment ${result.deployment_id}`);
276
+ console.log(`[pillar-sync] Actions: ${result.actions_count}`);
277
+ console.log(
278
+ `[pillar-sync] Created: ${result.created}, Updated: ${result.updated}, Deleted: ${result.deleted || 0}`
279
+ );
280
+ }
281
+ } catch (error) {
282
+ console.error("[pillar-sync] Sync failed:", error);
283
+ process.exit(1);
284
+ }
285
+ }
286
+ main();
@@ -3,13 +3,16 @@
3
3
  * A sidebar-style trigger that reserves space in the layout and slides out with the panel.
4
4
  * The trigger stays visible when panel opens, positioned at the panel's outer edge.
5
5
  */
6
- import type { FloatingButtonPosition, ResolvedConfig } from "../../core/config";
6
+ import type { ResolvedConfig } from "../../core/config";
7
+ import type { EventEmitter } from "../../core/events";
8
+ export type EdgeTriggerPosition = "left" | "right";
7
9
  /**
8
10
  * EdgeTrigger class that manages the edge trigger lifecycle
9
11
  * The trigger stays visible when panel opens, sliding out with it
10
12
  */
11
13
  export declare class EdgeTrigger {
12
14
  private config;
15
+ private events;
13
16
  private onClick;
14
17
  private rootContainer;
15
18
  private container;
@@ -19,16 +22,18 @@ export declare class EdgeTrigger {
19
22
  private unsubscribeOpen;
20
23
  private unsubscribeWidth;
21
24
  private unsubscribeHoverMode;
25
+ private unsubscribeMobileMode;
22
26
  private unsubscribeActiveTab;
23
27
  private themeObserver;
24
28
  private currentTheme;
25
- constructor(config: ResolvedConfig, onClick: () => void, rootContainer?: HTMLElement | null);
29
+ constructor(config: ResolvedConfig, events: EventEmitter, onClick: () => void, rootContainer?: HTMLElement | null);
26
30
  /**
27
31
  * Handle tab click - sets active tab and opens panel
32
+ * For non-assistant tabs, emits event for customer's code to handle (e.g., Intercom, Zendesk)
28
33
  */
29
34
  private handleTabClick;
30
35
  /**
31
- * Get position as 'left' or 'right' from the floating button position config
36
+ * Get position as 'left' or 'right' from the panel position config
32
37
  */
33
38
  private getEdgePosition;
34
39
  /**
@@ -65,11 +70,7 @@ export declare class EdgeTrigger {
65
70
  /**
66
71
  * Update trigger position
67
72
  */
68
- setPosition(position: FloatingButtonPosition): void;
69
- /**
70
- * Update trigger label
71
- */
72
- setLabel(label: string): void;
73
+ setPosition(position: EdgeTriggerPosition): void;
73
74
  /**
74
75
  * Destroy the trigger
75
76
  */
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Mobile Trigger Component
3
+ * A floating action button (FAB) that appears on small screens when the edge trigger is hidden.
4
+ * Provides a way to open the help panel on mobile devices.
5
+ */
6
+ import type { ResolvedConfig } from "../../core/config";
7
+ import type { EventEmitter } from "../../core/events";
8
+ /**
9
+ * MobileTrigger class that manages the mobile floating button lifecycle
10
+ * The trigger appears on small screens when the viewport is below mobileBreakpoint
11
+ */
12
+ export declare class MobileTrigger {
13
+ private config;
14
+ private events;
15
+ private onClick;
16
+ private rootContainer;
17
+ private container;
18
+ private stylesInjected;
19
+ private _isEnabled;
20
+ private unsubscribeMobileMode;
21
+ private unsubscribeOpen;
22
+ constructor(config: ResolvedConfig, events: EventEmitter, onClick: () => void, rootContainer?: HTMLElement | null);
23
+ /**
24
+ * Get the resolved size in pixels
25
+ */
26
+ private getSize;
27
+ /**
28
+ * Get the icon SVG string
29
+ */
30
+ private getIcon;
31
+ /**
32
+ * Get the background color (use theme primary as default)
33
+ */
34
+ private getBackgroundColor;
35
+ /**
36
+ * Initialize the mobile trigger
37
+ */
38
+ init(): void;
39
+ /**
40
+ * Show the trigger (enable it)
41
+ */
42
+ show(): void;
43
+ /**
44
+ * Hide the trigger (disable it)
45
+ */
46
+ hide(): void;
47
+ /**
48
+ * Check if the trigger is currently visible
49
+ */
50
+ get isVisible(): boolean;
51
+ /**
52
+ * Destroy the trigger
53
+ */
54
+ destroy(): void;
55
+ private render;
56
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Button Components Index
3
3
  */
4
- export { FloatingButton } from './FloatingButton';
5
- export { EdgeTrigger } from './EdgeTrigger';
4
+ export { EdgeTrigger, type EdgeTriggerPosition } from './EdgeTrigger';
5
+ export { MobileTrigger } from './MobileTrigger';