lua-cli 3.5.0-alpha.9 → 3.5.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.
- package/dist/api/backup.api.service.d.ts +9 -12
- package/dist/api/backup.api.service.d.ts.map +1 -1
- package/dist/api/backup.api.service.js +11 -11
- package/dist/api/backup.api.service.js.map +1 -1
- package/dist/api/unifiedto.api.service.d.ts +30 -2
- package/dist/api/unifiedto.api.service.d.ts.map +1 -1
- package/dist/api/unifiedto.api.service.js +17 -2
- package/dist/api/unifiedto.api.service.js.map +1 -1
- package/dist/cli/command-definitions.d.ts.map +1 -1
- package/dist/cli/command-definitions.js +9 -1
- package/dist/cli/command-definitions.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/integrations.js +111 -250
- package/dist/commands/integrations.js.map +1 -1
- package/dist/commands/pushBackup.d.ts +3 -2
- package/dist/commands/pushBackup.d.ts.map +1 -1
- package/dist/commands/pushBackup.js +11 -20
- package/dist/commands/pushBackup.js.map +1 -1
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +81 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/compiler/plugins/job.plugin.d.ts.map +1 -1
- package/dist/compiler/plugins/job.plugin.js +2 -3
- package/dist/compiler/plugins/job.plugin.js.map +1 -1
- package/dist/index.js +22 -20
- package/dist/index.js.map +1 -1
- package/dist/interfaces/backup.d.ts +0 -25
- package/dist/interfaces/backup.d.ts.map +1 -1
- package/dist/interfaces/unifiedto.d.ts +26 -0
- package/dist/interfaces/unifiedto.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/backup-helpers.d.ts +9 -7
- package/dist/utils/backup-helpers.d.ts.map +1 -1
- package/dist/utils/backup-helpers.js +54 -22
- package/dist/utils/backup-helpers.js.map +1 -1
- package/dist/utils/cli.d.ts +2 -1
- package/dist/utils/cli.d.ts.map +1 -1
- package/dist/utils/cli.js +42 -2
- package/dist/utils/cli.js.map +1 -1
- package/dist/utils/version-check.d.ts +37 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +120 -0
- package/dist/utils/version-check.js.map +1 -0
- package/docs/README.md +2 -2
- package/package.json +1 -1
- package/template/package.json +1 -1
|
@@ -27,20 +27,19 @@ import { fetchServersCore, activateServerCore, deactivateServerCore, } from './m
|
|
|
27
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
28
|
// Configuration
|
|
29
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
-
const UNIFIED_MCP_BASE_URL = 'https://mcp-api.unified.to/mcp';
|
|
31
30
|
const CALLBACK_PORT = 19837; // Random high port for OAuth callback
|
|
32
31
|
const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
33
|
-
// Webhook trigger configuration
|
|
32
|
+
// Webhook trigger configuration (AGENT_WEBHOOK_URL still used for display/info commands)
|
|
34
33
|
const AGENT_WEBHOOK_URL = `${BASE_URLS.API}/webhook/unifiedto/data`;
|
|
35
|
-
const DEFAULT_VIRTUAL_WEBHOOK_INTERVAL =
|
|
34
|
+
const DEFAULT_VIRTUAL_WEBHOOK_INTERVAL = 1; // minutes (paid Unified.to plan)
|
|
36
35
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
36
|
// Integration API Functions (via Lua API)
|
|
38
37
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
38
|
/**
|
|
40
39
|
* Fetches available (activated) integrations via Lua API
|
|
41
40
|
*/
|
|
42
|
-
async function fetchAvailableIntegrations(unifiedToApi) {
|
|
43
|
-
const result = await unifiedToApi.getAvailableIntegrations();
|
|
41
|
+
async function fetchAvailableIntegrations(unifiedToApi, agentId) {
|
|
42
|
+
const result = await unifiedToApi.getAvailableIntegrations(agentId);
|
|
44
43
|
if (!result.success || !result.data) {
|
|
45
44
|
throw new Error(`Failed to fetch integrations: ${result.error?.message || 'Unknown error'}`);
|
|
46
45
|
}
|
|
@@ -68,26 +67,31 @@ function startCallbackServer(timeoutMs = 300000) {
|
|
|
68
67
|
if (reqUrl.pathname === '/callback') {
|
|
69
68
|
const connectionId = reqUrl.searchParams.get('id');
|
|
70
69
|
const error = reqUrl.searchParams.get('error');
|
|
70
|
+
const logId = reqUrl.searchParams.get('log_id');
|
|
71
71
|
const integrationType = reqUrl.searchParams.get('type');
|
|
72
72
|
resolved = true;
|
|
73
73
|
if (error) {
|
|
74
|
-
//
|
|
74
|
+
// Unified.to redirects to failure_redirect with ?error=<oauth_error>&log_id=<trace_id>
|
|
75
|
+
const logIdHtml = logId
|
|
76
|
+
? `<p style="color: #888; font-size: 0.85em;">Log ID: <code>${logId}</code></p>`
|
|
77
|
+
: '';
|
|
75
78
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
76
79
|
res.end(`
|
|
77
80
|
<!DOCTYPE html>
|
|
78
81
|
<html>
|
|
79
82
|
<head><title>Connection Failed</title></head>
|
|
80
83
|
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e;">
|
|
81
|
-
<div style="text-align: center; color: white;">
|
|
82
|
-
<h1 style="color: #ff6b6b;"
|
|
84
|
+
<div style="text-align: center; color: white; max-width: 500px; padding: 0 20px;">
|
|
85
|
+
<h1 style="color: #ff6b6b;">Connection Failed</h1>
|
|
83
86
|
<p style="color: #ccc;">Error: ${error}</p>
|
|
87
|
+
${logIdHtml}
|
|
84
88
|
<p style="color: #888;">You can close this window and try again.</p>
|
|
85
89
|
</div>
|
|
86
90
|
</body>
|
|
87
91
|
</html>
|
|
88
92
|
`);
|
|
89
93
|
server.close();
|
|
90
|
-
resolve({ success: false, error });
|
|
94
|
+
resolve({ success: false, error, logId: logId || undefined });
|
|
91
95
|
}
|
|
92
96
|
else if (connectionId) {
|
|
93
97
|
// Send success page
|
|
@@ -456,32 +460,18 @@ async function showIntegrationInfo(context, integrationType, jsonOutput = false)
|
|
|
456
460
|
// Connect Flow
|
|
457
461
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
458
462
|
async function connectIntegrationFlow(context, options = {}) {
|
|
459
|
-
// Step 1: Fetch available integrations
|
|
463
|
+
// Step 1: Fetch available integrations (API excludes already-connected types)
|
|
460
464
|
writeProgress("🔄 Fetching integrations...");
|
|
461
|
-
let
|
|
462
|
-
let existingConnections = [];
|
|
465
|
+
let availableIntegrations;
|
|
463
466
|
try {
|
|
464
|
-
|
|
465
|
-
fetchAvailableIntegrations(context.unifiedToApi),
|
|
466
|
-
context.unifiedToApi.getConnections(context.agentId)
|
|
467
|
-
]);
|
|
468
|
-
integrations = integrationsResult;
|
|
469
|
-
existingConnections = connectionsResult.success ? connectionsResult.data || [] : [];
|
|
467
|
+
availableIntegrations = await fetchAvailableIntegrations(context.unifiedToApi, context.agentId);
|
|
470
468
|
}
|
|
471
469
|
catch (error) {
|
|
472
470
|
writeError(`❌ Failed to fetch integrations: ${error.message}`);
|
|
473
471
|
return;
|
|
474
472
|
}
|
|
475
|
-
if (integrations.length === 0) {
|
|
476
|
-
writeError("❌ No integrations available.");
|
|
477
|
-
console.log("💡 Contact support to enable integrations for your workspace.\n");
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
// Filter out integrations that already have a connection (1 connection per integration type)
|
|
481
|
-
const connectedTypes = new Set(existingConnections.map(c => c.integrationType));
|
|
482
|
-
const availableIntegrations = integrations.filter(i => !connectedTypes.has(i.value));
|
|
483
473
|
if (availableIntegrations.length === 0) {
|
|
484
|
-
writeInfo("All available integrations are already connected.");
|
|
474
|
+
writeInfo("All available integrations are already connected (or none are enabled).");
|
|
485
475
|
console.log("💡 Use 'lua integrations update' to change scopes on an existing connection.\n");
|
|
486
476
|
return;
|
|
487
477
|
}
|
|
@@ -489,17 +479,15 @@ async function connectIntegrationFlow(context, options = {}) {
|
|
|
489
479
|
// Step 2: Select integration (with search/filter support)
|
|
490
480
|
let selectedIntegration;
|
|
491
481
|
if (options.integration) {
|
|
492
|
-
// Check if already connected
|
|
493
|
-
if (connectedTypes.has(options.integration)) {
|
|
494
|
-
console.error(`❌ Integration "${options.integration}" is already connected.`);
|
|
495
|
-
console.log("💡 Use 'lua integrations update --integration " + options.integration + "' to change scopes.\n");
|
|
496
|
-
throw new Error("Integration");
|
|
497
|
-
}
|
|
498
482
|
selectedIntegration = availableIntegrations.find(i => i.value === options.integration);
|
|
499
483
|
if (!selectedIntegration) {
|
|
500
|
-
|
|
501
|
-
console.
|
|
502
|
-
|
|
484
|
+
// Not in available list — either already connected or doesn't exist
|
|
485
|
+
console.error(`❌ Integration "${options.integration}" not found or already connected.`);
|
|
486
|
+
console.log("💡 If already connected, use 'lua integrations update --integration " + options.integration + "' to change scopes.");
|
|
487
|
+
if (availableIntegrations.length > 0) {
|
|
488
|
+
console.log('\nAvailable integrations to connect:');
|
|
489
|
+
availableIntegrations.forEach(i => console.log(` - ${i.value} (${i.name})`));
|
|
490
|
+
}
|
|
503
491
|
throw new Error("Integration");
|
|
504
492
|
}
|
|
505
493
|
}
|
|
@@ -522,16 +510,8 @@ async function connectIntegrationFlow(context, options = {}) {
|
|
|
522
510
|
i.value.toLowerCase().includes(searchLower) ||
|
|
523
511
|
i.categories.some(c => c.toLowerCase().includes(searchLower)));
|
|
524
512
|
if (filteredIntegrations.length === 0) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
(c.integrationName && c.integrationName.toLowerCase().includes(searchLower)));
|
|
528
|
-
if (matchingConnected.length > 0) {
|
|
529
|
-
const connectedName = matchingConnected[0].integrationName || matchingConnected[0].integrationType;
|
|
530
|
-
writeInfo(`"${connectedName}" is already connected.`);
|
|
531
|
-
console.log(`💡 Use 'lua integrations update' to change scopes.\n`);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
writeInfo(`No integrations found matching "${searchAnswer.searchTerm}"`);
|
|
513
|
+
writeInfo(`No available integrations found matching "${searchAnswer.searchTerm}"`);
|
|
514
|
+
console.log(`💡 If the integration is already connected, use 'lua integrations update' to change scopes.`);
|
|
535
515
|
// Offer to browse all
|
|
536
516
|
const browseAnswer = await safePrompt([
|
|
537
517
|
{
|
|
@@ -858,7 +838,7 @@ async function connectIntegrationFlow(context, options = {}) {
|
|
|
858
838
|
});
|
|
859
839
|
const authUrlResult = await context.unifiedToApi.getAuthUrl(selectedIntegration.value, {
|
|
860
840
|
successRedirect: CALLBACK_URL,
|
|
861
|
-
failureRedirect:
|
|
841
|
+
failureRedirect: CALLBACK_URL,
|
|
862
842
|
scopes: authMethod === 'oauth' ? selectedScopes : undefined,
|
|
863
843
|
state,
|
|
864
844
|
externalXref, // Contains both agentId and userId as JSON
|
|
@@ -867,36 +847,19 @@ async function connectIntegrationFlow(context, options = {}) {
|
|
|
867
847
|
writeError(`❌ Failed to get authorization URL: ${authUrlResult.error?.message || 'Unknown error'}`);
|
|
868
848
|
return;
|
|
869
849
|
}
|
|
850
|
+
// Auth URL is already resolved by the API (redirect wrappers followed server-side)
|
|
870
851
|
const authUrl = authUrlResult.data.authUrl;
|
|
871
|
-
// Step 4:
|
|
872
|
-
let finalAuthUrl = authUrl;
|
|
873
|
-
try {
|
|
874
|
-
const response = await fetch(authUrl);
|
|
875
|
-
const responseText = await response.text();
|
|
876
|
-
if (responseText.includes('test.html?redirect=')) {
|
|
877
|
-
const urlMatch = responseText.match(/redirect=([^&\s]+)/);
|
|
878
|
-
if (urlMatch) {
|
|
879
|
-
finalAuthUrl = decodeURIComponent(urlMatch[1]);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
else if (responseText.startsWith('https://')) {
|
|
883
|
-
finalAuthUrl = responseText.trim();
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
catch (error) {
|
|
887
|
-
// Use original auth URL if pre-fetch fails
|
|
888
|
-
}
|
|
889
|
-
// Step 5: Start callback server and open browser
|
|
852
|
+
// Step 4: Start callback server and open browser
|
|
890
853
|
console.log("\n" + "─".repeat(60));
|
|
891
854
|
console.log("🌐 Starting OAuth authorization flow...");
|
|
892
855
|
console.log("─".repeat(60));
|
|
893
856
|
console.log(`\nIntegration: ${selectedIntegration.name}`);
|
|
894
|
-
console.log(`\n📋 Authorization URL (copy if browser doesn't open):\n ${
|
|
857
|
+
console.log(`\n📋 Authorization URL (copy if browser doesn't open):\n ${authUrl}\n`);
|
|
895
858
|
// Start the callback server
|
|
896
859
|
const callbackPromise = startCallbackServer(300000); // 5 minute timeout
|
|
897
860
|
// Auto-open browser
|
|
898
861
|
try {
|
|
899
|
-
await open(
|
|
862
|
+
await open(authUrl);
|
|
900
863
|
writeInfo("🌐 Browser opened - please complete the authorization");
|
|
901
864
|
}
|
|
902
865
|
catch (error) {
|
|
@@ -917,93 +880,48 @@ async function connectIntegrationFlow(context, options = {}) {
|
|
|
917
880
|
}
|
|
918
881
|
else {
|
|
919
882
|
writeError(`\n❌ Authorization failed: ${result.error || 'Unknown error'}`);
|
|
883
|
+
if (result.logId) {
|
|
884
|
+
console.log(` Log ID: ${result.logId}`);
|
|
885
|
+
}
|
|
886
|
+
// Report auth failure for observability (fire and forget)
|
|
887
|
+
context.unifiedToApi.reportAuthFailure({
|
|
888
|
+
integrationType: selectedIntegration.value,
|
|
889
|
+
error: result.error || 'Unknown OAuth failure',
|
|
890
|
+
logId: result.logId,
|
|
891
|
+
agentId: context.agentId,
|
|
892
|
+
source: 'cli',
|
|
893
|
+
}).catch(() => { });
|
|
920
894
|
console.log("💡 Please try again with 'lua integrations connect'\n");
|
|
921
895
|
}
|
|
922
896
|
}
|
|
923
897
|
/**
|
|
924
|
-
* Finalizes a new connection by
|
|
925
|
-
* The
|
|
926
|
-
* Webhooks enable the agent to be triggered by events from the connection
|
|
898
|
+
* Finalizes a new connection by calling the API to atomically set up MCP server and webhooks
|
|
899
|
+
* The API handles: MCP URL construction, upsert, activation, and webhook creation
|
|
927
900
|
*/
|
|
928
901
|
async function finalizeConnection(context, integration, connectionId, scopes, hideSensitive = true, webhookConfig = { triggers: [], webhookUrl: '', isCustomWebhook: false }) {
|
|
929
902
|
writeProgress(`🔄 Setting up ${integration.name} connection...`);
|
|
930
|
-
// Build
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
timeout: 30000,
|
|
949
|
-
};
|
|
950
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
951
|
-
// Step 1: Set up MCP Server (for tools)
|
|
952
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
953
|
-
let mcpSetupSuccess = false;
|
|
954
|
-
let isActive = false;
|
|
955
|
-
let mcpError = null;
|
|
956
|
-
try {
|
|
957
|
-
writeProgress(`🔄 Setting up MCP server...`);
|
|
958
|
-
const result = await context.developerApi.createMCPServer(mcpServerData);
|
|
959
|
-
if (result.success && result.data) {
|
|
960
|
-
// Auto-activate the MCP server so the agent can use the connection immediately
|
|
961
|
-
const activateResult = await context.developerApi.activateMCPServer(result.data.id);
|
|
962
|
-
isActive = activateResult.success && activateResult.data?.active === true;
|
|
963
|
-
mcpSetupSuccess = true;
|
|
964
|
-
}
|
|
965
|
-
else {
|
|
966
|
-
mcpError = result.error?.message || 'Unknown error';
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
catch (error) {
|
|
970
|
-
mcpError = error.message || 'Unknown error';
|
|
971
|
-
}
|
|
972
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
973
|
-
// Step 2: Set up Webhook Triggers (for event-driven agent wake-up)
|
|
974
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
975
|
-
const createdWebhooks = [];
|
|
976
|
-
const failedWebhooks = [];
|
|
977
|
-
if (webhookConfig.triggers.length > 0) {
|
|
978
|
-
writeProgress(`🔄 Setting up ${webhookConfig.triggers.length} trigger(s)...`);
|
|
979
|
-
for (const trigger of webhookConfig.triggers) {
|
|
980
|
-
try {
|
|
981
|
-
// Use per-trigger hookUrl/interval if available, otherwise fall back to defaults
|
|
982
|
-
const hookUrl = trigger.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL;
|
|
983
|
-
const interval = trigger.interval ?? (trigger.webhookType === 'virtual' ? DEFAULT_VIRTUAL_WEBHOOK_INTERVAL : undefined);
|
|
984
|
-
const webhookResult = await context.unifiedToApi.createWebhookSubscription(context.agentId, {
|
|
985
|
-
connectionId,
|
|
986
|
-
objectType: trigger.objectType,
|
|
987
|
-
event: trigger.event,
|
|
988
|
-
hookUrl,
|
|
989
|
-
interval,
|
|
990
|
-
});
|
|
991
|
-
if (webhookResult.success && webhookResult.data) {
|
|
992
|
-
createdWebhooks.push(webhookResult.data);
|
|
993
|
-
}
|
|
994
|
-
else {
|
|
995
|
-
const errorMsg = webhookResult.error?.message || 'Unknown error';
|
|
996
|
-
failedWebhooks.push(`${trigger.objectType}.${trigger.event}: ${errorMsg}`);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
catch (error) {
|
|
1000
|
-
const errorMsg = error?.message || 'Unknown error';
|
|
1001
|
-
failedWebhooks.push(`${trigger.objectType}.${trigger.event}: ${errorMsg}`);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
903
|
+
// Build trigger params for the API (include per-trigger hookUrl/interval if set)
|
|
904
|
+
const triggers = webhookConfig.triggers.map(t => ({
|
|
905
|
+
objectType: t.objectType,
|
|
906
|
+
event: t.event,
|
|
907
|
+
hookUrl: t.hookUrl || (webhookConfig.isCustomWebhook ? webhookConfig.webhookUrl : undefined),
|
|
908
|
+
interval: t.interval,
|
|
909
|
+
}));
|
|
910
|
+
// Single API call replaces: MCP URL build + upsert + activate + N webhook creates
|
|
911
|
+
const result = await context.unifiedToApi.finalizeConnection(context.agentId, {
|
|
912
|
+
connectionId,
|
|
913
|
+
integrationType: integration.value,
|
|
914
|
+
scopes: scopes.length > 0 ? scopes : undefined,
|
|
915
|
+
hideSensitive,
|
|
916
|
+
triggers: triggers.length > 0 ? triggers : undefined,
|
|
917
|
+
});
|
|
918
|
+
if (!result.success || !result.data) {
|
|
919
|
+
writeError(`❌ Failed to finalize connection: ${result.error?.message || 'Unknown error'}`);
|
|
920
|
+
return;
|
|
1004
921
|
}
|
|
922
|
+
const { mcpServer, webhooks, failedWebhooks } = result.data;
|
|
1005
923
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1006
|
-
//
|
|
924
|
+
// Display Results
|
|
1007
925
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1008
926
|
console.log("\n" + "─".repeat(60));
|
|
1009
927
|
console.log("🎉 Connection Established!");
|
|
@@ -1012,15 +930,9 @@ async function finalizeConnection(context, integration, connectionId, scopes, hi
|
|
|
1012
930
|
console.log(` Connection ID: ${connectionId}`);
|
|
1013
931
|
// MCP Server status
|
|
1014
932
|
console.log(`\n 📦 MCP Server (Tools):`);
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
console.log(` Hide Sensitive: ${hideSensitive ? '✅ Yes' : '❌ No'}`);
|
|
1019
|
-
}
|
|
1020
|
-
else {
|
|
1021
|
-
console.log(` Status: ❌ Failed`);
|
|
1022
|
-
console.log(` Error: ${mcpError}`);
|
|
1023
|
-
}
|
|
933
|
+
console.log(` Status: ${mcpServer.active ? '🟢 Active' : '🟡 Pending activation'}`);
|
|
934
|
+
console.log(` Name: ${mcpServer.name}`);
|
|
935
|
+
console.log(` Hide Sensitive: ${hideSensitive ? '✅ Yes' : '❌ No'}`);
|
|
1024
936
|
// Show requested scopes with friendly labels
|
|
1025
937
|
if (scopes.length > 0) {
|
|
1026
938
|
console.log(`\n 🔑 Permissions (${scopes.length}):`);
|
|
@@ -1038,58 +950,38 @@ async function finalizeConnection(context, integration, connectionId, scopes, hi
|
|
|
1038
950
|
console.log(`\n 🔑 Permissions: Default`);
|
|
1039
951
|
}
|
|
1040
952
|
// Webhook/Triggers status
|
|
1041
|
-
if (
|
|
1042
|
-
console.log(`\n ⚡ Triggers (${
|
|
1043
|
-
|
|
1044
|
-
const actualUrls = webhookConfig.triggers.map(t => t.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL);
|
|
1045
|
-
const uniqueUrls = [...new Set(actualUrls)];
|
|
1046
|
-
const allAgentTriggers = uniqueUrls.every(url => url === AGENT_WEBHOOK_URL);
|
|
1047
|
-
if (allAgentTriggers) {
|
|
1048
|
-
console.log(` Mode: Agent wake-up`);
|
|
1049
|
-
}
|
|
1050
|
-
else if (uniqueUrls.length === 1) {
|
|
953
|
+
if (triggers.length > 0) {
|
|
954
|
+
console.log(`\n ⚡ Triggers (${webhooks.length}/${triggers.length}):`);
|
|
955
|
+
if (webhookConfig.isCustomWebhook) {
|
|
1051
956
|
console.log(` Mode: Custom webhook`);
|
|
1052
|
-
console.log(` URL: ${uniqueUrls[0]}`);
|
|
1053
957
|
}
|
|
1054
958
|
else {
|
|
1055
|
-
console.log(` Mode:
|
|
1056
|
-
}
|
|
1057
|
-
if (createdWebhooks.length > 0) {
|
|
1058
|
-
createdWebhooks.forEach(wh => {
|
|
1059
|
-
const trigger = webhookConfig.triggers.find(t => t.objectType === wh.objectType && t.event === wh.event);
|
|
1060
|
-
const desc = trigger?.friendlyDescription || `${wh.objectType}.${wh.event}`;
|
|
1061
|
-
console.log(` ✓ ${desc}`);
|
|
1062
|
-
});
|
|
959
|
+
console.log(` Mode: Agent wake-up`);
|
|
1063
960
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
961
|
+
webhooks.forEach(wh => {
|
|
962
|
+
const trigger = webhookConfig.triggers.find(t => t.objectType === wh.objectType && t.event === wh.event);
|
|
963
|
+
const desc = trigger?.friendlyDescription || `${wh.objectType}.${wh.event}`;
|
|
964
|
+
console.log(` ✓ ${desc}`);
|
|
965
|
+
});
|
|
966
|
+
if (failedWebhooks && failedWebhooks.length > 0) {
|
|
967
|
+
failedWebhooks.forEach(t => console.log(` ✗ ${t.objectType}.${t.event}: ${t.error}`));
|
|
1066
968
|
}
|
|
1067
969
|
}
|
|
1068
970
|
// Summary message
|
|
1069
971
|
console.log("\n" + "─".repeat(60));
|
|
1070
972
|
const capabilities = [];
|
|
1071
|
-
if (
|
|
973
|
+
if (mcpServer.active) {
|
|
1072
974
|
capabilities.push('tools via MCP');
|
|
1073
975
|
}
|
|
1074
|
-
if (
|
|
1075
|
-
|
|
1076
|
-
const hasCustomUrls = webhookConfig.triggers.some(t => {
|
|
1077
|
-
const url = t.hookUrl || webhookConfig.webhookUrl || AGENT_WEBHOOK_URL;
|
|
1078
|
-
return url !== AGENT_WEBHOOK_URL;
|
|
1079
|
-
});
|
|
1080
|
-
if (hasCustomUrls) {
|
|
1081
|
-
capabilities.push('webhooks to custom URL');
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
capabilities.push('event-driven triggers');
|
|
1085
|
-
}
|
|
976
|
+
if (webhooks.length > 0) {
|
|
977
|
+
capabilities.push(webhookConfig.isCustomWebhook ? 'webhooks to custom URL' : 'event-driven triggers');
|
|
1086
978
|
}
|
|
1087
979
|
if (capabilities.length > 0) {
|
|
1088
980
|
console.log(`✅ ${integration.name} connected! Your agent now has: ${capabilities.join(', ')}`);
|
|
1089
981
|
}
|
|
1090
|
-
else if (
|
|
982
|
+
else if (!mcpServer.active) {
|
|
1091
983
|
console.log(`⚠️ Connection created but MCP server pending activation.`);
|
|
1092
|
-
console.log(` Run: lua mcp activate --server-name ${
|
|
984
|
+
console.log(` Run: lua mcp activate --server-name ${mcpServer.name}`);
|
|
1093
985
|
}
|
|
1094
986
|
else {
|
|
1095
987
|
console.log(`⚠️ Connection created but setup incomplete. Check errors above.`);
|
|
@@ -1099,22 +991,9 @@ async function finalizeConnection(context, integration, connectionId, scopes, hi
|
|
|
1099
991
|
async function listConnections(context) {
|
|
1100
992
|
writeProgress("🔄 Loading connections...");
|
|
1101
993
|
try {
|
|
1102
|
-
//
|
|
1103
|
-
const
|
|
1104
|
-
context.unifiedToApi.getConnections(context.agentId),
|
|
1105
|
-
context.developerApi.getMCPServers()
|
|
1106
|
-
]);
|
|
994
|
+
// API now returns connections enriched with MCP server info
|
|
995
|
+
const connectionsResult = await context.unifiedToApi.getConnections(context.agentId);
|
|
1107
996
|
const connections = connectionsResult.success ? connectionsResult.data || [] : [];
|
|
1108
|
-
const servers = serversResult.success ? serversResult.data || [] : [];
|
|
1109
|
-
const unifiedServers = servers.filter(s => s.source === 'unifiedto');
|
|
1110
|
-
// Map connection IDs to MCP servers to check tool availability
|
|
1111
|
-
const serverByConnectionId = new Map();
|
|
1112
|
-
for (const server of unifiedServers) {
|
|
1113
|
-
const connectionMatch = server.url?.match(/connection=([a-f0-9]+)/i);
|
|
1114
|
-
if (connectionMatch) {
|
|
1115
|
-
serverByConnectionId.set(connectionMatch[1], server);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
997
|
console.log("\n" + "=".repeat(60));
|
|
1119
998
|
console.log("🔗 Connected Integrations");
|
|
1120
999
|
console.log("=".repeat(60) + "\n");
|
|
@@ -1124,8 +1003,7 @@ async function listConnections(context) {
|
|
|
1124
1003
|
return;
|
|
1125
1004
|
}
|
|
1126
1005
|
for (const connection of connections) {
|
|
1127
|
-
const
|
|
1128
|
-
const toolsAvailable = linkedServer?.active === true;
|
|
1006
|
+
const toolsAvailable = connection.mcpServer?.active === true;
|
|
1129
1007
|
// Status based on connection health + tool availability
|
|
1130
1008
|
let statusIcon = '⚪';
|
|
1131
1009
|
let statusText = 'Inactive';
|
|
@@ -1148,8 +1026,8 @@ async function listConnections(context) {
|
|
|
1148
1026
|
console.log(`${statusIcon} ${connection.integrationName || connection.integrationType}`);
|
|
1149
1027
|
console.log(` Connection: ${connection.id}`);
|
|
1150
1028
|
console.log(` Status: ${statusText}`);
|
|
1151
|
-
if (
|
|
1152
|
-
console.log(` MCP Server: ${
|
|
1029
|
+
if (connection.mcpServer) {
|
|
1030
|
+
console.log(` MCP Server: ${connection.mcpServer.name} (${connection.mcpServer.active ? 'active' : 'inactive'})`);
|
|
1153
1031
|
}
|
|
1154
1032
|
console.log(` Connected: ${new Date(connection.createdAt).toLocaleDateString()}`);
|
|
1155
1033
|
console.log();
|
|
@@ -1164,15 +1042,7 @@ async function listConnections(context) {
|
|
|
1164
1042
|
async function disconnectIntegration(context, connectionId) {
|
|
1165
1043
|
writeProgress(`🔄 Disconnecting... (please wait, do not close this window)`);
|
|
1166
1044
|
try {
|
|
1167
|
-
//
|
|
1168
|
-
const mcpServers = await context.developerApi.getMCPServers();
|
|
1169
|
-
if (mcpServers.success && mcpServers.data) {
|
|
1170
|
-
const associatedServer = mcpServers.data.find(s => s.source === 'unifiedto' && s.url?.includes(`connection=${connectionId}`));
|
|
1171
|
-
if (associatedServer) {
|
|
1172
|
-
await context.developerApi.deleteMCPServer(associatedServer.id);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
// Delete the connection (also deletes webhook subscriptions)
|
|
1045
|
+
// Delete the connection (API handles MCP server + webhook cleanup)
|
|
1176
1046
|
const deleteResult = await context.unifiedToApi.deleteConnection(connectionId, context.agentId);
|
|
1177
1047
|
if (deleteResult.success) {
|
|
1178
1048
|
writeSuccess(`✅ Integration disconnected successfully!`);
|
|
@@ -1410,15 +1280,7 @@ async function updateConnectionFlow(context, options = {}) {
|
|
|
1410
1280
|
// Step 7: Delete the old connection (silently)
|
|
1411
1281
|
writeProgress(`🔄 Updating ${selectedIntegration.name}...`);
|
|
1412
1282
|
try {
|
|
1413
|
-
//
|
|
1414
|
-
const mcpServers = await context.developerApi.getMCPServers();
|
|
1415
|
-
if (mcpServers.success && mcpServers.data) {
|
|
1416
|
-
const associatedServer = mcpServers.data.find(s => s.source === 'unifiedto' && s.url?.includes(`connection=${selectedConnection.id}`));
|
|
1417
|
-
if (associatedServer) {
|
|
1418
|
-
await context.developerApi.deleteMCPServer(associatedServer.id);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
// Delete old connection (this also deletes associated webhooks in Unified.to)
|
|
1283
|
+
// Delete old connection (API handles MCP server + webhook cleanup)
|
|
1422
1284
|
await context.unifiedToApi.deleteConnection(selectedConnection.id, context.agentId);
|
|
1423
1285
|
}
|
|
1424
1286
|
catch (error) {
|
|
@@ -1438,7 +1300,7 @@ async function updateConnectionFlow(context, options = {}) {
|
|
|
1438
1300
|
});
|
|
1439
1301
|
const authUrlResult = await context.unifiedToApi.getAuthUrl(selectedIntegration.value, {
|
|
1440
1302
|
successRedirect: CALLBACK_URL,
|
|
1441
|
-
failureRedirect:
|
|
1303
|
+
failureRedirect: CALLBACK_URL,
|
|
1442
1304
|
scopes: selectedScopes,
|
|
1443
1305
|
state,
|
|
1444
1306
|
externalXref,
|
|
@@ -1447,33 +1309,16 @@ async function updateConnectionFlow(context, options = {}) {
|
|
|
1447
1309
|
writeError(`❌ Failed to get authorization URL: ${authUrlResult.error?.message || 'Unknown error'}`);
|
|
1448
1310
|
return;
|
|
1449
1311
|
}
|
|
1312
|
+
// Auth URL is already resolved by the API (redirect wrappers followed server-side)
|
|
1450
1313
|
const authUrl = authUrlResult.data.authUrl;
|
|
1451
|
-
// Pre-fetch to handle redirects
|
|
1452
|
-
let finalAuthUrl = authUrl;
|
|
1453
|
-
try {
|
|
1454
|
-
const response = await fetch(authUrl);
|
|
1455
|
-
const responseText = await response.text();
|
|
1456
|
-
if (responseText.includes('test.html?redirect=')) {
|
|
1457
|
-
const urlMatch = responseText.match(/redirect=([^&\s]+)/);
|
|
1458
|
-
if (urlMatch) {
|
|
1459
|
-
finalAuthUrl = decodeURIComponent(urlMatch[1]);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
else if (responseText.startsWith('https://')) {
|
|
1463
|
-
finalAuthUrl = responseText.trim();
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
catch (error) {
|
|
1467
|
-
// Use original auth URL if pre-fetch fails
|
|
1468
|
-
}
|
|
1469
1314
|
// Step 9: Open browser and wait for callback
|
|
1470
1315
|
console.log("\n" + "─".repeat(60));
|
|
1471
1316
|
console.log("🌐 Re-authorizing with new scopes...");
|
|
1472
1317
|
console.log("─".repeat(60));
|
|
1473
|
-
console.log(`\n📋 Authorization URL (copy if browser doesn't open):\n ${
|
|
1318
|
+
console.log(`\n📋 Authorization URL (copy if browser doesn't open):\n ${authUrl}\n`);
|
|
1474
1319
|
const callbackPromise = startCallbackServer(300000);
|
|
1475
1320
|
try {
|
|
1476
|
-
await open(
|
|
1321
|
+
await open(authUrl);
|
|
1477
1322
|
writeInfo("🌐 Browser opened - please complete the authorization");
|
|
1478
1323
|
}
|
|
1479
1324
|
catch (error) {
|
|
@@ -1520,6 +1365,17 @@ async function updateConnectionFlow(context, options = {}) {
|
|
|
1520
1365
|
}
|
|
1521
1366
|
else {
|
|
1522
1367
|
writeError(`\n❌ Authorization failed: ${result.error || 'Unknown error'}`);
|
|
1368
|
+
if (result.logId) {
|
|
1369
|
+
console.log(` Log ID: ${result.logId}`);
|
|
1370
|
+
}
|
|
1371
|
+
// Report auth failure for observability (fire and forget)
|
|
1372
|
+
context.unifiedToApi.reportAuthFailure({
|
|
1373
|
+
integrationType: selectedIntegration.value,
|
|
1374
|
+
error: result.error || 'Unknown OAuth failure',
|
|
1375
|
+
logId: result.logId,
|
|
1376
|
+
agentId: context.agentId,
|
|
1377
|
+
source: 'cli',
|
|
1378
|
+
}).catch(() => { });
|
|
1523
1379
|
console.log("💡 The old connection was removed. Please reconnect with 'lua integrations connect'\n");
|
|
1524
1380
|
}
|
|
1525
1381
|
}
|
|
@@ -1906,6 +1762,11 @@ async function webhooksCreateFlow(context, options) {
|
|
|
1906
1762
|
// Step 6: Get interval for virtual webhooks
|
|
1907
1763
|
let interval;
|
|
1908
1764
|
const intervalOptions = [
|
|
1765
|
+
{ name: '1 minute', value: 1 },
|
|
1766
|
+
{ name: '5 minutes', value: 5 },
|
|
1767
|
+
{ name: '10 minutes', value: 10 },
|
|
1768
|
+
{ name: '15 minutes', value: 15 },
|
|
1769
|
+
{ name: '30 minutes', value: 30 },
|
|
1909
1770
|
{ name: '1 hour', value: 60 },
|
|
1910
1771
|
{ name: '2 hours', value: 120 },
|
|
1911
1772
|
{ name: '4 hours', value: 240 },
|
|
@@ -1925,7 +1786,7 @@ async function webhooksCreateFlow(context, options) {
|
|
|
1925
1786
|
name: 'interval',
|
|
1926
1787
|
message: 'Select polling interval:',
|
|
1927
1788
|
choices: intervalOptions,
|
|
1928
|
-
default:
|
|
1789
|
+
default: 1,
|
|
1929
1790
|
}
|
|
1930
1791
|
]);
|
|
1931
1792
|
if (!intervalAnswer)
|