lua-cli 3.5.0-alpha.2 → 3.5.0-alpha.3

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.
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Integrations Command
3
- * Manages third-party account connections via Unified.to
3
+ * Manages third-party integrations via Unified.to
4
4
  *
5
5
  * Core concepts:
6
6
  * - Integration: A supported third-party service (e.g., Linear, Google Calendar)
7
- * - Connection: An authenticated link between your agent and a specific account
7
+ * - Connection: An authenticated link between your agent and a specific integration
8
8
  * - MCP Server: Technical implementation that exposes connection tools to the agent (auto-managed)
9
9
  *
10
10
  * Flow:
@@ -25,13 +25,16 @@ import { safePrompt } from '../utils/prompt-handler.js';
25
25
  import { validateConfig, validateAgentConfig } from '../utils/dev-helpers.js';
26
26
  import DeveloperApi from '../api/developer.api.service.js';
27
27
  import UnifiedToApi from '../api/unifiedto.api.service.js';
28
- import { fetchServersCore, activateServerCore, deactivateServerCore } from './mcp.js';
28
+ import { fetchServersCore, activateServerCore, deactivateServerCore, } from './mcp.js';
29
29
  // ─────────────────────────────────────────────────────────────────────────────
30
30
  // Configuration
31
31
  // ─────────────────────────────────────────────────────────────────────────────
32
32
  const UNIFIED_MCP_BASE_URL = 'https://mcp-api.unified.to/mcp';
33
33
  const CALLBACK_PORT = 19837; // Random high port for OAuth callback
34
34
  const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
35
+ // Webhook trigger configuration
36
+ const AGENT_WEBHOOK_URL = `${BASE_URLS.API}/webhook/unifiedto/data`;
37
+ const DEFAULT_VIRTUAL_WEBHOOK_INTERVAL = 60; // minutes
35
38
  // ─────────────────────────────────────────────────────────────────────────────
36
39
  // Integration API Functions (via Lua API)
37
40
  // ─────────────────────────────────────────────────────────────────────────────
