@pillar-ai/sdk 0.1.7 → 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
@@ -15,7 +15,6 @@ import { Pillar } from "@pillar-ai/sdk";
15
15
 
16
16
  await Pillar.init({
17
17
  helpCenter: "your-help-center",
18
- publicKey: "pk_live_xxx",
19
18
  });
20
19
  ```
21
20
 
@@ -25,27 +24,23 @@ await Pillar.init({
25
24
  Pillar.init({
26
25
  // Required
27
26
  helpCenter: "your-help-center",
28
- publicKey: "pk_live_xxx",
29
27
 
30
28
  // Optional configuration
31
- config: {
32
- // Panel configuration
33
- panel: {
34
- position: "right", // 'left' | 'right'
35
- mode: "push", // 'overlay' | 'push'
36
- },
29
+ panel: {
30
+ position: "right", // 'left' | 'right'
31
+ mode: "push", // 'overlay' | 'push'
32
+ },
37
33
 
38
- // Edge trigger (sidebar tab that opens the panel)
39
- edgeTrigger: {
40
- enabled: true, // Set to false to use your own custom button
41
- },
34
+ // Edge trigger (sidebar tab that opens the panel)
35
+ edgeTrigger: {
36
+ enabled: true, // Set to false to use your own custom button
37
+ },
42
38
 
43
- // Theme
44
- theme: {
45
- mode: "auto", // 'light' | 'dark' | 'auto'
46
- colors: {
47
- primary: "#6366f1",
48
- },
39
+ // Theme
40
+ theme: {
41
+ mode: "auto", // 'light' | 'dark' | 'auto'
42
+ colors: {
43
+ primary: "#6366f1",
49
44
  },
50
45
  },
51
46
  });
@@ -58,7 +53,6 @@ To use your own button instead of the built-in edge trigger:
58
53
  ```javascript
59
54
  Pillar.init({
60
55
  helpCenter: "your-help-center",
61
- publicKey: "pk_live_xxx",
62
56
  edgeTrigger: { enabled: false },
63
57
  });
64
58
 
@@ -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';
@@ -135,13 +135,13 @@ export declare class APIClient {
135
135
  * Get contextual help suggestions based on product context.
136
136
  * Returns relevant articles, videos, and actions.
137
137
  */
138
- getSuggestions(productContext: ProductContext, userProfile: UserProfile): Promise<Suggestion[]>;
138
+ getSuggestions(ctx: Context, userProfile: UserProfile): Promise<Suggestion[]>;
139
139
  /**
140
140
  * Chat with enhanced context.
141
141
  * Includes product context and user profile for better responses.
142
142
  *
143
143
  * Note: Context is passed to the MCP ask tool as additional arguments.
144
144
  */
145
- 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>;
146
146
  cancelAllRequests(): void;
147
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();
@@ -22,6 +22,7 @@ export declare class EdgeTrigger {
22
22
  private unsubscribeOpen;
23
23
  private unsubscribeWidth;
24
24
  private unsubscribeHoverMode;
25
+ private unsubscribeMobileMode;
25
26
  private unsubscribeActiveTab;
26
27
  private themeObserver;
27
28
  private currentTheme;
@@ -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
+ }
@@ -2,3 +2,4 @@
2
2
  * Button Components Index
3
3
  */
4
4
  export { EdgeTrigger, type EdgeTriggerPosition } from './EdgeTrigger';
5
+ export { MobileTrigger } from './MobileTrigger';