n8n integration (#1940)
This commit is contained in:
20
integrations/n8n/.editorconfig
Normal file
20
integrations/n8n/.editorconfig
Normal file
@@ -0,0 +1,20 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
58
integrations/n8n/.eslintrc.js
Normal file
58
integrations/n8n/.eslintrc.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
|
||||
parser: "@typescript-eslint/parser",
|
||||
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
sourceType: "module",
|
||||
extraFileExtensions: [".json"],
|
||||
},
|
||||
|
||||
ignorePatterns: [
|
||||
".eslintrc.js",
|
||||
"**/*.js",
|
||||
"**/node_modules/**",
|
||||
"**/dist/**",
|
||||
],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ["package.json"],
|
||||
plugins: ["eslint-plugin-n8n-nodes-base"],
|
||||
extends: ["plugin:n8n-nodes-base/community"],
|
||||
rules: {
|
||||
"n8n-nodes-base/community-package-json-name-still-default": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["./credentials/**/*.ts"],
|
||||
plugins: ["eslint-plugin-n8n-nodes-base"],
|
||||
extends: ["plugin:n8n-nodes-base/credentials"],
|
||||
rules: {
|
||||
"n8n-nodes-base/cred-class-field-documentation-url-missing": "off",
|
||||
"n8n-nodes-base/cred-class-field-documentation-url-miscased": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["./nodes/**/*.ts"],
|
||||
plugins: ["eslint-plugin-n8n-nodes-base"],
|
||||
extends: ["plugin:n8n-nodes-base/nodes"],
|
||||
rules: {
|
||||
"n8n-nodes-base/node-execute-block-missing-continue-on-fail": "off",
|
||||
"n8n-nodes-base/node-resource-description-filename-against-convention":
|
||||
"off",
|
||||
"n8n-nodes-base/node-param-fixed-collection-type-unsorted-items": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
16
integrations/n8n/.eslintrc.prepublish.js
Normal file
16
integrations/n8n/.eslintrc.prepublish.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: "./.eslintrc.js",
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ["package.json"],
|
||||
plugins: ["eslint-plugin-n8n-nodes-base"],
|
||||
rules: {
|
||||
"n8n-nodes-base/community-package-json-name-still-default": "error",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
8
integrations/n8n/.gitignore
vendored
Normal file
8
integrations/n8n/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.tmp
|
||||
tmp
|
||||
dist
|
||||
npm-debug.log*
|
||||
yarn.lock
|
||||
.vscode/launch.json
|
||||
4
integrations/n8n/.npmignore
Normal file
4
integrations/n8n/.npmignore
Normal file
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
node_modules/
|
||||
dist/
|
||||
51
integrations/n8n/.prettierrc.js
Normal file
51
integrations/n8n/.prettierrc.js
Normal file
@@ -0,0 +1,51 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#semicolons
|
||||
*/
|
||||
semi: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#trailing-commas
|
||||
*/
|
||||
trailingComma: "all",
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#bracket-spacing
|
||||
*/
|
||||
bracketSpacing: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#tabs
|
||||
*/
|
||||
useTabs: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#tab-width
|
||||
*/
|
||||
tabWidth: 2,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#arrow-function-parentheses
|
||||
*/
|
||||
arrowParens: "always",
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#quotes
|
||||
*/
|
||||
singleQuote: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#quote-props
|
||||
*/
|
||||
quoteProps: "as-needed",
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#end-of-line
|
||||
*/
|
||||
endOfLine: "lf",
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#print-width
|
||||
*/
|
||||
printWidth: 100,
|
||||
};
|
||||
90
integrations/n8n/CODE_OF_CONDUCT.md
Normal file
90
integrations/n8n/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,90 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||
|
||||
- [Contributor Covenant Code of Conduct](#contributor-covenant-code-of-conduct)
|
||||
- [Our Pledge](#our-pledge)
|
||||
- [Our Standards](#our-standards)
|
||||
- [Our Responsibilities](#our-responsibilities)
|
||||
- [Scope](#scope)
|
||||
- [Enforcement](#enforcement)
|
||||
- [Attribution](#attribution)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at jan@n8n.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
19
integrations/n8n/LICENSE.md
Normal file
19
integrations/n8n/LICENSE.md
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright 2022 n8n
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
45
integrations/n8n/README.md
Normal file
45
integrations/n8n/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||
|
||||
- [n8n-nodes-skyvern](#n8n-nodes-skyvern)
|
||||
- [Installation](#installation)
|
||||
- [Operations](#operations)
|
||||
- [Credentials](#credentials)
|
||||
- [Resources](#resources)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
# n8n-nodes-skyvern
|
||||
|
||||
This is an n8n community node. It lets you use [Skyvern](https://www.skyvern.com/) in your n8n workflows.
|
||||
|
||||
[Skyvern](https://www.skyvern.com/) is an [open-source](https://github.com/Skyvern-AI/skyvern) product. It automates browser-based workflows using LLMs and computer vision. It provides a simple API endpoint to fully automate manual workflows on a large number of websites, replacing brittle or unreliable automation solutions.
|
||||
|
||||
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
|
||||
|
||||
[Installation](#installation)
|
||||
[Operations](#operations)
|
||||
[Credentials](#credentials)
|
||||
[Resources](#resources)
|
||||
|
||||
## Installation
|
||||
|
||||
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
|
||||
|
||||
## Operations
|
||||
|
||||
- Task: interact with Skyvern task
|
||||
- Workflow: interact with Skyvern workflow
|
||||
|
||||
## Credentials
|
||||
|
||||
- API Token: Skyvern API token. You can get it from the Skyvern UI
|
||||
- Base URL: The endpoint for your Skyvern service. Default is Skyvern Cloud Service, you can also set it to your self-host Skyvern.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
|
||||
* [Skyvern documentation](https://docs.skyvern.com/introduction)
|
||||
|
||||
38
integrations/n8n/credentials/SkyvernApi.credentials.ts
Normal file
38
integrations/n8n/credentials/SkyvernApi.credentials.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
IAuthenticateGeneric,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class SkyvernApi implements ICredentialType {
|
||||
name = 'skyvernApi';
|
||||
displayName = 'Skyvern API';
|
||||
// Uses the link to this tutorial as an example
|
||||
// Replace with your own docs links when building your own nodes
|
||||
documentationUrl = 'https://docs.skyvern.ai/';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string',
|
||||
typeOptions: { password: true },
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Base URL',
|
||||
name: 'baseUrl',
|
||||
type: 'string',
|
||||
default: 'https://api.skyvern.com',
|
||||
placeholder: 'https://api.skyvern.com',
|
||||
},
|
||||
];
|
||||
authenticate: IAuthenticateGeneric = {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
headers: {
|
||||
'x-api-key': '={{$credentials.apiKey}}',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
16
integrations/n8n/gulpfile.js
Normal file
16
integrations/n8n/gulpfile.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const path = require("path");
|
||||
const { task, src, dest } = require("gulp");
|
||||
|
||||
task("build:icons", copyIcons);
|
||||
|
||||
function copyIcons() {
|
||||
const nodeSource = path.resolve("nodes", "**", "*.{png,svg}");
|
||||
const nodeDestination = path.resolve("dist", "nodes");
|
||||
|
||||
src(nodeSource).pipe(dest(nodeDestination));
|
||||
|
||||
const credSource = path.resolve("credentials", "**", "*.{png,svg}");
|
||||
const credDestination = path.resolve("dist", "credentials");
|
||||
|
||||
return src(credSource).pipe(dest(credDestination));
|
||||
}
|
||||
0
integrations/n8n/index.js
Normal file
0
integrations/n8n/index.js
Normal file
21
integrations/n8n/nodes/Skyvern/Skyvern.node.json
Normal file
21
integrations/n8n/nodes/Skyvern/Skyvern.node.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"node": "n8n-nodes-skyvern",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Miscellaneous",
|
||||
"Utility"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": ""
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
423
integrations/n8n/nodes/Skyvern/Skyvern.node.ts
Normal file
423
integrations/n8n/nodes/Skyvern/Skyvern.node.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
import { FieldType, IDataObject, IExecuteSingleFunctions, IHttpRequestMethods, IHttpRequestOptions, ILoadOptionsFunctions, INodePropertyOptions, INodeType, INodeTypeDescription, NodeConnectionType, ResourceMapperField, ResourceMapperFields } from 'n8n-workflow';
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
export class Skyvern implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Skyvern',
|
||||
name: 'skyvern',
|
||||
icon: 'file:skyvern.png', // eslint-disable-line
|
||||
group: ['transform'],
|
||||
description: 'Node to interact with Skyvern',
|
||||
defaults: {
|
||||
name: 'Skyvern',
|
||||
},
|
||||
inputs: [NodeConnectionType.Main], // eslint-disable-line
|
||||
outputs: [NodeConnectionType.Main], // eslint-disable-line
|
||||
credentials: [
|
||||
{
|
||||
name: 'skyvernApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'Workflow',
|
||||
value: 'workflow',
|
||||
},
|
||||
|
||||
],
|
||||
default: 'task',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'taskOperation',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'dispatch',
|
||||
options: [
|
||||
{
|
||||
name: 'Dispatch a Task',
|
||||
value: 'dispatch',
|
||||
description: 'Dispatch a task to execute asynchronously',
|
||||
},
|
||||
{
|
||||
name: 'Get a Task',
|
||||
value: 'get',
|
||||
description: 'Get a task by ID',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
|
||||
url: '={{"/api/" + ($parameter["taskOptions"]["engine"] ? $parameter["taskOptions"]["engine"] : "v2") + "/tasks"}}',
|
||||
},
|
||||
send: {
|
||||
preSend: [
|
||||
async function (this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
|
||||
const taskOperation = this.getNodeParameter('taskOperation');
|
||||
if (taskOperation === "get") return requestOptions;
|
||||
|
||||
const taskOptions: IDataObject = this.getNodeParameter('taskOptions') as IDataObject;
|
||||
if (taskOptions["engine"] !== "v1") return requestOptions;
|
||||
|
||||
// trigger the generate task v1 logic
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const userPrompt = this.getNodeParameter('userPrompt');
|
||||
const response = await fetch(credentials['baseUrl'] + '/api/v1/generate/task', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: userPrompt,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to generate Task V1 failed'); // eslint-disable-line
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
requestOptions.body = {
|
||||
url: data.url,
|
||||
navigation_goal: data.navigation_goal,
|
||||
navigation_payload: data.navigation_payload,
|
||||
data_extraction_goal: data.data_extraction_goal,
|
||||
extracted_information_schema: data.extracted_information_schema,
|
||||
};
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'User Prompt',
|
||||
description: 'The prompt for Skyvern to execute',
|
||||
name: 'userPrompt',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: 'eg: Navigate to the Hacker News homepage and get the top 3 posts.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
body: {
|
||||
user_prompt: '={{$value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
description: 'The URL to navigate to',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'eg: https://news.ycombinator.com/',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
body: {
|
||||
url: '={{$value ? $value : null}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
description: 'The ID of the task',
|
||||
name: 'taskId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['get'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '={{"/api/" + ($parameter["taskOptions"]["engine"] ? $parameter["taskOptions"]["engine"] : "v2") + "/tasks/" + $value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Task Options',
|
||||
name: 'taskOptions',
|
||||
type: 'collection',
|
||||
description: 'Optional Configuration for the task',
|
||||
placeholder: 'Add Task Options',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Engine',
|
||||
name: 'engine',
|
||||
type: 'options',
|
||||
default: 'v2',
|
||||
options: [
|
||||
{
|
||||
name: 'TaskV1',
|
||||
value: 'v1',
|
||||
},
|
||||
{
|
||||
name: 'TaskV2',
|
||||
value: 'v2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Title or ID', // eslint-disable-line
|
||||
description: 'The title of the workflow. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
name: 'workflowId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getWorkflows',
|
||||
loadOptionsDependsOn: ['resource'],
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Operation',
|
||||
name: 'workflowOperation',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'get',
|
||||
options: [
|
||||
{
|
||||
name: 'Get a Workflow Run',
|
||||
value: 'get',
|
||||
description: 'Get a workflow run by ID',
|
||||
},
|
||||
{
|
||||
name: 'Dispatch a Workflow Run',
|
||||
value: 'dispatch',
|
||||
description: 'Dispatch a workflow run to execute asynchronously',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Run ID',
|
||||
description: 'The ID of the workflow run',
|
||||
name: 'workflowRunId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
workflowOperation: ['get'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
url: '={{"/api/v1/workflows/" + $parameter["workflowId"] + "/runs/" + $value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Run Parameters',
|
||||
name: 'workflowRunParameters',
|
||||
type: 'resourceMapper',
|
||||
noDataExpression: true,
|
||||
description: 'The JSON-formatted parameters to pass the workflow run to execute',
|
||||
required: true,
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
workflowOperation: ['dispatch'],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['workflowId'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getWorkflowRunParameters',
|
||||
mode: 'update',
|
||||
fieldWords: {
|
||||
singular: 'workflowRunParameter',
|
||||
plural: 'workflowRunParameters',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
url: '={{"/api/v1/workflows/" + $parameter["workflowId"] + "/run"}}',
|
||||
body: {
|
||||
data: '={{$value["value"]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
version: 1,
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getWorkflows(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const resource = this.getCurrentNodeParameter('resource') as string;
|
||||
if (resource !== 'workflow') return [];
|
||||
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const response = await fetch(credentials['baseUrl'] + '/api/v1/workflows?page_size=100', {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to get workflows failed'); // eslint-disable-line
|
||||
}
|
||||
const data = await response.json();
|
||||
return data.map((workflow: any) => ({
|
||||
name: workflow.title,
|
||||
value: workflow.workflow_permanent_id,
|
||||
}));
|
||||
},
|
||||
},
|
||||
resourceMapping: {
|
||||
async getWorkflowRunParameters(this: ILoadOptionsFunctions): Promise<ResourceMapperFields> {
|
||||
const resource = this.getCurrentNodeParameter('resource') as string;
|
||||
if (resource !== 'workflow') return { fields: [] };
|
||||
|
||||
const workflowOperation = this.getCurrentNodeParameter('workflowOperation') as string;
|
||||
if (workflowOperation !== 'dispatch') return { fields: [] };
|
||||
|
||||
const workflowId = this.getCurrentNodeParameter('workflowId') as string;
|
||||
if (!workflowId) return { fields: [] };
|
||||
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const response = await fetch(credentials['baseUrl'] + '/api/v1/workflows/' + workflowId, {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to get workflow failed'); // eslint-disable-line
|
||||
}
|
||||
const workflow = await response.json();
|
||||
const parameters: any[] = workflow.workflow_definition.parameters;
|
||||
|
||||
const fields: ResourceMapperField[] = await Promise.all(
|
||||
parameters.filter((parameter: any) => parameter.parameter_type === 'workflow' || parameter.parameter_type === 'credential')
|
||||
.map(async (parameter: any) => {
|
||||
let options: INodePropertyOptions[] | undefined = undefined;
|
||||
let parameterType: FieldType | undefined = undefined;
|
||||
if (parameter.parameter_type === 'credential') {
|
||||
const response = await fetch(credentials['baseUrl'] + '/api/v1/credentials', {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to get credentials failed'); // eslint-disable-line
|
||||
}
|
||||
const data = await response.json();
|
||||
options = data.map((credential: any) => ({
|
||||
name: credential.name,
|
||||
value: credential.credential_id,
|
||||
}));
|
||||
parameterType = 'options';
|
||||
}else{
|
||||
const parameter_type_map: Record<string, string> = {
|
||||
'string': 'string',
|
||||
'integer': 'number',
|
||||
'float': 'number',
|
||||
'boolean': 'boolean',
|
||||
'json': 'json',
|
||||
'file_url': 'url',
|
||||
}
|
||||
parameterType = parameter_type_map[parameter.workflow_parameter_type] as FieldType;
|
||||
}
|
||||
|
||||
return {
|
||||
id: parameter.key,
|
||||
displayName: parameter.key,
|
||||
defaultMatch: true,
|
||||
canBeUsedToMatch: false,
|
||||
required: parameter.default_value === undefined || parameter.default_value === null,
|
||||
display: true,
|
||||
type: parameterType,
|
||||
options: options,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// HACK: If there are no parameters, add a empty field to avoid the resource mapper from crashing
|
||||
if (fields.length === 0) {
|
||||
fields.push({
|
||||
id: 'NO_PARAMETERS',
|
||||
displayName: 'No Parameters',
|
||||
defaultMatch: false,
|
||||
canBeUsedToMatch: false,
|
||||
required: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
fields: fields,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
BIN
integrations/n8n/nodes/Skyvern/skyvern.png
Normal file
BIN
integrations/n8n/nodes/Skyvern/skyvern.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
7712
integrations/n8n/package-lock.json
generated
Normal file
7712
integrations/n8n/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
62
integrations/n8n/package.json
Normal file
62
integrations/n8n/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "n8n-nodes-skyvern",
|
||||
"version": "0.0.1",
|
||||
"description": "Skyvern Node for n8n",
|
||||
"keywords": [
|
||||
"n8n-community-node-package"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "",
|
||||
"author": {
|
||||
"name": "lawy",
|
||||
"email": "lawy@skyvern.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Skyvern-AI/skyvern.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.10",
|
||||
"pnpm": ">=9.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.4",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "rimraf dist && tsc && gulp build:icons",
|
||||
"dev": "tsc --watch",
|
||||
"format": "prettier nodes credentials --write",
|
||||
"lint": "eslint nodes credentials package.json",
|
||||
"lintfix": "eslint nodes credentials package.json --fix",
|
||||
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes credentials package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"n8n": {
|
||||
"n8nNodesApiVersion": 1,
|
||||
"credentials": [
|
||||
"dist/credentials/SkyvernApi.credentials.js"
|
||||
],
|
||||
"nodes": [
|
||||
"dist/nodes/Skyvern/Skyvern.node.js"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.10",
|
||||
"@typescript-eslint/parser": "^7.15.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
||||
"gulp": "^4.0.2",
|
||||
"prettier": "^3.3.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"n8n-workflow": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "2",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
||||
5286
integrations/n8n/pnpm-lock.yaml
generated
Normal file
5286
integrations/n8n/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
integrations/n8n/tsconfig.json
Normal file
30
integrations/n8n/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2019",
|
||||
"lib": ["es2019", "es2020", "es2022.error"],
|
||||
"removeComments": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"preserveConstEnums": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"incremental": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist/",
|
||||
},
|
||||
"include": [
|
||||
"credentials/**/*",
|
||||
"nodes/**/*",
|
||||
"nodes/**/*.json",
|
||||
"package.json",
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user