@pillar-ai/sdk 0.1.0

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.
Files changed (114) hide show
  1. package/README.md +129 -0
  2. package/dist/actions/definitions/analytics.d.ts +18 -0
  3. package/dist/actions/definitions/content.d.ts +40 -0
  4. package/dist/actions/definitions/index.d.ts +26 -0
  5. package/dist/actions/definitions/navigation.d.ts +65 -0
  6. package/dist/actions/definitions/settings.d.ts +162 -0
  7. package/dist/actions/definitions/sources.d.ts +44 -0
  8. package/dist/actions/definitions/support.d.ts +15 -0
  9. package/dist/actions/definitions/team.d.ts +120 -0
  10. package/dist/actions/index.d.ts +33 -0
  11. package/dist/actions/registry.d.ts +110 -0
  12. package/dist/actions/types.d.ts +388 -0
  13. package/dist/api/client.d.ts +186 -0
  14. package/dist/api/mcp-client.d.ts +226 -0
  15. package/dist/button/FloatingButton.d.ts +44 -0
  16. package/dist/cli/sync.d.ts +2 -0
  17. package/dist/components/Button/EdgeTrigger.d.ts +78 -0
  18. package/dist/components/Button/FloatingButton.d.ts +46 -0
  19. package/dist/components/Button/index.d.ts +5 -0
  20. package/dist/components/Cards/ConfirmActionCard.d.ts +23 -0
  21. package/dist/components/Cards/index.d.ts +6 -0
  22. package/dist/components/Panel/ChatInput.d.ts +5 -0
  23. package/dist/components/Panel/ContextTag.d.ts +18 -0
  24. package/dist/components/Panel/Header.d.ts +14 -0
  25. package/dist/components/Panel/Panel.d.ts +104 -0
  26. package/dist/components/Panel/PanelContent.d.ts +6 -0
  27. package/dist/components/Panel/TaskButton.d.ts +59 -0
  28. package/dist/components/Panel/UnifiedChatInput.d.ts +23 -0
  29. package/dist/components/Panel/WorkflowChecklist.d.ts +9 -0
  30. package/dist/components/Panel/index.d.ts +13 -0
  31. package/dist/components/Panel/styles.d.ts +20 -0
  32. package/dist/components/Plan/InlinePlanView.d.ts +24 -0
  33. package/dist/components/Plan/PlanStepItem.d.ts +18 -0
  34. package/dist/components/Plan/PlanView.d.ts +10 -0
  35. package/dist/components/Plan/index.d.ts +8 -0
  36. package/dist/components/TextSelection/TextSelectionManager.d.ts +34 -0
  37. package/dist/components/TextSelection/TextSelectionPopover.d.ts +14 -0
  38. package/dist/components/TextSelection/index.d.ts +6 -0
  39. package/dist/components/TextSelection/styles.d.ts +5 -0
  40. package/dist/components/Tooltips/Tooltip.d.ts +46 -0
  41. package/dist/components/Tooltips/TooltipManager.d.ts +41 -0
  42. package/dist/components/Tooltips/index.d.ts +6 -0
  43. package/dist/components/Tooltips/styles.d.ts +5 -0
  44. package/dist/components/Views/ArticleChatView.d.ts +9 -0
  45. package/dist/components/Views/ArticleView.d.ts +10 -0
  46. package/dist/components/Views/CategoryView.d.ts +11 -0
  47. package/dist/components/Views/ChatView.d.ts +5 -0
  48. package/dist/components/Views/DeveloperView.d.ts +6 -0
  49. package/dist/components/Views/HomeView.d.ts +5 -0
  50. package/dist/components/Views/SearchView.d.ts +10 -0
  51. package/dist/components/Views/index.d.ts +5 -0
  52. package/dist/components/context.d.ts +21 -0
  53. package/dist/components/index.d.ts +9 -0
  54. package/dist/components/shared/ArticleCard.d.ts +17 -0
  55. package/dist/components/shared/CategoryCard.d.ts +17 -0
  56. package/dist/components/shared/Empty.d.ts +11 -0
  57. package/dist/components/shared/Loading.d.ts +6 -0
  58. package/dist/components/shared/MessageInputArea.d.ts +19 -0
  59. package/dist/components/shared/QuestionChip.d.ts +14 -0
  60. package/dist/components/shared/index.d.ts +7 -0
  61. package/dist/content/extensions/AccordionNode.d.ts +10 -0
  62. package/dist/content/extensions/CalloutNode.d.ts +11 -0
  63. package/dist/content/extensions/index.d.ts +5 -0
  64. package/dist/content/index.d.ts +5 -0
  65. package/dist/content/renderer.d.ts +24 -0
  66. package/dist/core/Pillar.d.ts +454 -0
  67. package/dist/core/config.d.ts +253 -0
  68. package/dist/core/context.d.ts +71 -0
  69. package/dist/core/events.d.ts +228 -0
  70. package/dist/core/plan-executor.d.ts +101 -0
  71. package/dist/core/plan.d.ts +155 -0
  72. package/dist/core/workflow.d.ts +89 -0
  73. package/dist/index.d.ts +32 -0
  74. package/dist/panel/Panel.d.ts +53 -0
  75. package/dist/panel/PanelUI.d.ts +43 -0
  76. package/dist/panel/components/ArticleCard.d.ts +10 -0
  77. package/dist/panel/components/CategoryCard.d.ts +10 -0
  78. package/dist/panel/components/ChatInput.d.ts +36 -0
  79. package/dist/panel/components/Header.d.ts +16 -0
  80. package/dist/panel/components/SearchInput.d.ts +11 -0
  81. package/dist/panel/styles.d.ts +5 -0
  82. package/dist/panel/views/ArticleView.d.ts +21 -0
  83. package/dist/panel/views/CategoryView.d.ts +20 -0
  84. package/dist/panel/views/ChatView.d.ts +30 -0
  85. package/dist/panel/views/HomeView.d.ts +18 -0
  86. package/dist/panel/views/SearchView.d.ts +22 -0
  87. package/dist/pillar.esm.js +10338 -0
  88. package/dist/pillar.esm.js.map +1 -0
  89. package/dist/pillar.js +10362 -0
  90. package/dist/pillar.js.map +1 -0
  91. package/dist/pillar.min.js +2 -0
  92. package/dist/pillar.min.js.map +1 -0
  93. package/dist/store/chat.d.ts +85 -0
  94. package/dist/store/context.d.ts +46 -0
  95. package/dist/store/developer.d.ts +19 -0
  96. package/dist/store/index.d.ts +10 -0
  97. package/dist/store/panel.d.ts +43 -0
  98. package/dist/store/plan-persistence.d.ts +47 -0
  99. package/dist/store/plan.d.ts +45 -0
  100. package/dist/store/router.d.ts +18 -0
  101. package/dist/store/tooltips.d.ts +21 -0
  102. package/dist/store/workflow.d.ts +48 -0
  103. package/dist/tooltips/Tooltip.d.ts +63 -0
  104. package/dist/tooltips/TooltipManager.d.ts +42 -0
  105. package/dist/tooltips/styles.d.ts +5 -0
  106. package/dist/types/index.d.ts +4 -0
  107. package/dist/types/user-context.d.ts +29 -0
  108. package/dist/utils/dom.d.ts +46 -0
  109. package/dist/utils/markdown.d.ts +9 -0
  110. package/dist/utils/positioning.d.ts +52 -0
  111. package/dist/utils/urlParams.d.ts +27 -0
  112. package/package.json +86 -0
  113. package/src/actions/types.ts +468 -0
  114. package/src/cli/sync.ts +477 -0
@@ -0,0 +1,477 @@
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();