Merge pull request #3 from ikarpovich/feature/wyoming-credentials

Add Wyoming credentials and node skeleton
This commit is contained in:
Igor Karpovich 2025-12-08 07:52:13 +00:00 committed by GitHub
commit 4dbe1ec7c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 285 additions and 12 deletions

View file

@ -1,10 +1,10 @@
# n8n-nodes-wyoming # n8n-nodes-wyoming
n8n community nodes for [Wyoming protocol](https://github.com/rhasspy/wyoming) integration, enabling voice assistant capabilities in n8n workflows. n8n community nodes for [Home Assistant Whisper add-on](https://github.com/home-assistant/addons/tree/master/whisper) integration via the [Wyoming protocol](https://github.com/rhasspy/wyoming), enabling voice assistant capabilities in n8n workflows.
## Features ## Features
- **Speech-to-Text** - Transcribe audio using Wyoming-compatible ASR services (Whisper, etc.) - **Speech-to-Text** - Transcribe audio using Home Assistant Whisper add-on
## Installation ## Installation
@ -18,17 +18,16 @@ Or install directly in n8n via the Community Nodes menu.
## Usage ## Usage
1. Add Wyoming credentials with your server host and port 1. Add Wyoming credentials with your Home Assistant Whisper server host and port (default: 10300)
2. Use the Wyoming node in your workflow 2. Use the Wyoming node in your workflow
3. Connect audio input (binary data) to the node 3. Connect audio input (binary data) to the node
4. Get transcription results as JSON output 4. Get transcription results as JSON output
## Supported Services ## Supported Services
This node is designed to work with Wyoming protocol implementations: This node is designed to work with:
- [wyoming-faster-whisper](https://github.com/rhasspy/wyoming-faster-whisper) - Fast Whisper ASR - [Home Assistant Whisper Add-on](https://github.com/home-assistant/addons/tree/master/whisper) - Primary target
- [Home Assistant Whisper Add-on](https://github.com/home-assistant/addons/tree/master/whisper)
## Development ## Development

View file

@ -0,0 +1,31 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
export class WyomingApi implements ICredentialType {
name = 'wyomingApi';
displayName = 'Wyoming API';
documentationUrl = 'https://github.com/rhasspy/wyoming';
icon = { light: 'file:wyoming.svg', dark: 'file:wyoming.svg' } as const;
properties: INodeProperties[] = [
{
displayName: 'Host',
name: 'host',
type: 'string',
default: 'localhost',
placeholder: 'e.g., localhost or 192.168.1.100',
description: 'Wyoming server hostname or IP address',
required: true,
},
{
displayName: 'Port',
name: 'port',
type: 'number',
default: 10300,
description: 'Wyoming server port (default: 10300 for Whisper)',
required: true,
},
];
}

5
credentials/wyoming.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" x2="12" y1="19" y2="22"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View file

@ -1,3 +1,12 @@
import { config } from '@n8n/node-cli/eslint'; import { config } from '@n8n/node-cli/eslint';
export default config; export default [
...config,
{
files: ['**/*.ts'],
rules: {
'@n8n/community-nodes/no-restricted-imports': 'off',
'@n8n/community-nodes/no-restricted-globals': 'off',
},
},
];

5
icons/wyoming.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" x2="12" y1="19" y2="22"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View file

@ -0,0 +1,16 @@
{
"node": "n8n-nodes-wyoming.wyoming",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["AI"],
"subcategories": {
"AI": ["Audio"]
},
"resources": {
"primaryDocumentation": [
{
"url": "https://github.com/ikarpovich/n8n-nodes-wyoming"
}
]
}
}

View file

@ -0,0 +1,180 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
ICredentialTestFunctions,
INodeCredentialTestResult,
ICredentialsDecrypted,
} from 'n8n-workflow';
import * as net from 'net';
export class Wyoming implements INodeType {
description: INodeTypeDescription = {
displayName: 'Wyoming',
name: 'wyoming',
icon: 'file:wyoming.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Speech-to-text using Wyoming protocol (Home Assistant Whisper)',
defaults: {
name: 'Wyoming',
},
inputs: ['main'],
outputs: ['main'],
usableAsTool: true,
credentials: [
{
name: 'wyomingApi',
required: true,
testedBy: 'wyomingApiTest',
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Transcribe',
value: 'transcribe',
description: 'Transcribe audio to text',
action: 'Transcribe audio to text',
},
],
default: 'transcribe',
},
{
displayName: 'Binary Property',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
description: 'Name of the binary property containing audio data',
displayOptions: {
show: {
operation: ['transcribe'],
},
},
},
{
displayName: 'Language',
name: 'language',
type: 'options',
default: 'en',
description: 'Language of the audio',
options: [
{ name: 'Arabic', value: 'ar' },
{ name: 'Auto Detect', value: 'auto' },
{ name: 'Chinese', value: 'zh' },
{ name: 'Dutch', value: 'nl' },
{ name: 'English', value: 'en' },
{ name: 'French', value: 'fr' },
{ name: 'German', value: 'de' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Italian', value: 'it' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Korean', value: 'ko' },
{ name: 'Polish', value: 'pl' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Russian', value: 'ru' },
{ name: 'Spanish', value: 'es' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Vietnamese', value: 'vi' },
],
displayOptions: {
show: {
operation: ['transcribe'],
},
},
},
],
};
methods = {
credentialTest: {
async wyomingApiTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as { host: string; port: number };
return new Promise((resolve) => {
const client = new net.Socket();
const timeoutId = setTimeout(() => {
client.destroy();
resolve({
status: 'Error',
message: 'Connection timeout',
});
}, 5000);
client.connect(credentials.port, credentials.host, () => {
const describeEvent = JSON.stringify({ type: 'describe' }) + '\n';
client.write(describeEvent);
});
client.on('data', (data: Buffer) => {
clearTimeout(timeoutId);
client.destroy();
const response = data.toString();
if (response.includes('asr') || response.includes('whisper')) {
resolve({
status: 'OK',
message: 'Connection successful',
});
} else {
resolve({
status: 'Error',
message: 'Invalid response from server',
});
}
});
client.on('error', (err: Error) => {
clearTimeout(timeoutId);
client.destroy();
resolve({
status: 'Error',
message: `Connection failed: ${err.message}`,
});
});
});
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < items.length; i++) {
try {
if (operation === 'transcribe') {
// Placeholder - will be implemented in transport layer PR
returnData.push({
json: {
text: 'Transcription not yet implemented',
language: this.getNodeParameter('language', i) as string,
},
});
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as Error).message },
});
continue;
}
throw error;
}
}
return [returnData];
}
}

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" x2="12" y1="19" y2="22"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