@@ -98,7 +101,7 @@ function startCallbackServer(timeoutMs = 300000) {
98
101
  <body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;">
99
102
  <div style="text-align: center; color: white;">
100
103
  <h1 style="color: #4ade80;">✅ Connection Successful!</h1>
101
- <p style="color: #ccc;">Your account has been connected.</p>
104
+ <p style="color: #ccc;">Your integration has been connected.</p>
102
105
  <p style="color: #888;">You can close this window and return to the terminal.</p>
103
106
  </div>
104
107
  </body>
@@ -182,7 +185,13 @@ async function executeNonInteractive(context, action, cmdOptions) {
182
185
  authMethod: cmdOptions?.authMethod,
183
186
  scopes: cmdOptions?.scopes,
184
187
  hideSensitive: cmdOptions?.hideSensitive !== 'false', // Default true, only false if explicitly set
188
+ // Trigger options
189
+ triggers: cmdOptions?.triggers,
190
+ customWebhook: cmdOptions?.customWebhook === true,
191
+ hookUrl: cmdOptions?.hookUrl,
185
192
  };
193
+ // Check for JSON output flag
194
+ const jsonOutput = cmdOptions?.json === true;
186
195
  switch (normalizedAction) {
187
196
  case 'connect':
188
197
  await connectIntegrationFlow(context, options);
@@ -201,6 +210,18 @@ async function executeNonInteractive(context, action, cmdOptions) {
201
210
  case 'available':
202
211
  await listAvailableIntegrations(context);
203
212
  break;
213
+ case 'info':
214
+ // New command: Show detailed info about an integration type
215
+ // Integration type can be passed as positional arg (info <type>) or as --integration flag
216
+ const infoIntegrationType = options.integration || cmdOptions?._?.[0];
217
+ if (!infoIntegrationType) {
218
+ console.error("❌ Integration type is required");
219
+ console.log("\nUsage: lua integrations info <type>");
220
+ console.log(" lua integrations info <type> --json");
221
+ process.exit(1);
222
+ }
223
+ await showIntegrationInfo(context, infoIntegrationType, jsonOutput);
224
+ break;
204
225
  case 'disconnect':
205
226
  if (!options.connectionId) {
206
227
  console.error("❌ --connection-id is required for disconnect");
@@ -228,7 +249,7 @@ async function interactiveIntegrationsManagement(context) {
228
249
  let continueManaging = true;
229
250
  while (continueManaging) {
230
251
  console.log("\n" + "=".repeat(60));
231
- console.log("🔗 Third-Party Integrations (via Unified.to)");
252
+ console.log("🔗 Integrations");
232
253
  console.log("=".repeat(60) + "\n");
233
254
  const actionAnswer = await safePrompt([
234
255
  {
@@ -236,13 +257,13 @@ async function interactiveIntegrationsManagement(context) {
236
257
  name: 'action',
237
258
  message: 'What would you like to do?',
238
259
  choices: [
239
- { name: '➕ Connect a new account', value: 'connect' },
260
+ { name: '➕ Connect a new integration', value: 'connect' },
240
261
  { name: '🔄 Update connection scopes', value: 'update' },
241
- { name: '📋 List connected accounts', value: 'list' },
262
+ { name: '📋 List connected integrations', value: 'list' },
242
263
  { name: '🔍 View available integrations', value: 'available' },
243
- { name: '🔔 Manage webhook subscriptions', value: 'webhooks' },
264
+ { name: '🔔 Manage triggers', value: 'webhooks' },
244
265
  { name: '🔌 Manage MCP servers', value: 'mcp' },
245
- { name: '🗑️ Disconnect an account', value: 'disconnect' },
266
+ { name: '🗑️ Disconnect an integration', value: 'disconnect' },
246
267
  { name: '❌ Exit', value: 'exit' }
247
268
  ]
248
269
  }
@@ -319,6 +340,130 @@ async function listAvailableIntegrations(context) {
319
340
  }
320
341
  }
321
342
  // ─────────────────────────────────────────────────────────────────────────────
343
+ // Integration Info (Non-Interactive Discovery)
344
+ // ─────────────────────────────────────────────────────────────────────────────
345
+ /**
346
+ * Show detailed information about an integration type
347
+ * Used for non-interactive discovery of OAuth scopes and webhook events
348
+ */
349
+ async function showIntegrationInfo(context, integrationType, jsonOutput = false) {
350
+ try {
351
+ // Fetch integration details
352
+ const integrations = await fetchAvailableIntegrations(context.unifiedToApi);
353
+ const integration = integrations.find(i => i.value === integrationType);
354
+ if (!integration) {
355
+ if (jsonOutput) {
356
+ console.log(JSON.stringify({ error: `Integration not found: ${integrationType}` }));
357
+ }
358
+ else {
359
+ console.error(`❌ Integration not found: ${integrationType}`);
360
+ console.log('\nAvailable integrations:');
361
+ integrations.forEach(i => console.log(` - ${i.value} (${i.name})`));
362
+ }
363
+ process.exit(1);
364
+ }
365
+ // Fetch available webhook events for this integration
366
+ let webhookEvents = [];
367
+ try {
368
+ const eventsResult = await context.unifiedToApi.getAvailableWebhookEventsByType(integrationType);
369
+ if (eventsResult.success && eventsResult.data) {
370
+ webhookEvents = eventsResult.data;
371
+ }
372
+ }
373
+ catch {
374
+ // Silently ignore if webhook events can't be fetched
375
+ }
376
+ if (jsonOutput) {
377
+ // Output as JSON for scripting
378
+ const output = {
379
+ type: integration.value,
380
+ name: integration.name,
381
+ categories: integration.categories,
382
+ authSupport: integration.authSupport,
383
+ oauthConfigured: integration.oauthConfigured,
384
+ oauthScopes: integration.oauthScopes || [],
385
+ tokenFields: integration.tokenFields || [],
386
+ webhookEvents: webhookEvents.map(e => ({
387
+ trigger: `${e.objectType}.${e.event}`,
388
+ objectType: e.objectType,
389
+ event: e.event,
390
+ webhookType: e.webhookType,
391
+ friendlyLabel: e.friendlyLabel,
392
+ friendlyDescription: e.friendlyDescription,
393
+ availableFilters: e.availableFilters,
394
+ })),
395
+ webhookDefaults: {
396
+ agentWebhookUrl: AGENT_WEBHOOK_URL,
397
+ defaultVirtualInterval: DEFAULT_VIRTUAL_WEBHOOK_INTERVAL,
398
+ },
399
+ };
400
+ console.log(JSON.stringify(output, null, 2));
401
+ }
402
+ else {
403
+ // Human-readable output
404
+ console.log("\n" + "=".repeat(60));
405
+ console.log(`📋 Integration: ${integration.name}`);
406
+ console.log("=".repeat(60));
407
+ console.log(`\n Type: ${integration.value}`);
408
+ console.log(` Categories: ${integration.categories.join(', ')}`);
409
+ console.log(` Auth Support: ${integration.authSupport}`);
410
+ console.log(` OAuth Configured: ${integration.oauthConfigured ? 'Yes' : 'No'}`);
411
+ // OAuth Scopes
412
+ if (integration.oauthScopes && integration.oauthScopes.length > 0) {
413
+ console.log('\n OAuth Scopes:');
414
+ integration.oauthScopes.forEach(s => {
415
+ if (s.friendlyLabel) {
416
+ console.log(` - ${s.friendlyLabel} (${s.unifiedScope})`);
417
+ if (s.friendlyDescription) {
418
+ console.log(` ${s.friendlyDescription}`);
419
+ }
420
+ }
421
+ else {
422
+ console.log(` - ${s.unifiedScope} → [${s.originalScopes.join(', ')}]`);
423
+ }
424
+ });
425
+ }
426
+ // Token Fields
427
+ if (integration.tokenFields && integration.tokenFields.length > 0) {
428
+ console.log('\n Token Fields:');
429
+ integration.tokenFields.forEach(f => {
430
+ console.log(` - ${f.name}`);
431
+ if (f.instructions) {
432
+ console.log(` 💡 ${f.instructions}`);
433
+ }
434
+ });
435
+ }
436
+ // Webhook Events/Triggers
437
+ if (webhookEvents.length > 0) {
438
+ console.log('\n Available Triggers:');
439
+ webhookEvents.forEach(e => {
440
+ console.log(` - ${e.objectType}.${e.event} [${e.webhookType}] - ${e.friendlyDescription}`);
441
+ });
442
+ }
443
+ else {
444
+ console.log('\n Available Triggers: None');
445
+ }
446
+ console.log("\n" + "=".repeat(60));
447
+ console.log("\n💡 Use with non-interactive commands:");
448
+ console.log(` lua integrations connect --integration ${integrationType} --auth-method oauth --scopes all`);
449
+ if (webhookEvents.length > 0) {
450
+ const exampleTrigger = `${webhookEvents[0].objectType}.${webhookEvents[0].event}`;
451
+ console.log(` lua integrations connect --integration ${integrationType} --triggers ${exampleTrigger}`);
452
+ }
453
+ console.log();
454
+ }
455
+ }
456
+ catch (error) {
457
+ if (jsonOutput) {
458
+ console.log(JSON.stringify({ error: error.message }));
459
+ }
460
+ else {
461
+ writeError(`❌ Failed to get integration info: ${error.message}`);
462
+ }
463
+ process.exit(1);
464
+ }
465
+ }
466
+ // ─────────────────────────────────────────────────────────────────────────────
322
467
  // Connect Flow
323
468
  // ─────────────────────────────────────────────────────────────────────────────
324
469
  async function connectIntegrationFlow(context, options = {}) {
@@ -527,11 +672,18 @@ async function connectIntegrationFlow(context, options = {}) {
527
672
  message: 'Select OAuth scopes (Space to toggle, Enter to confirm, leave empty for all):',
528
673
  pageSize: 15,
529
674
  loop: false,
530
- choices: selectedIntegration.oauthScopes.map(s => ({
531
- name: `${s.unifiedScope} → [${s.originalScopes.join(', ')}]`,
532
- value: s.unifiedScope,
533
- checked: false
534
- }))
675
+ choices: selectedIntegration.oauthScopes.map(s => {
676
+ // Use friendly label if available, otherwise fall back to technical name
677
+ const displayName = s.friendlyLabel || s.unifiedScope;
678
+ const description = s.friendlyDescription
679
+ ? ` (${s.friendlyDescription})`
680
+ : ` → [${s.originalScopes.join(', ')}]`;
681
+ return {
682
+ name: `${displayName}${description}`,
683
+ value: s.unifiedScope,
684
+ checked: false
685
+ };
686
+ })
535
687
  }]);
536
688
  if (!scopeAnswer)
537
689
  return;
@@ -577,7 +729,132 @@ async function connectIntegrationFlow(context, options = {}) {
577
729
  hideSensitive = sensitiveAnswer.hide;
578
730
  }
579
731
  }
580
- // Step 4: Get authorization URL from Lua API
732
+ // Step 4: Select webhook triggers (agent wake-up events)
733
+ let selectedTriggers = [];
734
+ let webhookUrl = AGENT_WEBHOOK_URL; // Default: agent trigger mode
735
+ let isCustomWebhook = false;
736
+ try {
737
+ const eventsResult = await context.unifiedToApi.getAvailableWebhookEventsByType(selectedIntegration.value);
738
+ if (eventsResult.success && eventsResult.data && eventsResult.data.length > 0) {
739
+ const availableEvents = eventsResult.data;
740
+ // Determine webhook mode
741
+ if (options.customWebhook || options.hookUrl) {
742
+ isCustomWebhook = true;
743
+ if (options.hookUrl) {
744
+ webhookUrl = options.hookUrl;
745
+ }
746
+ }
747
+ if (options.triggers) {
748
+ // Non-interactive: parse comma-separated triggers (e.g., 'task_task.created,task_task.updated')
749
+ const requestedTriggers = options.triggers.split(',').map(t => t.trim());
750
+ for (const trigger of requestedTriggers) {
751
+ const [objectType, event] = trigger.split('.');
752
+ const matchingEvent = availableEvents.find(e => e.objectType === objectType && e.event === event);
753
+ if (!matchingEvent) {
754
+ console.error(`❌ Invalid trigger: ${trigger}`);
755
+ console.log(`\nAvailable triggers for ${selectedIntegration.name}:`);
756
+ availableEvents.forEach(e => {
757
+ console.log(` - ${e.objectType}.${e.event} [${e.webhookType}] - ${e.friendlyDescription}`);
758
+ });
759
+ process.exit(1);
760
+ }
761
+ selectedTriggers.push(matchingEvent);
762
+ }
763
+ writeInfo(`Selected ${selectedTriggers.length} trigger(s)`);
764
+ // If custom webhook mode, prompt for URL if not provided
765
+ if (isCustomWebhook && !options.hookUrl) {
766
+ const urlAnswer = await safePrompt([{
767
+ type: 'input',
768
+ name: 'url',
769
+ message: 'Enter your webhook URL:',
770
+ validate: (input) => {
771
+ try {
772
+ new URL(input);
773
+ return true;
774
+ }
775
+ catch {
776
+ return 'Please enter a valid URL';
777
+ }
778
+ }
779
+ }]);
780
+ if (!urlAnswer)
781
+ return;
782
+ webhookUrl = urlAnswer.url;
783
+ }
784
+ }
785
+ else {
786
+ // Interactive: show trigger selection
787
+ console.log('\n' + '─'.repeat(60));
788
+ console.log('📡 Webhook Triggers');
789
+ console.log('─'.repeat(60));
790
+ console.log('\nSelect events that will wake up your agent:');
791
+ console.log('(Space to toggle, Enter to confirm. All selected by default.)');
792
+ const triggerAnswer = await safePrompt([{
793
+ type: 'checkbox',
794
+ name: 'triggers',
795
+ message: 'Select triggers:',
796
+ pageSize: 15,
797
+ loop: false,
798
+ choices: availableEvents.map(e => ({
799
+ name: `${e.friendlyDescription} [${e.webhookType}]`,
800
+ value: e,
801
+ checked: true // Pre-select all by default
802
+ }))
803
+ }]);
804
+ if (triggerAnswer && triggerAnswer.triggers.length > 0) {
805
+ selectedTriggers = triggerAnswer.triggers;
806
+ // Ask about webhook mode only if triggers are selected
807
+ const modeAnswer = await safePrompt([{
808
+ type: 'list',
809
+ name: 'mode',
810
+ message: 'How should these events be handled?',
811
+ choices: [
812
+ { name: '🤖 Agent Trigger (recommended) - Events wake up your agent', value: 'agent' },
813
+ { name: '🔗 Custom URL - Send events to your own webhook URL', value: 'custom' }
814
+ ]
815
+ }]);
816
+ if (!modeAnswer)
817
+ return;
818
+ if (modeAnswer.mode === 'custom') {
819
+ isCustomWebhook = true;
820
+ const urlAnswer = await safePrompt([{
821
+ type: 'input',
822
+ name: 'url',
823
+ message: 'Enter your webhook URL:',
824
+ validate: (input) => {
825
+ try {
826
+ new URL(input);
827
+ return true;
828
+ }
829
+ catch {
830
+ return 'Please enter a valid URL';
831
+ }
832
+ }
833
+ }]);
834
+ if (!urlAnswer)
835
+ return;
836
+ webhookUrl = urlAnswer.url;
837
+ }
838
+ console.log(`\n✓ ${selectedTriggers.length} trigger(s) selected`);
839
+ if (isCustomWebhook) {
840
+ console.log(` → Events will be sent to: ${webhookUrl}`);
841
+ }
842
+ else {
843
+ console.log(` → Events will wake up your agent`);
844
+ }
845
+ }
846
+ else {
847
+ console.log(`\n⏭️ No triggers selected - your agent won't be event-driven`);
848
+ console.log(` You can add triggers later with: lua integrations webhooks create`);
849
+ }
850
+ }
851
+ }
852
+ }
853
+ catch (error) {
854
+ // Non-fatal: continue without triggers if we can't fetch events
855
+ writeInfo(`Note: Could not fetch available triggers (${error.message})`);
856
+ }
857
+ // Step 5: Get authorization URL from Lua API
581
858
  writeProgress("🔄 Preparing authorization...");
582
859
  const state = Buffer.from(JSON.stringify({
583
860
  agentId: context.agentId,
@@ -642,22 +919,24 @@ async function connectIntegrationFlow(context, options = {}) {
642
919
  const result = await callbackPromise;
643
920
  if (result.success && result.connectionId) {
644
921
  writeSuccess("\n✅ Authorization successful!");
645
- // Finalize the connection (sets up MCP server internally)
646
- await finalizeConnection(context, selectedIntegration, result.connectionId, selectedScopes, hideSensitive);
922
+ // Finalize the connection (sets up MCP server internally and creates webhooks)
923
+ await finalizeConnection(context, selectedIntegration, result.connectionId, selectedScopes, hideSensitive, {
924
+ triggers: selectedTriggers,
925
+ webhookUrl,
926
+ isCustomWebhook,
927
+ });
647
928
  }
648
929
  else {
649
930
  writeError(`\n❌ Authorization failed: ${result.error || 'Unknown error'}`);
650
931
  console.log("💡 Please try again with 'lua integrations connect'\n");
651
932
  }
652
933
  }
653
- // ─────────────────────────────────────────────────────────────────────────────
654
- // Connection Management
655
- // ─────────────────────────────────────────────────────────────────────────────
656
934
  /**
657
- * Finalizes a new connection by setting up the MCP server (internal implementation)
935
+ * Finalizes a new connection by setting up the MCP server and creating webhooks
658
936
  * The MCP server enables the agent to use tools from this connection
937
+ * Webhooks enable the agent to be triggered by events from the connection
659
938
  */
660
- async function finalizeConnection(context, integration, connectionId, scopes, hideSensitive = true) {
939
+ async function finalizeConnection(context, integration, connectionId, scopes, hideSensitive = true, webhookConfig = { triggers: [], webhookUrl: '', isCustomWebhook: false }) {
661
940
  writeProgress(`🔄 Setting up ${integration.name} connection...`);
662
941
  // Build MCP URL
663
942
  let mcpUrl = `${UNIFIED_MCP_BASE_URL}?connection=${connectionId}`;
@@ -679,44 +958,154 @@ async function finalizeConnection(context, integration, connectionId, scopes, hi
679
958
  source: 'unifiedto', // Flag for runtime auth injection
680
959
  timeout: 30000,
681
960
  };
961
+ // ─────────────────────────────────────────────────────────────────────────
962
+ // Step 1: Set up MCP Server (for tools)
963
+ // ─────────────────────────────────────────────────────────────────────────
964
+ let mcpSetupSuccess = false;
965
+ let isActive = false;
966
+ let mcpError = null;
682
967
  try {
968
+ writeProgress(`🔄 Setting up MCP server...`);
683
969
  const result = await context.developerApi.createMCPServer(mcpServerData);
684
970
  if (result.success && result.data) {
685
971
  // Auto-activate the MCP server so the agent can use the connection immediately
686
972
  const activateResult = await context.developerApi.activateMCPServer(result.data.id);
687
- const isActive = activateResult.success && activateResult.data?.active;
688
- // Connection success message
689
- console.log("\n" + "─".repeat(60));
690
- console.log("🎉 Connection Established!");
691
- console.log("─".repeat(60));
692
- console.log(`\n Integration: ${integration.name}`);
693
- console.log(` Connection ID: ${connectionId}`);
694
- console.log(` Status: ${isActive ? '🟢 Active' : '⚪ Pending'}`);
695
- // Show requested scopes (actual capabilities)
696
- if (scopes.length > 0) {
697
- console.log(`\n Scopes:`);
698
- scopes.forEach(s => console.log(` - ${s}`));
973
+ isActive = activateResult.success && activateResult.data?.active === true;
974
+ mcpSetupSuccess = true;
975
+ }
976
+ else {
977
+ mcpError = result.error?.message || 'Unknown error';
978
+ }
979
+ }
980
+ catch (error) {
981
+ mcpError = error.message || 'Unknown error';
982
+ }
983
+ // ─────────────────────────────────────────────────────────────────────────
984
+ // Step 2: Set up Webhook Triggers (for event-driven agent wake-up)
985
+ // ─────────────────────────────────────────────────────────────────────────
986
+ const createdWebhooks = [];
987
+ const failedWebhooks = [];
988
+ if (webhookConfig.triggers.length > 0) {
989
+ writeProgress(`🔄 Setting up ${webhookConfig.triggers.length} trigger(s)...`);
990
+ for (const trigger of webhookConfig.triggers) {
991
+ try {
992
+ // Use per-trigger hookUrl/interval if available, otherwise fall back to defaults
993
+ const hookUrl = trigger.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL;
994
+ const interval = trigger.interval ?? (trigger.webhookType === 'virtual' ? DEFAULT_VIRTUAL_WEBHOOK_INTERVAL : undefined);
995
+ const webhookResult = await context.unifiedToApi.createWebhookSubscription(context.agentId, {
996
+ connectionId,
997
+ objectType: trigger.objectType,
998
+ event: trigger.event,
999
+ hookUrl,
1000
+ interval,
1001
+ });
1002
+ if (webhookResult.success && webhookResult.data) {
1003
+ createdWebhooks.push(webhookResult.data);
1004
+ }
1005
+ else {
1006
+ const errorMsg = webhookResult.error?.message || 'Unknown error';
1007
+ failedWebhooks.push(`${trigger.objectType}.${trigger.event}: ${errorMsg}`);
1008
+ }
699
1009
  }
700
- else {
701
- console.log(`\n Scopes: Default permissions`);
1010
+ catch (error) {
1011
+ const errorMsg = error?.message || 'Unknown error';
1012
+ failedWebhooks.push(`${trigger.objectType}.${trigger.event}: ${errorMsg}`);
702
1013
  }
703
- console.log(` Hide Sensitive: ${hideSensitive ? '✅ Yes (recommended)' : '❌ No'}`);
704
- if (!isActive) {
705
- console.log(`\n⚠️ Connection is pending activation.`);
706
- console.log(` Run: lua mcp activate --server-name ${serverName}`);
1014
+ }
1015
+ }
1016
+ // ─────────────────────────────────────────────────────────────────────────
1017
+ // Step 3: Display Results
1018
+ // ─────────────────────────────────────────────────────────────────────────
1019
+ console.log("\n" + "─".repeat(60));
1020
+ console.log("🎉 Connection Established!");
1021
+ console.log("─".repeat(60));
1022
+ console.log(`\n Integration: ${integration.name}`);
1023
+ console.log(` Connection ID: ${connectionId}`);
1024
+ // MCP Server status
1025
+ console.log(`\n 📦 MCP Server (Tools):`);
1026
+ if (mcpSetupSuccess) {
1027
+ console.log(` Status: ${isActive ? '🟢 Active' : '🟡 Pending activation'}`);
1028
+ console.log(` Name: ${serverName}`);
1029
+ console.log(` Hide Sensitive: ${hideSensitive ? '✅ Yes' : '❌ No'}`);
1030
+ }
1031
+ else {
1032
+ console.log(` Status: ❌ Failed`);
1033
+ console.log(` Error: ${mcpError}`);
1034
+ }
1035
+ // Show requested scopes with friendly labels
1036
+ if (scopes.length > 0) {
1037
+ console.log(`\n 🔑 Permissions (${scopes.length}):`);
1038
+ scopes.forEach(scopeName => {
1039
+ const scopeInfo = integration.oauthScopes?.find(s => s.unifiedScope === scopeName);
1040
+ if (scopeInfo?.friendlyLabel) {
1041
+ console.log(` • ${scopeInfo.friendlyLabel}`);
707
1042
  }
708
1043
  else {
709
- console.log(`\n✅ Your agent can now use ${integration.name} tools!`);
1044
+ console.log(` • ${scopeName}`);
710
1045
  }
711
- console.log();
1046
+ });
1047
+ }
1048
+ else {
1049
+ console.log(`\n 🔑 Permissions: Default`);
1050
+ }
1051
+ // Webhook/Triggers status
1052
+ if (webhookConfig.triggers.length > 0) {
1053
+ console.log(`\n ⚡ Triggers (${createdWebhooks.length}/${webhookConfig.triggers.length}):`);
1054
+ // Compute actual URLs being used (per-trigger hookUrl or default)
1055
+ const actualUrls = webhookConfig.triggers.map(t => t.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL);
1056
+ const uniqueUrls = [...new Set(actualUrls)];
1057
+ const allAgentTriggers = uniqueUrls.every(url => url === AGENT_WEBHOOK_URL);
1058
+ if (allAgentTriggers) {
1059
+ console.log(` Mode: Agent wake-up`);
1060
+ }
1061
+ else if (uniqueUrls.length === 1) {
1062
+ console.log(` Mode: Custom webhook`);
1063
+ console.log(` URL: ${uniqueUrls[0]}`);
712
1064
  }
713
1065
  else {
714
- writeError(`❌ Failed to set up connection: ${result.error?.message}`);
1066
+ console.log(` Mode: Mixed (per-trigger URLs)`);
1067
+ }
1068
+ if (createdWebhooks.length > 0) {
1069
+ createdWebhooks.forEach(wh => {
1070
+ const trigger = webhookConfig.triggers.find(t => t.objectType === wh.objectType && t.event === wh.event);
1071
+ const desc = trigger?.friendlyDescription || `${wh.objectType}.${wh.event}`;
1072
+ console.log(` ✓ ${desc}`);
1073
+ });
1074
+ }
1075
+ if (failedWebhooks.length > 0) {
1076
+ failedWebhooks.forEach(t => console.log(` ✗ ${t}`));
715
1077
  }
716
1078
  }
717
- catch (error) {
718
- writeError(`❌ Error setting up connection: ${error.message}`);
1079
+ // Summary message
1080
+ console.log("\n" + "─".repeat(60));
1081
+ const capabilities = [];
1082
+ if (mcpSetupSuccess && isActive) {
1083
+ capabilities.push('tools via MCP');
1084
+ }
1085
+ if (createdWebhooks.length > 0) {
1086
+ // Check if any triggers use custom URLs
1087
+ const hasCustomUrls = webhookConfig.triggers.some(t => {
1088
+ const url = t.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL;
1089
+ return url !== AGENT_WEBHOOK_URL;
1090
+ });
1091
+ if (hasCustomUrls) {
1092
+ capabilities.push('webhooks to custom URL');
1093
+ }
1094
+ else {
1095
+ capabilities.push('event-driven triggers');
1096
+ }
719
1097
  }
1098
+ if (capabilities.length > 0) {
1099
+ console.log(`✅ ${integration.name} connected! Your agent now has: ${capabilities.join(', ')}`);
1100
+ }
1101
+ else if (mcpSetupSuccess && !isActive) {
1102
+ console.log(`⚠️ Connection created but MCP server pending activation.`);
1103
+ console.log(` Run: lua mcp activate --server-name ${serverName}`);
1104
+ }
1105
+ else {
1106
+ console.log(`⚠️ Connection created but setup incomplete. Check errors above.`);
1107
+ }
1108
+ console.log();
720
1109
  }
721
1110
  async function listConnections(context) {
722
1111
  writeProgress("🔄 Loading connections...");
@@ -738,11 +1127,11 @@ async function listConnections(context) {
738
1127
  }
739
1128
  }
740
1129
  console.log("\n" + "=".repeat(60));
741
- console.log("🔗 Connected Accounts");
1130
+ console.log("🔗 Connected Integrations");
742
1131
  console.log("=".repeat(60) + "\n");
743
1132
  if (connections.length === 0) {
744
- console.log("ℹ️ No accounts connected yet.");
745
- console.log("💡 Run 'lua integrations connect' to connect a new account.\n");
1133
+ console.log("ℹ️ No integrations connected yet.");
1134
+ console.log("💡 Run 'lua integrations connect' to connect an integration.\n");
746
1135
  return;
747
1136
  }
748
1137
  for (const connection of connections) {
@@ -765,23 +1154,26 @@ async function listConnections(context) {
765
1154
  }
766
1155
  else if (connection.status === 'active') {
767
1156
  statusIcon = '🟡';
768
- statusText = 'Connected (tools pending)';
1157
+ statusText = 'Connected (MCP pending)';
769
1158
  }
770
1159
  console.log(`${statusIcon} ${connection.integrationName || connection.integrationType}`);
771
- console.log(` ID: ${connection.id}`);
1160
+ console.log(` Connection: ${connection.id}`);
772
1161
  console.log(` Status: ${statusText}`);
1162
+ if (linkedServer) {
1163
+ console.log(` MCP Server: ${linkedServer.name} (${linkedServer.active ? 'active' : 'inactive'})`);
1164
+ }
773
1165
  console.log(` Connected: ${new Date(connection.createdAt).toLocaleDateString()}`);
774
1166
  console.log();
775
1167
  }
776
1168
  console.log("=".repeat(60));
777
- console.log(`Total: ${connections.length} connection(s)\n`);
1169
+ console.log(`Total: ${connections.length} integration(s)\n`);
778
1170
  }
779
1171
  catch (error) {
780
1172
  writeError(`❌ Error loading connections: ${error.message}`);
781
1173
  }
782
1174
  }
783
1175
  async function disconnectIntegration(context, connectionId) {
784
- writeProgress(`🔄 Disconnecting...`);
1176
+ writeProgress(`🔄 Disconnecting... (please wait, do not close this window)`);
785
1177
  try {
786
1178
  // Clean up associated MCP server (internal implementation detail)
787
1179
  const mcpServers = await context.developerApi.getMCPServers();
@@ -794,9 +1186,9 @@ async function disconnectIntegration(context, connectionId) {
794
1186
  // Delete the connection (also deletes webhook subscriptions)
795
1187
  const deleteResult = await context.unifiedToApi.deleteConnection(connectionId, context.agentId);
796
1188
  if (deleteResult.success) {
797
- writeSuccess(`✅ Account disconnected successfully!`);
1189
+ writeSuccess(`✅ Integration disconnected successfully!`);
798
1190
  if (deleteResult.data?.deletedWebhooksCount && deleteResult.data.deletedWebhooksCount > 0) {
799
- console.log(` ✓ Deleted ${deleteResult.data.deletedWebhooksCount} webhook subscription(s)`);
1191
+ console.log(` ✓ Deleted ${deleteResult.data.deletedWebhooksCount} trigger(s)`);
800
1192
  }
801
1193
  console.log();
802
1194
  }
@@ -817,14 +1209,14 @@ async function disconnectIntegrationInteractive(context) {
817
1209
  }
818
1210
  const connections = connectionsResult.data || [];
819
1211
  if (connections.length === 0) {
820
- console.log("\nℹ️ No accounts to disconnect.\n");
1212
+ console.log("\nℹ️ No integrations to disconnect.\n");
821
1213
  return;
822
1214
  }
823
1215
  const connectionAnswer = await safePrompt([
824
1216
  {
825
1217
  type: 'list',
826
1218
  name: 'connection',
827
- message: 'Select an account to disconnect:',
1219
+ message: 'Select an integration to disconnect:',
828
1220
  choices: connections.map(c => ({
829
1221
  name: `${c.integrationName || c.integrationType} (${c.id.substring(0, 8)}...)`,
830
1222
  value: c.id
@@ -838,7 +1230,7 @@ async function disconnectIntegrationInteractive(context) {
838
1230
  {
839
1231
  type: 'confirm',
840
1232
  name: 'confirm',
841
- message: `Disconnect ${selectedConnection?.integrationName || selectedConnection?.integrationType}? Your agent will lose access to this account.`,
1233
+ message: `Disconnect ${selectedConnection?.integrationName || selectedConnection?.integrationType}? Your agent will lose access to this integration.`,
842
1234
  default: false
843
1235
  }
844
1236
  ]);
@@ -873,8 +1265,8 @@ async function updateConnectionFlow(context, options = {}) {
873
1265
  return;
874
1266
  }
875
1267
  if (connections.length === 0) {
876
- writeInfo("No connections to update.");
877
- console.log("💡 Run 'lua integrations connect' to connect a new account.\n");
1268
+ writeInfo("No integrations to update.");
1269
+ console.log("💡 Run 'lua integrations connect' to connect an integration.\n");
878
1270
  return;
879
1271
  }
880
1272
  // Step 2: Select connection to update
@@ -953,11 +1345,18 @@ async function updateConnectionFlow(context, options = {}) {
953
1345
  message: 'Select new OAuth scopes (Space to toggle, Enter to confirm, leave empty for all):',
954
1346
  pageSize: 15,
955
1347
  loop: false,
956
- choices: selectedIntegration.oauthScopes.map(s => ({
957
- name: `${s.unifiedScope} → [${s.originalScopes.join(', ')}]`,
958
- value: s.unifiedScope,
959
- checked: false
960
- }))
1348
+ choices: selectedIntegration.oauthScopes.map(s => {
1349
+ // Use friendly label if available, otherwise fall back to technical name
1350
+ const displayName = s.friendlyLabel || s.unifiedScope;
1351
+ const description = s.friendlyDescription
1352
+ ? ` (${s.friendlyDescription})`
1353
+ : ` → [${s.originalScopes.join(', ')}]`;
1354
+ return {
1355
+ name: `${displayName}${description}`,
1356
+ value: s.unifiedScope,
1357
+ checked: false
1358
+ };
1359
+ })
961
1360
  }]);
962
1361
  if (!scopeAnswer)
963
1362
  return;
@@ -983,7 +1382,28 @@ async function updateConnectionFlow(context, options = {}) {
983
1382
  hideSensitive = sensitiveAnswer.hide;
984
1383
  }
985
1384
  }
986
- // Step 5: Confirm the update
1385
+ // Step 5: Fetch existing triggers before deletion (so we can re-create them)
1386
+ let existingTriggers = [];
1387
+ try {
1388
+ const webhooksResult = await context.unifiedToApi.getWebhookSubscriptions(context.agentId);
1389
+ if (webhooksResult.success && webhooksResult.data && webhooksResult.data.length > 0) {
1390
+ // Filter webhooks to only those belonging to this connection
1391
+ const connectionWebhooks = webhooksResult.data.filter(w => w.connectionId === selectedConnection.id);
1392
+ existingTriggers = connectionWebhooks.map(w => ({
1393
+ objectType: w.objectType,
1394
+ event: w.event,
1395
+ hookUrl: w.hookUrl,
1396
+ interval: w.interval,
1397
+ }));
1398
+ if (existingTriggers.length > 0) {
1399
+ writeInfo(`Found ${existingTriggers.length} existing trigger(s) - will restore after update`);
1400
+ }
1401
+ }
1402
+ }
1403
+ catch (error) {
1404
+ // If we can't fetch existing triggers, continue without them
1405
+ }
1406
+ // Step 6: Confirm the update
987
1407
  if (!options.integration) {
988
1408
  const confirmAnswer = await safePrompt([
989
1409
  {
@@ -998,7 +1418,7 @@ async function updateConnectionFlow(context, options = {}) {
998
1418
  return;
999
1419
  }
1000
1420
  }
1001
- // Step 6: Delete the old connection (silently)
1421
+ // Step 7: Delete the old connection (silently)
1002
1422
  writeProgress(`🔄 Updating ${selectedIntegration.name}...`);
1003
1423
  try {
1004
1424
  // Clean up old MCP server
@@ -1009,14 +1429,14 @@ async function updateConnectionFlow(context, options = {}) {
1009
1429
  await context.developerApi.deleteMCPServer(associatedServer.id);
1010
1430
  }
1011
1431
  }
1012
- // Delete old connection
1432
+ // Delete old connection (this also deletes associated webhooks in Unified.to)
1013
1433
  await context.unifiedToApi.deleteConnection(selectedConnection.id, context.agentId);
1014
1434
  }
1015
1435
  catch (error) {
1016
1436
  writeError(`❌ Failed to remove old connection: ${error.message}`);
1017
1437
  return;
1018
1438
  }
1019
- // Step 7: Create new connection with new scopes
1439
+ // Step 8: Create new connection with new scopes
1020
1440
  const state = Buffer.from(JSON.stringify({
1021
1441
  agentId: context.agentId,
1022
1442
  integration: selectedIntegration.value,
@@ -1057,7 +1477,7 @@ async function updateConnectionFlow(context, options = {}) {
1057
1477
  catch (error) {
1058
1478
  // Use original auth URL if pre-fetch fails
1059
1479
  }
1060
- // Step 8: Open browser and wait for callback
1480
+ // Step 9: Open browser and wait for callback
1061
1481
  console.log("\n" + "─".repeat(60));
1062
1482
  console.log("🌐 Re-authorizing with new scopes...");
1063
1483
  console.log("─".repeat(60));
@@ -1075,7 +1495,39 @@ async function updateConnectionFlow(context, options = {}) {
1075
1495
  const result = await callbackPromise;
1076
1496
  if (result.success && result.connectionId) {
1077
1497
  writeSuccess("\n✅ Authorization successful!");
1078
- await finalizeConnection(context, selectedIntegration, result.connectionId, selectedScopes, hideSensitive);
1498
+ // Check if any triggers use custom webhooks
1499
+ const hasCustomWebhook = existingTriggers.some(t => t.hookUrl !== AGENT_WEBHOOK_URL);
1500
+ // Convert existing triggers to TriggerWithSettings format, preserving per-trigger hookUrl and interval
1501
+ let triggersToRestore = [];
1502
+ if (existingTriggers.length > 0) {
1503
+ try {
1504
+ const eventsResult = await context.unifiedToApi.getAvailableWebhookEventsByType(selectedIntegration.value);
1505
+ if (eventsResult.success && eventsResult.data) {
1506
+ for (const trigger of existingTriggers) {
1507
+ const matchingEvent = eventsResult.data.find(e => e.objectType === trigger.objectType && e.event === trigger.event);
1508
+ if (matchingEvent) {
1509
+ // Preserve per-trigger hookUrl and interval from original webhook
1510
+ triggersToRestore.push({
1511
+ ...matchingEvent,
1512
+ hookUrl: trigger.hookUrl,
1513
+ interval: trigger.interval,
1514
+ });
1515
+ }
1516
+ }
1517
+ }
1518
+ }
1519
+ catch (error) {
1520
+ // If we can't match triggers, continue without them
1521
+ }
1522
+ }
1523
+ await finalizeConnection(context, selectedIntegration, result.connectionId, selectedScopes, hideSensitive, {
1524
+ triggers: triggersToRestore,
1525
+ webhookUrl: AGENT_WEBHOOK_URL, // Default, but per-trigger hookUrl will override if set
1526
+ isCustomWebhook: hasCustomWebhook,
1527
+ });
1528
+ if (triggersToRestore.length > 0) {
1529
+ writeSuccess(`Restored ${triggersToRestore.length} trigger(s) from previous connection`);
1530
+ }
1079
1531
  }
1080
1532
  else {
1081
1533
  writeError(`\n❌ Authorization failed: ${result.error || 'Unknown error'}`);
@@ -1095,12 +1547,14 @@ async function webhooksSubcommand(context, cmdOptions) {
1095
1547
  webhookId: cmdOptions?.webhookId,
1096
1548
  objectType: cmdOptions?.object,
1097
1549
  event: cmdOptions?.event,
1098
- webhook: cmdOptions?.webhook,
1550
+ hookUrl: cmdOptions?.hookUrl,
1099
1551
  interval: cmdOptions?.interval ? parseInt(cmdOptions.interval, 10) : undefined,
1552
+ integration: cmdOptions?.integration,
1100
1553
  };
1554
+ const jsonOutput = cmdOptions?.json === true;
1101
1555
  switch (subAction) {
1102
1556
  case 'list':
1103
- await webhooksListFlow(context);
1557
+ await webhooksListFlow(context, jsonOutput);
1104
1558
  break;
1105
1559
  case 'create':
1106
1560
  await webhooksCreateFlow(context, options);
@@ -1108,27 +1562,122 @@ async function webhooksSubcommand(context, cmdOptions) {
1108
1562
  case 'delete':
1109
1563
  if (!options.webhookId) {
1110
1564
  console.error("❌ --webhook-id is required for delete");
1111
- console.log("\n💡 Run 'lua integrations webhooks list' to see webhook IDs");
1565
+ console.log("\n💡 Run 'lua integrations webhooks list' to see trigger IDs");
1112
1566
  process.exit(1);
1113
1567
  }
1114
1568
  await webhooksDeleteFlow(context, options.webhookId);
1115
1569
  break;
1570
+ case 'events':
1571
+ // New: List available webhook events for a connection or integration type
1572
+ await webhooksEventsFlow(context, options, jsonOutput);
1573
+ break;
1116
1574
  default:
1117
1575
  console.error(`❌ Invalid webhooks action: "${subAction || '(none)'}"`);
1118
1576
  console.log('\nUsage:');
1119
- console.log(' lua integrations webhooks list List webhook subscriptions');
1120
- console.log(' lua integrations webhooks create Create subscription (interactive)');
1121
- console.log(' lua integrations webhooks create --connection <id> --object <type> --event <event> --webhook <name>');
1122
- console.log(' lua integrations webhooks delete --webhook-id <id> Delete subscription');
1577
+ console.log(' lua integrations webhooks list List triggers');
1578
+ console.log(' lua integrations webhooks events --connection <id> List available events for a connection');
1579
+ console.log(' lua integrations webhooks events --integration <type> List available events for an integration');
1580
+ console.log(' lua integrations webhooks create Create trigger (interactive)');
1581
+ console.log(' lua integrations webhooks create --connection <id> --object <type> --event <event> --hook-url <url>');
1582
+ console.log(' lua integrations webhooks delete --webhook-id <id> Delete trigger');
1123
1583
  process.exit(1);
1124
1584
  }
1125
1585
  }
1586
+ /**
1587
+ * Show available webhook events for a connection or integration type
1588
+ * Used for non-interactive discovery
1589
+ */
1590
+ async function webhooksEventsFlow(context, options, jsonOutput = false) {
1591
+ try {
1592
+ let webhookEvents = [];
1593
+ let sourceName = '';
1594
+ if (options.connectionId) {
1595
+ // Get events by connection ID
1596
+ const eventsResult = await context.unifiedToApi.getAvailableWebhookEvents(context.agentId, options.connectionId);
1597
+ if (eventsResult.success && eventsResult.data) {
1598
+ webhookEvents = eventsResult.data;
1599
+ }
1600
+ sourceName = `connection ${options.connectionId}`;
1601
+ }
1602
+ else if (options.integration) {
1603
+ // Get events by integration type
1604
+ const eventsResult = await context.unifiedToApi.getAvailableWebhookEventsByType(options.integration);
1605
+ if (eventsResult.success && eventsResult.data) {
1606
+ webhookEvents = eventsResult.data;
1607
+ }
1608
+ sourceName = `integration ${options.integration}`;
1609
+ }
1610
+ else {
1611
+ if (jsonOutput) {
1612
+ console.log(JSON.stringify({ error: 'Either --connection or --integration is required' }));
1613
+ }
1614
+ else {
1615
+ console.error("❌ Either --connection <id> or --integration <type> is required");
1616
+ console.log("\nUsage:");
1617
+ console.log(" lua integrations webhooks events --connection <id>");
1618
+ console.log(" lua integrations webhooks events --integration <type>");
1619
+ }
1620
+ process.exit(1);
1621
+ }
1622
+ if (jsonOutput) {
1623
+ const output = {
1624
+ source: sourceName,
1625
+ events: webhookEvents.map(e => ({
1626
+ trigger: `${e.objectType}.${e.event}`,
1627
+ objectType: e.objectType,
1628
+ event: e.event,
1629
+ webhookType: e.webhookType,
1630
+ friendlyLabel: e.friendlyLabel,
1631
+ friendlyDescription: e.friendlyDescription,
1632
+ availableFilters: e.availableFilters,
1633
+ })),
1634
+ defaults: {
1635
+ agentWebhookUrl: AGENT_WEBHOOK_URL,
1636
+ defaultVirtualInterval: DEFAULT_VIRTUAL_WEBHOOK_INTERVAL,
1637
+ },
1638
+ };
1639
+ console.log(JSON.stringify(output, null, 2));
1640
+ }
1641
+ else {
1642
+ console.log("\n" + "=".repeat(60));
1643
+ console.log(`📡 Available Trigger Events for ${sourceName}`);
1644
+ console.log("=".repeat(60) + "\n");
1645
+ if (webhookEvents.length === 0) {
1646
+ console.log("ℹ️ No trigger events available for this integration.\n");
1647
+ }
1648
+ else {
1649
+ webhookEvents.forEach(e => {
1650
+ console.log(` ${e.objectType}.${e.event} [${e.webhookType}]`);
1651
+ console.log(` ${e.friendlyDescription}`);
1652
+ if (e.availableFilters.length > 0) {
1653
+ console.log(` Filters: ${e.availableFilters.join(', ')}`);
1654
+ }
1655
+ console.log();
1656
+ });
1657
+ console.log("─".repeat(60));
1658
+ console.log(`Total: ${webhookEvents.length} event(s) available`);
1659
+ console.log("\n💡 Use with --triggers flag:");
1660
+ const exampleTriggers = webhookEvents.slice(0, 2).map(e => `${e.objectType}.${e.event}`).join(',');
1661
+ console.log(` lua integrations connect --integration <type> --triggers ${exampleTriggers}\n`);
1662
+ }
1663
+ }
1664
+ }
1665
+ catch (error) {
1666
+ if (jsonOutput) {
1667
+ console.log(JSON.stringify({ error: error.message }));
1668
+ }
1669
+ else {
1670
+ writeError(`❌ Failed to get trigger events: ${error.message}`);
1671
+ }
1672
+ process.exit(1);
1673
+ }
1674
+ }
1126
1675
  /**
1127
1676
  * Interactive webhooks menu
1128
1677
  */
1129
1678
  async function webhooksInteractiveMenu(context) {
1130
1679
  console.log("\n" + "─".repeat(60));
1131
- console.log("🔔 Webhook Subscriptions");
1680
+ console.log("🔔 Triggers");
1132
1681
  console.log("─".repeat(60) + "\n");
1133
1682
  const actionAnswer = await safePrompt([
1134
1683
  {
@@ -1136,7 +1685,7 @@ async function webhooksInteractiveMenu(context) {
1136
1685
  name: 'action',
1137
1686
  message: 'What would you like to do?',
1138
1687
  choices: [
1139
- { name: '📋 List webhook subscriptions', value: 'list' },
1688
+ { name: '📋 List triggers', value: 'list' },
1140
1689
  { name: '➕ Create new subscription', value: 'create' },
1141
1690
  { name: '🗑️ Delete subscription', value: 'delete' },
1142
1691
  { name: '← Back', value: 'back' }
@@ -1160,35 +1709,81 @@ async function webhooksInteractiveMenu(context) {
1160
1709
  /**
1161
1710
  * List all webhook subscriptions
1162
1711
  */
1163
- async function webhooksListFlow(context) {
1164
- writeProgress("🔄 Loading webhook subscriptions...");
1712
+ async function webhooksListFlow(context, jsonOutput = false) {
1713
+ writeProgress("🔄 Loading triggers...");
1165
1714
  try {
1166
1715
  const result = await context.unifiedToApi.getWebhookSubscriptions(context.agentId);
1167
1716
  if (!result.success) {
1168
- writeError(`❌ Failed to load webhooks: ${result.error?.message}`);
1717
+ if (jsonOutput) {
1718
+ console.log(JSON.stringify({ error: result.error?.message || 'Failed to load triggers' }));
1719
+ }
1720
+ else {
1721
+ writeError(`❌ Failed to load triggers: ${result.error?.message}`);
1722
+ }
1169
1723
  return;
1170
1724
  }
1171
1725
  const webhooks = result.data || [];
1172
- console.log("\n" + "─".repeat(80));
1173
- console.log("🔔 Webhook Subscriptions");
1174
- console.log("─".repeat(80) + "\n");
1726
+ if (jsonOutput) {
1727
+ // Output as JSON for scripting
1728
+ console.log(JSON.stringify({
1729
+ triggers: webhooks.map(wh => ({
1730
+ id: wh.id,
1731
+ integrationType: wh.integrationType,
1732
+ objectType: wh.objectType,
1733
+ event: wh.event,
1734
+ webhookType: wh.webhookType,
1735
+ hookUrl: wh.hookUrl,
1736
+ status: wh.status,
1737
+ interval: wh.interval,
1738
+ connectionId: wh.connectionId,
1739
+ })),
1740
+ total: webhooks.length,
1741
+ }, null, 2));
1742
+ return;
1743
+ }
1744
+ console.log("\n" + "─".repeat(60));
1745
+ console.log("⚡ Triggers / Webhook Subscriptions");
1746
+ console.log("─".repeat(60) + "\n");
1175
1747
  if (webhooks.length === 0) {
1176
- console.log("ℹ️ No webhook subscriptions found.\n");
1177
- console.log("💡 Create one with: lua integrations webhooks create\n");
1748
+ console.log("ℹ️ No triggers configured yet.\n");
1749
+ console.log("💡 Add triggers when connecting: lua integrations connect --triggers <events>");
1750
+ console.log(" Or manually: lua integrations webhooks create\n");
1178
1751
  return;
1179
1752
  }
1753
+ // Group by integration
1754
+ const byIntegration = new Map();
1180
1755
  for (const wh of webhooks) {
1181
- const status = wh.status === 'active' ? '✅ active' : wh.status;
1182
- console.log(` ID: ${wh.id}`);
1183
- console.log(` Integration: ${wh.integrationType} | Event: ${wh.objectType}.${wh.event} [${wh.webhookType}]`);
1184
- console.log(` URL: ${wh.hookUrl}`);
1185
- console.log(` Status: ${status}`);
1186
- console.log("─".repeat(80));
1756
+ const key = wh.integrationType;
1757
+ if (!byIntegration.has(key))
1758
+ byIntegration.set(key, []);
1759
+ byIntegration.get(key).push(wh);
1760
+ }
1761
+ for (const [integration, integrationWebhooks] of byIntegration) {
1762
+ console.log(`📦 ${integration}`);
1763
+ for (const wh of integrationWebhooks) {
1764
+ const statusIcon = wh.status === 'active' ? '✅' : '⚪';
1765
+ const typeLabel = wh.webhookType === 'native' ? 'push' : 'poll';
1766
+ const isAgentTrigger = wh.hookUrl.includes('webhook/unifiedto');
1767
+ const mode = isAgentTrigger ? 'agent trigger' : 'custom URL';
1768
+ // Show webhook ID for delete operations
1769
+ console.log(` ${statusIcon} ${wh.objectType}.${wh.event} (${typeLabel}, ${mode})`);
1770
+ console.log(` ID: ${wh.id}`);
1771
+ if (!isAgentTrigger) {
1772
+ console.log(` URL: ${wh.hookUrl}`);
1773
+ }
1774
+ }
1775
+ console.log();
1187
1776
  }
1188
- console.log(`\nTotal: ${webhooks.length} webhook subscription(s)\n`);
1777
+ console.log("─".repeat(60));
1778
+ console.log(`Total: ${webhooks.length} trigger(s)\n`);
1189
1779
  }
1190
1780
  catch (error) {
1191
- writeError(`❌ Error: ${error.message}`);
1781
+ if (jsonOutput) {
1782
+ console.log(JSON.stringify({ error: error.message }));
1783
+ }
1784
+ else {
1785
+ writeError(`❌ Error: ${error.message}`);
1786
+ }
1192
1787
  }
1193
1788
  }
1194
1789
  /**
@@ -1212,7 +1807,7 @@ async function webhooksCreateFlow(context, options) {
1212
1807
  }
1213
1808
  if (connections.length === 0) {
1214
1809
  writeInfo("No connections available.");
1215
- console.log("💡 Run 'lua integrations connect' to connect an account first.\n");
1810
+ console.log("💡 Run 'lua integrations connect' to connect an integration first.\n");
1216
1811
  return;
1217
1812
  }
1218
1813
  // Step 2: Select connection
@@ -1258,8 +1853,8 @@ async function webhooksCreateFlow(context, options) {
1258
1853
  return;
1259
1854
  }
1260
1855
  if (availableEvents.length === 0) {
1261
- writeInfo(`No webhook events available for ${selectedConnection.integrationName || selectedConnection.integrationType}.`);
1262
- console.log("💡 This integration may not support webhooks.\n");
1856
+ writeInfo(`No trigger events available for ${selectedConnection.integrationName || selectedConnection.integrationType}.`);
1857
+ console.log("💡 This integration may not support triggers.\n");
1263
1858
  return;
1264
1859
  }
1265
1860
  // Step 4: Select event
@@ -1274,7 +1869,7 @@ async function webhooksCreateFlow(context, options) {
1274
1869
  }
1275
1870
  }
1276
1871
  else {
1277
- console.log(`\nAvailable webhook events for ${selectedConnection.integrationName || selectedConnection.integrationType}:\n`);
1872
+ console.log(`\nAvailable trigger events for ${selectedConnection.integrationName || selectedConnection.integrationType}:\n`);
1278
1873
  const eventAnswer = await safePrompt([
1279
1874
  {
1280
1875
  type: 'list',
@@ -1293,8 +1888,8 @@ async function webhooksCreateFlow(context, options) {
1293
1888
  }
1294
1889
  // Step 5: Get webhook URL
1295
1890
  let hookUrl;
1296
- if (options.webhook) {
1297
- hookUrl = options.webhook;
1891
+ if (options.hookUrl) {
1892
+ hookUrl = options.hookUrl;
1298
1893
  }
1299
1894
  else {
1300
1895
  const webhookAnswer = await safePrompt([
@@ -1357,7 +1952,7 @@ async function webhooksCreateFlow(context, options) {
1357
1952
  // Step 7: Confirmation (interactive only)
1358
1953
  if (!options.connectionId) {
1359
1954
  console.log("\n" + "─".repeat(60));
1360
- console.log("📋 Webhook Subscription Summary");
1955
+ console.log("📋 Trigger Summary");
1361
1956
  console.log("─".repeat(60));
1362
1957
  console.log(` Connection: ${selectedConnection.integrationName || selectedConnection.integrationType}`);
1363
1958
  console.log(` Event: ${selectedEvent.objectType}.${selectedEvent.event} [${selectedEvent.webhookType}]`);
@@ -1369,7 +1964,7 @@ async function webhooksCreateFlow(context, options) {
1369
1964
  {
1370
1965
  type: 'confirm',
1371
1966
  name: 'confirm',
1372
- message: 'Create this webhook subscription?',
1967
+ message: 'Create this trigger?',
1373
1968
  default: true
1374
1969
  }
1375
1970
  ]);
@@ -1379,7 +1974,7 @@ async function webhooksCreateFlow(context, options) {
1379
1974
  }
1380
1975
  }
1381
1976
  // Step 8: Create the webhook
1382
- writeProgress("🔄 Creating webhook subscription...");
1977
+ writeProgress("🔄 Creating trigger...");
1383
1978
  try {
1384
1979
  const createResult = await context.unifiedToApi.createWebhookSubscription(context.agentId, {
1385
1980
  connectionId: selectedConnection.id,
@@ -1389,22 +1984,32 @@ async function webhooksCreateFlow(context, options) {
1389
1984
  interval,
1390
1985
  });
1391
1986
  if (!createResult.success || !createResult.data) {
1392
- writeError(`❌ Failed to create webhook: ${createResult.error?.message}`);
1987
+ writeError(`❌ Failed to create trigger: ${createResult.error?.message}`);
1393
1988
  return;
1394
1989
  }
1395
1990
  const webhook = createResult.data;
1991
+ const isAgentTrigger = webhook.hookUrl.includes('webhook/unifiedto');
1992
+ const typeLabel = webhook.webhookType === 'native' ? 'push' : 'polling';
1396
1993
  console.log("\n" + "─".repeat(60));
1397
- writeSuccess("✅ Webhook subscription created!");
1994
+ writeSuccess("✅ Trigger created!");
1398
1995
  console.log("─".repeat(60));
1399
- console.log(` ID: ${webhook.id}`);
1400
1996
  console.log(` Integration: ${webhook.integrationType}`);
1401
- console.log(` Event: ${webhook.objectType}.${webhook.event} [${webhook.webhookType}]`);
1402
- console.log(` Webhook URL: ${webhook.hookUrl}`);
1997
+ console.log(` Event: ${webhook.objectType}.${webhook.event}`);
1998
+ console.log(` Type: ${typeLabel}`);
1999
+ console.log(` Mode: ${isAgentTrigger ? 'Agent wake-up' : 'Custom URL'}`);
2000
+ if (!isAgentTrigger) {
2001
+ console.log(` URL: ${webhook.hookUrl}`);
2002
+ }
1403
2003
  if (webhook.interval)
1404
- console.log(` Interval: ${formatInterval(webhook.interval)}`);
2004
+ console.log(` Poll interval: ${formatInterval(webhook.interval)}`);
1405
2005
  console.log(` Status: ${webhook.status}`);
1406
2006
  console.log("─".repeat(60) + "\n");
1407
- console.log("💡 Ensure your webhook endpoint is deployed and ready to receive events.\n");
2007
+ if (isAgentTrigger) {
2008
+ console.log("💡 Your agent will now wake up when this event occurs.\n");
2009
+ }
2010
+ else {
2011
+ console.log("💡 Ensure your webhook endpoint is ready to receive events.\n");
2012
+ }
1408
2013
  }
1409
2014
  catch (error) {
1410
2015
  writeError(`❌ Error: ${error.message}`);
@@ -1414,23 +2019,23 @@ async function webhooksCreateFlow(context, options) {
1414
2019
  * Delete a webhook subscription (interactive)
1415
2020
  */
1416
2021
  async function webhooksDeleteInteractive(context) {
1417
- writeProgress("🔄 Loading webhook subscriptions...");
2022
+ writeProgress("🔄 Loading triggers...");
1418
2023
  try {
1419
2024
  const result = await context.unifiedToApi.getWebhookSubscriptions(context.agentId);
1420
2025
  if (!result.success) {
1421
- writeError(`❌ Failed to load webhooks: ${result.error?.message}`);
2026
+ writeError(`❌ Failed to load triggers: ${result.error?.message}`);
1422
2027
  return;
1423
2028
  }
1424
2029
  const webhooks = result.data || [];
1425
2030
  if (webhooks.length === 0) {
1426
- console.log("\nℹ️ No webhook subscriptions to delete.\n");
2031
+ console.log("\nℹ️ No triggers to delete.\n");
1427
2032
  return;
1428
2033
  }
1429
2034
  const webhookAnswer = await safePrompt([
1430
2035
  {
1431
2036
  type: 'list',
1432
2037
  name: 'webhook',
1433
- message: 'Select a webhook subscription to delete:',
2038
+ message: 'Select a trigger to delete:',
1434
2039
  choices: webhooks.map(wh => {
1435
2040
  // Show last part of URL for readability
1436
2041
  const urlParts = (wh.hookUrl || '').split('/');
@@ -1449,7 +2054,7 @@ async function webhooksDeleteInteractive(context) {
1449
2054
  {
1450
2055
  type: 'confirm',
1451
2056
  name: 'confirm',
1452
- message: `Delete webhook subscription for ${selectedWebhook?.objectType}.${selectedWebhook?.event}?`,
2057
+ message: `Delete trigger for ${selectedWebhook?.objectType}.${selectedWebhook?.event}?`,
1453
2058
  default: false
1454
2059
  }
1455
2060
  ]);
@@ -1467,14 +2072,14 @@ async function webhooksDeleteInteractive(context) {
1467
2072
  * Delete a webhook subscription (non-interactive)
1468
2073
  */
1469
2074
  async function webhooksDeleteFlow(context, webhookId) {
1470
- writeProgress("🔄 Deleting webhook subscription...");
2075
+ writeProgress("🔄 Deleting trigger...");
1471
2076
  try {
1472
2077
  const result = await context.unifiedToApi.deleteWebhookSubscription(context.agentId, webhookId);
1473
2078
  if (result.success) {
1474
- writeSuccess(`✅ Webhook subscription deleted: ${webhookId}\n`);
2079
+ writeSuccess(`✅ Trigger deleted: ${webhookId}\n`);
1475
2080
  }
1476
2081
  else {
1477
- writeError(`❌ Failed to delete webhook: ${result.error?.message}`);
2082
+ writeError(`❌ Failed to delete trigger: ${result.error?.message}`);
1478
2083
  }
1479
2084
  }
1480
2085
  catch (error) {
@@ -1758,21 +2363,30 @@ async function mcpDeactivateInteractive(context) {
1758
2363
  function showUsage() {
1759
2364
  console.log('\nUsage:');
1760
2365
  console.log(' lua integrations Interactive integration management');
1761
- console.log(' lua integrations connect Connect a new account (interactive)');
2366
+ console.log(' lua integrations connect Connect a new integration (interactive)');
1762
2367
  console.log(' lua integrations connect --integration <type> Connect a specific integration');
2368
+ console.log(' lua integrations connect --integration <type> --triggers <events> Connect with triggers');
1763
2369
  console.log(' lua integrations update Update connection scopes (interactive)');
1764
2370
  console.log(' lua integrations update --integration <type> Update scopes for a specific integration');
1765
- console.log(' lua integrations list List connected accounts');
2371
+ console.log(' lua integrations list List connected integrations');
1766
2372
  console.log(' lua integrations available List available integrations');
1767
- console.log(' lua integrations disconnect --connection-id <id> Disconnect an account');
1768
- console.log('\nWebhook Subscriptions:');
1769
- console.log(' lua integrations webhooks list List all webhook subscriptions');
1770
- console.log(' lua integrations webhooks create Create subscription (interactive)');
1771
- console.log(' lua integrations webhooks create --connection <id> --object <type> --event <event> --webhook <full-url>');
1772
- console.log(' lua integrations webhooks delete --webhook-id <id> Delete a subscription');
2373
+ console.log(' lua integrations info <type> Show integration details (scopes, triggers)');
2374
+ console.log(' lua integrations info <type> --json Output as JSON for scripting');
2375
+ console.log(' lua integrations disconnect --connection-id <id> Disconnect an integration');
2376
+ console.log('\nTriggers:');
2377
+ console.log(' lua integrations webhooks list List all triggers');
2378
+ console.log(' lua integrations webhooks events --connection <id> List available events for a connection');
2379
+ console.log(' lua integrations webhooks events --integration <type> List available events for an integration');
2380
+ console.log(' lua integrations webhooks create Create trigger (interactive)');
2381
+ console.log(' lua integrations webhooks create --connection <id> --object <type> --event <event> --hook-url <url>');
2382
+ console.log(' lua integrations webhooks delete --webhook-id <id> Delete a trigger');
1773
2383
  console.log('\nMCP Server Management:');
1774
2384
  console.log(' lua integrations mcp list List connections with MCP status');
1775
2385
  console.log(' lua integrations mcp activate --connection <id> Activate MCP server');
1776
2386
  console.log(' lua integrations mcp deactivate --connection <id> Deactivate MCP server');
2387
+ console.log('\nTrigger Options (use with connect):');
2388
+ console.log(' --triggers <events> Comma-separated triggers (e.g., task_task.created,task_task.updated)');
2389
+ console.log(' --custom-webhook Use custom URL instead of agent trigger');
2390
+ console.log(' --hook-url <url> Custom URL for triggers');
1777
2391
  }
1778
2392
  //# sourceMappingURL=integrations.js.map