n8n-nodes-safeagent 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/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # n8n-nodes-safeagent
2
+
3
+ ## Requirements
4
+
5
+ - Python 3.10+
6
+ - `pip install safeagent-exec-guard`
7
+ - `python3` must be available on PATH in the environment where n8n is running
8
+
9
+ > **Note:** If you're running n8n via Docker, you'll need a custom image with Python installed, or use the Postgres backend via an external SafeAgent API endpoint.
10
+
11
+ ---
12
+
13
+ An [n8n](https://n8n.io) community node that wraps the
14
+ [`safeagent-exec-guard`](https://pypi.org/project/safeagent-exec-guard/) Python library to bring
15
+ the **claim-before-execute** idempotency pattern to your n8n workflows.
16
+
17
+ ---
18
+
19
+ ## The Claim-Before-Execute Pattern
20
+
21
+ AI agents and event-driven workflows face a hard problem: the same logical request can arrive more
22
+ than once (webhook retries, queue redeliveries, user double-clicks). Without a guard, every
23
+ duplicate triggers the side effect again — double charges, duplicate emails, duplicate DB rows.
24
+
25
+ **Claim-before-execute** solves this with a single atomic database write:
26
+
27
+ ```
28
+ 1. Before doing anything irreversible, claim the (requestId, actionName) pair.
29
+ 2. If the claim succeeds → you are the first runner. Proceed with the action.
30
+ 3. If the claim is denied → someone else already ran it. Return the cached receipt and stop.
31
+ ```
32
+
33
+ The claim is implemented as a SQL `INSERT … ON CONFLICT DO NOTHING` (or equivalent), making it
34
+ naturally atomic and race-condition-safe even under concurrent workers.
35
+
36
+ ```
37
+ Incoming event
38
+
39
+
40
+ ┌────────────────────┐
41
+ │ SafeAgent Guard │ ← this n8n node
42
+ │ insert_if_not_ │
43
+ │ exists(rid, act) │
44
+ └────────┬───────────┘
45
+
46
+ ┌─────┴──────┐
47
+ │ │
48
+ inserted=true inserted=false
49
+ │ │
50
+ ▼ ▼
51
+ Proceed Return cached
52
+ with receipt → skip
53
+ action downstream nodes
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Installation
59
+
60
+ ### Prerequisites
61
+
62
+ - n8n ≥ 0.190.0
63
+ - Python 3.9+ in `PATH`
64
+ - The `safeagent-exec-guard` Python package:
65
+
66
+ ```bash
67
+ pip install safeagent-exec-guard
68
+ # or, for Postgres support:
69
+ pip install "safeagent-exec-guard[postgres]"
70
+ ```
71
+
72
+ ### Add to n8n
73
+
74
+ ```bash
75
+ # In your n8n data directory
76
+ npm install n8n-nodes-safeagent
77
+ ```
78
+
79
+ Or use the n8n **Settings → Community Nodes → Install** UI and enter `n8n-nodes-safeagent`.
80
+
81
+ ---
82
+
83
+ ## Node Fields
84
+
85
+ | Field | Type | Description |
86
+ |---|---|---|
87
+ | **Request ID** | String | Unique identifier for the logical request (e.g. webhook event ID, message UUID). Supports n8n expressions like `{{ $json["id"] }}`. |
88
+ | **Action Name** | String | Name of the side-effectful action being guarded (e.g. `send_invoice`, `charge_card`). Together with Request ID it forms the idempotency key. |
89
+ | **Backend** | Dropdown | `SQLite (local file)` — zero-config, good for dev / single-instance. `PostgreSQL` — distributed-safe, recommended for production / multi-worker. |
90
+ | **SQLite Database Path** | String *(SQLite only)* | Path to the `.db` file. Defaults to `safeagent.db` in the n8n working directory. |
91
+ | **Postgres Credentials** | Credential *(Postgres only)* | Host, port, database, user, password, SSL toggle. |
92
+
93
+ ---
94
+
95
+ ## Output Fields
96
+
97
+ The node adds the following fields to the item's JSON:
98
+
99
+ | Field | Type | Meaning |
100
+ |---|---|---|
101
+ | `requestId` | string | Echo of the input Request ID |
102
+ | `actionName` | string | Echo of the input Action Name |
103
+ | `backend` | string | `"sqlite"` or `"postgres"` |
104
+ | `isDuplicate` | boolean | `true` if this (requestId, actionName) was already claimed |
105
+ | `shouldProceed` | boolean | `true` if this is a new claim and the action should run |
106
+ | `inserted` | boolean | Raw value from the guard library |
107
+ | `receipt` | object | The stored execution record (timestamps, metadata, etc.) |
108
+
109
+ ---
110
+
111
+ ## Usage in a Workflow
112
+
113
+ ### Pattern A — IF branch
114
+
115
+ ```
116
+ Webhook → SafeAgent Guard → IF (shouldProceed == true)
117
+ ├─ true → Charge Card → Mark Complete
118
+ └─ false → Return Cached Receipt
119
+ ```
120
+
121
+ ### Pattern B — Stop-and-error on duplicate
122
+
123
+ Add an **IF** node after the guard:
124
+
125
+ - **Condition**: `{{ $json.isDuplicate }}` equals `true`
126
+ - **True branch**: Stop And Error (or Respond to Webhook with the cached receipt)
127
+ - **False branch**: continue with the real work
128
+
129
+ ### Minimal inline example
130
+
131
+ ```json
132
+ {
133
+ "nodes": [
134
+ {
135
+ "type": "n8n-nodes-safeagent.safeAgent",
136
+ "parameters": {
137
+ "requestId": "={{ $json[\"webhookEventId\"] }}",
138
+ "actionName": "send_welcome_email",
139
+ "backend": "sqlite",
140
+ "sqlitePath": "/data/safeagent.db"
141
+ }
142
+ }
143
+ ]
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Postgres Setup
150
+
151
+ 1. Create a dedicated database and user:
152
+
153
+ ```sql
154
+ CREATE DATABASE safeagent;
155
+ CREATE USER safeagent_user WITH PASSWORD 'secret';
156
+ GRANT ALL PRIVILEGES ON DATABASE safeagent TO safeagent_user;
157
+ ```
158
+
159
+ 2. The guard library creates the `execution_claims` table automatically on first run (`init_db()`).
160
+
161
+ 3. In n8n, add a **SafeAgent Postgres Credentials** credential and select it in the node.
162
+
163
+ For high-throughput use-cases add an index:
164
+
165
+ ```sql
166
+ CREATE UNIQUE INDEX IF NOT EXISTS ux_execution_claims
167
+ ON execution_claims (request_id, action_name);
168
+ ```
169
+
170
+ ---
171
+
172
+ ## How the Subprocess Works
173
+
174
+ The node calls the guard library via `python3 -c "..."` so that:
175
+
176
+ - No additional n8n-side dependencies are needed beyond Node.js.
177
+ - The Python environment (virtualenv, system packages, etc.) is fully under your control.
178
+ - The library can be upgraded independently of the n8n node.
179
+
180
+ The script follows this pattern:
181
+
182
+ ```python
183
+ import json
184
+ from safeagent_exec_guard.sqlite_store import SQLiteExecutionStore
185
+
186
+ store = SQLiteExecutionStore('safeagent.db')
187
+ store.init_db()
188
+ result = store.insert_if_not_exists('<requestId>', '<actionName>')
189
+ print(json.dumps(result))
190
+ ```
191
+
192
+ The JSON printed to stdout is parsed and merged into the n8n item.
193
+
194
+ ---
195
+
196
+ ## Security Considerations
197
+
198
+ - Request ID and Action Name values are single-quote–escaped before being embedded in the Python
199
+ string literal to prevent injection.
200
+ - Postgres credentials are never written to disk; they are passed via an in-memory DSN string
201
+ constructed at runtime.
202
+ - The subprocess timeout is 15 seconds. Long-running `init_db()` migrations (first run on a large
203
+ Postgres cluster) may need the timeout raised in the source.
204
+
205
+ ---
206
+
207
+ ## Development
208
+
209
+ ```bash
210
+ git clone https://github.com/your-org/n8n-nodes-safeagent
211
+ cd n8n-nodes-safeagent
212
+ npm install
213
+ npm run build # compiles TypeScript → dist/
214
+ npm run dev # watch mode
215
+ npm run lint
216
+ ```
217
+
218
+ To test locally against a running n8n instance:
219
+
220
+ ```bash
221
+ export N8N_CUSTOM_EXTENSIONS="/path/to/n8n-nodes-safeagent"
222
+ n8n start
223
+ ```
224
+
225
+ ---
226
+
227
+ ## License
228
+
229
+ MIT © Your Name
@@ -0,0 +1,7 @@
1
+ import { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class PostgresApiCredentials implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresApiCredentials = void 0;
4
+ class PostgresApiCredentials {
5
+ constructor() {
6
+ this.name = 'postgresApiCredentials';
7
+ this.displayName = 'SafeAgent Postgres Credentials';
8
+ this.documentationUrl = 'https://github.com/your-org/n8n-nodes-safeagent#postgres-setup';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Host',
12
+ name: 'host',
13
+ type: 'string',
14
+ default: 'localhost',
15
+ placeholder: 'localhost',
16
+ description: 'PostgreSQL server hostname',
17
+ },
18
+ {
19
+ displayName: 'Port',
20
+ name: 'port',
21
+ type: 'number',
22
+ default: 5432,
23
+ description: 'PostgreSQL server port',
24
+ },
25
+ {
26
+ displayName: 'Database',
27
+ name: 'database',
28
+ type: 'string',
29
+ default: 'safeagent',
30
+ description: 'Name of the database that holds the execution-guard table',
31
+ },
32
+ {
33
+ displayName: 'User',
34
+ name: 'user',
35
+ type: 'string',
36
+ default: '',
37
+ description: 'Database user',
38
+ },
39
+ {
40
+ displayName: 'Password',
41
+ name: 'password',
42
+ type: 'string',
43
+ typeOptions: { password: true },
44
+ default: '',
45
+ description: 'Database password',
46
+ },
47
+ {
48
+ displayName: 'SSL',
49
+ name: 'ssl',
50
+ type: 'boolean',
51
+ default: false,
52
+ description: 'Whether to connect using SSL/TLS',
53
+ },
54
+ ];
55
+ }
56
+ }
57
+ exports.PostgresApiCredentials = PostgresApiCredentials;
58
+ //# sourceMappingURL=PostgresApiCredentials.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresApiCredentials.credentials.js","sourceRoot":"","sources":["../../credentials/PostgresApiCredentials.credentials.ts"],"names":[],"mappings":";;;AAKA,MAAa,sBAAsB;IAAnC;QACE,SAAI,GAAG,wBAAwB,CAAC;QAChC,gBAAW,GAAG,gCAAgC,CAAC;QAC/C,qBAAgB,GAAG,gEAAgE,CAAC;QAEpF,eAAU,GAAsB;YAC9B;gBACE,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,WAAW;gBACpB,WAAW,EAAE,WAAW;gBACxB,WAAW,EAAE,4BAA4B;aAC1C;YACD;gBACE,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,IAAI;gBACb,WAAW,EAAE,wBAAwB;aACtC;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,WAAW;gBACpB,WAAW,EAAE,2DAA2D;aACzE;YACD;gBACE,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,eAAe;aAC7B;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,mBAAmB;aACjC;YACD;gBACE,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,KAAK;gBACd,WAAW,EAAE,kCAAkC;aAChD;SACF,CAAC;IACJ,CAAC;CAAA;AAnDD,wDAmDC"}
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class SafeAgent implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SafeAgent = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const child_process_1 = require("child_process");
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+ /** Escape single-quotes so the value is safe to embed in a Python string literal. */
10
+ function pyStr(value) {
11
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
12
+ }
13
+ /**
14
+ * Build the inline Python snippet for the SQLite backend.
15
+ *
16
+ * Uses safeagent_exec_guard.sqlite_store.SQLiteExecutionStore which exposes:
17
+ * insert_if_not_exists(request_id, action_name) -> dict
18
+ *
19
+ * The dict has at minimum:
20
+ * { "inserted": bool, "receipt": { ... } }
21
+ */
22
+ function buildSQLiteScript(requestId, actionName, dbPath) {
23
+ const rid = pyStr(requestId);
24
+ const act = pyStr(actionName);
25
+ const db = pyStr(dbPath);
26
+ return ('import json; ' +
27
+ 'from safeagent_exec_guard.sqlite_store import SQLiteExecutionStore; ' +
28
+ `store = SQLiteExecutionStore('${db}'); ` +
29
+ 'store.init_db(); ' +
30
+ `result = store.insert_if_not_exists('${rid}', '${act}'); ` +
31
+ 'print(json.dumps(result))');
32
+ }
33
+ /**
34
+ * Build the inline Python snippet for the Postgres backend.
35
+ *
36
+ * Uses safeagent_exec_guard.pg_store.PostgresExecutionStore which accepts a
37
+ * libpq DSN string.
38
+ */
39
+ function buildPostgresScript(requestId, actionName, dsn) {
40
+ const rid = pyStr(requestId);
41
+ const act = pyStr(actionName);
42
+ const d = pyStr(dsn);
43
+ return ('import json; ' +
44
+ 'from safeagent_exec_guard.pg_store import PostgresExecutionStore; ' +
45
+ `store = PostgresExecutionStore('${d}'); ` +
46
+ 'store.init_db(); ' +
47
+ `result = store.insert_if_not_exists('${rid}', '${act}'); ` +
48
+ 'print(json.dumps(result))');
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Node definition
52
+ // ---------------------------------------------------------------------------
53
+ class SafeAgent {
54
+ constructor() {
55
+ this.description = {
56
+ displayName: 'SafeAgent Execution Guard',
57
+ name: 'safeAgent',
58
+ // Built-in placeholder icon; replace with a custom SVG in nodes/SafeAgent/safeagent.svg
59
+ icon: 'fa:shield-alt',
60
+ group: ['transform'],
61
+ version: 1,
62
+ subtitle: '={{$parameter["actionName"]}}',
63
+ description: 'Claim-before-execute idempotency guard powered by the safeagent-exec-guard Python library. ' +
64
+ 'Returns a cached receipt when the (requestId, actionName) pair was already processed, ' +
65
+ 'or a "proceed" signal when it is new.',
66
+ defaults: {
67
+ name: 'SafeAgent Guard',
68
+ },
69
+ inputs: ['main'],
70
+ outputs: ['main'],
71
+ credentials: [
72
+ {
73
+ name: 'postgresApiCredentials',
74
+ required: false,
75
+ displayOptions: {
76
+ show: {
77
+ backend: ['postgres'],
78
+ },
79
+ },
80
+ },
81
+ ],
82
+ properties: [
83
+ // ── Request ID ────────────────────────────────────────────────────────
84
+ {
85
+ displayName: 'Request ID',
86
+ name: 'requestId',
87
+ type: 'string',
88
+ default: '',
89
+ required: true,
90
+ placeholder: '={{ $json["requestId"] }}',
91
+ description: 'Unique identifier for this logical request (e.g. webhook event ID, message UUID). ' +
92
+ 'Used together with Action Name to form the idempotency key.',
93
+ },
94
+ // ── Action Name ───────────────────────────────────────────────────────
95
+ {
96
+ displayName: 'Action Name',
97
+ name: 'actionName',
98
+ type: 'string',
99
+ default: '',
100
+ required: true,
101
+ placeholder: 'send_invoice',
102
+ description: 'Name of the side-effectful action being guarded (e.g. "send_invoice", "charge_card"). ' +
103
+ 'Combined with Request ID to create a unique execution claim.',
104
+ },
105
+ // ── Backend ───────────────────────────────────────────────────────────
106
+ {
107
+ displayName: 'Backend',
108
+ name: 'backend',
109
+ type: 'options',
110
+ options: [
111
+ {
112
+ name: 'SQLite (local file)',
113
+ value: 'sqlite',
114
+ description: 'Lightweight; good for single-instance or development use',
115
+ },
116
+ {
117
+ name: 'PostgreSQL',
118
+ value: 'postgres',
119
+ description: 'Distributed-safe; recommended for multi-worker / production deployments',
120
+ },
121
+ ],
122
+ default: 'sqlite',
123
+ description: 'Storage backend used by the execution-guard library',
124
+ },
125
+ // ── SQLite path (only shown when backend = sqlite) ────────────────────
126
+ {
127
+ displayName: 'SQLite Database Path',
128
+ name: 'sqlitePath',
129
+ type: 'string',
130
+ default: 'safeagent.db',
131
+ displayOptions: {
132
+ show: {
133
+ backend: ['sqlite'],
134
+ },
135
+ },
136
+ description: 'Filesystem path to the SQLite database file. ' +
137
+ 'Relative paths are resolved from the n8n working directory.',
138
+ },
139
+ ],
140
+ };
141
+ }
142
+ // ── Execute ──────────────────────────────────────────────────────────────
143
+ async execute() {
144
+ const items = this.getInputData();
145
+ const returnData = [];
146
+ for (let i = 0; i < items.length; i++) {
147
+ const requestId = this.getNodeParameter('requestId', i);
148
+ const actionName = this.getNodeParameter('actionName', i);
149
+ const backend = this.getNodeParameter('backend', i);
150
+ if (!requestId.trim()) {
151
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Request ID must not be empty.', {
152
+ itemIndex: i,
153
+ });
154
+ }
155
+ if (!actionName.trim()) {
156
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Action Name must not be empty.', {
157
+ itemIndex: i,
158
+ });
159
+ }
160
+ let script;
161
+ if (backend === 'sqlite') {
162
+ const dbPath = this.getNodeParameter('sqlitePath', i);
163
+ script = buildSQLiteScript(requestId, actionName, dbPath || 'safeagent.db');
164
+ }
165
+ else {
166
+ // Build a libpq DSN from the stored credentials
167
+ const creds = await this.getCredentials('postgresApiCredentials');
168
+ const host = creds.host;
169
+ const port = creds.port;
170
+ const database = creds.database;
171
+ const user = creds.user;
172
+ const password = creds.password;
173
+ const ssl = creds.ssl;
174
+ const sslMode = ssl ? 'require' : 'disable';
175
+ const dsn = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}?sslmode=${sslMode}`;
176
+ script = buildPostgresScript(requestId, actionName, dsn);
177
+ }
178
+ let rawOutput;
179
+ try {
180
+ rawOutput = (0, child_process_1.execSync)(`python3 -c "${script.replace(/"/g, '\\"')}"`, {
181
+ encoding: 'utf8',
182
+ timeout: 15000,
183
+ }).trim();
184
+ }
185
+ catch (err) {
186
+ const message = err instanceof Error ? err.message : String(err);
187
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `safeagent-exec-guard subprocess failed: ${message}`, { itemIndex: i });
188
+ }
189
+ let guardResult;
190
+ try {
191
+ guardResult = JSON.parse(rawOutput);
192
+ }
193
+ catch {
194
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unexpected output from safeagent-exec-guard (expected JSON): ${rawOutput}`, { itemIndex: i });
195
+ }
196
+ // `inserted: true` → new claim; caller should proceed with the action.
197
+ // `inserted: false` → already executed; caller should skip and use receipt.
198
+ const isDuplicate = guardResult.inserted === false;
199
+ returnData.push({
200
+ json: {
201
+ requestId,
202
+ actionName,
203
+ backend,
204
+ isDuplicate,
205
+ shouldProceed: !isDuplicate,
206
+ ...guardResult,
207
+ },
208
+ pairedItem: { item: i },
209
+ });
210
+ }
211
+ return [returnData];
212
+ }
213
+ }
214
+ exports.SafeAgent = SafeAgent;
215
+ //# sourceMappingURL=SafeAgent.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SafeAgent.node.js","sourceRoot":"","sources":["../../../nodes/SafeAgent/SafeAgent.node.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "n8n-nodes-safeagent",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node — SafeAgent execution guard (claim-before-execute idempotency pattern)",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "safeagent",
8
+ "idempotency",
9
+ "execution-guard"
10
+ ],
11
+ "license": "MIT",
12
+ "homepage": "https://github.com/your-org/n8n-nodes-safeagent",
13
+ "author": {
14
+ "name": "Your Name",
15
+ "email": "you@example.com"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/your-org/n8n-nodes-safeagent.git"
20
+ },
21
+ "main": "index.js",
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsc --watch",
25
+ "format": "prettier nodes credentials --write",
26
+ "lint": "eslint nodes credentials package.json",
27
+ "lintfix": "eslint nodes credentials package.json --fix",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "n8n": {
34
+ "n8nNodesApiVersion": 1,
35
+ "credentials": [
36
+ "dist/credentials/PostgresApiCredentials.credentials.js"
37
+ ],
38
+ "nodes": [
39
+ "dist/nodes/SafeAgent/SafeAgent.node.js"
40
+ ]
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^18.16.0",
44
+ "n8n-workflow": "*",
45
+ "typescript": "^5.1.0"
46
+ },
47
+ "peerDependencies": {
48
+ "n8n-workflow": "*"
49
+ }
50
+ }