@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.
@@ -56,7 +56,7 @@ export declare const clearPendingImages: () => void;
56
56
  export declare const hasMessages: import("@preact/signals-core").ReadonlySignal<boolean>;
57
57
  export declare const addUserMessage: (content: string, userContext?: UserContextItem[], images?: ChatImage[]) => void;
58
58
  export declare const addAssistantMessage: (content: string, messageId?: string) => void;
59
- export declare const updateLastAssistantMessage: (content: string, messageId?: string, actions?: TaskButtonData[], sources?: ArticleSummary[]) => void;
59
+ export declare const updateLastAssistantMessage: (content: string | undefined, messageId?: string, actions?: TaskButtonData[], sources?: ArticleSummary[]) => void;
60
60
  export declare const setActionPending: (messageIndex: number, actionName: string) => void;
61
61
  export declare const setActionComplete: (actionName: string, success: boolean, errorMessage?: string) => void;
62
62
  export declare const updateMessageContent: (messageIndex: number, content: string) => void;
@@ -1,28 +1,22 @@
1
1
  /**
2
2
  * Context Store
3
- * Signal-based state for product context and user profile
4
- *
5
- * Phase 3: Product Integration - Context-aware assistance
3
+ * Signal-based state for context and user profile
6
4
  */
7
- import type { ProductContext, UserProfile } from '../core/context';
8
- export declare const productContext: import("@preact/signals-core").Signal<ProductContext>;
5
+ import type { Context, InternalContext, UserProfile } from '../core/context';
6
+ export declare const context: import("@preact/signals-core").Signal<InternalContext>;
9
7
  export declare const userProfile: import("@preact/signals-core").Signal<UserProfile>;
10
8
  export declare const hasContext: import("@preact/signals-core").ReadonlySignal<boolean>;
11
9
  export declare const hasError: import("@preact/signals-core").ReadonlySignal<boolean>;
12
10
  /**
13
- * Set the complete product context.
11
+ * Set context fields (merges with existing).
14
12
  */
15
- export declare const setProductContext: (context: ProductContext) => void;
16
- /**
17
- * Update specific product context fields.
18
- */
19
- export declare const updateContext: (updates: Partial<ProductContext>) => void;
13
+ export declare const setContext: (ctx: Partial<Context>) => void;
20
14
  /**
21
15
  * Set user profile.
22
16
  */
23
17
  export declare const setUserProfile: (profile: UserProfile) => void;
24
18
  /**
25
- * Report a user action.
19
+ * Report a user action (tracked internally).
26
20
  */
27
21
  export declare const reportAction: (action: string) => void;
28
22
  /**
@@ -37,7 +31,7 @@ export declare const clearErrorState: () => void;
37
31
  * Get the full assistant context for API calls.
38
32
  */
39
33
  export declare const getAssistantContext: () => {
40
- product: ProductContext;
34
+ product: InternalContext;
41
35
  user_profile: UserProfile;
42
36
  };
43
37
  /**
@@ -10,12 +10,24 @@ export declare const mode: import("@preact/signals-core").Signal<PanelMode>;
10
10
  export declare const width: import("@preact/signals-core").Signal<number>;
11
11
  export declare const hoverBreakpoint: import("@preact/signals-core").Signal<number | false>;
12
12
  export declare const hoverBackdrop: import("@preact/signals-core").Signal<boolean>;
13
+ export declare const mobileBreakpoint: import("@preact/signals-core").Signal<number>;
14
+ export declare const fullWidthBreakpoint: import("@preact/signals-core").Signal<number>;
13
15
  export declare const viewportWidth: import("@preact/signals-core").Signal<number>;
14
16
  /**
15
17
  * Whether the panel is currently in hover mode based on viewport width
16
18
  * Returns true when viewport is below hoverBreakpoint (and breakpoint is not disabled)
17
19
  */
18
20
  export declare const isHoverMode: import("@preact/signals-core").ReadonlySignal<boolean>;