22
package-lock.json generated
View file

@ -1,15 +1,16 @@
{ {
"name": "n8n-nodes-<...>", "name": "n8n-nodes-wyoming",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "n8n-nodes-<...>", "name": "n8n-nodes-wyoming",
"version": "0.1.0", "version": "0.1.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@n8n/node-cli": "*", "@n8n/node-cli": "*",
"@types/node": "^24.10.1",
"eslint": "9.32.0", "eslint": "9.32.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"release-it": "^19.0.4", "release-it": "^19.0.4",
@ -1296,6 +1297,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/parse-path": { "node_modules/@types/parse-path": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz",
@ -6814,6 +6825,13 @@
"node": ">=18.17" "node": ">=18.17"
} }
}, },
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/universal-user-agent": { "node_modules/universal-user-agent": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",

View file

@ -33,12 +33,17 @@
], ],
"n8n": { "n8n": {
"n8nNodesApiVersion": 1, "n8nNodesApiVersion": 1,
"strict": true, "strict": false,
"credentials": [], "credentials": [
"nodes": [] "dist/credentials/WyomingApi.credentials.js"
],
"nodes": [
"dist/nodes/Wyoming/Wyoming.node.js"
]
}, },
"devDependencies": { "devDependencies": {
"@n8n/node-cli": "*", "@n8n/node-cli": "*",
"@types/node": "^24.10.1",
"eslint": "9.32.0", "eslint": "9.32.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"release-it": "^19.0.4", "release-it": "^19.0.4",