@boltic/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -0
- package/api/integration.js +469 -0
- package/api/login.js +50 -0
- package/cli.js +128 -0
- package/commands/integration.js +952 -0
- package/commands/login.js +170 -0
- package/config/environments.js +13 -0
- package/helper/command-suggestions.js +54 -0
- package/helper/env.js +27 -0
- package/helper/error.js +123 -0
- package/helper/folder.js +204 -0
- package/helper/secure-storage.js +74 -0
- package/helper/verbose.js +20 -0
- package/index.js +14 -0
- package/package.json +57 -0
- package/templates/schemas.js +506 -0
- package/utils/integration.js +47 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
import { confirm, input, search } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
editIntegration,
|
|
8
|
+
getIntegrationById,
|
|
9
|
+
getIntegrationGroups,
|
|
10
|
+
listAllIntegrations,
|
|
11
|
+
pullIntegration,
|
|
12
|
+
purgeCache,
|
|
13
|
+
saveIntegration,
|
|
14
|
+
sendIntegrationForReview,
|
|
15
|
+
syncIntegration,
|
|
16
|
+
updateIntegration,
|
|
17
|
+
uploadFileToCloud,
|
|
18
|
+
} from "../api/integration.js";
|
|
19
|
+
import {
|
|
20
|
+
createExistingIntegrationsFolder,
|
|
21
|
+
createIntegrationFolderStructure,
|
|
22
|
+
} from "../helper/folder.js";
|
|
23
|
+
|
|
24
|
+
import { getCurrentEnv } from "../helper/env.js";
|
|
25
|
+
import { pickSvgFile } from "../utils/integration.js";
|
|
26
|
+
|
|
27
|
+
// Define commands and their descriptions
|
|
28
|
+
const commands = {
|
|
29
|
+
create: {
|
|
30
|
+
description: "Create a new integration",
|
|
31
|
+
action: handleCreate,
|
|
32
|
+
},
|
|
33
|
+
edit: {
|
|
34
|
+
description: "Edit an existing integration",
|
|
35
|
+
action: handleEdit,
|
|
36
|
+
},
|
|
37
|
+
publish: {
|
|
38
|
+
description: "Publish an integration",
|
|
39
|
+
action: handlePublish,
|
|
40
|
+
},
|
|
41
|
+
sync: {
|
|
42
|
+
description: "Sync a draft integration",
|
|
43
|
+
action: handleSync,
|
|
44
|
+
},
|
|
45
|
+
pull: {
|
|
46
|
+
description: "Pull an integration",
|
|
47
|
+
action: handlePull,
|
|
48
|
+
},
|
|
49
|
+
status: {
|
|
50
|
+
description: "Show detailed information about an integration",
|
|
51
|
+
action: handleStatus,
|
|
52
|
+
},
|
|
53
|
+
help: {
|
|
54
|
+
description: "Show help for integration commands",
|
|
55
|
+
action: showHelp,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Common function to read and parse schema files
|
|
60
|
+
async function readSchemaFiles(currentDir) {
|
|
61
|
+
const schemas = {};
|
|
62
|
+
const schemasDir = path.join(currentDir, "schemas");
|
|
63
|
+
const resourcesDir = path.join(schemasDir, "resources");
|
|
64
|
+
|
|
65
|
+
// Read authentication schema
|
|
66
|
+
const authSchemaPath = path.join(schemasDir, "authentication.json");
|
|
67
|
+
if (fs.existsSync(authSchemaPath)) {
|
|
68
|
+
schemas.authentication = JSON.parse(
|
|
69
|
+
fs.readFileSync(authSchemaPath, "utf8")
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Read base schema
|
|
74
|
+
const baseSchemaPath = path.join(schemasDir, "base.json");
|
|
75
|
+
if (fs.existsSync(baseSchemaPath)) {
|
|
76
|
+
schemas.configuration = JSON.parse(
|
|
77
|
+
fs.readFileSync(baseSchemaPath, "utf8")
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read webhook schema
|
|
82
|
+
const webhookSchemaPath = path.join(schemasDir, "webhook.json");
|
|
83
|
+
if (fs.existsSync(webhookSchemaPath)) {
|
|
84
|
+
schemas.webhook = JSON.parse(
|
|
85
|
+
fs.readFileSync(webhookSchemaPath, "utf8")
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Read resource schemas
|
|
90
|
+
if (fs.existsSync(resourcesDir)) {
|
|
91
|
+
schemas.resources = {};
|
|
92
|
+
const resourceFiles = fs.readdirSync(resourcesDir);
|
|
93
|
+
for (const file of resourceFiles) {
|
|
94
|
+
if (file.endsWith(".json")) {
|
|
95
|
+
const resourceName = path.basename(file, ".json");
|
|
96
|
+
const resourcePath = path.join(resourcesDir, file);
|
|
97
|
+
const resourceSchema = JSON.parse(
|
|
98
|
+
fs.readFileSync(resourcePath, "utf8")
|
|
99
|
+
);
|
|
100
|
+
schemas.resources[resourceName] = resourceSchema;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Read documentation files
|
|
106
|
+
const authDocPath = path.join(currentDir, "Authentication.mdx");
|
|
107
|
+
const generalDocPath = path.join(currentDir, "Documentation.mdx");
|
|
108
|
+
|
|
109
|
+
if (fs.existsSync(authDocPath)) {
|
|
110
|
+
schemas.authentication_documentation = fs.readFileSync(
|
|
111
|
+
authDocPath,
|
|
112
|
+
"utf8"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(generalDocPath)) {
|
|
117
|
+
schemas.documentation = fs.readFileSync(generalDocPath, "utf8");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return schemas;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Sync an integration
|
|
124
|
+
async function handleSync(args) {
|
|
125
|
+
// Parse command line arguments
|
|
126
|
+
let currentDir = process.cwd();
|
|
127
|
+
const pathIndex = args.indexOf("--path");
|
|
128
|
+
|
|
129
|
+
if (pathIndex !== -1 && args[pathIndex + 1]) {
|
|
130
|
+
currentDir = args[pathIndex + 1];
|
|
131
|
+
// Validate the provided path
|
|
132
|
+
if (!fs.existsSync(currentDir)) {
|
|
133
|
+
console.error(
|
|
134
|
+
chalk.red(
|
|
135
|
+
`Error: The specified path does not exist: ${currentDir}`
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { apiUrl, token, accountId, session, frontendUrl } =
|
|
143
|
+
await getCurrentEnv();
|
|
144
|
+
|
|
145
|
+
// Read spec.json to get updated integration configuration
|
|
146
|
+
const specPath = path.join(currentDir, "spec.json");
|
|
147
|
+
if (fs.existsSync(specPath)) {
|
|
148
|
+
const specContent = JSON.parse(fs.readFileSync(specPath, "utf8"));
|
|
149
|
+
// Update integration with spec.json content
|
|
150
|
+
const updatedIntegration = await updateIntegration(
|
|
151
|
+
apiUrl,
|
|
152
|
+
token,
|
|
153
|
+
accountId,
|
|
154
|
+
session,
|
|
155
|
+
{
|
|
156
|
+
id: specContent.id,
|
|
157
|
+
name: specContent.name,
|
|
158
|
+
description: specContent.description,
|
|
159
|
+
icon: specContent.icon,
|
|
160
|
+
activity_type: specContent.activity_type,
|
|
161
|
+
trigger_type: specContent.trigger_type,
|
|
162
|
+
meta: specContent.meta,
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
if (updatedIntegration) {
|
|
166
|
+
console.log(
|
|
167
|
+
chalk.green(
|
|
168
|
+
"• Integration configuration updated from spec.json"
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.cyan(`\nSyncing integration: ${specContent.name}`));
|
|
174
|
+
|
|
175
|
+
console.log("Validating schemas...");
|
|
176
|
+
const schemas = await readSchemaFiles(currentDir);
|
|
177
|
+
schemas.status = "draft";
|
|
178
|
+
|
|
179
|
+
console.log(chalk.green("Schemas validated successfully"));
|
|
180
|
+
|
|
181
|
+
const data = await syncIntegration(apiUrl, token, accountId, session, {
|
|
182
|
+
integration_id: specContent.id,
|
|
183
|
+
...schemas,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await purgeCache(apiUrl, token, accountId, session, {
|
|
187
|
+
integration_id: specContent.id,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (data) {
|
|
191
|
+
console.log(chalk.green("• Integration synced successfully"));
|
|
192
|
+
// Get environment name from consoleUrl
|
|
193
|
+
const workflowUrl = `${frontendUrl}/accounts/${accountId}/workflow/workflow-builder`;
|
|
194
|
+
console.log(chalk.cyan("\nWorkflow URL:"));
|
|
195
|
+
console.log(chalk.underline.blue(workflowUrl));
|
|
196
|
+
} else {
|
|
197
|
+
const errorMessage =
|
|
198
|
+
data?.message || "API Error: Integration syncing failed";
|
|
199
|
+
console.error(
|
|
200
|
+
chalk.red(`• Failed to syncing integration: ${errorMessage}`)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Publish an integration
|
|
207
|
+
async function handlePublish(args) {
|
|
208
|
+
console.log(chalk.green("Publishing integration...\n"));
|
|
209
|
+
// Parse command line arguments
|
|
210
|
+
let currentDir = process.cwd();
|
|
211
|
+
const pathIndex = args.indexOf("--path");
|
|
212
|
+
|
|
213
|
+
if (pathIndex !== -1 && args[pathIndex + 1]) {
|
|
214
|
+
currentDir = args[pathIndex + 1];
|
|
215
|
+
// Validate the provided path
|
|
216
|
+
if (!fs.existsSync(currentDir)) {
|
|
217
|
+
console.error(
|
|
218
|
+
chalk.red(
|
|
219
|
+
`Error: The specified path does not exist: ${currentDir}`
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
227
|
+
|
|
228
|
+
// Read spec.json to get updated integration configuration
|
|
229
|
+
const specPath = path.join(currentDir, "spec.json");
|
|
230
|
+
if (fs.existsSync(specPath)) {
|
|
231
|
+
const specContent = JSON.parse(fs.readFileSync(specPath, "utf8"));
|
|
232
|
+
// Update integration with spec.json content
|
|
233
|
+
const updatedIntegration = await updateIntegration(
|
|
234
|
+
apiUrl,
|
|
235
|
+
token,
|
|
236
|
+
accountId,
|
|
237
|
+
session,
|
|
238
|
+
{
|
|
239
|
+
id: specContent.id,
|
|
240
|
+
name: specContent.name,
|
|
241
|
+
description: specContent.description,
|
|
242
|
+
icon: specContent.icon,
|
|
243
|
+
activity_type: specContent.activity_type,
|
|
244
|
+
trigger_type: specContent.trigger_type,
|
|
245
|
+
meta: specContent.meta,
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
if (updatedIntegration) {
|
|
249
|
+
console.log(
|
|
250
|
+
chalk.green(
|
|
251
|
+
"• Integration configuration updated from spec.json"
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(chalk.cyan(`\nSyncing integration: ${specContent.name}`));
|
|
257
|
+
|
|
258
|
+
console.log("Validating schemas...");
|
|
259
|
+
const schemas = await readSchemaFiles(currentDir);
|
|
260
|
+
schemas.status = "draft";
|
|
261
|
+
|
|
262
|
+
console.log(chalk.green("Schemas validated successfully"));
|
|
263
|
+
|
|
264
|
+
const data = await syncIntegration(apiUrl, token, accountId, session, {
|
|
265
|
+
integration_id: specContent.id,
|
|
266
|
+
...schemas,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (data) {
|
|
270
|
+
const review_payload = {
|
|
271
|
+
integration_id: specContent.id,
|
|
272
|
+
name: specContent.name,
|
|
273
|
+
description: specContent.description?.integration || "",
|
|
274
|
+
icon: specContent.icon,
|
|
275
|
+
status: "in-review",
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const review = await sendIntegrationForReview(
|
|
279
|
+
apiUrl,
|
|
280
|
+
token,
|
|
281
|
+
accountId,
|
|
282
|
+
session,
|
|
283
|
+
review_payload
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (review) {
|
|
287
|
+
console.log(
|
|
288
|
+
chalk.green(
|
|
289
|
+
"\n✅ Integration sent to review successfully!\n"
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
console.error(
|
|
295
|
+
chalk.red("\n❌ Error publishing integration:", data.message)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Execute the integration command
|
|
302
|
+
const execute = async (args) => {
|
|
303
|
+
const subCommand = args[0];
|
|
304
|
+
|
|
305
|
+
if (!subCommand) {
|
|
306
|
+
showHelp();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!commands[subCommand]) {
|
|
311
|
+
console.log(chalk.red("Unknown or missing integration sub-command.\n"));
|
|
312
|
+
showHelp();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const commandObj = commands[subCommand];
|
|
317
|
+
await commandObj.action(args.slice(1));
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Create a new integration
|
|
321
|
+
async function handleCreate() {
|
|
322
|
+
try {
|
|
323
|
+
// Fetch integration groups from API
|
|
324
|
+
const { apiUrl, token, session, accountId } = await getCurrentEnv();
|
|
325
|
+
let integrationGroupsChoices = [];
|
|
326
|
+
try {
|
|
327
|
+
const integrationGroups = await getIntegrationGroups(
|
|
328
|
+
apiUrl,
|
|
329
|
+
accountId,
|
|
330
|
+
token,
|
|
331
|
+
session
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
if (!integrationGroups || !Array.isArray(integrationGroups)) {
|
|
335
|
+
console.error(
|
|
336
|
+
chalk.red(
|
|
337
|
+
"\n❌ Failed to fetch integration groups: Invalid response format"
|
|
338
|
+
)
|
|
339
|
+
);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
integrationGroupsChoices = integrationGroups.map((group) => ({
|
|
344
|
+
name: group.name,
|
|
345
|
+
value: group.id,
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
if (integrationGroupsChoices.length === 0) {
|
|
349
|
+
console.error(
|
|
350
|
+
chalk.red(
|
|
351
|
+
"\n❌ No integration groups available. Please create an integration group first."
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error(
|
|
358
|
+
chalk.red("\n❌ Failed to fetch integration groups: ") +
|
|
359
|
+
(error.message || "Unknown error")
|
|
360
|
+
);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
console.log(
|
|
365
|
+
chalk.green(
|
|
366
|
+
"Please provide the following details for the integration:\n"
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Prompt for integration details
|
|
371
|
+
const name = await input({
|
|
372
|
+
message: "Integration name (e.g., My_Integration):",
|
|
373
|
+
validate: (input) => {
|
|
374
|
+
const formattedInput = input.trim().replace(/\s+/g, "_");
|
|
375
|
+
|
|
376
|
+
if (!formattedInput) return "Name is required";
|
|
377
|
+
if (formattedInput.length > 50)
|
|
378
|
+
return "Name cannot exceed 50 characters";
|
|
379
|
+
if (!/^[a-zA-Z_]+$/.test(formattedInput)) {
|
|
380
|
+
return "Name can only contain letters and underscores (no numbers or hyphens)";
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
},
|
|
384
|
+
transform: (input) => input.trim().replace(/\s+/g, "_"),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const iconPath = await pickSvgFile();
|
|
388
|
+
|
|
389
|
+
if (!iconPath || !iconPath?.endsWith(".svg")) {
|
|
390
|
+
console.log(
|
|
391
|
+
chalk.yellow(
|
|
392
|
+
"⚠️ File selection was cancelled or not a valid SVG."
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!iconPath || !iconPath.endsWith(".svg")) {
|
|
399
|
+
console.error(chalk.red("❌ Invalid or no SVG file selected."));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
let icon = "";
|
|
403
|
+
try {
|
|
404
|
+
const iconData = await uploadFileToCloud(
|
|
405
|
+
apiUrl,
|
|
406
|
+
token,
|
|
407
|
+
accountId,
|
|
408
|
+
session,
|
|
409
|
+
iconPath
|
|
410
|
+
);
|
|
411
|
+
icon = iconData.url;
|
|
412
|
+
console.log(chalk.cyan(`\nIcon: ${icon}`));
|
|
413
|
+
} catch (e) {
|
|
414
|
+
console.error(chalk.red("❌ Could not upload icon: " + e.message));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Add boolean prompts for activity and trigger with updated terminology
|
|
419
|
+
const isActivity = await confirm({
|
|
420
|
+
message:
|
|
421
|
+
"Would you like to create this integration as a workflow activity? (Activities are reusable components that perform specific tasks in your workflow)",
|
|
422
|
+
default: true,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
let integration_ai_description = "";
|
|
426
|
+
let trigger_ai_description = "";
|
|
427
|
+
let integration_description = "";
|
|
428
|
+
let trigger_description = "";
|
|
429
|
+
|
|
430
|
+
if (isActivity) {
|
|
431
|
+
integration_description = await input({
|
|
432
|
+
message: "Workflow Activity Description:",
|
|
433
|
+
validate: (input) => {
|
|
434
|
+
if (!input.trim())
|
|
435
|
+
return "Workflow Activity Description is required";
|
|
436
|
+
if (input.length > 300) {
|
|
437
|
+
return "Workflow Activity Description must not exceed 300 characters";
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
integration_ai_description = await input({
|
|
444
|
+
message: "Workflow Activity AI Description:",
|
|
445
|
+
validate: (input) => {
|
|
446
|
+
if (!input.trim())
|
|
447
|
+
return "Workflow Activity AI Description is required";
|
|
448
|
+
if (input.length > 300) {
|
|
449
|
+
return "Workflow Activity AI Description must not exceed 300 characters";
|
|
450
|
+
}
|
|
451
|
+
return true;
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const isTrigger = await confirm({
|
|
457
|
+
message:
|
|
458
|
+
"Would you like to create this integration as a workflow trigger? (Triggers start your workflow based on external events)",
|
|
459
|
+
default: false,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
if (isTrigger) {
|
|
463
|
+
trigger_description = await input({
|
|
464
|
+
message: "Workflow Trigger Description:",
|
|
465
|
+
validate: (input) => {
|
|
466
|
+
if (!input.trim())
|
|
467
|
+
return "Workflow Trigger Description is required";
|
|
468
|
+
if (input.length > 300) {
|
|
469
|
+
return "Workflow Description must not exceed 300 characters";
|
|
470
|
+
}
|
|
471
|
+
return true;
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
trigger_ai_description = await input({
|
|
476
|
+
message: "Workflow Trigger AI Description:",
|
|
477
|
+
validate: (input) => {
|
|
478
|
+
if (!input.trim())
|
|
479
|
+
return "Workflow Trigger AI Description is required";
|
|
480
|
+
if (input.length > 300) {
|
|
481
|
+
return "Workflow Trigger AI Description must not exceed 300 characters";
|
|
482
|
+
}
|
|
483
|
+
return true;
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const integrationGroup = await search({
|
|
489
|
+
message: "Search and select an integration group:",
|
|
490
|
+
source: async (term) => {
|
|
491
|
+
if (!term) return integrationGroupsChoices;
|
|
492
|
+
return integrationGroupsChoices.filter((group) =>
|
|
493
|
+
group.name.toLowerCase().includes(term.toLowerCase())
|
|
494
|
+
);
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (!isActivity && !isTrigger) {
|
|
499
|
+
console.log(
|
|
500
|
+
chalk.red(
|
|
501
|
+
"\n❌ Both activity and trigger cannot be false. Please select at least one."
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Create the integration
|
|
508
|
+
try {
|
|
509
|
+
const integration = await saveIntegration(
|
|
510
|
+
apiUrl,
|
|
511
|
+
token,
|
|
512
|
+
accountId,
|
|
513
|
+
session,
|
|
514
|
+
{
|
|
515
|
+
name,
|
|
516
|
+
icon,
|
|
517
|
+
activity_type: isActivity ? "customActivity" : null,
|
|
518
|
+
trigger_type: isTrigger ? "CloudTrigger" : null,
|
|
519
|
+
integration_group_id: integrationGroup,
|
|
520
|
+
description: {
|
|
521
|
+
integration: integration_description || "",
|
|
522
|
+
trigger: trigger_description || "",
|
|
523
|
+
},
|
|
524
|
+
meta: {
|
|
525
|
+
ai_description: {
|
|
526
|
+
integration: integration_ai_description || "",
|
|
527
|
+
trigger: trigger_ai_description || "",
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if (integration) {
|
|
534
|
+
console.log(
|
|
535
|
+
chalk.green("\n✅ Integration created successfully!")
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
// Create folder structure with the integration name
|
|
539
|
+
await createIntegrationFolderStructure(integration);
|
|
540
|
+
|
|
541
|
+
// Also share Documentation URL to the user: https://docs.boltic.io/docs/activityBuilder/develop/boilerplate
|
|
542
|
+
const documentationUrl =
|
|
543
|
+
"https://docs.boltic.io/docs/activityBuilder/develop/boilerplate";
|
|
544
|
+
console.log(
|
|
545
|
+
chalk.cyan(
|
|
546
|
+
"\n📄 Documentation URL: " +
|
|
547
|
+
chalk.underline.blue(documentationUrl)
|
|
548
|
+
)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error(
|
|
553
|
+
chalk.red("\n❌ Failed to create integration: ") +
|
|
554
|
+
(error.message || "Unknown error")
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (
|
|
559
|
+
error.message &&
|
|
560
|
+
error.message.includes("User force closed the prompt")
|
|
561
|
+
) {
|
|
562
|
+
console.log(chalk.yellow("\n⚠️ Operation cancelled by user"));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Handle other errors
|
|
566
|
+
console.error(
|
|
567
|
+
chalk.red("\n❌ An error occurred:"),
|
|
568
|
+
error.message || "Unknown error"
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Handle edit integration command
|
|
574
|
+
async function handleEdit() {
|
|
575
|
+
console.log(chalk.green("Please select the integration to edit...\n"));
|
|
576
|
+
try {
|
|
577
|
+
const { apiUrl, token, session, accountId } = await getCurrentEnv();
|
|
578
|
+
const integrations = await listAllIntegrations(
|
|
579
|
+
apiUrl,
|
|
580
|
+
token,
|
|
581
|
+
accountId,
|
|
582
|
+
session
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
if (!integrations || !Array.isArray(integrations)) {
|
|
586
|
+
console.error(
|
|
587
|
+
chalk.red(
|
|
588
|
+
"\n❌ Failed to fetch integrations: Invalid response format"
|
|
589
|
+
)
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (integrations.length === 0) {
|
|
595
|
+
console.error(chalk.red("\n❌ No integrations found to edit."));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Let user select an integration
|
|
600
|
+
const choices =
|
|
601
|
+
integrations
|
|
602
|
+
.filter((integration) =>
|
|
603
|
+
["customActivity", "CloudTrigger"].includes(
|
|
604
|
+
integration.activity_type || integration.trigger_type
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
.map((integration) => ({
|
|
608
|
+
name: `${integration.name} - ${integration.status} - ${integration.activity_type ? `(activity_type: ${integration.activity_type})` : ""} ${integration.trigger_type ? `(trigger_type: ${integration.trigger_type})` : ""}`,
|
|
609
|
+
value: integration,
|
|
610
|
+
})) || [];
|
|
611
|
+
|
|
612
|
+
const selectedIntegration = await search({
|
|
613
|
+
message: "Search and select an integration to edit:",
|
|
614
|
+
source: async (term) => {
|
|
615
|
+
if (!term) return choices;
|
|
616
|
+
return choices?.filter((choice) =>
|
|
617
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
618
|
+
);
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
console.log(
|
|
623
|
+
chalk.cyan("\nSelected integration:"),
|
|
624
|
+
selectedIntegration.name
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
const draftIntegration = await editIntegration(
|
|
628
|
+
apiUrl,
|
|
629
|
+
token,
|
|
630
|
+
accountId,
|
|
631
|
+
session,
|
|
632
|
+
{
|
|
633
|
+
id: selectedIntegration.id,
|
|
634
|
+
parent_id: selectedIntegration.parent_id,
|
|
635
|
+
status: selectedIntegration.status,
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
if (draftIntegration) {
|
|
640
|
+
const isFolderCreated =
|
|
641
|
+
await createExistingIntegrationsFolder(draftIntegration);
|
|
642
|
+
if (isFolderCreated) {
|
|
643
|
+
console.log(
|
|
644
|
+
chalk.green(
|
|
645
|
+
"\n✅ Integration folder structure created successfully!"
|
|
646
|
+
)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (
|
|
652
|
+
error.message &&
|
|
653
|
+
error.message.includes("User force closed the prompt")
|
|
654
|
+
) {
|
|
655
|
+
console.log(chalk.yellow("\n⚠️ Operation cancelled by user"));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// Handle other errors
|
|
659
|
+
console.error(
|
|
660
|
+
chalk.red("\n❌ An error occurred:"),
|
|
661
|
+
error.message || "Unknown error"
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Pull the latest content of a particular integration. It will pull the latest content from the API and update the local integration folder with the latest content. Don't create a folder this time but update the contents of the integration
|
|
667
|
+
async function handlePull(args) {
|
|
668
|
+
console.log(chalk.green("Pulling integration...\n"));
|
|
669
|
+
try {
|
|
670
|
+
// Parse command line arguments
|
|
671
|
+
let currentDir = process.cwd();
|
|
672
|
+
const pathIndex = args.indexOf("--path");
|
|
673
|
+
|
|
674
|
+
if (pathIndex !== -1 && args[pathIndex + 1]) {
|
|
675
|
+
currentDir = args[pathIndex + 1];
|
|
676
|
+
// Validate the provided path
|
|
677
|
+
if (!fs.existsSync(currentDir)) {
|
|
678
|
+
console.error(
|
|
679
|
+
chalk.red(
|
|
680
|
+
`Error: The specified path does not exist: ${currentDir}`
|
|
681
|
+
)
|
|
682
|
+
);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
688
|
+
|
|
689
|
+
const specPath = path.join(currentDir, "spec.json");
|
|
690
|
+
if (fs.existsSync(specPath)) {
|
|
691
|
+
const specContent = JSON.parse(fs.readFileSync(specPath, "utf8"));
|
|
692
|
+
const integration = await pullIntegration(
|
|
693
|
+
apiUrl,
|
|
694
|
+
token,
|
|
695
|
+
accountId,
|
|
696
|
+
session,
|
|
697
|
+
specContent.id
|
|
698
|
+
);
|
|
699
|
+
if (!integration) {
|
|
700
|
+
console.error(
|
|
701
|
+
chalk.red(
|
|
702
|
+
"\n❌ Failed to fetch integration details. Please try again later."
|
|
703
|
+
)
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const integrationName = specContent.name
|
|
708
|
+
.toLowerCase()
|
|
709
|
+
.replace(/\s+/g, "-");
|
|
710
|
+
|
|
711
|
+
const integrationDir = path.join(process.cwd(), integrationName);
|
|
712
|
+
|
|
713
|
+
if (!fs.existsSync(integrationDir)) {
|
|
714
|
+
console.log(
|
|
715
|
+
chalk.yellow(
|
|
716
|
+
`\nWarning: Directory ${integrationDir} does not exist. Creating it now...`
|
|
717
|
+
)
|
|
718
|
+
);
|
|
719
|
+
fs.mkdirSync(integrationDir, { recursive: true });
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Now, replace all the files and content with the latest content and everthing
|
|
723
|
+
const isFolderCreated =
|
|
724
|
+
await createExistingIntegrationsFolder(integration);
|
|
725
|
+
if (isFolderCreated) {
|
|
726
|
+
console.log(
|
|
727
|
+
chalk.green("\n✅ Integration folder updated successfully!")
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
} else {
|
|
731
|
+
console.log("No spec.json file found in the current directory.");
|
|
732
|
+
console.log(
|
|
733
|
+
chalk.green(
|
|
734
|
+
"Please select the integration to pull from the list below:"
|
|
735
|
+
)
|
|
736
|
+
);
|
|
737
|
+
const integrations = await listAllIntegrations(
|
|
738
|
+
apiUrl,
|
|
739
|
+
token,
|
|
740
|
+
accountId,
|
|
741
|
+
session
|
|
742
|
+
);
|
|
743
|
+
if (!integrations || !Array.isArray(integrations)) {
|
|
744
|
+
console.error(
|
|
745
|
+
chalk.red(
|
|
746
|
+
"\n❌ Failed to fetch integrations: Invalid response format"
|
|
747
|
+
)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (integrations.length === 0) {
|
|
751
|
+
console.error(chalk.red("\n❌ No integrations found."));
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
// Let user select an integration
|
|
755
|
+
const choices =
|
|
756
|
+
integrations
|
|
757
|
+
.filter((integration) =>
|
|
758
|
+
["customActivity", "CloudTrigger"].includes(
|
|
759
|
+
integration.activity_type ||
|
|
760
|
+
integration.trigger_type
|
|
761
|
+
)
|
|
762
|
+
)
|
|
763
|
+
.map((integration) => ({
|
|
764
|
+
name: `${integration.name} - ${integration.status} - ${integration.activity_type ? `(activity_type: ${integration.activity_type})` : ""} ${integration.trigger_type ? `(trigger_type: ${integration.trigger_type})` : ""}`,
|
|
765
|
+
value: integration,
|
|
766
|
+
})) || [];
|
|
767
|
+
|
|
768
|
+
const selectedIntegration = await search({
|
|
769
|
+
message: "Search and select an integration to edit:",
|
|
770
|
+
source: async (term) => {
|
|
771
|
+
if (!term) return choices;
|
|
772
|
+
return choices?.filter((choice) =>
|
|
773
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
774
|
+
);
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
console.log(
|
|
779
|
+
chalk.cyan("\nSelected integration:"),
|
|
780
|
+
selectedIntegration.name
|
|
781
|
+
);
|
|
782
|
+
const pulledIntegration = await pullIntegration(
|
|
783
|
+
apiUrl,
|
|
784
|
+
token,
|
|
785
|
+
accountId,
|
|
786
|
+
session,
|
|
787
|
+
selectedIntegration.id
|
|
788
|
+
);
|
|
789
|
+
if (!pulledIntegration) {
|
|
790
|
+
console.error(
|
|
791
|
+
chalk.red(
|
|
792
|
+
"\n❌ Failed to fetch integration details. Please try again later."
|
|
793
|
+
)
|
|
794
|
+
);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
// Update the integration folder with the latest content
|
|
798
|
+
|
|
799
|
+
const isFolderCreated =
|
|
800
|
+
await createExistingIntegrationsFolder(pulledIntegration);
|
|
801
|
+
if (isFolderCreated) {
|
|
802
|
+
console.log(
|
|
803
|
+
chalk.green(
|
|
804
|
+
"\n✅ Integration folder structure created successfully!"
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
} catch (error) {
|
|
810
|
+
if (
|
|
811
|
+
error.message &&
|
|
812
|
+
error.message.includes("User force closed the prompt")
|
|
813
|
+
) {
|
|
814
|
+
console.log(chalk.yellow("\n⚠️ Operation cancelled by user"));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
// Handle other errors
|
|
818
|
+
console.error(
|
|
819
|
+
chalk.red("\n❌ An error occurred:"),
|
|
820
|
+
error.message || "Unknown error"
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Show help for integration commands
|
|
826
|
+
function showHelp() {
|
|
827
|
+
console.log(chalk.cyan("\nIntegration Commands:\n"));
|
|
828
|
+
Object.entries(commands).forEach(([cmd, details]) => {
|
|
829
|
+
console.log(chalk.bold(`${cmd}`) + ` - ${details.description}`);
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Show detailed information about an integration
|
|
834
|
+
async function handleStatus() {
|
|
835
|
+
console.log(chalk.green("Fetching integration information...\n"));
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
const env = await getCurrentEnv();
|
|
839
|
+
if (!env || !env.token || !env.session) {
|
|
840
|
+
console.error(
|
|
841
|
+
chalk.red("\n❌ Authentication required. Please login first.")
|
|
842
|
+
);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const { apiUrl, token, session, accountId } = env;
|
|
846
|
+
|
|
847
|
+
// Fetch all integrations
|
|
848
|
+
const integrations = await listAllIntegrations(
|
|
849
|
+
apiUrl,
|
|
850
|
+
token,
|
|
851
|
+
accountId,
|
|
852
|
+
session
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
if (!integrations || integrations.length === 0) {
|
|
856
|
+
console.log(chalk.yellow("No integrations found."));
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Let user select an integration
|
|
861
|
+
const choices =
|
|
862
|
+
integrations
|
|
863
|
+
.filter((integration) =>
|
|
864
|
+
["customActivity", "CloudTrigger"].includes(
|
|
865
|
+
integration.activity_type || integration.trigger_type
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
.map((integration) => ({
|
|
869
|
+
name: `${integration.name} ${integration.activity_type ? `(activity_type: ${integration.activity_type})` : ""} ${integration.trigger_type ? `(trigger_type: ${integration.trigger_type})` : ""}`,
|
|
870
|
+
value: integration.id,
|
|
871
|
+
})) || [];
|
|
872
|
+
|
|
873
|
+
const selectedIntegration = await search({
|
|
874
|
+
message: "Search and select an integration to edit:",
|
|
875
|
+
source: async (term) => {
|
|
876
|
+
if (!term) return choices;
|
|
877
|
+
return choices?.filter((choice) =>
|
|
878
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
879
|
+
);
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
// Use this selected integration and do an API call using it's value. There is already an API named getIntegrationById. Use it.
|
|
884
|
+
const integration = await getIntegrationById(
|
|
885
|
+
apiUrl,
|
|
886
|
+
token,
|
|
887
|
+
accountId,
|
|
888
|
+
session,
|
|
889
|
+
selectedIntegration
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
if (!integration) {
|
|
893
|
+
console.log(chalk.yellow("Integration not found."));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Display integration details
|
|
898
|
+
console.log(chalk.cyan.bold("\n=== Integration Details ==="));
|
|
899
|
+
console.log(chalk.cyan("\nBasic Information:"));
|
|
900
|
+
console.log(`${chalk.dim("ID:")} ${integration.id}`);
|
|
901
|
+
console.log(`${chalk.dim("Name:")} ${integration.name}`);
|
|
902
|
+
console.log(`${chalk.dim("Slug:")} ${integration.slug}`);
|
|
903
|
+
console.log(
|
|
904
|
+
`${chalk.dim("Activity Type:")} ${integration.activity_type}`
|
|
905
|
+
);
|
|
906
|
+
console.log(
|
|
907
|
+
`${chalk.dim("Trigger Type:")} ${integration.trigger_type}`
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
console.log(`${chalk.dim("Description:")} ${integration.description}`);
|
|
911
|
+
|
|
912
|
+
console.log(chalk.cyan("\nStatus Information:"));
|
|
913
|
+
console.log(
|
|
914
|
+
`${chalk.dim("Status:")} ${integration.status === "published" ? chalk.green(integration.status) : chalk.yellow(integration.status)}`
|
|
915
|
+
);
|
|
916
|
+
console.log(
|
|
917
|
+
`${chalk.dim("Active:")} ${integration.active ? chalk.green("Yes") : chalk.red("No")}`
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
console.log(chalk.cyan("\nMeta Information:"));
|
|
921
|
+
console.log(
|
|
922
|
+
`${chalk.dim("AI Description:")} ${integration.meta?.ai_description || "N/A"}`
|
|
923
|
+
);
|
|
924
|
+
console.log(
|
|
925
|
+
`${chalk.dim("Is Trigger:")} ${integration.meta?.is_trigger ? "Yes" : "No"}`
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
console.log(chalk.cyan("\nTimestamps:"));
|
|
929
|
+
console.log(
|
|
930
|
+
`${chalk.dim("Created At:")} ${new Date(integration.created_at).toLocaleString()}`
|
|
931
|
+
);
|
|
932
|
+
console.log(
|
|
933
|
+
`${chalk.dim("Updated At:")} ${new Date(integration.updated_at).toLocaleString()}`
|
|
934
|
+
);
|
|
935
|
+
console.log(`${chalk.dim("Created By:")} ${integration.created_by}`);
|
|
936
|
+
console.log(`${chalk.dim("Modified By:")} ${integration.modified_by}`);
|
|
937
|
+
|
|
938
|
+
if (integration.documentation) {
|
|
939
|
+
console.log(chalk.cyan("\nDocumentation:"));
|
|
940
|
+
console.log(integration.documentation);
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.error(
|
|
944
|
+
chalk.red("\n❌ Error fetching integration status:"),
|
|
945
|
+
error.message || "Unknown error"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export default {
|
|
951
|
+
execute,
|
|
952
|
+
};
|