21
+ /**
22
+ * Whether we're in mobile mode (below mobileBreakpoint)
23
+ * When true, edge trigger hides and mobile trigger shows
24
+ */
25
+ export declare const isMobileMode: import("@preact/signals-core").ReadonlySignal<boolean>;
26
+ /**
27
+ * Whether the panel should take full screen width
28
+ * Returns true when viewport is below fullWidthBreakpoint
29
+ */
30
+ export declare const isFullWidth: import("@preact/signals-core").ReadonlySignal<boolean>;
19
31
  /**
20
32
  * The effective panel mode based on responsive breakpoint
21
33
  * Returns 'overlay' when in hover mode (below breakpoint), otherwise returns configured mode
@@ -30,6 +42,8 @@ export declare const setMode: (m: PanelMode) => void;
30
42
  export declare const setWidth: (w: number) => void;
31
43
  export declare const setHoverBreakpoint: (bp: number | false) => void;
32
44
  export declare const setHoverBackdrop: (show: boolean) => void;
45
+ export declare const setMobileBreakpoint: (bp: number) => void;
46
+ export declare const setFullWidthBreakpoint: (bp: number) => void;
33
47
  export declare const setActiveTab: (tabId: string) => void;
34
48
  /**
35
49
  * Initialize the viewport resize listener for responsive behavior
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pillar-ai/sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Pillar Embedded Help SDK - Add contextual help and AI chat to your application",
5
5
  "type": "module",
6
6
  "main": "./dist/pillar.esm.js",
7
7
  "module": "./dist/pillar.esm.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "bin": {
10
- "pillar-sync": "./src/cli/sync.ts"
10
+ "pillar-sync": "./dist/cli/sync.js"
11
11
  },
12
12
  "exports": {
13
13
  ".": {
@@ -19,12 +19,13 @@
19
19
  "files": [
20
20
  "dist/pillar.esm.js",
21
21
  "dist/**/*.d.ts",
22
- "src/cli",
22
+ "dist/cli/sync.js",
23
23
  "src/actions/types.ts",
24
24
  "README.md"
25
25
  ],
