@mspcopilot/n8n-nodes-connectwise 0.2.4 → 0.3.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.
@@ -117,9 +117,18 @@ class ConnectWisePsaApi {
117
117
  name: "checkForUpdates",
118
118
  type: "boolean",
119
119
  default: !0,
120
- hint: "Current version: 0.2.4 (community edition)",
120
+ hint: "Current version: 0.3.1 (community edition)",
121
121
  description: "Check for new versions of this node package and show notifications when updates are available"
122
- } ];
122
+ }, ...process.env.MSPCOPILOT_BETA === "true" ? [ {
123
+ displayName: "Access Key",
124
+ name: "accessKey",
125
+ type: "string",
126
+ typeOptions: {
127
+ password: !0
128
+ },
129
+ default: "",
130
+ hint: "To enable beta or server-side features"
131
+ } ] : [] ];
123
132
  async authenticate(credentials, requestOptions) {
124
133
  const serverUrl = credentials.site === "custom" ? credentials.customServer : credentials.site, base64Auth = Buffer.from(`${credentials.companyId}+${credentials.publicKey}:${credentials.privateKey}`).toString("base64");
125
134
  return requestOptions.baseURL || (requestOptions.baseURL = `https://${serverUrl}`),
@@ -74,7 +74,7 @@ const resources = {
74
74
 
75
75
  class ConnectWisePsa {
76
76
  constructor() {
77
- console.log(`🔌 ConnectWise PSA Node Loaded | 📦 v0.2.4 | 🏗️ Build: ${"community".toUpperCase()} (${"prod".toUpperCase()} mode)`);
77
+ console.log(`🔌 ConnectWise PSA Node Loaded | 📦 v0.3.1 | 🏗️ Build: ${"community".toUpperCase()} (${"prod".toUpperCase()} mode)`);
78
78
  }
79
79
  description={
80
80
  displayName: "ConnectWise PSA",
@@ -0,0 +1,506 @@
1
+ var __defProp = Object.defineProperty;
2
+
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+
9
+ var __export = (target, all) => {
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: !0
13
+ });
14
+ }, __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, {
16
+ get: () => from[key],
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ return to;
20
+ };
21
+
22
+ var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
23
+ value: !0
24
+ }), mod);
25
+
26
+ var ConnectWisePsaTrigger_node_exports = {};
27
+
28
+ __export(ConnectWisePsaTrigger_node_exports, {
29
+ ConnectWisePsaTrigger: () => ConnectWisePsaTrigger
30
+ });
31
+
32
+ module.exports = __toCommonJS(ConnectWisePsaTrigger_node_exports);
33
+
34
+ var import_n8n_workflow = require("n8n-workflow"), import_methods = require("./methods"), import_client = require("./transport/client"), import_common = require("./descriptions/common.descriptions");
35
+
36
+ const WEBHOOK_REGISTRY_URL = "https://api.mspcopilot.io/watchtower/api/n8n/triggers";
37
+
38
+ function toWatchtowerQuery(query) {
39
+ const keyMap = {
40
+ boardIds: "board.id",
41
+ statusIds: "status.id",
42
+ companyIds: "company.id",
43
+ priorityIds: "priority.id",
44
+ typeIds: "type.id",
45
+ subtypeIds: "subtype.id",
46
+ itemIds: "item.id",
47
+ sourceIds: "source.id"
48
+ }, result = {};
49
+ for (const [key, value] of Object.entries(query)) {
50
+ const mappedKey = keyMap[key] ?? key;
51
+ result[mappedKey] = value;
52
+ }
53
+ return result;
54
+ }
55
+
56
+ function formatWebhookError(error) {
57
+ const e = error, status = e.response?.status, serverMessage = e.response?.data?.message;
58
+ return serverMessage ? status ? `[${status}] ${serverMessage}` : serverMessage : status === 401 ? "[401] Authentication failed. Check your Access Key in credentials." : status === 403 ? "[403] Access denied. Check that your Access Key is valid and the Watchtower service is reachable." : status === 502 || status === 503 || status === 504 ? `[${status}] Watchtower service is temporarily unavailable. Try again in a moment.` : status ? `[${status}] ${e.message ?? "Request failed"}` : e.message ?? "Unknown error";
59
+ }
60
+
61
+ async function notifyWebhookAction(context, options) {
62
+ const {action: action, webhookUrl: webhookUrl, webhookId: webhookId, type: type, query: query, options: triggerOptions, isTest: isTest, metadata: metadata} = options, credentials = await context.getCredentials("connectWisePsaApi"), companyId = credentials.companyId, accessKey = credentials.accessKey;
63
+ if (!accessKey) throw new Error("An Access Key is required to use this trigger. Get one at https://link.mspcopilot.io/settings?tab=support, then edit this node's ConnectWise PSA credentials in n8n and paste it into the Access Key field.");
64
+ await context.helpers.httpRequest({
65
+ method: "POST",
66
+ url: WEBHOOK_REGISTRY_URL,
67
+ headers: {
68
+ "X-Company-Id": companyId,
69
+ Authorization: `Bearer ${accessKey}`
70
+ },
71
+ body: {
72
+ action: action,
73
+ webhookId: webhookId,
74
+ webhookUrl: webhookUrl,
75
+ type: type,
76
+ query: action === "delete" ? void 0 : query ? toWatchtowerQuery(query) : void 0,
77
+ options: action === "delete" ? void 0 : triggerOptions,
78
+ isTest: isTest,
79
+ metadata: {
80
+ workflowId: metadata?.workflowId,
81
+ workflowName: metadata?.workflowName
82
+ }
83
+ },
84
+ json: !0
85
+ });
86
+ }
87
+
88
+ function getFilterParameters(context) {
89
+ const getParam = (name, defaultValue) => {
90
+ try {
91
+ return context.getNodeParameter(name, defaultValue);
92
+ } catch {
93
+ return defaultValue;
94
+ }
95
+ }, resource = getParam("resource", "ticket"), query = {}, boardIds = getParam("boardIds", []);
96
+ boardIds.length > 0 && (query.boardIds = boardIds);
97
+ const statusIds = getParam("statusIds", []);
98
+ statusIds.length > 0 && (query.statusIds = statusIds);
99
+ const priorityIds = getParam("priorityIds", []);
100
+ priorityIds.length > 0 && (query.priorityIds = priorityIds);
101
+ const sourceIds = getParam("sourceIds", []);
102
+ sourceIds.length > 0 && (query.sourceIds = sourceIds);
103
+ const options = {}, timingConditions = getParam("timingConditions", {});
104
+ timingConditions.minTicketAge && (options.minTicketAge = timingConditions.minTicketAge),
105
+ timingConditions.minTimeInStatus && (options.minTimeInStatus = timingConditions.minTimeInStatus);
106
+ const triggerOn = getParam("triggerOn", [ "enter" ]);
107
+ if (options.onEnter = triggerOn.includes("enter"), options.onExit = triggerOn.includes("exit"),
108
+ options.onCreate = triggerOn.includes("create"), options.onUpdate = triggerOn.includes("update"),
109
+ options.onDelete = triggerOn.includes("delete"), triggerOn.includes("repeat")) {
110
+ const repeatEvery = getParam("repeatEvery", "");
111
+ if (!repeatEvery) throw new import_n8n_workflow.NodeOperationError(context.getNode(), "Repeat Every is required when Repeat lifecycle event is selected");
112
+ options.repeatEvery = repeatEvery;
113
+ const maxRepeats = getParam("maxRepeats", "");
114
+ options.maxRepeats = maxRepeats !== "" && Number(maxRepeats) > 0 ? Number(maxRepeats) : 0;
115
+ }
116
+ const skipExistingMatches = getParam("skipExistingMatches", !0);
117
+ options.skipExistingMatches = skipExistingMatches;
118
+ const rearm = getParam("rearm", "never");
119
+ if (options.rearm = rearm, rearm === "cooldown") {
120
+ const cooldown = getParam("cooldown", "");
121
+ if (!cooldown) throw new import_n8n_workflow.NodeOperationError(context.getNode(), "Cooldown Duration is required when rearm is set to Notify After Cooldown");
122
+ options.cooldown = cooldown;
123
+ }
124
+ return {
125
+ resource: resource,
126
+ query: query,
127
+ options: options
128
+ };
129
+ }
130
+
131
+ class ConnectWisePsaTrigger {
132
+ description={
133
+ hidden: process.env.MSPCOPILOT_BETA !== "true",
134
+ displayName: "ConnectWise PSA Trigger",
135
+ name: "connectWisePsaTrigger",
136
+ icon: {
137
+ light: "file:connectwise-light.svg",
138
+ dark: "file:connectwise-dark.svg"
139
+ },
140
+ group: [ "trigger" ],
141
+ version: 1,
142
+ subtitle: '={{{"ticket": "Ticket"}[$parameter["resource"]]}}',
143
+ description: "Trigger on ConnectWise PSA events",
144
+ defaults: {
145
+ name: "ConnectWise PSA Trigger"
146
+ },
147
+ inputs: [],
148
+ outputs: [ "main" ],
149
+ credentials: [ {
150
+ name: "connectWisePsaApi",
151
+ required: !0
152
+ } ],
153
+ webhooks: [ {
154
+ name: "default",
155
+ httpMethod: "POST",
156
+ responseMode: "onReceived",
157
+ path: "webhook"
158
+ } ],
159
+ properties: [ {
160
+ displayName: `Powered by Watchtower — see the <a href="https://${import_common.DOCURL_PREFIX}/ticket-trigger${import_common.DOCURL_SUFFIX}" target="_blank">Ticket Trigger Guide</a> for setup and details.`,
161
+ name: "triggerNotice",
162
+ type: "notice",
163
+ default: ""
164
+ }, {
165
+ displayName: "Trigger On",
166
+ name: "resource",
167
+ type: "options",
168
+ noDataExpression: !0,
169
+ options: [ {
170
+ name: "Ticket",
171
+ value: "ticket",
172
+ description: "Trigger on service ticket events"
173
+ } ],
174
+ default: "ticket"
175
+ }, {
176
+ displayName: "Lifecycle Events",
177
+ name: "triggerOn",
178
+ type: "multiOptions",
179
+ displayOptions: {
180
+ show: {
181
+ resource: [ "ticket" ]
182
+ }
183
+ },
184
+ options: [ {
185
+ name: "Enter",
186
+ value: "enter",
187
+ description: "When a ticket first matches conditions"
188
+ }, {
189
+ name: "Repeat",
190
+ value: "repeat",
191
+ description: "Periodically while a ticket continues matching conditions"
192
+ }, {
193
+ name: "Exit",
194
+ value: "exit",
195
+ description: "When a ticket stops matching conditions"
196
+ }, {
197
+ name: "Create",
198
+ value: "create",
199
+ description: "When a ticket is first created and matches conditions"
200
+ }, {
201
+ name: "Update",
202
+ value: "update",
203
+ description: "Immediately when a matching ticket is updated in ConnectWise"
204
+ }, {
205
+ name: "Delete",
206
+ value: "delete",
207
+ description: "Immediately when a matching ticket is deleted from ConnectWise"
208
+ } ],
209
+ default: [ "enter" ],
210
+ description: "Which lifecycle events should fire the trigger"
211
+ }, {
212
+ displayName: "Ticket Type",
213
+ name: "ticketType",
214
+ type: "options",
215
+ options: [ {
216
+ name: "Service Ticket",
217
+ value: "service"
218
+ } ],
219
+ default: "service",
220
+ description: "Type of ticket to monitor"
221
+ }, {
222
+ displayName: "Board Names or IDs",
223
+ name: "boardIds",
224
+ type: "multiOptions",
225
+ typeOptions: {
226
+ loadOptionsMethod: "loadOptions_service_boards",
227
+ loadOptionsDependsOn: [ "ticketType" ]
228
+ },
229
+ displayOptions: {
230
+ show: {
231
+ resource: [ "ticket" ]
232
+ }
233
+ },
234
+ default: [],
235
+ description: 'Filter by boards. Leave empty for all boards. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>.'
236
+ }, {
237
+ displayName: "Status Names or IDs",
238
+ name: "statusIds",
239
+ type: "multiOptions",
240
+ typeOptions: {
241
+ loadOptionsMethod: "loadOptions_service_statuses_all",
242
+ loadOptionsDependsOn: [ "boardIds" ]
243
+ },
244
+ displayOptions: {
245
+ show: {
246
+ resource: [ "ticket" ]
247
+ }
248
+ },
249
+ default: [],
250
+ description: 'Filter by statuses (select boards first to filter). Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>.'
251
+ }, {
252
+ displayName: "Priority Names or IDs",
253
+ name: "priorityIds",
254
+ type: "multiOptions",
255
+ typeOptions: {
256
+ loadOptionsMethod: "loadOptions_service_priorities"
257
+ },
258
+ displayOptions: {
259
+ show: {
260
+ resource: [ "ticket" ]
261
+ }
262
+ },
263
+ default: [],
264
+ description: 'Filter by priorities. Leave empty for all priorities. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>.'
265
+ }, {
266
+ displayName: "Source Names or IDs",
267
+ name: "sourceIds",
268
+ type: "multiOptions",
269
+ typeOptions: {
270
+ loadOptionsMethod: "loadOptions_service_sources"
271
+ },
272
+ displayOptions: {
273
+ show: {
274
+ resource: [ "ticket" ]
275
+ }
276
+ },
277
+ default: [],
278
+ description: 'Filter by ticket sources. Leave empty for all sources. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>.'
279
+ }, {
280
+ displayName: "Timing Conditions",
281
+ name: "timingConditions",
282
+ type: "collection",
283
+ placeholder: "Add Condition",
284
+ displayOptions: {
285
+ show: {
286
+ resource: [ "ticket" ]
287
+ }
288
+ },
289
+ default: {},
290
+ options: [ {
291
+ displayName: "Minimum Ticket Age",
292
+ name: "minTicketAge",
293
+ type: "string",
294
+ default: "",
295
+ placeholder: "15m, 2h, 3d",
296
+ description: "Only trigger for tickets older than this duration"
297
+ }, {
298
+ displayName: "Minimum Time in Current Status",
299
+ name: "minTimeInStatus",
300
+ type: "string",
301
+ default: "",
302
+ placeholder: "30m, 1h, 1d",
303
+ description: "Only trigger for tickets in current status longer than this"
304
+ } ]
305
+ }, {
306
+ displayName: "Repeat Every",
307
+ name: "repeatEvery",
308
+ type: "string",
309
+ displayOptions: {
310
+ show: {
311
+ resource: [ "ticket" ],
312
+ triggerOn: [ "repeat" ]
313
+ }
314
+ },
315
+ default: "15m",
316
+ placeholder: "15m, 1h, 2d",
317
+ description: "How often to send repeat notifications"
318
+ }, {
319
+ displayName: "Maximum Repeats",
320
+ name: "maxRepeats",
321
+ type: "number",
322
+ displayOptions: {
323
+ show: {
324
+ resource: [ "ticket" ],
325
+ triggerOn: [ "repeat" ]
326
+ }
327
+ },
328
+ typeOptions: {
329
+ minValue: 1
330
+ },
331
+ default: "",
332
+ placeholder: "Unlimited",
333
+ description: "Maximum repeat notifications per ticket. Leave blank for unlimited."
334
+ }, {
335
+ displayName: "If Ticket Re-Enters",
336
+ name: "rearm",
337
+ type: "options",
338
+ displayOptions: {
339
+ show: {
340
+ resource: [ "ticket" ]
341
+ }
342
+ },
343
+ options: [ {
344
+ name: "Never Notify Again",
345
+ value: "never",
346
+ description: "Once triggered, will not trigger again even if it re-enters"
347
+ }, {
348
+ name: "Notify Immediately",
349
+ value: "immediate",
350
+ description: "Trigger again immediately when ticket re-enters"
351
+ }, {
352
+ name: "Notify After Cooldown",
353
+ value: "cooldown",
354
+ description: "Wait for cooldown period before allowing re-trigger"
355
+ } ],
356
+ default: "never",
357
+ description: "Only applies to Enter events. What happens if a ticket exits and later re-enters conditions.",
358
+ hint: "This setting controls re-entry behavior for Enter events only"
359
+ }, {
360
+ displayName: "Cooldown Duration",
361
+ name: "cooldown",
362
+ type: "string",
363
+ displayOptions: {
364
+ show: {
365
+ resource: [ "ticket" ],
366
+ rearm: [ "cooldown" ]
367
+ }
368
+ },
369
+ default: "15m",
370
+ placeholder: "15m, 1h, 2d",
371
+ description: "How long to wait before ticket can re-trigger"
372
+ }, {
373
+ displayName: "Skip Existing Matches",
374
+ name: "skipExistingMatches",
375
+ type: "boolean",
376
+ displayOptions: {
377
+ show: {
378
+ resource: [ "ticket" ]
379
+ }
380
+ },
381
+ default: !0,
382
+ description: "On creation or update, don't trigger for existing matches"
383
+ } ]
384
+ };
385
+ methods={
386
+ loadOptions: {
387
+ ...import_methods.loadOptions,
388
+ async loadOptions_service_statuses_all() {
389
+ let selectedBoardIds = [];
390
+ try {
391
+ const boardIds = this.getNodeParameter("boardIds", []);
392
+ Array.isArray(boardIds) && boardIds.length > 0 && (selectedBoardIds = boardIds);
393
+ } catch {}
394
+ let boards;
395
+ if (selectedBoardIds.length > 0) {
396
+ const boardConditions = selectedBoardIds.map(id => `id=${id}`).join(" or ");
397
+ boards = await import_client.connectWiseApiRequest.call(this, "GET", "/service/boards", {}, {
398
+ conditions: `(${boardConditions})`,
399
+ fields: "id,name",
400
+ orderBy: "name",
401
+ pageSize: 100
402
+ }, {
403
+ optionsCache: !0
404
+ });
405
+ } else boards = await import_client.connectWiseApiRequest.call(this, "GET", "/service/boards", {}, {
406
+ conditions: "inactiveFlag=false and projectFlag=false",
407
+ fields: "id,name",
408
+ orderBy: "name",
409
+ pageSize: 100
410
+ }, {
411
+ optionsCache: !0
412
+ });
413
+ const statusPromises = boards.map(async board => {
414
+ try {
415
+ return (await import_client.connectWiseApiRequest.call(this, "GET", `/service/boards/${board.id}/statuses`, {}, {
416
+ fields: "id,name",
417
+ orderBy: "sortOrder",
418
+ pageSize: 100
419
+ }, {
420
+ optionsCache: !0
421
+ })).map(status => ({
422
+ statusName: status.name,
423
+ boardName: board.name,
424
+ value: status.id
425
+ }));
426
+ } catch {
427
+ return [];
428
+ }
429
+ }), allStatuses = (await Promise.all(statusPromises)).flat();
430
+ return allStatuses.sort((a, b) => {
431
+ const nameCompare = a.statusName.localeCompare(b.statusName);
432
+ return nameCompare !== 0 ? nameCompare : a.boardName.localeCompare(b.boardName);
433
+ }), allStatuses.map(s => ({
434
+ name: s.statusName,
435
+ value: s.value,
436
+ description: s.boardName
437
+ }));
438
+ }
439
+ }
440
+ };
441
+ webhookMethods={
442
+ default: {
443
+ async checkExists() {
444
+ return !1;
445
+ },
446
+ async create() {
447
+ const webhookUrl = this.getNodeWebhookUrl("default") ?? "", {resource: resource, query: query, options: options} = getFilterParameters(this), node = this.getNode(), workflow = this.getWorkflow(), isTest = webhookUrl.includes("/webhook-test/");
448
+ try {
449
+ await notifyWebhookAction(this, {
450
+ action: "create",
451
+ webhookUrl: webhookUrl,
452
+ webhookId: node.webhookId,
453
+ type: resource,
454
+ query: query,
455
+ options: options,
456
+ isTest: isTest,
457
+ metadata: {
458
+ workflowId: workflow.id,
459
+ workflowName: workflow.name
460
+ }
461
+ });
462
+ } catch (error) {
463
+ throw console.error("Webhook registration failed:", formatWebhookError(error)),
464
+ new import_n8n_workflow.NodeOperationError(node, formatWebhookError(error));
465
+ }
466
+ return !0;
467
+ },
468
+ async delete() {
469
+ const webhookUrl = this.getNodeWebhookUrl("default") ?? "", isTest = webhookUrl.includes("/webhook-test/");
470
+ if (isTest) return !0;
471
+ const {resource: resource} = getFilterParameters(this), node = this.getNode(), workflow = this.getWorkflow();
472
+ try {
473
+ await notifyWebhookAction(this, {
474
+ action: "delete",
475
+ webhookUrl: webhookUrl,
476
+ webhookId: node.webhookId,
477
+ type: resource,
478
+ isTest: isTest,
479
+ metadata: {
480
+ workflowId: workflow.id,
481
+ workflowName: workflow.name
482
+ }
483
+ });
484
+ } catch (error) {
485
+ console.error("Webhook deregistration failed:", formatWebhookError(error));
486
+ }
487
+ return !0;
488
+ }
489
+ }
490
+ };
491
+ async webhook() {
492
+ const req = this.getRequestObject(), workflow = this.getWorkflow(), executionId = this.getExecutionId(), baseUrl = this.getInstanceBaseUrl();
493
+ return {
494
+ workflowData: [ this.helpers.returnJsonArray(req.body) ],
495
+ webhookResponse: {
496
+ executionId: executionId,
497
+ workflowId: workflow.id,
498
+ executionUrl: executionId ? `${baseUrl}workflow/${workflow.id}/executions/${executionId}` : `${baseUrl}workflow/${workflow.id}/executions`
499
+ }
500
+ };
501
+ }
502
+ }
503
+
504
+ 0 && (module.exports = {
505
+ ConnectWisePsaTrigger: ConnectWisePsaTrigger
506
+ });
@@ -95,7 +95,7 @@ const HINT_TYPES = {
95
95
  accesskey_missing: {
96
96
  aggregate: "dedupe",
97
97
  type: "warning",
98
- template: () => "🔑 An access key is needed to use this feature. Add one in your ConnectWise PSA credentials."
98
+ template: () => `🔑 An access key is needed to use this feature. Get one at <a href="https://link.mspcopilot.io/settings?tab=support" target="_blank">link.mspcopilot.io</a>, then edit this node's ConnectWise PSA credentials and paste it into the Access Key field.`
99
99
  },
100
100
  accesskey_invalid: {
101
101
  aggregate: "dedupe",
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(version_check_exports);
39
39
 
40
40
  var fs = __toESM(require("fs/promises")), path = __toESM(require("path")), os = __toESM(require("os")), import_common = require("../descriptions/common.descriptions"), import_hint_collector = require("../helpers/hint-collector");
41
41
 
42
- const CACHE_FILE_PATH = path.join(os.tmpdir(), "connectwise-n8n-version-cache.json"), CURRENT_VERSION = "0.2.4";
42
+ const CACHE_FILE_PATH = path.join(os.tmpdir(), "connectwise-n8n-version-cache.json"), CURRENT_VERSION = "0.3.1";
43
43
 
44
44
  let inMemoryCache = null, cacheLoadPromise = null, fetchInProgress = !1;
45
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mspcopilot/n8n-nodes-connectwise",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "n8n node to integrate with ConnectWise PSA",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"
@@ -32,7 +32,8 @@
32
32
  "dist-community/credentials/ConnectWisePsaApi.credentials.js"
33
33
  ],
34
34
  "nodes": [
35
- "dist-community/nodes/ConnectWise/PSA/ConnectWisePsa.node.js"
35
+ "dist-community/nodes/ConnectWise/PSA/ConnectWisePsa.node.js",
36
+ "dist-community/nodes/ConnectWise/PSA/ConnectWisePsaTrigger.node.js"
36
37
  ]
37
38
  },
38
39
  "dependencies": {