n8n-nodes-openclaw 1.0.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,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Jason Williscroft
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # n8n-nodes-openclaw
2
+
3
+ [n8n](https://n8n.io/) community nodes for the [OpenClaw](https://github.com/openclaw/openclaw) Gateway API.
4
+
5
+ Control your OpenClaw instance from n8n workflows — manage sessions, send messages, run cron jobs, search the web, execute commands, and more.
6
+
7
+ ![OpenClaw Node in n8n](https://img.shields.io/badge/n8n-community_node-ff6d5a?logo=n8n)
8
+ [![npm version](https://img.shields.io/npm/v/n8n-nodes-openclaw)](https://www.npmjs.com/package/n8n-nodes-openclaw)
9
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD--3--Clause-blue.svg)](LICENSE)
10
+
11
+ ## Installation
12
+
13
+ ### Community Nodes (recommended)
14
+
15
+ 1. Open **Settings → Community Nodes** in your n8n instance
16
+ 2. Click **Install a community node**
17
+ 3. Enter `n8n-nodes-openclaw`
18
+ 4. Click **Install**
19
+
20
+ ### Manual Installation
21
+
22
+ ```bash
23
+ # In your n8n custom nodes directory
24
+ cd ~/.n8n/custom
25
+ npm install n8n-nodes-openclaw
26
+ ```
27
+
28
+ Or link from source:
29
+
30
+ ```bash
31
+ git clone https://github.com/karmaniverous/n8n-nodes-openclaw.git
32
+ cd n8n-nodes-openclaw
33
+ npm install
34
+ npm run build
35
+
36
+ # Link into n8n
37
+ mkdir -p ~/.n8n/custom/node_modules
38
+ ln -s "$(pwd)" ~/.n8n/custom/node_modules/n8n-nodes-openclaw
39
+ ```
40
+
41
+ Then restart n8n.
42
+
43
+ ## Setup
44
+
45
+ 1. In n8n, go to **Settings → Credentials → Add Credential**
46
+ 2. Search for **OpenClaw API**
47
+ 3. Enter your:
48
+ - **Gateway URL** — e.g. `http://127.0.0.1:18789`
49
+ - **Gateway Token** — found in your OpenClaw config file under `gateway.auth.token`
50
+
51
+ ## Available Tools
52
+
53
+ The OpenClaw node exposes **20 tools** via a Resource dropdown, each with contextual parameters:
54
+
55
+ | Resource | Description |
56
+ |----------|-------------|
57
+ | **Agents List** | List available agent IDs for spawning sub-agents |
58
+ | **Browser** | Control a browser — open pages, screenshots, snapshots, click, fill forms |
59
+ | **Canvas** | Control canvas overlays on paired nodes |
60
+ | **Cron** | Manage scheduled jobs — list, add, update, remove, run, wake |
61
+ | **Execute Command** | Run shell commands on the host system |
62
+ | **Gateway** | Manage the gateway — restart, read/write config, trigger updates |
63
+ | **Image** | Analyze images with a vision model |
64
+ | **Memory Get** | Read snippets from agent memory files |
65
+ | **Memory Search** | Semantically search agent memory |
66
+ | **Message** | Send, read, edit, delete messages across channels (Slack, Discord, Telegram, etc.) |
67
+ | **Nodes** | Discover and control paired nodes — notifications, camera, location, commands |
68
+ | **Process Manager** | Manage running exec sessions — poll, log, write stdin, kill |
69
+ | **Session Status** | Get session status, usage stats, and set model overrides |
70
+ | **Sessions History** | Retrieve conversation history for a session |
71
+ | **Sessions List** | List active sessions with filters |
72
+ | **Sessions Send** | Send messages into other sessions (cross-session communication) |
73
+ | **Sessions Spawn** | Spawn background sub-agents in isolated sessions |
74
+ | **Text to Speech** | Convert text to speech audio |
75
+ | **Web Fetch** | Fetch and extract readable content from URLs (HTML → markdown) |
76
+ | **Web Search** | Search the web via Brave Search |
77
+
78
+ ### Raw Mode
79
+
80
+ Every resource supports **Raw Mode** — toggle it on to send arbitrary JSON arguments instead of using the form fields. Useful for edge cases or new parameters not yet in the schema.
81
+
82
+ ## CLI Commands Not in the API
83
+
84
+ The OpenClaw CLI (`openclaw`) has commands that are **not available** via the Gateway HTTP API — notably `openclaw gateway status`, `openclaw gateway start`, and `openclaw gateway stop`. These manage the local daemon and require OS-level access.
85
+
86
+ If you need these in a workflow, use n8n's built-in **Execute Command** node to run them directly:
87
+
88
+ ```
89
+ openclaw gateway status
90
+ ```
91
+
92
+ The CLI does not currently support JSON output, so you'll need to parse the human-readable text.
93
+
94
+ ## Examples
95
+
96
+ See the [`examples/`](examples/) directory for ready-to-import n8n workflow files:
97
+
98
+ - **List Sessions** — basic workflow showing session listing
99
+ - **Send Slack Message** — send a message to a Slack channel
100
+ - **Cron Job Management** — list and run cron jobs
101
+ - **Web Search → Message** — search the web and post results to a channel
102
+
103
+ Import any example via **Workflows → Import from File** in n8n.
104
+
105
+ ## How It Works
106
+
107
+ This node calls the OpenClaw Gateway's [`POST /tools/invoke`](https://docs.openclaw.ai) endpoint. Each resource/action combination maps to a tool invocation:
108
+
109
+ ```json
110
+ {
111
+ "tool": "sessions_list",
112
+ "args": { "limit": 5, "kinds": ["main", "group"] }
113
+ }
114
+ ```
115
+
116
+ The gateway enforces tool policies, authentication, and session context. See the [OpenClaw docs](https://docs.openclaw.ai) for full API details.
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ git clone https://github.com/karmaniverous/n8n-nodes-openclaw.git
122
+ cd n8n-nodes-openclaw
123
+ npm install
124
+ npm run build
125
+ npm test
126
+ ```
127
+
128
+ ### Project Structure
129
+
130
+ ```
131
+ src/
132
+ credentials/ # OpenClaw API credential type
133
+ nodes/OpenClaw/ # Main node implementation
134
+ OpenClaw.node.ts # Node class with execute function
135
+ toolSchemas.ts # Static tool schema loader
136
+ openclaw.svg # Node icon
137
+ tool-schemas/ # JSON schema files for each tool (20 files)
138
+ examples/ # Example n8n workflows
139
+ ```
140
+
141
+ ### Running Tests
142
+
143
+ ```bash
144
+ npm test # Unit tests
145
+ OPENCLAW_TOKEN=<token> npm test # + integration tests against live gateway
146
+ ```
147
+
148
+ ### Linting & Type Checking
149
+
150
+ ```bash
151
+ npm run lint
152
+ npm run typecheck
153
+ npm run knip # Unused exports/dependencies check
154
+ ```
155
+
156
+ ## License
157
+
158
+ [BSD-3-Clause](LICENSE)
159
+
160
+ ## Links
161
+
162
+ - [OpenClaw](https://github.com/openclaw/openclaw) — the AI agent framework
163
+ - [OpenClaw Docs](https://docs.openclaw.ai) — full documentation
164
+ - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/) — how community nodes work
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ class OpenClawApi {
4
+ name = 'openClawApi';
5
+ displayName = 'OpenClaw API';
6
+ documentationUrl = 'https://docs.openclaw.ai/gateway/tools-invoke-http-api';
7
+ properties = [
8
+ {
9
+ displayName: 'Gateway URL',
10
+ name: 'gatewayUrl',
11
+ type: 'string',
12
+ default: 'http://127.0.0.1:18789',
13
+ placeholder: 'http://127.0.0.1:18789',
14
+ description: 'The URL of your OpenClaw Gateway. Default port is 18789.',
15
+ required: true,
16
+ },
17
+ {
18
+ displayName: 'Gateway Token',
19
+ name: 'gatewayToken',
20
+ type: 'string',
21
+ typeOptions: { password: true },
22
+ default: '',
23
+ description: 'The authentication token for your OpenClaw Gateway. Found in your gateway config under gateway.auth.token.',
24
+ required: true,
25
+ },
26
+ ];
27
+ }
28
+
29
+ exports.OpenClawApi = OpenClawApi;
@@ -0,0 +1,46 @@
1
+ import { ICredentialType, INodeProperties, INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+
3
+ declare class OpenClawApi implements ICredentialType {
4
+ name: string;
5
+ displayName: string;
6
+ documentationUrl: string;
7
+ properties: INodeProperties[];
8
+ }
9
+
10
+ declare class OpenClaw implements INodeType {
11
+ description: INodeTypeDescription;
12
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
13
+ }
14
+
15
+ /**
16
+ * Tool schema type definition.
17
+ */
18
+ interface ToolParameterDef {
19
+ type: string;
20
+ required?: boolean;
21
+ enum?: string[];
22
+ description?: string;
23
+ default?: unknown;
24
+ placeholder?: string;
25
+ /** If true, the n8n field is a comma-separated string that should be split into an array before sending to the API. */
26
+ splitAsArray?: boolean;
27
+ displayOptions?: {
28
+ show?: {
29
+ action?: string[];
30
+ };
31
+ };
32
+ }
33
+ interface ToolSchema {
34
+ name: string;
35
+ label: string;
36
+ description: string;
37
+ actions?: string[];
38
+ parameters: Record<string, ToolParameterDef>;
39
+ }
40
+ /**
41
+ * All tool schemas, sorted by label for UI display.
42
+ */
43
+ declare const toolSchemas: ToolSchema[];
44
+
45
+ export { OpenClaw, OpenClawApi, toolSchemas };
46
+ export type { ToolParameterDef, ToolSchema };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var credentials_OpenClawApi_credentials = require('./credentials/OpenClawApi.credentials.js');
4
+ var nodes_OpenClaw_OpenClaw_node = require('./nodes/OpenClaw/OpenClaw.node.js');
5
+ var nodes_OpenClaw_toolSchemas = require('./nodes/OpenClaw/toolSchemas.js');
6
+ require('n8n-workflow');
7
+
8
+
9
+
10
+ exports.OpenClawApi = credentials_OpenClawApi_credentials.OpenClawApi;
11
+ exports.OpenClaw = nodes_OpenClaw_OpenClaw_node.OpenClaw;
12
+ exports.toolSchemas = nodes_OpenClaw_toolSchemas.toolSchemas;
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ var n8nWorkflow = require('n8n-workflow');
4
+ var nodes_OpenClaw_toolSchemas = require('./toolSchemas.js');
5
+
6
+ /**
7
+ * Build n8n node properties from tool schemas.
8
+ */
9
+ function buildResourceOptions() {
10
+ return nodes_OpenClaw_toolSchemas.toolSchemas.map((schema) => ({
11
+ name: schema.label,
12
+ value: schema.name,
13
+ description: schema.description,
14
+ }));
15
+ }
16
+ /**
17
+ * Build action (operation) options for a specific tool.
18
+ */
19
+ function buildActionProperties() {
20
+ const properties = [];
21
+ for (const schema of nodes_OpenClaw_toolSchemas.toolSchemas) {
22
+ if (!schema.actions)
23
+ continue;
24
+ properties.push({
25
+ displayName: 'Action',
26
+ name: 'action',
27
+ type: 'options',
28
+ displayOptions: {
29
+ show: { resource: [schema.name] },
30
+ },
31
+ options: schema.actions.map((action) => ({
32
+ name: action.charAt(0).toUpperCase() + action.slice(1),
33
+ value: action,
34
+ })),
35
+ default: schema.actions[0],
36
+ noDataExpression: true,
37
+ required: true,
38
+ description: `The ${schema.label} operation to perform`,
39
+ });
40
+ }
41
+ return properties;
42
+ }
43
+ /**
44
+ * Build parameter fields for each tool, respecting displayOptions.
45
+ */
46
+ function buildParameterProperties() {
47
+ const properties = [];
48
+ for (const schema of nodes_OpenClaw_toolSchemas.toolSchemas) {
49
+ const params = schema.parameters;
50
+ for (const [paramName, paramDef] of Object.entries(params)) {
51
+ // Skip 'action' — already handled above
52
+ if (paramName === 'action')
53
+ continue;
54
+ // Skip gateway override params — handled by credentials
55
+ if (paramName === 'gatewayUrl' || paramName === 'gatewayToken')
56
+ continue;
57
+ const displayOptions = {
58
+ show: { resource: [schema.name] },
59
+ };
60
+ // If the param has its own displayOptions.show.action, merge it
61
+ if (paramDef.displayOptions?.show?.action) {
62
+ displayOptions.show.action =
63
+ paramDef.displayOptions.show.action;
64
+ }
65
+ const n8nType = mapType(paramDef.type);
66
+ const prop = {
67
+ displayName: formatDisplayName(paramName),
68
+ name: paramName,
69
+ type: n8nType,
70
+ displayOptions,
71
+ default: paramDef.default ?? (n8nType === 'boolean' ? false : ''),
72
+ required: paramDef.required ?? false,
73
+ description: paramDef.description ?? '',
74
+ };
75
+ if (paramDef.enum && n8nType === 'options') {
76
+ prop.options = paramDef.enum.map((v) => ({
77
+ name: v.charAt(0).toUpperCase() + v.slice(1),
78
+ value: v,
79
+ }));
80
+ }
81
+ if (paramDef.placeholder) {
82
+ prop.placeholder = paramDef.placeholder;
83
+ }
84
+ // For object/json types, use the json type
85
+ if (paramDef.type === 'object') {
86
+ prop.type = 'json';
87
+ prop.default = '{}';
88
+ }
89
+ properties.push(prop);
90
+ }
91
+ }
92
+ return properties;
93
+ }
94
+ function mapType(schemaType) {
95
+ switch (schemaType) {
96
+ case 'string':
97
+ return 'string';
98
+ case 'number':
99
+ return 'number';
100
+ case 'boolean':
101
+ return 'boolean';
102
+ case 'object':
103
+ return 'json';
104
+ default:
105
+ return 'string';
106
+ }
107
+ }
108
+ function formatDisplayName(name) {
109
+ return name
110
+ .replace(/([A-Z])/g, ' $1')
111
+ .replace(/[_-]/g, ' ')
112
+ .replace(/\b\w/g, (c) => c.toUpperCase())
113
+ .trim();
114
+ }
115
+ class OpenClaw {
116
+ description = {
117
+ displayName: 'OpenClaw',
118
+ name: 'openClaw',
119
+ icon: 'file:openclaw.svg',
120
+ group: ['transform'],
121
+ version: 1,
122
+ subtitle: '={{$parameter["resource"] + ": " + ($parameter["action"] || "execute")}}',
123
+ description: 'Interact with the OpenClaw Gateway API',
124
+ defaults: {
125
+ name: 'OpenClaw',
126
+ },
127
+ inputs: ['main'],
128
+ outputs: ['main'],
129
+ credentials: [
130
+ {
131
+ name: 'openClawApi',
132
+ required: true,
133
+ },
134
+ ],
135
+ properties: [
136
+ // Resource selector (tool)
137
+ {
138
+ displayName: 'Resource',
139
+ name: 'resource',
140
+ type: 'options',
141
+ noDataExpression: true,
142
+ options: buildResourceOptions(),
143
+ default: 'sessions_list',
144
+ required: true,
145
+ description: 'The OpenClaw tool to invoke',
146
+ },
147
+ // Raw mode option
148
+ {
149
+ displayName: 'Raw Mode',
150
+ name: 'rawMode',
151
+ type: 'boolean',
152
+ default: false,
153
+ description: 'Whether to send raw JSON arguments instead of using the form fields. Useful for tools not yet supported with typed fields.',
154
+ },
155
+ {
156
+ displayName: 'Raw Arguments (JSON)',
157
+ name: 'rawArgs',
158
+ type: 'json',
159
+ default: '{}',
160
+ displayOptions: {
161
+ show: { rawMode: [true] },
162
+ },
163
+ description: 'Raw JSON arguments to pass to the tool',
164
+ },
165
+ // Action selectors per resource
166
+ ...buildActionProperties(),
167
+ // Parameter fields per resource
168
+ ...buildParameterProperties(),
169
+ ],
170
+ };
171
+ async execute() {
172
+ const items = this.getInputData();
173
+ const returnData = [];
174
+ const credentials = await this.getCredentials('openClawApi');
175
+ const gatewayUrl = credentials.gatewayUrl;
176
+ const gatewayToken = credentials.gatewayToken;
177
+ for (let i = 0; i < items.length; i++) {
178
+ try {
179
+ const resource = this.getNodeParameter('resource', i);
180
+ const rawMode = this.getNodeParameter('rawMode', i, false);
181
+ let args;
182
+ if (rawMode) {
183
+ const rawArgs = this.getNodeParameter('rawArgs', i, '{}');
184
+ args = JSON.parse(rawArgs);
185
+ }
186
+ else {
187
+ args = buildArgsFromParameters.call(this, resource, i);
188
+ }
189
+ const body = {
190
+ tool: resource,
191
+ args,
192
+ };
193
+ // If there's an action, also set it at the top level
194
+ if (args.action) {
195
+ body.action = args.action;
196
+ }
197
+ const options = {
198
+ method: 'POST',
199
+ url: `${gatewayUrl.replace(/\/$/, '')}/tools/invoke`,
200
+ body,
201
+ headers: {
202
+ 'Content-Type': 'application/json',
203
+ Authorization: `Bearer ${gatewayToken}`,
204
+ },
205
+ json: true,
206
+ };
207
+ const response = (await this.helpers.httpRequest(options));
208
+ if (response.ok === false) {
209
+ throw new n8nWorkflow.NodeApiError(this.getNode(), response, {
210
+ message: response.error?.message ?? 'Unknown error',
211
+ });
212
+ }
213
+ returnData.push({
214
+ json: (response.result ?? response),
215
+ });
216
+ }
217
+ catch (error) {
218
+ if (this.continueOnFail()) {
219
+ returnData.push({
220
+ json: { error: error.message },
221
+ });
222
+ continue;
223
+ }
224
+ throw error;
225
+ }
226
+ }
227
+ return [returnData];
228
+ }
229
+ }
230
+ /**
231
+ * Build args object from n8n parameter values for a given resource.
232
+ */
233
+ function buildArgsFromParameters(resource, itemIndex) {
234
+ const schema = nodes_OpenClaw_toolSchemas.toolSchemas.find((s) => s.name === resource);
235
+ if (!schema)
236
+ return {};
237
+ const args = {};
238
+ // Get action if this tool has actions
239
+ if (schema.actions && schema.actions.length > 0) {
240
+ try {
241
+ args.action = this.getNodeParameter('action', itemIndex);
242
+ }
243
+ catch {
244
+ // action not available for this resource
245
+ }
246
+ }
247
+ // Get each parameter
248
+ for (const [paramName, paramDef] of Object.entries(schema.parameters)) {
249
+ if (paramName === 'action')
250
+ continue;
251
+ if (paramName === 'gatewayUrl' || paramName === 'gatewayToken')
252
+ continue;
253
+ try {
254
+ const value = this.getNodeParameter(paramName, itemIndex, undefined);
255
+ if (value !== undefined && !(typeof value === 'string' && value === '')) {
256
+ if (paramDef.type === 'object' && typeof value === 'string') {
257
+ // Parse JSON strings for object types
258
+ try {
259
+ args[paramName] = JSON.parse(value);
260
+ }
261
+ catch {
262
+ args[paramName] = value;
263
+ }
264
+ }
265
+ else if (paramDef.splitAsArray && typeof value === 'string') {
266
+ // Split comma-separated strings into arrays
267
+ args[paramName] = value
268
+ .split(',')
269
+ .map((s) => s.trim())
270
+ .filter((s) => s.length > 0);
271
+ }
272
+ else {
273
+ args[paramName] = value;
274
+ }
275
+ }
276
+ }
277
+ catch {
278
+ // Parameter not available (hidden by displayOptions)
279
+ }
280
+ }
281
+ return args;
282
+ }
283
+
284
+ exports.OpenClaw = OpenClaw;
@@ -0,0 +1,22 @@
1
+ <svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="lobster-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#ff4d4d"/>
5
+ <stop offset="100%" stop-color="#991b1b"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Body -->
9
+ <path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#lobster-gradient)"/>
10
+ <!-- Left Claw -->
11
+ <path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#lobster-gradient)"/>
12
+ <!-- Right Claw -->
13
+ <path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#lobster-gradient)"/>
14
+ <!-- Antenna -->
15
+ <path d="M45 15 Q35 5 30 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
16
+ <path d="M75 15 Q85 5 90 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
17
+ <!-- Eyes -->
18
+ <circle cx="45" cy="35" r="6" fill="#050810"/>
19
+ <circle cx="75" cy="35" r="6" fill="#050810"/>
20
+ <circle cx="46" cy="34" r="2.5" fill="#00e5cc"/>
21
+ <circle cx="76" cy="34" r="2.5" fill="#00e5cc"/>
22
+ </svg>