26
26
  "scripts": {
27
- "build": "rollup -c && npm run build:types",
27
+ "build": "rollup -c && npm run build:cli && npm run build:types",
28
+ "build:cli": "npx esbuild src/cli/sync.ts --bundle --platform=node --target=node18 --outfile=dist/cli/sync.js --format=esm --banner:js='#!/usr/bin/env node'",
28
29
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
29
30
  "dev": "rollup -c -w",
30
31
  "clean": "rm -rf dist",
@@ -68,6 +69,7 @@
68
69
  "@rollup/plugin-terser": "^0.4.4",
69
70
  "@rollup/plugin-typescript": "^11.1.6",
70
71
  "@types/node": "^20.11.0",
72
+ "esbuild": "^0.20.0",
71
73
  "rollup": "^4.9.0",
72
74
  "tslib": "^2.6.2",
73
75
  "tsx": "^4.7.0",
@@ -46,6 +46,23 @@ export type ActionType =
46
46
  */
47
47
  export type Platform = 'web' | 'ios' | 'android' | 'desktop';
48
48
 
49
+ /**
50
+ * Schema property definition for a single field.
51
+ * Supports nested objects and arrays with items.
52
+ */
53
+ export interface ActionDataSchemaProperty {
54
+ type: 'string' | 'number' | 'boolean' | 'array' | 'object';
55
+ description?: string;
56
+ enum?: string[];
57
+ default?: unknown;
58
+ /** Items schema for array types */
59
+ items?: ActionDataSchemaProperty;
60
+ /** Nested properties for object types */
61
+ properties?: Record<string, ActionDataSchemaProperty>;
62
+ /** Required fields for nested object types */
63
+ required?: string[];
64
+ }
65
+
49
66
  /**
50
67
  * JSON Schema definition for action data.
51
68
  *
@@ -54,15 +71,7 @@ export type Platform = 'web' | 'ios' | 'android' | 'desktop';
54
71
  */
55
72
  export interface ActionDataSchema {
56
73
  type: 'object';
57
- properties: Record<
58
- string,
59
- {
60
- type: 'string' | 'number' | 'boolean' | 'array' | 'object';
61
- description?: string;
62
- enum?: string[];
63
- default?: unknown;
64
- }
65
- >;
74
+ properties: Record<string, ActionDataSchemaProperty>;
66
75
  required?: string[];
67
76
  }
68
77
 
package/src/cli/sync.ts DELETED
@@ -1,477 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * Pillar Action Sync CLI
4
- *
5
- * Syncs action definitions to the Pillar backend.
6
- * Run this in your CI/CD pipeline after building your app.
7
- *
8
- * Usage:
9
- * npx pillar-sync --actions ./path/to/actions.ts
10
- *
11
- * Environment (required):
12
- * PILLAR_SLUG - Your help center slug (e.g., "acme-corp")
13
- * PILLAR_SECRET - Secret token for authentication
14
- *
15
- * Environment (optional):
16
- * PILLAR_API_URL - Pillar API URL (defaults to https://api.trypillar.com)
17
- * PILLAR_PLATFORM - Platform identifier (web, ios, android, desktop)
18
- * PILLAR_VERSION - App version (semver or git SHA)
19
- * GIT_SHA - Git commit SHA (optional, for traceability)
20
- */
21
- import * as fs from 'fs';
22
- import * as path from 'path';
23
- import { execSync } from 'child_process';
24
- import { pathToFileURL } from 'url';
25
-
26
- // ============================================================================
27
- // Types (inline to make CLI self-contained)
28
- // ============================================================================
29
-
30
- type ActionType =
31
- | 'navigate'
32
- | 'open_modal'
33
- | 'fill_form'
34
- | 'trigger_action'
35
- | 'copy_text'
36
- | 'external_link'
37
- | 'start_tutorial'
38
- | 'inline_ui';
39
-
40
- type Platform = 'web' | 'ios' | 'android' | 'desktop';
41
-
42
- interface ActionDataSchema {
43
- type: 'object';
44
- properties: Record<
45
- string,
46
- {
47
- type: 'string' | 'number' | 'boolean' | 'array' | 'object';
48
- description?: string;
49
- enum?: string[];
50
- default?: unknown;
51
- }
52
- >;
53
- required?: string[];
54
- }
55
-
56
- interface SyncActionDefinition {
57
- description: string;
58
- examples?: string[];
59
- type: ActionType;
60
- path?: string;
61
- externalUrl?: string;
62
- dataSchema?: ActionDataSchema;
63
- defaultData?: Record<string, unknown>;
64
- requiredContext?: Record<string, unknown>;
65
- autoRun?: boolean;
66
- autoComplete?: boolean;
67
- returns?: boolean;
68
- }
69
-
70
- type SyncActionDefinitions = Record<string, SyncActionDefinition>;
71
-
72
- interface ActionManifestEntry {
73
- name: string;
74
- description: string;
75
- examples?: string[];
76
- type: ActionType;
77
- path?: string;
78
- external_url?: string;
79
- auto_run?: boolean;
80
- auto_complete?: boolean;
81
- returns_data?: boolean;
82
- data_schema?: ActionDataSchema;
83
- default_data?: Record<string, unknown>;
84
- required_context?: Record<string, unknown>;
85
- }
86
-
87
- interface ActionManifest {
88
- platform: Platform;
89
- version: string;
90
- gitSha?: string;
91
- generatedAt: string;
92
- actions: ActionManifestEntry[];
93
- }
94
-
95
- interface SyncResponse {
96
- status: 'created' | 'unchanged' | 'accepted';
97
- deployment_id?: string;
98
- version: string;
99
- actions_count?: number;
100
- created?: number;
101
- updated?: number;
102
- deleted?: number;
103
- job_id?: string;
104
- status_url?: string;
105
- }
106
-
107
- interface StatusResponse {
108
- status: 'pending' | 'processing' | 'completed' | 'failed';
109
- is_complete: boolean;
110
- progress: {
111
- total: number;
112
- processed: number;
113
- created: number;
114
- updated: number;
115
- deleted: number;
116
- };
117
- deployment_id?: string;
118
- error?: string;
119
- }
120
-
121
- // ============================================================================
122
- // CLI Implementation
123
- // ============================================================================
124
-
125
- // Default API URL for production
126
- const DEFAULT_API_URL = 'https://api.trypillar.com';
127
- const LOCAL_API_URL = 'http://localhost:8003';
128
-
129
- function parseArgs(args: string[]): Record<string, string | boolean> {
130
- const result: Record<string, string | boolean> = {};
131
-
132
- for (let i = 0; i < args.length; i++) {
133
- const arg = args[i];
134
- if (arg.startsWith('--')) {
135
- const key = arg.slice(2);
136
- const nextArg = args[i + 1];
137
-
138
- if (nextArg && !nextArg.startsWith('--')) {
139
- result[key] = nextArg;
140
- i++;
141
- } else {
142
- result[key] = true;
143
- }
144
- }
145
- }
146
-
147
- return result;
148
- }
149
-
150
- function printUsage(): void {
151
- console.log(`
152
- Pillar Action Sync CLI
153
-
154
- Usage:
155
- npx pillar-sync --actions <path> [--local]
156
-
157
- Arguments:
158
- --actions <path> Path to your actions definition file (required)
159
- Supports .ts, .js, .mjs files
160
- --local Use localhost:8003 as the API URL (for local development)
161
- --help Show this help message
162
-
163
- Environment Variables:
164
- PILLAR_SLUG Your help center slug (required)
165
- PILLAR_SECRET Secret token for authentication (required)
166
- PILLAR_API_URL API URL (default: https://api.trypillar.com)
167
- PILLAR_PLATFORM Platform: web, ios, android, desktop (default: web)
168
- PILLAR_VERSION App version (default: from package.json)
169
- GIT_SHA Git commit SHA for traceability
170
-
171
- Examples:
172
- # Production
173
- PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts
174
-
175
- # Local development
176
- PILLAR_SLUG=my-app PILLAR_SECRET=xxx npx pillar-sync --actions ./lib/actions.ts --local
177
- `);
178
- }
179
-
180
- async function loadActions(actionsPath: string): Promise<SyncActionDefinitions> {
181
- const absolutePath = path.resolve(process.cwd(), actionsPath);
182
-
183
- if (!fs.existsSync(absolutePath)) {
184
- throw new Error(`Actions file not found: ${absolutePath}`);
185
- }
186
-
187
- // Convert to file URL for ESM compatibility
188
- const fileUrl = pathToFileURL(absolutePath).href;
189
-
190
- try {
191
- const module = await import(fileUrl);
192
-
193
- // Support default export or named 'actions' export
194
- const actions = module.default || module.actions;
195
-
196
- if (!actions || typeof actions !== 'object') {
197
- throw new Error(
198
- 'Actions file must export an actions object as default or named export "actions"'
199
- );
200
- }
201
-
202
- return actions;
203
- } catch (error) {
204
- if (error instanceof Error && error.message.includes('Unknown file extension')) {
205
- console.error('[pillar-sync] TypeScript files require tsx.');
206
- console.error('[pillar-sync] Make sure tsx is installed: npm install -D tsx');
207
- console.error('[pillar-sync] Then run: npx pillar-sync --actions ./actions.ts');
208
- }
209
- throw error;
210
- }
211
- }
212
-
213
- function buildManifest(
214
- actions: SyncActionDefinitions,
215
- platform: Platform,
216
- version: string,
217
- gitSha?: string
218
- ): ActionManifest {
219
- const entries: ActionManifestEntry[] = [];
220
-
221
- for (const [name, definition] of Object.entries(actions)) {
222
- const entry: ActionManifestEntry = {
223
- name,
224
- description: definition.description,
225
- type: definition.type,
226
- };
227
-
228
- // Only include optional fields if they have values
229
- if (definition.examples?.length) entry.examples = definition.examples;
230
- if (definition.path) entry.path = definition.path;
231
- if (definition.externalUrl) entry.external_url = definition.externalUrl;
232
- if (definition.autoRun) entry.auto_run = definition.autoRun;
233
- if (definition.autoComplete) entry.auto_complete = definition.autoComplete;
234
- if (definition.returns) entry.returns_data = definition.returns;
235
- if (definition.dataSchema) entry.data_schema = definition.dataSchema;
236
- if (definition.defaultData) entry.default_data = definition.defaultData;
237
- if (definition.requiredContext) entry.required_context = definition.requiredContext;
238
-
239
- entries.push(entry);
240
- }
241
-
242
- return {
243
- platform,
244
- version,
245
- gitSha,
246
- generatedAt: new Date().toISOString(),
247
- actions: entries,
248
- };
249
- }
250
-
251
- async function pollStatus(
252
- statusUrl: string,
253
- secret: string,
254
- maxWaitSeconds: number = 300
255
- ): Promise<void> {
256
- const startTime = Date.now();
257
- let lastProgress = { processed: 0, total: 0 };
258
-
259
- while (true) {
260
- try {
261
- const response = await fetch(statusUrl, {
262
- headers: {
263
- 'X-Pillar-Secret': secret,
264
- },
265
- });
266
-
267
- if (!response.ok) {
268
- throw new Error(`Status check failed: ${response.status} ${response.statusText}`);
269
- }
270
-
271
- const status: StatusResponse = await response.json();
272
-
273
- // Show progress updates
274
- if (
275
- status.progress &&
276
- (status.progress.processed !== lastProgress.processed ||
277
- status.progress.total !== lastProgress.total)
278
- ) {
279
- const { processed, total, created, updated, deleted } = status.progress;
280
- const percent = total > 0 ? Math.round((processed / total) * 100) : 0;
281
- console.log(
282
- `[pillar-sync] Progress: ${processed}/${total} (${percent}%) - ` +
283
- `Created: ${created}, Updated: ${updated}, Deleted: ${deleted}`
284
- );
285
- lastProgress = { processed, total };
286
- }
287
-
288
- // Check completion
289
- if (status.status === 'completed' && status.is_complete) {
290
- console.log(`[pillar-sync] ✓ Sync completed successfully`);
291
- if (status.deployment_id) {
292
- console.log(`[pillar-sync] Deployment: ${status.deployment_id}`);
293
- }
294
- return;
295
- }
296
-
297
- if (status.status === 'failed') {
298
- throw new Error(status.error || 'Sync job failed');
299
- }
300
-
301
- // Check timeout
302
- const elapsed = (Date.now() - startTime) / 1000;
303
- if (elapsed > maxWaitSeconds) {
304
- throw new Error(`Timeout after ${maxWaitSeconds} seconds`);
305
- }
306
-
307
- // Wait before next poll
308
- await sleep(2000);
309
- } catch (error) {
310
- if (error instanceof Error && error.message.includes('Timeout')) {
311
- throw error;
312
- }
313
- console.error(`[pillar-sync] Poll error: ${error}`);
314
- await sleep(2000);
315
- }
316
- }
317
- }
318
-
319
- function sleep(ms: number): Promise<void> {
320
- return new Promise((resolve) => setTimeout(resolve, ms));
321
- }
322
-
323
- function getPackageVersion(): string {
324
- try {
325
- const pkgPath = path.join(process.cwd(), 'package.json');
326
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
327
- return pkg.version || '0.0.0';
328
- } catch {
329
- return '0.0.0';
330
- }
331
- }
332
-
333
- function getGitSha(): string | undefined {
334
- try {
335
- return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim().slice(0, 7);
336
- } catch {
337
- return undefined;
338
- }
339
- }
340
-
341
- async function main(): Promise<void> {
342
- const args = parseArgs(process.argv.slice(2));
343
-
344
- // Show help
345
- if (args.help) {
346
- printUsage();
347
- process.exit(0);
348
- }
349
-
350
- // Validate --actions argument
351
- const actionsPath = args.actions as string | undefined;
352
- if (!actionsPath) {
353
- console.error('[pillar-sync] Missing required --actions argument');
354
- console.error('');
355
- printUsage();
356
- process.exit(1);
357
- }
358
-
359
- // Get configuration from environment
360
- const isLocal = args.local === true;
361
- const apiUrl = isLocal ? LOCAL_API_URL : (process.env.PILLAR_API_URL || DEFAULT_API_URL);
362
- const slug = process.env.PILLAR_SLUG;
363
- const secret = process.env.PILLAR_SECRET;
364
-
365
- if (isLocal) {
366
- console.log(`[pillar-sync] Using local API: ${LOCAL_API_URL}`);
367
- }
368
-
369
- if (!slug || !secret) {
370
- console.error('[pillar-sync] Missing required environment variables:');
371
- if (!slug) console.error(' - PILLAR_SLUG');
372
- if (!secret) console.error(' - PILLAR_SECRET');
373
- console.error('');
374
- console.error('Get these from the Pillar admin: Actions → Configure Sync');
375
- process.exit(1);
376
- }
377
-
378
- // Load actions from user's file
379
- console.log(`[pillar-sync] Loading actions from: ${actionsPath}`);
380
- let actions: SyncActionDefinitions;
381
- try {
382
- actions = await loadActions(actionsPath);
383
- } catch (error) {
384
- console.error(`[pillar-sync] Failed to load actions:`, error);
385
- process.exit(1);
386
- }
387
-
388
- const actionCount = Object.keys(actions).length;
389
- console.log(`[pillar-sync] Found ${actionCount} actions`);
390
-
391
- if (actionCount === 0) {
392
- console.warn('[pillar-sync] No actions found. Nothing to sync.');
393
- process.exit(0);
394
- }
395
-
396
- // Build configuration
397
- const platform = (process.env.PILLAR_PLATFORM || 'web') as Platform;
398
- const version = process.env.PILLAR_VERSION || getPackageVersion();
399
- const gitSha = process.env.GIT_SHA || getGitSha();
400
-
401
- console.log(`[pillar-sync] Platform: ${platform}`);
402
- console.log(`[pillar-sync] Version: ${version}`);
403
- console.log(`[pillar-sync] Git SHA: ${gitSha || 'not available'}`);
404
-
405
- // Generate manifest
406
- const manifest = buildManifest(actions, platform, version, gitSha);
407
-
408
- // Optionally write manifest to disk for debugging
409
- if (process.env.PILLAR_DEBUG) {
410
- const manifestPath = path.join(process.cwd(), 'actions-manifest.json');
411
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
412
- console.log(`[pillar-sync] Wrote manifest to ${manifestPath}`);
413
- }
414
-
415
- // Sync to backend
416
- console.log(`[pillar-sync] Help Center: ${slug}`);
417
-
418
- const requestBody = {
419
- platform: manifest.platform,
420
- version: manifest.version,
421
- git_sha: gitSha,
422
- actions: manifest.actions,
423
- };
424
-
425
- const syncUrl = `${apiUrl}/api/admin/configs/${slug}/actions/sync/?async=true`;
426
- console.log(`[pillar-sync] POST ${syncUrl}`);
427
-
428
- try {
429
- const response = await fetch(syncUrl, {
430
- method: 'POST',
431
- headers: {
432
- 'Content-Type': 'application/json',
433
- 'X-Pillar-Secret': secret,
434
- },
435
- body: JSON.stringify(requestBody),
436
- });
437
-
438
- if (!response.ok) {
439
- const errorText = await response.text();
440
- console.error(`[pillar-sync] Sync failed: ${response.status} ${response.statusText}`);
441
- console.error(`[pillar-sync] Response: ${errorText}`);
442
- process.exit(1);
443
- }
444
-
445
- const result: SyncResponse = await response.json();
446
-
447
- if (result.status === 'unchanged') {
448
- console.log(`[pillar-sync] ✓ Manifest unchanged (deployment ${result.deployment_id})`);
449
- return;
450
- }
451
-
452
- if (result.status === 'accepted' && result.job_id && result.status_url) {
453
- console.log(`[pillar-sync] ✓ Job accepted (job ${result.job_id})`);
454
- console.log(`[pillar-sync] Polling for completion...`);
455
-
456
- const statusUrl = result.status_url.startsWith('http')
457
- ? result.status_url
458
- : `${apiUrl}${result.status_url}`;
459
-
460
- await pollStatus(statusUrl, secret);
461
- return;
462
- }
463
-
464
- if (result.status === 'created') {
465
- console.log(`[pillar-sync] ✓ Created deployment ${result.deployment_id}`);
466
- console.log(`[pillar-sync] Actions: ${result.actions_count}`);
467
- console.log(
468
- `[pillar-sync] Created: ${result.created}, Updated: ${result.updated}, Deleted: ${result.deleted || 0}`
469
- );
470
- }
471
- } catch (error) {
472
- console.error('[pillar-sync] Sync failed:', error);
473
- process.exit(1);
474
- }
475
- }
476
-
477
- main();