├── .editorconfig
├── .eslintrc.js
├── .eslintrc.prepublish.js
├── .gitignore
├── .idea
└── workspace.xml
├── .npmignore
├── .prettierrc.js
├── .vscode
└── extensions.json
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── README_TEMPLATE.md
├── credentials
├── ExampleCredentialsApi.credentials.ts
└── HttpBinApi.credentials.ts
├── gulpfile.js
├── index.js
├── nodemon.json
├── nodes
├── Agent
│ ├── Agent.node.ts
│ └── interface.ts
├── Crew
│ └── Crew.node.ts
├── ExampleNode
│ ├── ExampleNode.node.ts
│ └── sample.tss
├── HttpBin
│ ├── HttpBin.node.json
│ ├── HttpBin.node.ts
│ ├── HttpVerbDescription.ts
│ └── httpbin.svg
└── Task
│ ├── Task.node.ts
│ └── interface.ts
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── python
├── .idea
│ ├── .gitignore
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── python.iml
│ └── vcs.xml
├── n8n-crewai.py
├── sample_request_01.json
└── sample_request_01.ts
├── sample.py
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = tab
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [package.json]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [*.yml]
19 | indent_style = space
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@types/eslint').ESLint.ConfigData}
3 | */
4 | module.exports = {
5 | root: true,
6 |
7 | env: {
8 | browser: true,
9 | es6: true,
10 | node: true,
11 | },
12 |
13 | parser: '@typescript-eslint/parser',
14 |
15 | parserOptions: {
16 | project: ['./tsconfig.json'],
17 | sourceType: 'module',
18 | extraFileExtensions: ['.json'],
19 | },
20 |
21 | ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'],
22 |
23 | overrides: [
24 | {
25 | files: ['package.json'],
26 | plugins: ['eslint-plugin-n8n-nodes-base'],
27 | extends: ['plugin:n8n-nodes-base/community'],
28 | rules: {
29 | 'n8n-nodes-base/community-package-json-name-still-default': 'off',
30 | },
31 | },
32 | {
33 | files: ['./credentials/**/*.ts'],
34 | plugins: ['eslint-plugin-n8n-nodes-base'],
35 | extends: ['plugin:n8n-nodes-base/credentials'],
36 | rules: {
37 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off',
38 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off',
39 | },
40 | },
41 | {
42 | files: ['./nodes/**/*.ts'],
43 | plugins: ['eslint-plugin-n8n-nodes-base'],
44 | extends: ['plugin:n8n-nodes-base/nodes'],
45 | rules: {
46 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off',
47 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off',
48 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off',
49 | },
50 | },
51 | ],
52 | };
53 |
--------------------------------------------------------------------------------
/.eslintrc.prepublish.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@types/eslint').ESLint.ConfigData}
3 | */
4 | module.exports = {
5 | extends: "./.eslintrc.js",
6 |
7 | overrides: [
8 | {
9 | files: ['package.json'],
10 | plugins: ['eslint-plugin-n8n-nodes-base'],
11 | rules: {
12 | 'n8n-nodes-base/community-package-json-name-still-default': 'error',
13 | },
14 | },
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules
3 | .DS_Store
4 | .tmp
5 | tmp
6 | dist
7 | npm-debug.log*
8 | yarn.lock
9 | .vscode/launch.json
10 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {
38 | "associatedIndex": 0
39 | }
40 |
41 |
42 |
43 |
44 |
45 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | 1713609254989
110 |
111 |
112 | 1713609254989
113 |
114 |
115 |
116 |
117 |
118 | 1713609789481
119 |
120 |
121 |
122 | 1713609789481
123 |
124 |
125 |
126 | 1713611223226
127 |
128 |
129 |
130 | 1713611223226
131 |
132 |
133 |
134 | 1713611273847
135 |
136 |
137 |
138 | 1713611273847
139 |
140 |
141 |
142 | 1713612740722
143 |
144 |
145 |
146 | 1713612740722
147 |
148 |
149 |
150 | 1713614023182
151 |
152 |
153 |
154 | 1713614023182
155 |
156 |
157 |
158 | 1713614566189
159 |
160 |
161 |
162 | 1713614566189
163 |
164 |
165 |
166 | 1713615579840
167 |
168 |
169 |
170 | 1713615579840
171 |
172 |
173 |
174 | 1713619221273
175 |
176 |
177 |
178 | 1713619221273
179 |
180 |
181 |
182 | 1713619320553
183 |
184 |
185 |
186 | 1713619320553
187 |
188 |
189 |
190 | 1713619745155
191 |
192 |
193 |
194 | 1713619745155
195 |
196 |
197 |
198 | 1713623445641
199 |
200 |
201 |
202 | 1713623445641
203 |
204 |
205 |
206 | 1713624478850
207 |
208 |
209 |
210 | 1713624478850
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.tsbuildinfo
3 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * https://prettier.io/docs/en/options.html#semicolons
4 | */
5 | semi: true,
6 |
7 | /**
8 | * https://prettier.io/docs/en/options.html#trailing-commas
9 | */
10 | trailingComma: 'all',
11 |
12 | /**
13 | * https://prettier.io/docs/en/options.html#bracket-spacing
14 | */
15 | bracketSpacing: true,
16 |
17 | /**
18 | * https://prettier.io/docs/en/options.html#tabs
19 | */
20 | useTabs: true,
21 |
22 | /**
23 | * https://prettier.io/docs/en/options.html#tab-width
24 | */
25 | tabWidth: 2,
26 |
27 | /**
28 | * https://prettier.io/docs/en/options.html#arrow-function-parentheses
29 | */
30 | arrowParens: 'always',
31 |
32 | /**
33 | * https://prettier.io/docs/en/options.html#quotes
34 | */
35 | singleQuote: true,
36 |
37 | /**
38 | * https://prettier.io/docs/en/options.html#quote-props
39 | */
40 | quoteProps: 'as-needed',
41 |
42 | /**
43 | * https://prettier.io/docs/en/options.html#end-of-line
44 | */
45 | endOfLine: 'lf',
46 |
47 | /**
48 | * https://prettier.io/docs/en/options.html#print-width
49 | */
50 | printWidth: 100,
51 | };
52 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "EditorConfig.EditorConfig",
5 | "esbenp.prettier-vscode",
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at jan@n8n.io. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2022 n8n
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # n8n-crewai
2 |
3 | This project is a work-in-progress, creating custom N8N nodes for CrewAI.
4 |
5 | 
6 |
7 | # TODOs
8 |
9 | - [x] Create a Simple N8N Custom Node
10 | - I`ve cloned the n8n-nodes-starter
11 | - [x] Try to Call a Python in this poc
12 | - [x] Try to use the Node type used in new LLM nodes
13 | - [X] Understand how to manipulate the properties to enable pass them to the crewAI Python
14 | - [X] Create a initial version of a Python CrewAI that receives the result of a crew
15 | - [ ] Map the N8N parameters to the script
16 | - [ ] Configure credentials in N8N
17 | - [ ] Create a initial version of a Python CrewAI that receives the parameters from N8N
18 | - [ ] Study and use versioning
19 | https://docs.n8n.io/integrations/creating-nodes/build/reference/node-versioning/
20 | - [ ] Have followed the guidelines on build custom nodes
21 | https://docs.n8n.io/integrations/community-nodes/build-community-nodes/
22 |
23 | # Random
24 |
25 | - n8n-nodes-starter
26 | https://github.com/n8n-io/n8n-nodes-starter/blob/master/package.json
27 | - Sample of a Structured Node
28 | https://docs.n8n.io/integrations/creating-nodes/build/reference/node-versioning/
29 | - Outline structure for a programmatic-style node#
30 | https://docs.n8n.io/integrations/creating-nodes/build/reference/node-base-files/#outline-structure-for-a-programmatic-style-node
31 | - https://docs.n8n.io/integrations/creating-nodes/test/run-node-locally/
32 | - How to pass json as parameter
33 | --json "{\"agents\":
34 |
--------------------------------------------------------------------------------
/README_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | # n8n-nodes-_node-name_
3 |
4 | This is an n8n community node. It lets you use _app/service name_ in your n8n workflows.
5 |
6 | _App/service name_ is _one or two sentences describing the service this node integrates with_.
7 |
8 | [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
9 |
10 | [Installation](#installation)
11 | [Operations](#operations)
12 | [Credentials](#credentials)
13 | [Compatibility](#compatibility)
14 | [Usage](#usage)
15 | [Resources](#resources)
16 | [Version history](#version-history)
17 |
18 | ## Installation
19 |
20 | Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
21 |
22 | ## Operations
23 |
24 | _List the operations supported by your node._
25 |
26 | ## Credentials
27 |
28 | _If users need to authenticate with the app/service, provide details here. You should include prerequisites (such as signing up with the service), available authentication methods, and how to set them up._
29 |
30 | ## Compatibility
31 |
32 | _State the minimum n8n version, as well as which versions you test against. You can also include any known version incompatibility issues._
33 |
34 | ## Usage
35 |
36 | _This is an optional section. Use it to help users with any difficult or confusing aspects of the node._
37 |
38 | _By the time users are looking for community nodes, they probably already know n8n basics. But if you expect new users, you can link to the [Try it out](https://docs.n8n.io/try-it-out/) documentation to help them get started._
39 |
40 | ## Resources
41 |
42 | * [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
43 | * _Link to app/service documentation._
44 |
45 | ## Version history
46 |
47 | _This is another optional section. If your node has multiple versions, include a short description of available versions and what changed, as well as any compatibility impact._
48 |
49 |
50 |
--------------------------------------------------------------------------------
/credentials/ExampleCredentialsApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IAuthenticateGeneric,
3 | ICredentialTestRequest,
4 | ICredentialType,
5 | INodeProperties,
6 | } from 'n8n-workflow';
7 |
8 | export class ExampleCredentialsApi implements ICredentialType {
9 | name = 'exampleCredentialsApi';
10 | displayName = 'Example Credentials API';
11 | properties: INodeProperties[] = [
12 | // The credentials to get from user and save encrypted.
13 | // Properties can be defined exactly in the same way
14 | // as node properties.
15 | {
16 | displayName: 'User Name',
17 | name: 'username',
18 | type: 'string',
19 | default: '',
20 | },
21 | {
22 | displayName: 'Password',
23 | name: 'password',
24 | type: 'string',
25 | typeOptions: {
26 | password: true,
27 | },
28 | default: '',
29 | },
30 | ];
31 |
32 | // This credential is currently not used by any node directly
33 | // but the HTTP Request node can use it to make requests.
34 | // The credential is also testable due to the `test` property below
35 | authenticate: IAuthenticateGeneric = {
36 | type: 'generic',
37 | properties: {
38 | auth: {
39 | username: '={{ $credentials.username }}',
40 | password: '={{ $credentials.password }}',
41 | },
42 | qs: {
43 | // Send this as part of the query string
44 | n8n: 'rocks',
45 | },
46 | },
47 | };
48 |
49 | // The block below tells how this credential can be tested
50 | test: ICredentialTestRequest = {
51 | request: {
52 | baseURL: 'https://example.com/',
53 | url: '',
54 | },
55 | };
56 | }
57 |
--------------------------------------------------------------------------------
/credentials/HttpBinApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IAuthenticateGeneric,
3 | ICredentialTestRequest,
4 | ICredentialType,
5 | INodeProperties,
6 | } from 'n8n-workflow';
7 |
8 | export class HttpBinApi implements ICredentialType {
9 | name = 'httpbinApi';
10 | displayName = 'HttpBin API';
11 | documentationUrl = '';
12 | properties: INodeProperties[] = [
13 | {
14 | displayName: 'Token',
15 | name: 'token',
16 | type: 'string',
17 | default: '',
18 | },
19 | {
20 | displayName: 'Domain',
21 | name: 'domain',
22 | type: 'string',
23 | default: 'https://httpbin.org',
24 | },
25 | ];
26 |
27 | // This allows the credential to be used by other parts of n8n
28 | // stating how this credential is injected as part of the request
29 | // An example is the Http Request node that can make generic calls
30 | // reusing this credential
31 | authenticate: IAuthenticateGeneric = {
32 | type: 'generic',
33 | properties: {
34 | headers: {
35 | Authorization: '={{"Bearer " + $credentials.token}}',
36 | },
37 | },
38 | };
39 |
40 | // The block below tells how this credential can be tested
41 | test: ICredentialTestRequest = {
42 | request: {
43 | baseURL: '={{$credentials?.domain}}',
44 | url: '/bearer',
45 | },
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { task, src, dest } = require('gulp');
3 |
4 | task('build:icons', copyIcons);
5 |
6 | function copyIcons() {
7 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg}');
8 | const nodeDestination = path.resolve('dist', 'nodes');
9 |
10 | src(nodeSource).pipe(dest(nodeDestination));
11 |
12 | const credSource = path.resolve('credentials', '**', '*.{png,svg}');
13 | const credDestination = path.resolve('dist', 'credentials');
14 |
15 | return src(credSource).pipe(dest(credDestination));
16 | }
17 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biliboss/n8n-nodes-crewai/c9bd40e6ebb22b5c6269ffea362c6bb785a7892b/index.js
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["nodes", "python"],
3 | "ext": "ts",
4 | "exec": "npm run build-and-run-n8n"
5 | }
6 |
--------------------------------------------------------------------------------
/nodes/Agent/Agent.node.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable n8n-nodes-base/node-dirname-against-convention */
2 | import type {
3 | IExecuteFunctions,
4 | INodeType,
5 | INodeTypeDescription,
6 | SupplyData,
7 | } from 'n8n-workflow';
8 | import { NodeConnectionType } from 'n8n-workflow';
9 |
10 | // import { Agent } from '@langchain/crewai'; // Assuming this is the path to Agent class
11 |
12 | export class Agent implements INodeType {
13 | description: INodeTypeDescription = {
14 | displayName: 'CrewAI Agent',
15 | name: 'agentNode',
16 | icon: 'fa:user-secret',
17 | group: ['transform'],
18 | version: 1,
19 | description: 'Configure a CrewAI agent',
20 | defaults: {
21 | name: 'CrewAI Agent',
22 | },
23 | codex: {
24 | categories: ['AI'],
25 | subcategories: {
26 | AI: ['Agents'],
27 | },
28 | resources: {
29 | primaryDocumentation: [
30 | {
31 | url: 'https://docs.crewai.example.com/agents',
32 | },
33 | ],
34 | },
35 | },
36 | inputs: [],
37 | outputs: [NodeConnectionType.AiAgent],
38 | outputNames: ['Agent'],
39 | properties: [
40 | // getAgentConnectionHintNoticeField([NodeConnectionType.AiTool]),
41 | {
42 | displayName: 'Role',
43 | name: 'role',
44 | type: 'string',
45 | default: '',
46 | placeholder: 'Senior Researcher',
47 | },
48 | {
49 | displayName: 'Goal',
50 | name: 'goal',
51 | type: 'string',
52 | default: '',
53 | placeholder: 'Uncover groundbreaking technologies in {topic}',
54 | typeOptions: {
55 | rows: 3,
56 | },
57 | },
58 | {
59 | displayName: 'Memory',
60 | name: 'memory',
61 | type: 'boolean',
62 | default: false,
63 | description: 'Enable memory for the agent',
64 | },
65 | {
66 | displayName: 'Verbose',
67 | name: 'verbose',
68 | type: 'boolean',
69 | default: false,
70 | description: 'Enable verbose mode for the agent',
71 | },
72 | {
73 | displayName: 'Backstory',
74 | name: 'backstory',
75 | type: 'string',
76 | default: '',
77 | placeholder: 'Driven by curiosity, you\'re at the forefront of innovation...',
78 | typeOptions: {
79 | rows: 4,
80 | },
81 | },
82 | ],
83 | };
84 |
85 | async supplyData(this: IExecuteFunctions, itemIndex: number): Promise {
86 | const role = this.getNodeParameter('role', itemIndex) as string;
87 | const goal = this.getNodeParameter('goal', itemIndex) as string;
88 | const memory = this.getNodeParameter('memory', itemIndex) as boolean;
89 | const verbose = this.getNodeParameter('verbose', itemIndex) as boolean;
90 | const backstory = this.getNodeParameter('backstory', itemIndex) as string;
91 |
92 | // Assuming the Agent class from CrewAI has a similar constructor
93 | // const agent = new Agent({
94 | // role,
95 | // goal,
96 | // memory,
97 | // verbose,
98 | // backstory,
99 | // // Include tools if necessary, similar to the dynamic tool creation in ToolCode
100 | // });
101 |
102 | // Depending on the CrewAI Agent implementation, you might directly execute tasks or return the agent configuration
103 | return {
104 | response: {role, goal, memory, verbose, backstory}
105 | };
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/nodes/Agent/interface.ts:
--------------------------------------------------------------------------------
1 | export interface CrewAIAgent {
2 | role: string;
3 | goal: string;
4 | verbose: boolean;
5 | memory: boolean;
6 | backstory: string;
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/Crew/Crew.node.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IExecuteFunctions,
3 | INodeExecutionData,
4 | INodeType,
5 | INodeTypeDescription, NodeConnectionType,
6 | NodeOperationError,
7 | } from 'n8n-workflow';
8 |
9 | import {exec} from 'child_process';
10 | import {promisify} from 'util';
11 | import {CrewAIAgent} from "../Agent/interface";
12 | import {CrewAITask} from "../Task/interface";
13 |
14 | const execPromise = promisify(exec);
15 |
16 | function getInputs() {
17 | const inputs = [
18 | {displayName: 'Input Main', type: NodeConnectionType.Main},
19 | {
20 | displayName: 'Agents',
21 | // maxConnections: 20,
22 | type: NodeConnectionType.AiAgent,
23 | required: true,
24 | },
25 | {
26 | displayName: 'Tasks',
27 | // maxConnections: 20,
28 | type: NodeConnectionType.AiTool,
29 | required: true,
30 | },
31 | ];
32 |
33 | // If `hasOutputParser` is undefined it must be version 1.3 or earlier so we
34 | // always add the output parser input
35 | // if (hasOutputParser === undefined || hasOutputParser === true) {
36 | // inputs.push({ displayName: 'Output Parser', type: NodeConnectionType.AiOutputParser });
37 | // }
38 | return inputs;
39 | }
40 |
41 | export class Crew implements INodeType {
42 |
43 | description: INodeTypeDescription = {
44 | displayName: 'CrewAI Crew',
45 | name: 'crewAICrew',
46 | group: ['transform'],
47 | version: 1,
48 | description: 'Configure and execute a CrewAI crew with autonomous agents, each tailored for specific roles and goals, directly within your n8n workflows. This node provides a structured approach to define agent configurations, facilitating sophisticated AI-driven task execution.',
49 | defaults: {
50 | name: 'CrewAI Crew',
51 | },
52 | inputs: `={{ ((parameter) => { ${getInputs.toString()}; return getInputs(parameter) })($parameter) }}`,
53 | outputs: ['main'],
54 | properties: [
55 | {
56 | displayName: 'Allow Delegation',
57 | name: 'allowDelegation',
58 | type: 'boolean',
59 | default: false,
60 | description: 'If true, the agent can delegate tasks to other agents when necessary.',
61 | },
62 | // Define additional properties for each aspect of CrewAI agent configuration here
63 | {
64 | displayName: 'Execution Mode',
65 | name: 'executionMode',
66 | type: 'options',
67 | options: [
68 | {
69 | name: 'Sequential',
70 | value: 'sequential',
71 | description: 'Executes tasks sequentially, one after the other.',
72 | },
73 | {
74 | name: 'Parallel',
75 | value: 'parallel',
76 | description: 'Executes tasks in parallel, based on dependencies and resources.',
77 | },
78 | // Additional execution modes can be considered here
79 | ],
80 | default: 'sequential',
81 | description: 'Choose the execution mode for CrewAI tasks. Sequential mode ensures tasks are handled one at a time, while Parallel mode enables simultaneous execution where applicable.',
82 | },
83 | ],
84 | };
85 |
86 |
87 | async execute(this: IExecuteFunctions): Promise {
88 | const items = this.getInputData();
89 |
90 | let item: INodeExecutionData;
91 | let myString: string;
92 |
93 | const agents = (await this.getInputConnectionData(
94 | NodeConnectionType.AiAgent,
95 | 0,
96 | )) as CrewAIAgent[];
97 |
98 | const tasks = (await this.getInputConnectionData(
99 | NodeConnectionType.AiTool,
100 | 0,
101 | )) as CrewAITask[];
102 |
103 | for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
104 | try {
105 | myString = this.getNodeParameter('myString', itemIndex, '') as string;
106 | item = items[itemIndex];
107 | myString = myString;
108 |
109 |
110 |
111 | console.log('running');
112 | let jsonParams: any = {};
113 | jsonParams.agents = agents;
114 | jsonParams.tasks = tasks;
115 | jsonParams = JSON.stringify(jsonParams);
116 | jsonParams = jsonParams.replace(/"/g, '\\"');
117 | jsonParams = `"${jsonParams}"`;
118 |
119 | console.log(`python python/n8n-crewai.py --json ${jsonParams}`);
120 | // Calling python function
121 | const { stdout } = await execPromise(`python python/n8n-crewai.py --json ${jsonParams}`);
122 | // if (error) {
123 | // console.error(`exec error: ${error}`);
124 | // return;
125 | // }
126 |
127 | console.log(`Result from Python Script: ${stdout}`);
128 |
129 | // Assume that your Python function returns a string that you
130 | // want to add to the json object.
131 | item.json['myString3'] = stdout;
132 |
133 | } catch (error) {
134 | if (this.continueOnFail()) {
135 | items.push({json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex});
136 | } else {
137 | if (error.context) {
138 | error.context.itemIndex = itemIndex;
139 | throw error;
140 | }
141 | throw new NodeOperationError(this.getNode(), error, {
142 | itemIndex,
143 | });
144 | }
145 | }
146 | }
147 |
148 | return this.prepareOutputData(items);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/nodes/ExampleNode/ExampleNode.node.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IExecuteFunctions,
3 | INodeExecutionData,
4 | INodeType,
5 | INodeTypeDescription, NodeConnectionType,
6 | NodeOperationError,
7 | } from 'n8n-workflow';
8 |
9 | import { exec } from 'child_process';
10 | import { promisify } from 'util';
11 |
12 | const execPromise = promisify(exec);
13 |
14 | function getInputs() {
15 | const inputs = [
16 | { displayName: '', type: NodeConnectionType.Main },
17 | {
18 | displayName: 'Model',
19 | maxConnections: 20,
20 | type: NodeConnectionType.AiLanguageModel,
21 | required: true,
22 | },
23 | ];
24 |
25 | // If `hasOutputParser` is undefined it must be version 1.3 or earlier so we
26 | // always add the output parser input
27 | // if (hasOutputParser === undefined || hasOutputParser === true) {
28 | // inputs.push({ displayName: 'Output Parser', type: NodeConnectionType.AiOutputParser });
29 | // }
30 | return inputs;
31 | }
32 | export class ExampleNode implements INodeType {
33 |
34 | description: INodeTypeDescription = {
35 | displayName: 'Example Node',
36 | name: 'exampleNode',
37 | group: ['transform'],
38 | version: 1,
39 | description: 'Basic Example Node',
40 | defaults: {
41 | name: 'Example Node',
42 | },
43 | // inputs: ['main'],
44 | inputs: `={{ ((parameter) => { ${getInputs.toString()}; return getInputs(parameter) })($parameter) }}`,
45 | outputs: ['main'],
46 | properties: [
47 | {
48 | displayName: 'My String',
49 | name: 'myString',
50 | type: 'string',
51 | default: '',
52 | placeholder: 'Placeholder value',
53 | description: 'The description text',
54 | },
55 | ],
56 | };
57 |
58 |
59 |
60 | async execute(this: IExecuteFunctions): Promise {
61 | const items = this.getInputData();
62 |
63 | let item: INodeExecutionData;
64 | let myString: string;
65 |
66 | for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
67 | try {
68 | myString = this.getNodeParameter('myString', itemIndex, '') as string;
69 | item = items[itemIndex];
70 | myString = myString;
71 |
72 | // Calling python function
73 | const { stdout } = await execPromise('python sample.py');
74 | // if (error) {
75 | // console.error(`exec error: ${error}`);
76 | // return;
77 | // }
78 |
79 | console.log(`Result from Python Script: ${stdout}`);
80 |
81 | // Assume that your Python function returns a string that you
82 | // want to add to the json object.
83 | item.json['myString3'] = stdout;
84 |
85 | } catch (error) {
86 | if (this.continueOnFail()) {
87 | items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
88 | } else {
89 | if (error.context) {
90 | error.context.itemIndex = itemIndex;
91 | throw error;
92 | }
93 | throw new NodeOperationError(this.getNode(), error, {
94 | itemIndex,
95 | });
96 | }
97 | }
98 | }
99 |
100 | return this.prepareOutputData(items);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/nodes/ExampleNode/sample.tss:
--------------------------------------------------------------------------------
1 |
2 | description: INodeTypeDescription = {
3 | displayName: 'Basic LLM Chain',
4 | name: 'chainLlm',
5 | icon: 'fa:link',
6 | group: ['transform'],
7 | version: [1, 1.1, 1.2, 1.3, 1.4],
8 | description: 'A simple chain to prompt a large language model',
9 | defaults: {
10 | name: 'Basic LLM Chain',
11 | color: '#909298',
12 | },
13 | codex: {
14 | alias: ['LangChain'],
15 | categories: ['AI'],
16 | subcategories: {
17 | AI: ['Chains'],
18 | },
19 | resources: {
20 | primaryDocumentation: [
21 | {
22 | url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm/',
23 | },
24 | ],
25 | },
26 | },
27 | inputs: `={{ ((parameter) => { ${getInputs.toString()}; return getInputs(parameter) })($parameter) }}`,
28 | outputs: [NodeConnectionType.Main],
29 | credentials: [],
30 | properties: [
31 | getTemplateNoticeField(1978),
32 | {
33 | displayName: 'Prompt',
34 | name: 'prompt',
35 | type: 'string',
36 | required: true,
37 | default: '={{ $json.input }}',
38 | displayOptions: {
39 | show: {
40 | '@version': [1],
41 | },
42 | },
43 | },
44 | {
45 | displayName: 'Prompt',
46 | name: 'prompt',
47 | type: 'string',
48 | required: true,
49 | default: '={{ $json.chat_input }}',
50 | displayOptions: {
51 | show: {
52 | '@version': [1.1, 1.2],
53 | },
54 | },
55 | },
56 | {
57 | displayName: 'Prompt',
58 | name: 'prompt',
59 | type: 'string',
60 | required: true,
61 | default: '={{ $json.chatInput }}',
62 | displayOptions: {
63 | show: {
64 | '@version': [1.3],
65 | },
66 | },
67 | },
68 | {
69 | displayName: 'Prompt',
70 | name: 'promptType',
71 | type: 'options',
72 | options: [
73 | {
74 | // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
75 | name: 'Take from previous node automatically',
76 | value: 'auto',
77 | description: 'Looks for an input field called chatInput',
78 | },
79 | {
80 | // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
81 | name: 'Define below',
82 | value: 'define',
83 | description:
84 | 'Use an expression to reference data in previous nodes or enter static text',
85 | },
86 | ],
87 | displayOptions: {
88 | hide: {
89 | '@version': [1, 1.1, 1.2, 1.3],
90 | },
91 | },
92 | default: 'auto',
93 | },
94 | {
95 | displayName: 'Text',
96 | name: 'text',
97 | type: 'string',
98 | required: true,
99 | default: '',
100 | placeholder: 'e.g. Hello, how can you help me?',
101 | typeOptions: {
102 | rows: 2,
103 | },
104 | displayOptions: {
105 | show: {
106 | promptType: ['define'],
107 | },
108 | },
109 | },
110 | {
111 | displayName: 'Require Specific Output Format',
112 | name: 'hasOutputParser',
113 | type: 'boolean',
114 | default: false,
115 | noDataExpression: true,
116 | displayOptions: {
117 | hide: {
118 | '@version': [1, 1.1, 1.3],
119 | },
120 | },
121 | },
122 | {
123 | displayName: 'Chat Messages (if Using a Chat Model)',
124 | name: 'messages',
125 | type: 'fixedCollection',
126 | typeOptions: {
127 | multipleValues: true,
128 | },
129 | default: {},
130 | placeholder: 'Add prompt',
131 | options: [
132 | {
133 | name: 'messageValues',
134 | displayName: 'Prompt',
135 | values: [
136 | {
137 | displayName: 'Type Name or ID',
138 | name: 'type',
139 | type: 'options',
140 | options: [
141 | {
142 | name: 'AI',
143 | value: AIMessagePromptTemplate.lc_name(),
144 | },
145 | {
146 | name: 'System',
147 | value: SystemMessagePromptTemplate.lc_name(),
148 | },
149 | {
150 | name: 'User',
151 | value: HumanMessagePromptTemplate.lc_name(),
152 | },
153 | ],
154 | default: SystemMessagePromptTemplate.lc_name(),
155 | },
156 | {
157 | displayName: 'Message Type',
158 | name: 'messageType',
159 | type: 'options',
160 | displayOptions: {
161 | show: {
162 | type: [HumanMessagePromptTemplate.lc_name()],
163 | },
164 | },
165 | options: [
166 | {
167 | name: 'Text',
168 | value: 'text',
169 | description: 'Simple text message',
170 | },
171 | {
172 | name: 'Image (Binary)',
173 | value: 'imageBinary',
174 | description: 'Process the binary input from the previous node',
175 | },
176 | {
177 | name: 'Image (URL)',
178 | value: 'imageUrl',
179 | description: 'Process the image from the specified URL',
180 | },
181 | ],
182 | default: 'text',
183 | },
184 | {
185 | displayName: 'Image Data Field Name',
186 | name: 'binaryImageDataKey',
187 | type: 'string',
188 | default: 'data',
189 | required: true,
190 | description:
191 | 'The name of the field in the chain’s input that contains the binary image file to be processed',
192 | displayOptions: {
193 | show: {
194 | messageType: ['imageBinary'],
195 | },
196 | },
197 | },
198 | {
199 | displayName: 'Image URL',
200 | name: 'imageUrl',
201 | type: 'string',
202 | default: '',
203 | required: true,
204 | description: 'URL to the image to be processed',
205 | displayOptions: {
206 | show: {
207 | messageType: ['imageUrl'],
208 | },
209 | },
210 | },
211 | {
212 | displayName: 'Image Details',
213 | description:
214 | 'Control how the model processes the image and generates its textual understanding',
215 | name: 'imageDetail',
216 | type: 'options',
217 | displayOptions: {
218 | show: {
219 | type: [HumanMessagePromptTemplate.lc_name()],
220 | messageType: ['imageBinary', 'imageUrl'],
221 | },
222 | },
223 | options: [
224 | {
225 | name: 'Auto',
226 | value: 'auto',
227 | description:
228 | 'Model will use the auto setting which will look at the image input size and decide if it should use the low or high setting',
229 | },
230 | {
231 | name: 'Low',
232 | value: 'low',
233 | description:
234 | 'The model will receive a low-res 512px x 512px version of the image, and represent the image with a budget of 65 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail.',
235 | },
236 | {
237 | name: 'High',
238 | value: 'high',
239 | description:
240 | 'Allows the model to see the low res image and then creates detailed crops of input images as 512px squares based on the input image size. Each of the detailed crops uses twice the token budget (65 tokens) for a total of 129 tokens.',
241 | },
242 | ],
243 | default: 'auto',
244 | },
245 |
246 | {
247 | displayName: 'Message',
248 | name: 'message',
249 | type: 'string',
250 | required: true,
251 | displayOptions: {
252 | hide: {
253 | messageType: ['imageBinary', 'imageUrl'],
254 | },
255 | },
256 | default: '',
257 | },
258 | ],
259 | },
260 | ],
261 | },
262 | {
263 | displayName: `Connect an output parser on the canvas to specify the output format you require`,
264 | name: 'notice',
265 | type: 'notice',
266 | default: '',
267 | displayOptions: {
268 | show: {
269 | hasOutputParser: [true],
270 | },
271 | },
272 | },
273 | ],
274 | };
275 |
--------------------------------------------------------------------------------
/nodes/HttpBin/HttpBin.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "node": "n8n-nodes-base.httpbin",
3 | "nodeVersion": "1.0",
4 | "codexVersion": "1.0",
5 | "categories": ["Development", "Developer Tools"],
6 | "resources": {
7 | "credentialDocumentation": [
8 | {
9 | "url": "http://httpbin.org/#/Auth/get_bearer"
10 | }
11 | ],
12 | "primaryDocumentation": [
13 | {
14 | "url": "http://httpbin.org/"
15 | }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/nodes/HttpBin/HttpBin.node.ts:
--------------------------------------------------------------------------------
1 | import { INodeType, INodeTypeDescription } from 'n8n-workflow';
2 | import { httpVerbFields, httpVerbOperations } from './HttpVerbDescription';
3 |
4 | export class HttpBin implements INodeType {
5 | description: INodeTypeDescription = {
6 | displayName: 'HttpBin',
7 | name: 'httpBin',
8 | icon: 'file:httpbin.svg',
9 | group: ['transform'],
10 | version: 1,
11 | subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
12 | description: 'Interact with HttpBin API',
13 | defaults: {
14 | name: 'HttpBin',
15 | },
16 | inputs: ['main'],
17 | outputs: ['main'],
18 | credentials: [
19 | {
20 | name: 'httpbinApi',
21 | required: false,
22 | },
23 | ],
24 | requestDefaults: {
25 | baseURL: 'https://httpbin.org',
26 | url: '',
27 | headers: {
28 | Accept: 'application/json',
29 | 'Content-Type': 'application/json',
30 | },
31 | },
32 | /**
33 | * In the properties array we have two mandatory options objects required
34 | *
35 | * [Resource & Operation]
36 | *
37 | * https://docs.n8n.io/integrations/creating-nodes/code/create-first-node/#resources-and-operations
38 | *
39 | * In our example, the operations are separated into their own file (HTTPVerbDescription.ts)
40 | * to keep this class easy to read.
41 | *
42 | */
43 | properties: [
44 | {
45 | displayName: 'Resource',
46 | name: 'resource',
47 | type: 'options',
48 | noDataExpression: true,
49 | options: [
50 | {
51 | name: 'HTTP Verb',
52 | value: 'httpVerb',
53 | },
54 | ],
55 | default: 'httpVerb',
56 | },
57 |
58 | ...httpVerbOperations,
59 | ...httpVerbFields,
60 | ],
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/nodes/HttpBin/HttpVerbDescription.ts:
--------------------------------------------------------------------------------
1 | import { INodeProperties } from 'n8n-workflow';
2 |
3 | // When the resource `httpVerb` is selected, this `operation` parameter will be shown.
4 | export const httpVerbOperations: INodeProperties[] = [
5 | {
6 | displayName: 'Operation',
7 | name: 'operation',
8 | type: 'options',
9 | noDataExpression: true,
10 |
11 | displayOptions: {
12 | show: {
13 | resource: ['httpVerb'],
14 | },
15 | },
16 | options: [
17 | {
18 | name: 'GET',
19 | value: 'get',
20 | action: 'Perform a GET request',
21 | routing: {
22 | request: {
23 | method: 'GET',
24 | url: '/get',
25 | },
26 | },
27 | },
28 | {
29 | name: 'DELETE',
30 | value: 'delete',
31 | action: 'Perform a DELETE request',
32 | routing: {
33 | request: {
34 | method: 'DELETE',
35 | url: '/delete',
36 | },
37 | },
38 | },
39 | ],
40 | default: 'get',
41 | },
42 | ];
43 |
44 | // Here we define what to show when the `get` operation is selected.
45 | // We do that by adding `operation: ["get"]` to `displayOptions.show`
46 | const getOperation: INodeProperties[] = [
47 | {
48 | displayName: 'Type of Data',
49 | name: 'typeofData',
50 | default: 'queryParameter',
51 | description: 'Select type of data to send [Query Parameters]',
52 | displayOptions: {
53 | show: {
54 | resource: ['httpVerb'],
55 | operation: ['get'],
56 | },
57 | },
58 | type: 'options',
59 | options: [
60 | {
61 | name: 'Query',
62 | value: 'queryParameter',
63 | },
64 | ],
65 | required: true,
66 | },
67 | {
68 | displayName: 'Query Parameters',
69 | name: 'arguments',
70 | default: {},
71 | description: "The request's query parameters",
72 | displayOptions: {
73 | show: {
74 | resource: ['httpVerb'],
75 | operation: ['get'],
76 | },
77 | },
78 | options: [
79 | {
80 | name: 'keyvalue',
81 | displayName: 'Key:Value',
82 | values: [
83 | {
84 | displayName: 'Key',
85 | name: 'key',
86 | type: 'string',
87 | default: '',
88 | required: true,
89 | description: 'Key of query parameter',
90 | },
91 | {
92 | displayName: 'Value',
93 | name: 'value',
94 | type: 'string',
95 | default: '',
96 | routing: {
97 | send: {
98 | property: '={{$parent.key}}',
99 | type: 'query',
100 | },
101 | },
102 | required: true,
103 | description: 'Value of query parameter',
104 | },
105 | ],
106 | },
107 | ],
108 | type: 'fixedCollection',
109 | typeOptions: {
110 | multipleValues: true,
111 | },
112 | },
113 | ];
114 |
115 | // Here we define what to show when the DELETE Operation is selected.
116 | // We do that by adding `operation: ["delete"]` to `displayOptions.show`
117 | const deleteOperation: INodeProperties[] = [
118 | {
119 | displayName: 'Type of Data',
120 | name: 'typeofData',
121 | default: 'queryParameter',
122 | description: 'Select type of data to send [Query Parameter Arguments, JSON-Body]',
123 | displayOptions: {
124 | show: {
125 | resource: ['httpVerb'],
126 | operation: ['delete'],
127 | },
128 | },
129 | options: [
130 | {
131 | name: 'Query',
132 | value: 'queryParameter',
133 | },
134 | {
135 | name: 'JSON',
136 | value: 'jsonData',
137 | },
138 | ],
139 | required: true,
140 | type: 'options',
141 | },
142 | {
143 | displayName: 'Query Parameters',
144 | name: 'arguments',
145 | default: {},
146 | description: "The request's query parameters",
147 | displayOptions: {
148 | show: {
149 | resource: ['httpVerb'],
150 | operation: ['delete'],
151 | typeofData: ['queryParameter'],
152 | },
153 | },
154 | options: [
155 | {
156 | name: 'keyvalue',
157 | displayName: 'Key:Value',
158 | values: [
159 | {
160 | displayName: 'Key',
161 | name: 'key',
162 | type: 'string',
163 | default: '',
164 | required: true,
165 | description: 'Key of query parameter',
166 | },
167 | {
168 | displayName: 'Value',
169 | name: 'value',
170 | type: 'string',
171 | default: '',
172 | routing: {
173 | send: {
174 | property: '={{$parent.key}}',
175 | type: 'query',
176 | },
177 | },
178 | required: true,
179 | description: 'Value of query parameter',
180 | },
181 | ],
182 | },
183 | ],
184 | type: 'fixedCollection',
185 | typeOptions: {
186 | multipleValues: true,
187 | },
188 | },
189 | {
190 | displayName: 'JSON Object',
191 | name: 'arguments',
192 | default: {},
193 | description: "The request's JSON properties",
194 | displayOptions: {
195 | show: {
196 | resource: ['httpVerb'],
197 | operation: ['delete'],
198 | typeofData: ['jsonData'],
199 | },
200 | },
201 | options: [
202 | {
203 | name: 'keyvalue',
204 | displayName: 'Key:Value',
205 | values: [
206 | {
207 | displayName: 'Key',
208 | name: 'key',
209 | type: 'string',
210 | default: '',
211 | required: true,
212 | description: 'Key of JSON property',
213 | },
214 | {
215 | displayName: 'Value',
216 | name: 'value',
217 | type: 'string',
218 | default: '',
219 | routing: {
220 | send: {
221 | property: '={{$parent.key}}',
222 | type: 'body',
223 | },
224 | },
225 | required: true,
226 | description: 'Value of JSON property',
227 | },
228 | ],
229 | },
230 | ],
231 | type: 'fixedCollection',
232 | typeOptions: {
233 | multipleValues: true,
234 | },
235 | },
236 | ];
237 |
238 | export const httpVerbFields: INodeProperties[] = [
239 | /* -------------------------------------------------------------------------- */
240 | /* httpVerb:get */
241 | /* -------------------------------------------------------------------------- */
242 | ...getOperation,
243 |
244 | /* -------------------------------------------------------------------------- */
245 | /* httpVerb:delete */
246 | /* -------------------------------------------------------------------------- */
247 | ...deleteOperation,
248 | ];
249 |
--------------------------------------------------------------------------------
/nodes/HttpBin/httpbin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/nodes/Task/Task.node.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable n8n-nodes-base/node-dirname-against-convention */
2 | import type {
3 | IExecuteFunctions,
4 | INodeType,
5 | INodeTypeDescription,
6 | SupplyData,
7 | } from 'n8n-workflow';
8 | import {NodeConnectionType} from 'n8n-workflow';
9 |
10 | // import { Task } from '@langchain/crewai'; // Assuming this is the path to Task class
11 |
12 | export class Task implements INodeType {
13 | description: INodeTypeDescription = {
14 | displayName: 'CrewAI Task',
15 | name: 'taskNode',
16 | icon: 'fa:tasks',
17 | group: ['transform'],
18 | version: 1,
19 | description: 'Configure a CrewAI task',
20 | defaults: {
21 | name: 'CrewAI Task',
22 | },
23 | codex: {
24 | categories: ['AI'],
25 | subcategories: {
26 | AI: ['Tasks'],
27 | },
28 | resources: {
29 | primaryDocumentation: [
30 | {
31 | url: 'https://docs.crewai.example.com/tasks',
32 | },
33 | ],
34 | },
35 | },
36 | inputs: [], // Assuming a task might be related to an agent
37 | outputs: [NodeConnectionType.AiTool],
38 | outputNames: ['Task'],
39 | properties: [
40 | {
41 | displayName: 'Description',
42 | name: 'description',
43 | type: 'string',
44 | default: '',
45 | placeholder: 'Identify the next big trend in {topic}.',
46 | typeOptions: {
47 | rows: 4,
48 | },
49 | },
50 | {
51 | displayName: 'Expected Output',
52 | name: 'expectedOutput',
53 | type: 'string',
54 | default: '',
55 | placeholder: 'A comprehensive report on the latest AI trends.',
56 | typeOptions: {
57 | rows: 2,
58 | },
59 | },
60 | {
61 | displayName: 'Agent',
62 | name: 'agent',
63 | type: 'string',
64 | default: '',
65 | placeholder: 'Marketing Researcher - The name of your agent'
66 | },
67 | // Here you might include properties related to tool association, async execution, output file customization, etc.
68 | // Additional properties can be added as required to match the Task class signature in CrewAI.
69 | ],
70 | };
71 |
72 | async supplyData(this: IExecuteFunctions, itemIndex: number): Promise {
73 | const description = this.getNodeParameter('description', itemIndex) as string;
74 | const expectedOutput = this.getNodeParameter('expectedOutput', itemIndex) as string;
75 | const agent = this.getNodeParameter('agent', itemIndex) as string;
76 |
77 | // Assuming the Task class from CrewAI has a constructor that matches these parameters
78 | // const task = new Task({
79 | // description,
80 | // expectedOutput,
81 | // // Additional parameters as needed
82 | // });
83 |
84 | // Depending on the CrewAI Task implementation, you might directly execute the task or return the task configuration
85 | return {
86 | response: {
87 | description,
88 | expected_output: expectedOutput,
89 | agent
90 | }
91 | };
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/nodes/Task/interface.ts:
--------------------------------------------------------------------------------
1 | export interface CrewAITask {
2 | description: string;
3 | expected_output: string;
4 | agent: string;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "n8n-nodes-crewai",
3 | "version": "0.1.0",
4 | "description": "",
5 | "keywords": [
6 | "n8n-community-node-package"
7 | ],
8 | "license": "MIT",
9 | "homepage": "",
10 | "author": {
11 | "name": "",
12 | "email": ""
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/<...>/n8n-nodes-<...>.git"
17 | },
18 | "main": "index.js",
19 | "scripts": {
20 | "build": "tsc && gulp build:icons",
21 | "build-and-run-n8n": "tsc && gulp build:icons && n8n start",
22 | "dev": "tsc --watch",
23 | "format": "prettier nodes credentials --write",
24 | "lint": "eslint nodes credentials package.json",
25 | "lintfix": "eslint nodes credentials package.json --fix",
26 | "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json"
27 | },
28 | "files": [
29 | "dist"
30 | ],
31 | "n8n": {
32 | "n8nNodesApiVersion": 1,
33 | "credentials": [
34 | "dist/credentials/ExampleCredentialsApi.credentials.js",
35 | "dist/credentials/HttpBinApi.credentials.js"
36 | ],
37 | "nodes": [
38 | "dist/nodes/Crew/Crew.node.js",
39 | "dist/nodes/Agent/Agent.node.js",
40 | "dist/nodes/Task/Task.node.js",
41 | "dist/nodes/ExampleNode/ExampleNode.node.js",
42 | "dist/nodes/HttpBin/HttpBin.node.js"
43 | ]
44 | },
45 | "devDependencies": {
46 | "@types/express": "^4.17.6",
47 | "@types/request-promise-native": "~1.0.15",
48 | "@typescript-eslint/parser": "~5.45",
49 | "eslint-plugin-n8n-nodes-base": "^1.11.0",
50 | "gulp": "^4.0.2",
51 | "n8n-core": "*",
52 | "n8n-workflow": "*",
53 | "prettier": "^2.7.1",
54 | "typescript": "~4.8.4"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/python/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # GitHub Copilot persisted chat sessions
5 | /copilot/chatSessions
6 |
--------------------------------------------------------------------------------
/python/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/python/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/python/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/python/.idea/python.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/python/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/python/n8n-crewai.py:
--------------------------------------------------------------------------------
1 | import json
2 | import argparse
3 |
4 | from crewai import Agent
5 | from crewai import Crew
6 | from crewai import Task
7 | from langchain_openai import ChatOpenAI
8 |
9 | llm = ChatOpenAI(
10 | model="gpt-3.5-turbo-0125",
11 | api_key=""
12 | )
13 | # Function to create agents from JSON definitions
14 | def create_agents(agents_json):
15 |
16 | agents = []
17 | for agent_def in agents_json:
18 | agent = Agent(
19 | role=agent_def["role"],
20 | goal=agent_def["goal"],
21 | verbose=agent_def.get("verbose", False),
22 | memory=agent_def.get("memory", False),
23 | backstory=agent_def["backstory"],
24 | llm=llm
25 | )
26 | agents.append(agent)
27 | return agents
28 |
29 |
30 | # Function to create tasks from JSON definitions
31 | def create_tasks(tasks_json, agents):
32 | print('tasks')
33 | print(tasks_json)
34 | tasks = []
35 | for task_def in tasks_json:
36 | # Find the agent for this task
37 | print('task_def')
38 | print(task_def)
39 | agent = next((agent for agent in agents if agent.role == task_def["agent"]), None)
40 | if not agent:
41 | print(f"No agent found for the role: {task_def['agent']}")
42 | continue
43 |
44 | task = Task(
45 | description=task_def["description"],
46 | expected_output=task_def["expected_output"],
47 | agent=agent
48 | )
49 | tasks.append(task)
50 | return tasks
51 |
52 |
53 | # Main function to parse arguments, load JSON, and create agents and tasks
54 | def main():
55 | parser = argparse.ArgumentParser(description="Process CrewAI agent and task definitions.")
56 | parser.add_argument('--json', type=str, help="JSON string with agent and task definitions")
57 | parser.add_argument('--file', type=str, help="Path to a JSON file with agent and task definitions")
58 |
59 | args = parser.parse_args()
60 |
61 | if args.json:
62 | print(args.json)
63 | definitions = json.loads(args.json)
64 | elif args.file:
65 | with open(args.file, 'r') as json_file:
66 | definitions = json.load(json_file)
67 | else:
68 | raise ValueError("Either --json or --file must be provided.")
69 |
70 | agents = create_agents(definitions["agents"])
71 | tasks = create_tasks(definitions["tasks"], agents)
72 |
73 | # Example of how you might initialize a crew with these agents and tasks
74 | crew = Crew(agents=agents, tasks=tasks)
75 | print(f"Crew initialized with {len(agents)} agents and {len(tasks)} tasks.")
76 | crew.kickoff()
77 |
78 |
79 | if __name__ == "__main__":
80 | main()
81 |
--------------------------------------------------------------------------------
/python/sample_request_01.json:
--------------------------------------------------------------------------------
1 | {
2 | "agents": [
3 | {
4 | "role": "Senior Researcher",
5 | "goal": "Investigate cutting-edge AI technologies",
6 | "verbose": true,
7 | "memory": true,
8 | "backstory": "With an extensive background in AI research, this agent is always on the lookout for the next big breakthrough."
9 | },
10 | {
11 | "role": "Content Writer",
12 | "goal": "Craft engaging and informative content on AI advancements",
13 | "verbose": true,
14 | "memory": true,
15 | "backstory": "A talented writer skilled in making complex topics accessible and interesting to a wide audience."
16 | }
17 | ],
18 | "tasks": [
19 | {
20 | "description": "Identify and analyze the latest trends in AI research.",
21 | "expected_output": "A detailed report highlighting the most promising AI technologies.",
22 | "agent_role": "Senior Researcher"
23 | },
24 | {
25 | "description": "Write an article summarizing recent AI advancements for our blog.",
26 | "expected_output": "An engaging 1000-word article suitable for publication.",
27 | "agent_role": "Content Writer"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/python/sample_request_01.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "agents": [
3 | {
4 | "role": "Mathematician",
5 | "goal": "Sum numbers",
6 | "verbose": true,
7 | "memory": true,
8 | "backstory": "A dedicated mathematician who has diverse knowledge and experience in various fields of mathematics."
9 | }
10 | ],
11 | "tasks": [
12 | {
13 | "description": "Sum two numbers, 3 and 4",
14 | "expected_output": "The result of operation",
15 | "agent_role": "Mathematician"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/sample.py:
--------------------------------------------------------------------------------
1 | print('hello world')
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "target": "es2019",
7 | "lib": ["es2019", "es2020", "es2022.error"],
8 | "removeComments": true,
9 | "useUnknownInCatchVariables": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitAny": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "preserveConstEnums": true,
16 | "esModuleInterop": true,
17 | "resolveJsonModule": true,
18 | "incremental": true,
19 | "declaration": true,
20 | "sourceMap": true,
21 | "skipLibCheck": true,
22 | "outDir": "./dist/",
23 | },
24 | "include": [
25 | "credentials/**/*",
26 | "nodes/**/*",
27 | "nodes/**/*.json",
28 | "package.json",
29 | ],
30 | }
31 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "linterOptions": {
3 | "exclude": [
4 | "node_modules/**/*"
5 | ]
6 | },
7 | "defaultSeverity": "error",
8 | "jsRules": {},
9 | "rules": {
10 | "array-type": [
11 | true,
12 | "array-simple"
13 | ],
14 | "arrow-return-shorthand": true,
15 | "ban": [
16 | true,
17 | {
18 | "name": "Array",
19 | "message": "tsstyle#array-constructor"
20 | }
21 | ],
22 | "ban-types": [
23 | true,
24 | [
25 | "Object",
26 | "Use {} instead."
27 | ],
28 | [
29 | "String",
30 | "Use 'string' instead."
31 | ],
32 | [
33 | "Number",
34 | "Use 'number' instead."
35 | ],
36 | [
37 | "Boolean",
38 | "Use 'boolean' instead."
39 | ]
40 | ],
41 | "class-name": true,
42 | "curly": [
43 | true,
44 | "ignore-same-line"
45 | ],
46 | "forin": true,
47 | "jsdoc-format": true,
48 | "label-position": true,
49 | "indent": [
50 | true,
51 | "tabs",
52 | 2
53 | ],
54 | "member-access": [
55 | true,
56 | "no-public"
57 | ],
58 | "new-parens": true,
59 | "no-angle-bracket-type-assertion": true,
60 | "no-any": true,
61 | "no-arg": true,
62 | "no-conditional-assignment": true,
63 | "no-construct": true,
64 | "no-debugger": true,
65 | "no-default-export": true,
66 | "no-duplicate-variable": true,
67 | "no-inferrable-types": true,
68 | "ordered-imports": [
69 | true,
70 | {
71 | "import-sources-order": "any",
72 | "named-imports-order": "case-insensitive"
73 | }
74 | ],
75 | "no-namespace": [
76 | true,
77 | "allow-declarations"
78 | ],
79 | "no-reference": true,
80 | "no-string-throw": true,
81 | "no-unused-expression": true,
82 | "no-var-keyword": true,
83 | "object-literal-shorthand": true,
84 | "only-arrow-functions": [
85 | true,
86 | "allow-declarations",
87 | "allow-named-functions"
88 | ],
89 | "prefer-const": true,
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always",
94 | "ignore-bound-class-methods"
95 | ],
96 | "switch-default": true,
97 | "trailing-comma": [
98 | true,
99 | {
100 | "multiline": {
101 | "objects": "always",
102 | "arrays": "always",
103 | "functions": "always",
104 | "typeLiterals": "ignore"
105 | },
106 | "esSpecCompliant": true
107 | }
108 | ],
109 | "triple-equals": [
110 | true,
111 | "allow-null-check"
112 | ],
113 | "use-isnan": true,
114 | "quotes": [
115 | "error",
116 | "single"
117 | ],
118 | "variable-name": [
119 | true,
120 | "check-format",
121 | "ban-keywords",
122 | "allow-leading-underscore",
123 | "allow-trailing-underscore"
124 | ]
125 | },
126 | "rulesDirectory": []
127 | }
128 |
--------------------------------------------------------------------------------