n8n-nodes-stacksona 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stacksona
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # n8n-nodes-stacksona
2
+
3
+ n8n community node for Stacksona Gate, the approval and audit layer for AI agents and automated workflows.
4
+
5
+ `n8n-nodes-stacksona` lets n8n workflows log activity, request human approval before gated actions, check decision status, validate signed approval tokens, and wait for reviewer approval before continuing automation.
6
+
7
+ Use it to add human-in-the-loop approval, audit trails, and runtime AI governance to n8n workflows without building a custom integration.
8
+
9
+ ## What it does
10
+
11
+ This n8n community node connects n8n to Stacksona Gate.
12
+
13
+ With this package, your n8n workflows can:
14
+
15
+ * Log workflow and agent events for audit history
16
+ * Request approval before sensitive or risky actions
17
+ * Wait for a human approve or reject decision
18
+ * Check decision status by task or thread
19
+ * Validate signed one-time approval tokens
20
+ * Add governance to AI agents and automated workflows
21
+ * Keep approval records in Stacksona Gate
22
+
23
+ ## Install in n8n
24
+
25
+ Install this community package in n8n:
26
+
27
+ ```text
28
+ n8n-nodes-stacksona
29
+ ```
30
+
31
+ In n8n, go to:
32
+
33
+ ```text
34
+ Settings → Community Nodes → Install
35
+ ```
36
+
37
+ Then enter:
38
+
39
+ ```text
40
+ n8n-nodes-stacksona
41
+ ```
42
+
43
+ After installation, search for **Stacksona Gate** in the n8n node picker.
44
+
45
+ ## Requirements
46
+
47
+ * n8n with community nodes enabled
48
+ * A Stacksona Gate workspace
49
+ * A Stacksona Gate URL
50
+ * A Stacksona Agent API key starting with `sg_`
51
+
52
+ ## Credentials
53
+
54
+ Create a **Stacksona Gate API** credential in n8n.
55
+
56
+ Required fields:
57
+
58
+ | Field | Required | Description |
59
+ | ------------- | -------: | ------------------------------------------- |
60
+ | Gate URL | Yes | Your Stacksona Gate tenant URL |
61
+ | Agent API Key | Yes | Stacksona Agent API key starting with `sg_` |
62
+
63
+ Example Gate URL:
64
+
65
+ ```text
66
+ https://subdomain.stacksona.cloud
67
+ ```
68
+
69
+ This is only an example. Your Stacksona Gate tenant subdomain may be different.
70
+
71
+ ## Available operations
72
+
73
+ ### Log Event
74
+
75
+ Logs task, workflow, or agent activity to Stacksona Gate.
76
+
77
+ Use this for audit trails, reviewer context, observability, and workflow history.
78
+
79
+ ### Request Decision
80
+
81
+ Requests an approval decision before a gated action runs.
82
+
83
+ Use this before sensitive actions such as sending emails, issuing refunds, updating records, calling external APIs, or triggering operational workflows.
84
+
85
+ ### Get Decision
86
+
87
+ Fetches the current decision status.
88
+
89
+ Use this to check whether a request is still pending, approved, rejected, or otherwise resolved.
90
+
91
+ ### Validate Approval Token
92
+
93
+ Validates a signed one-time approval token.
94
+
95
+ Use this when signed approval tokens are enabled and your workflow needs proof that a gated action was approved before execution.
96
+
97
+ ### Request Decision and Wait
98
+
99
+ Requests a decision and waits for reviewer approval or rejection.
100
+
101
+ Use this when the n8n workflow should pause until Stacksona Gate returns a final decision.
102
+
103
+ ## Typical workflow
104
+
105
+ A common Stacksona Gate workflow in n8n looks like this:
106
+
107
+ 1. Use **Log Event** to record task or agent activity.
108
+ 2. Use **Request Decision** before the gated action.
109
+ 3. If the decision status is `allow`, continue the workflow.
110
+ 4. If the decision status is `pending_review`, use **Get Decision** in a loop until the status becomes `approved` or `rejected`.
111
+ 5. If signed tokens are enabled, use **Validate Approval Token** before executing the approved action.
112
+ 6. Continue only when the action is allowed or approved.
113
+
114
+ ## Waiting for approval
115
+
116
+ Use **Request Decision and Wait** when the n8n workflow should wait for a human reviewer.
117
+
118
+ This operation polls Stacksona Gate for the final decision. It starts with Gate's `recommended_poll_after_seconds` when returned, then uses the configured fallback interval.
119
+
120
+ Webhook delivery is configured per agent in Stacksona Gate Admin, not inside the n8n node.
121
+
122
+ To handle approval webhooks in n8n, use an n8n **Webhook** trigger, then call **Get Decision** or continue your downstream workflow logic.
123
+
124
+ ## Common use cases
125
+
126
+ Use `n8n-nodes-stacksona` when your workflow needs approval before actions like:
127
+
128
+ * Sending emails
129
+ * Issuing refunds
130
+ * Updating CRM records
131
+ * Calling external APIs
132
+ * Posting to Slack or Teams
133
+ * Creating support tickets
134
+ * Deploying code
135
+ * Updating databases
136
+ * Changing customer account settings
137
+ * Triggering financial or operational workflows
138
+
139
+ ## Why use Stacksona Gate with n8n?
140
+
141
+ n8n is powerful for automation, but some workflow steps should not run without human review.
142
+
143
+ Stacksona Gate adds a governance layer between automation intent and execution. Your workflow can pause, ask for approval, log the decision, and continue only when the action is allowed or approved.
144
+
145
+ This is useful for AI agents, internal automations, compliance workflows, support operations, and any process where automation needs oversight.
146
+
147
+ ## n8n package metadata
148
+
149
+ This package declares the n8n entry in `package.json` so n8n can find the compiled node and credential files:
150
+
151
+ ```json
152
+ {
153
+ "n8n": {
154
+ "n8nNodesApiVersion": 1,
155
+ "credentials": [
156
+ "dist/credentials/StacksonaGateApi.credentials.js"
157
+ ],
158
+ "nodes": [
159
+ "dist/nodes/StacksonaGate/StacksonaGate.node.js"
160
+ ]
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## Keywords
166
+
167
+ n8n community node, n8n AI approval, Stacksona Gate, AI agent approval, human-in-the-loop automation, workflow approval, agent audit logs, AI observability, runtime AI governance, approval workflow, gated automation, n8n governance, approval token validation.
168
+
169
+ ## Links
170
+
171
+ * Website: https://stacksona.com
172
+ * Community package: `n8n-nodes-stacksona`
@@ -0,0 +1,7 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class StacksonaGateApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -0,0 +1,25 @@
1
+ export class StacksonaGateApi {
2
+ name = 'stacksonaGateApi';
3
+ displayName = 'Stacksona Gate API';
4
+ documentationUrl = 'https://stacksona.com';
5
+ properties = [
6
+ {
7
+ displayName: 'Gate URL',
8
+ name: 'baseUrl',
9
+ type: 'string',
10
+ default: '',
11
+ placeholder: 'https://gate-1.stacksona.cloud',
12
+ required: true,
13
+ description: 'Required base URL for your Stacksona Gate instance. Example only: https://gate-1.stacksona.cloud',
14
+ },
15
+ {
16
+ displayName: 'Agent API Key',
17
+ name: 'apiKey',
18
+ type: 'string',
19
+ typeOptions: { password: true },
20
+ default: '',
21
+ required: true,
22
+ description: 'Agent API key generated in Stacksona Gate Admin. It starts with sg_.',
23
+ },
24
+ ];
25
+ }
@@ -0,0 +1 @@
1
+ export { StacksonaGate } from './nodes/StacksonaGate/StacksonaGate.node.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { StacksonaGate } from './nodes/StacksonaGate/StacksonaGate.node.js';
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class StacksonaGate implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,293 @@
1
+ import { NodeOperationError } from 'n8n-workflow';
2
+ export class StacksonaGate {
3
+ description = {
4
+ displayName: 'Stacksona Gate',
5
+ name: 'stacksonaGate',
6
+ icon: 'file:stacksona.svg',
7
+ group: ['transform'],
8
+ version: 1,
9
+ subtitle: '={{$parameter["operation"]}}',
10
+ description: 'Log events, request decisions, and poll approvals in Stacksona Gate',
11
+ defaults: { name: 'Stacksona Gate' },
12
+ inputs: ['main'],
13
+ outputs: ['main'],
14
+ credentials: [{ name: 'stacksonaGateApi', required: true }],
15
+ properties: [
16
+ {
17
+ displayName: 'Operation',
18
+ name: 'operation',
19
+ type: 'options',
20
+ noDataExpression: true,
21
+ default: 'requestDecision',
22
+ options: [
23
+ { name: 'Log Event', value: 'logEvent', description: 'Log a task event', action: 'Log a task event' },
24
+ { name: 'Request Decision', value: 'requestDecision', description: 'Ask Gate for a decision', action: 'Request a decision' },
25
+ { name: 'Request Decision and Wait', value: 'requestDecisionAndWait', description: 'Ask Gate for a decision and poll until approved or rejected when review is required', action: 'Request a decision and wait' },
26
+ { name: 'Get Decision', value: 'getDecision', description: 'Get a decision by thread or task', action: 'Get a decision' },
27
+ { name: 'Validate Approval Token', value: 'validateApprovalToken', description: 'Validate a signed approval token', action: 'Validate an approval token' },
28
+ ],
29
+ },
30
+ {
31
+ displayName: 'Task ID',
32
+ name: 'taskId',
33
+ type: 'string',
34
+ default: '',
35
+ required: true,
36
+ displayOptions: { show: { operation: ['logEvent', 'requestDecision', 'requestDecisionAndWait'] } },
37
+ },
38
+ {
39
+ displayName: 'Event Type',
40
+ name: 'eventType',
41
+ type: 'string',
42
+ default: 'task.started',
43
+ required: true,
44
+ displayOptions: { show: { operation: ['logEvent'] } },
45
+ },
46
+ {
47
+ displayName: 'Event Summary',
48
+ name: 'eventSummary',
49
+ type: 'string',
50
+ default: '',
51
+ required: true,
52
+ displayOptions: { show: { operation: ['logEvent'] } },
53
+ },
54
+ {
55
+ displayName: 'Workflow Name',
56
+ name: 'workflowName',
57
+ type: 'string',
58
+ default: '',
59
+ required: true,
60
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
61
+ },
62
+ {
63
+ displayName: 'Task Label',
64
+ name: 'taskLabel',
65
+ type: 'string',
66
+ default: '',
67
+ required: true,
68
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
69
+ },
70
+ {
71
+ displayName: 'Tool Name',
72
+ name: 'toolName',
73
+ type: 'string',
74
+ default: '',
75
+ required: true,
76
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
77
+ },
78
+ {
79
+ displayName: 'Subject',
80
+ name: 'subject',
81
+ type: 'string',
82
+ default: '',
83
+ required: true,
84
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
85
+ },
86
+ {
87
+ displayName: 'Preview',
88
+ name: 'preview',
89
+ type: 'string',
90
+ default: '',
91
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
92
+ },
93
+ {
94
+ displayName: 'Risk Level',
95
+ name: 'riskLevel',
96
+ type: 'options',
97
+ default: 'medium',
98
+ options: [
99
+ { name: 'Low', value: 'low' },
100
+ { name: 'Medium', value: 'medium' },
101
+ { name: 'High', value: 'high' },
102
+ { name: 'Critical', value: 'critical' },
103
+ ],
104
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
105
+ },
106
+ {
107
+ displayName: 'Summary Items',
108
+ name: 'summaryItems',
109
+ type: 'fixedCollection',
110
+ default: {},
111
+ typeOptions: { multipleValues: true },
112
+ displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
113
+ options: [{ name: 'item', displayName: 'Item', values: [{ displayName: 'Text', name: 'text', type: 'string', default: '' }] }],
114
+ },
115
+ {
116
+ displayName: 'Thread ID',
117
+ name: 'threadId',
118
+ type: 'string',
119
+ default: '',
120
+ displayOptions: { show: { operation: ['getDecision'] } },
121
+ },
122
+ {
123
+ displayName: 'Task ID',
124
+ name: 'decisionTaskId',
125
+ type: 'string',
126
+ default: '',
127
+ displayOptions: { show: { operation: ['getDecision', 'validateApprovalToken'] } },
128
+ },
129
+ {
130
+ displayName: 'Approval Token / Signature',
131
+ name: 'signature',
132
+ type: 'string',
133
+ typeOptions: { password: true },
134
+ default: '',
135
+ required: true,
136
+ displayOptions: { show: { operation: ['validateApprovalToken'] } },
137
+ },
138
+ {
139
+ displayName: 'Fallback Poll Interval Seconds',
140
+ name: 'pollIntervalSeconds',
141
+ type: 'number',
142
+ default: 15,
143
+ description: 'Used when Gate does not return recommended_poll_after_seconds',
144
+ displayOptions: { show: { operation: ['requestDecisionAndWait'] } },
145
+ },
146
+ {
147
+ displayName: 'Max Wait Seconds',
148
+ name: 'maxWaitSeconds',
149
+ type: 'number',
150
+ default: 600,
151
+ description: 'Maximum time to wait for an approved or rejected decision',
152
+ displayOptions: { show: { operation: ['requestDecisionAndWait'] } },
153
+ },
154
+ {
155
+ displayName: 'Payload JSON',
156
+ name: 'payloadJson',
157
+ type: 'json',
158
+ default: '{}',
159
+ description: 'Arbitrary JSON payload sent to Stacksona Gate',
160
+ displayOptions: { show: { operation: ['logEvent', 'requestDecision', 'requestDecisionAndWait'] } },
161
+ },
162
+ ],
163
+ };
164
+ async execute() {
165
+ const items = this.getInputData();
166
+ const returnData = [];
167
+ const credentials = await this.getCredentials('stacksonaGateApi');
168
+ const baseUrl = String(credentials.baseUrl).replace(/\/+$/, '');
169
+ const apiKey = String(credentials.apiKey);
170
+ for (let i = 0; i < items.length; i++) {
171
+ try {
172
+ const operation = this.getNodeParameter('operation', i);
173
+ let response;
174
+ if (operation === 'logEvent') {
175
+ const taskId = this.getNodeParameter('taskId', i);
176
+ const payload = parseJsonParameter(this.getNodeParameter('payloadJson', i, '{}'), this, i);
177
+ await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', `/api/agent/tasks/${encodeURIComponent(taskId)}/events`, {
178
+ event_type: this.getNodeParameter('eventType', i),
179
+ event_summary: this.getNodeParameter('eventSummary', i),
180
+ payload,
181
+ });
182
+ response = { ok: true };
183
+ }
184
+ if (operation === 'requestDecision' || operation === 'requestDecisionAndWait') {
185
+ const taskId = this.getNodeParameter('taskId', i);
186
+ const payload = parseJsonParameter(this.getNodeParameter('payloadJson', i, '{}'), this, i);
187
+ const summaryCollection = this.getNodeParameter('summaryItems', i, {});
188
+ response = await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', `/api/agent/tasks/${encodeURIComponent(taskId)}/requests`, {
189
+ workflow_name: this.getNodeParameter('workflowName', i),
190
+ task_label: this.getNodeParameter('taskLabel', i),
191
+ tool_name: this.getNodeParameter('toolName', i),
192
+ subject: this.getNodeParameter('subject', i),
193
+ preview: this.getNodeParameter('preview', i, ''),
194
+ risk_level: this.getNodeParameter('riskLevel', i, 'medium'),
195
+ summary: (summaryCollection.item ?? []).map((entry) => entry.text).filter(Boolean),
196
+ payload,
197
+ });
198
+ if (operation === 'requestDecisionAndWait') {
199
+ const initial = response;
200
+ if (initial.status === 'pending_review') {
201
+ response = await pollDecision.call(this, baseUrl, apiKey, {
202
+ threadId: typeof initial.thread_id === 'string' ? initial.thread_id : undefined,
203
+ taskId: typeof initial.task_id === 'string' ? initial.task_id : taskId,
204
+ }, {
205
+ initialRecommendedSeconds: typeof initial.recommended_poll_after_seconds === 'number' ? initial.recommended_poll_after_seconds : undefined,
206
+ fallbackIntervalSeconds: this.getNodeParameter('pollIntervalSeconds', i, 15),
207
+ maxWaitSeconds: this.getNodeParameter('maxWaitSeconds', i, 600),
208
+ });
209
+ }
210
+ }
211
+ }
212
+ if (operation === 'getDecision') {
213
+ const threadId = this.getNodeParameter('threadId', i, '');
214
+ const taskId = this.getNodeParameter('decisionTaskId', i, '');
215
+ const params = new URLSearchParams();
216
+ if (threadId)
217
+ params.set('thread_id', threadId);
218
+ if (taskId)
219
+ params.set('task_id', taskId);
220
+ if (!params.toString())
221
+ throw new NodeOperationError(this.getNode(), 'Thread ID or Task ID is required', { itemIndex: i });
222
+ response = await stacksonaRequest.call(this, baseUrl, apiKey, 'GET', `/api/agent/decisions?${params.toString()}`);
223
+ }
224
+ if (operation === 'validateApprovalToken') {
225
+ response = await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', '/api/agent/approvals/validate', {
226
+ task_id: this.getNodeParameter('decisionTaskId', i),
227
+ signature: this.getNodeParameter('signature', i),
228
+ });
229
+ }
230
+ returnData.push({ json: toN8nJson(response), pairedItem: { item: i } });
231
+ }
232
+ catch (error) {
233
+ if (this.continueOnFail()) {
234
+ returnData.push({ json: { error: error instanceof Error ? error.message : String(error) }, pairedItem: { item: i } });
235
+ continue;
236
+ }
237
+ throw error;
238
+ }
239
+ }
240
+ return [returnData];
241
+ }
242
+ }
243
+ function parseJsonParameter(value, ctx, itemIndex) {
244
+ if (typeof value === 'object' && value !== null)
245
+ return value;
246
+ try {
247
+ return JSON.parse(String(value || '{}'));
248
+ }
249
+ catch {
250
+ throw new NodeOperationError(ctx.getNode(), 'Payload JSON must be valid JSON', { itemIndex });
251
+ }
252
+ }
253
+ function toN8nJson(value) {
254
+ if (value && typeof value === 'object' && !Array.isArray(value))
255
+ return value;
256
+ return { value: value };
257
+ }
258
+ async function sleep(ms) {
259
+ await new Promise((resolve) => setTimeout(resolve, ms));
260
+ }
261
+ async function pollDecision(baseUrl, apiKey, query, options) {
262
+ const started = Date.now();
263
+ let intervalSeconds = options.initialRecommendedSeconds ?? options.fallbackIntervalSeconds ?? 15;
264
+ while (true) {
265
+ const params = new URLSearchParams();
266
+ if (query.threadId)
267
+ params.set('thread_id', query.threadId);
268
+ if (query.taskId)
269
+ params.set('task_id', query.taskId);
270
+ const decision = await stacksonaRequest.call(this, baseUrl, apiKey, 'GET', `/api/agent/decisions?${params.toString()}`);
271
+ if (decision.status === 'approved' || decision.status === 'rejected')
272
+ return decision;
273
+ if (Date.now() - started > options.maxWaitSeconds * 1000) {
274
+ throw new NodeOperationError(this.getNode(), 'Timed out waiting for Stacksona Gate decision');
275
+ }
276
+ if (typeof decision.recommended_poll_after_seconds === 'number') {
277
+ intervalSeconds = decision.recommended_poll_after_seconds;
278
+ }
279
+ await sleep(intervalSeconds * 1000);
280
+ }
281
+ }
282
+ async function stacksonaRequest(baseUrl, apiKey, method, path, body) {
283
+ return this.helpers.httpRequest({
284
+ method,
285
+ url: `${baseUrl}${path}`,
286
+ headers: {
287
+ Authorization: `Bearer ${apiKey}`,
288
+ 'Content-Type': 'application/json',
289
+ },
290
+ body,
291
+ json: true,
292
+ });
293
+ }
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="216" height="216">
3
+ <path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,63 L142,69 L139,87 L137,92 L137,103 L135,114 L125,129 L119,140 L113,149 L106,162 L104,162 L102,167 L100,171 L-7,171 L-14,160 L-20,151 L-26,141 L-29,138 L-39,122 L-44,115 L-46,104 L-46,91 L-48,88 L-51,70 L-50,63 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z " fill="#5A56BD" transform="translate(63,23)"/>
4
+ <path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,63 L142,69 L139,87 L137,92 L137,103 L135,114 L125,129 L119,140 L113,149 L106,162 L104,162 L102,167 L100,171 L-7,171 L-14,160 L-20,151 L-26,141 L-29,138 L-39,122 L-44,115 L-46,104 L-46,91 L-48,88 L-51,70 L-50,63 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z M4,28 L-8,37 L-24,48 L-36,56 L-37,60 L-28,70 L-23,75 L-24,78 L-30,82 L-29,87 L-16,104 L-21,99 L-31,85 L-34,85 L-33,90 L-28,98 L-23,103 L-21,108 L-13,119 L-11,129 L-10,140 L-8,142 L100,142 L103,139 L105,119 L108,113 L113,108 L116,102 L123,94 L126,90 L126,85 L122,86 L112,100 L110,102 L117,92 L122,85 L122,82 L116,78 L118,74 L120,69 L128,59 L128,56 L112,46 L102,39 L91,32 L86,28 Z " fill="#8492CD" transform="translate(63,23)"/>
5
+ <path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,62 L136,63 L128,57 L112,47 L102,40 L91,33 L86,29 L4,29 L-8,38 L-24,49 L-36,57 L-44,63 L-46,63 L-42,69 L-32,82 L-33,84 L-40,76 L-47,67 L-48,62 L-51,62 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z " fill="#A292D4" transform="translate(63,23)"/>
6
+ <path d="M0,0 L82,0 L93,8 L104,15 L119,25 L124,29 L119,32 L110,42 L108,45 L103,44 L87,32 L80,27 L2,27 L-14,40 L-18,43 L-20,43 L-16,50 L-17,52 L-21,48 L-21,46 L-24,47 L-23,51 L-19,55 L-17,67 L-16,80 L-15,82 L100,83 L99,89 L-13,89 L-16,84 L-19,80 L-22,73 L-34,58 L-33,53 L-27,49 L-29,44 L-38,36 L-41,32 L-40,28 L-28,20 L-11,8 Z " fill="#7463C7" transform="translate(67,51)"/>
7
+ <path d="M0,0 L3,0 L7,5 L6,9 L127,9 L126,5 L129,1 L133,2 L132,7 L130,10 L128,10 L127,28 L125,35 L123,37 L9,37 L7,34 L5,12 L2,7 L-1,4 Z " fill="#4E44B6" transform="translate(43,97)"/>
8
+ <path d="M0,0 L4,2 L8,5 L13,6 L14,12 L11,30 L9,35 L8,39 L1,49 L-9,64 L-19,80 L-23,88 L-27,90 L-28,85 L-26,82 L-24,62 L-20,55 L-15,50 L-13,45 L-6,37 L-3,33 L-2,28 L-6,30 L-15,43 L-20,48 L-18,43 L-7,28 L-6,25 L-11,23 L-13,19 L-11,17 L-9,12 Z " fill="#7C86C0" transform="translate(191,80)"/>
9
+ <path d="M0,0 L2,0 L9,9 L16,15 L17,19 L10,24 L11,29 L24,46 L23,48 L10,32 L8,27 L5,27 L8,34 L11,39 L16,44 L19,50 L27,61 L29,71 L29,81 L24,76 L21,69 L9,52 L6,49 L2,45 L-3,37 L-5,41 L-5,46 L-7,46 L-7,33 L-9,30 L-12,12 L-11,4 L-9,4 L-7,9 L2,20 L5,23 L-5,10 L-7,5 Z " fill="#8080C3" transform="translate(24,81)"/>
10
+ <path d="M0,0 L2,1 L1,15 L-2,22 L-11,36 L-16,45 L-22,54 L-29,67 L-34,68 L-34,53 L-142,53 L-142,50 L-37,50 L-36,47 L-34,47 L-33,51 L-30,49 L-27,42 L-15,23 L-5,8 Z " fill="#9FA1BB" transform="translate(198,118)"/>
11
+ <path d="M0,0 L4,5 L13,18 L15,23 L19,27 L27,39 L31,44 L34,51 L38,55 L39,72 L33,68 L26,58 L19,46 L16,43 L6,27 L1,20 L0,16 Z " fill="#A2A6C6" transform="translate(18,118)"/>
12
+ <path d="M0,0 L4,2 L8,5 L13,6 L14,12 L9,13 L8,9 L-3,24 L-3,28 L-8,33 L-18,47 L-20,48 L-18,43 L-7,28 L-6,25 L-11,23 L-13,19 L-11,17 L-9,12 Z " fill="#7B77BA" transform="translate(191,80)"/>
13
+ <path d="M0,0 L2,0 L3,4 L3,7 L-106,6 L-106,3 L-1,3 Z " fill="#A9B4DF" transform="translate(162,165)"/>
14
+ <path d="M0,0 L112,0 L110,1 L110,3 L1,3 Z " fill="#5F64C3" transform="translate(54,134)"/>
15
+ <path d="M0,0 L2,4 L5,3 L5,11 L2,24 L1,26 L-4,26 L-12,19 L-11,15 Z " fill="#898AC9" transform="translate(199,88)"/>
16
+ <path d="M0,0 L2,0 L2,16 L-2,23 L-7,24 L-14,18 L-8,10 L-2,2 Z " fill="#A9A5C4" transform="translate(206,62)"/>
17
+ <path d="M0,0 L5,5 L13,16 L13,19 L8,23 L4,21 L1,19 L-1,8 L-1,1 Z " fill="#A5A4C6" transform="translate(14,92)"/>
18
+ <path d="M0,0 L3,0 L5,5 L4,12 L3,14 L-2,14 L-6,11 L-6,6 L-1,1 Z " fill="#A5A5C0" transform="translate(197,100)"/>
19
+ <path d="M0,0 L2,1 L2,16 L0,19 L0,10 L1,7 L-6,12 L-9,13 L-7,9 Z " fill="#887EAF" transform="translate(207,55)"/>
20
+ <path d="M0,0 L2,0 L6,6 L18,20 L18,22 L15,21 L6,10 L-1,2 Z " fill="#B2A3DE" transform="translate(12,56)"/>
21
+ <path d="M0,0 L5,5 L14,15 L16,20 L9,14 L0,3 L-2,1 Z " fill="#897ACE" transform="translate(30,77)"/>
22
+ <path d="M0,0 L2,0 L4,5 L7,8 L5,12 L1,8 L-1,8 Z " fill="#7272A7" transform="translate(13,85)"/>
23
+ <path d="M0,0 L4,2 L6,6 L3,8 L3,15 L1,15 L1,2 Z " fill="#747B97" transform="translate(16,112)"/>
24
+ <path d="M0,0 L2,0 L1,4 L-24,4 L-24,3 L-1,3 Z " fill="#9FABDD" transform="translate(162,165)"/>
25
+ <path d="M0,0 L3,1 L4,7 L-1,8 L-1,1 Z " fill="#70719F" transform="translate(201,85)"/>
26
+ <path d="M0,0 L2,0 L1,10 L-1,10 L-3,6 L-4,4 L-1,3 Z " fill="#89899B" transform="translate(202,95)"/>
27
+ <path d="M0,0 L2,0 L2,4 L4,4 L3,11 L1,9 Z " fill="#595665" transform="translate(8,70)"/>
28
+ <path d="M0,0 L3,1 L7,5 L8,9 L4,8 Z " fill="#6F707E" transform="translate(31,155)"/>
29
+ <path d="M0,0 L2,0 L2,4 L0,4 L-1,8 L-4,7 L-1,1 Z " fill="#A9A9C9" transform="translate(202,91)"/>
30
+ </svg>
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "n8n-nodes-stacksona",
3
+ "version": "0.1.0",
4
+ "description": "n8n community nodes for Stacksona Gate approvals and audit events.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc && gulp build:icons",
15
+ "clean": "rimraf dist",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "n8n-community-node-package",
20
+ "stacksona",
21
+ "approval",
22
+ "ai-governance",
23
+ "agent"
24
+ ],
25
+ "license": "MIT",
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "n8n": {
30
+ "n8nNodesApiVersion": 1,
31
+ "credentials": [
32
+ "dist/credentials/StacksonaGateApi.credentials.js"
33
+ ],
34
+ "nodes": [
35
+ "dist/nodes/StacksonaGate/StacksonaGate.node.js"
36
+ ]
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.11.30",
40
+ "gulp": "^4.0.2",
41
+ "n8n-workflow": "^1.82.0",
42
+ "rimraf": "^5.0.5",
43
+ "typescript": "^5.4.5"
44
+ },
45
+ "homepage": "https://Stacksonas.com",
46
+ "author": "Stacksona"
47
+ }