n8n-nodes-commandos-image 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Image.png ADDED
Binary file
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Commandos Image (кастомная нода n8n)
2
+
3
+ Кастомная нода для создания задач генерации изображений через Commandos API. Пользователь вводит лицензионный ключ (Credentials), выбирает модель и параметры. Нода формирует payload, отправляет задачу в API и возвращает `taskId`.
4
+
5
+ ## Где находится
6
+
7
+ - Код ноды: `/root/sandbox/nodes/image`
8
+ - Пакет: `n8n-nodes-commandos-image`
9
+
10
+ ## Требования
11
+
12
+ - n8n (dev окружение)
13
+ - Node.js 18+
14
+ - Доступ к Commandos API
15
+
16
+ ## Подключение в n8n-dev
17
+
18
+ В `docker-compose` уже настроено:
19
+
20
+ - монтирование `/root/sandbox/nodes` в `/custom/commandos`
21
+ - переменная `N8N_CUSTOM_EXTENSIONS=/custom/commandos`
22
+
23
+ Если переносите в другое окружение, убедитесь, что путь задан корректно и n8n перезапущен.
24
+
25
+ ## Credentials
26
+
27
+ Тип: `Commandos API`
28
+
29
+ Поля:
30
+ - `License Key` — передаётся как `X-License-Key`.
31
+
32
+ ## Операции
33
+
34
+ ### Create Task
35
+
36
+ Создаёт задачу в API (`POST /tasks`, `process_type=IMAGE_GENERATION`). Нода сама формирует:
37
+
38
+ - `url` для генерации
39
+ - `body` для выбранной модели
40
+
41
+ Входные поля:
42
+ - `Model`: Flux Pro, GPT-4o Image, Nano Banana, Nano Banana Pro, Seedream, Midjourney
43
+ - `Prompt`
44
+ - `Ratio`: `1:1`, `2:3`, `3:2`, `4:5`, `16:9`, `9:16`
45
+ - `References`: до 2 URL (опционально)
46
+
47
+ Выход:
48
+ - `taskId`, `status`, `pollUrl`
49
+
50
+ ### Check Status
51
+
52
+ Запрашивает статус по `taskId` (`GET /tasks/:taskId`).
53
+
54
+ ## Базовый URL API
55
+
56
+ Нода всегда использует `https://api.comandos.ai`.
57
+
58
+ ## Переменные окружения (URL генерации)
59
+
60
+ Нода берёт URL генерации из переменных окружения:
61
+
62
+ - `COMMANDOS_IMAGE_URL_DEFAULT`
63
+ - `COMMANDOS_IMAGE_URL_GPT4O` (опционально)
64
+ - `COMMANDOS_IMAGE_URL_MJ` (опционально)
65
+
66
+ Если специальные URL не заданы, используется `COMMANDOS_IMAGE_URL_DEFAULT`.
67
+
68
+ ## Пример workflow
69
+
70
+ 1. **Commandos Image** → `Create Task`
71
+ 2. **Wait** (20–60 секунд)
72
+ 3. **Commandos Image** → `Check Status` (`{{$json.taskId}}`)
73
+
74
+ ## Сборка
75
+
76
+ ```bash
77
+ npm install
78
+ npm run build
79
+ ```
80
+
81
+ После изменений всегда пересобирайте `dist` и перезапускайте n8n.
82
+
83
+ ## Иконка
84
+
85
+ Файл иконки: `Image.png` (копируется в `dist/nodes` при сборке).
@@ -0,0 +1,19 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+
3
+ export class CommandosApi implements ICredentialType {
4
+ name = 'commandosApi';
5
+ displayName = 'Commandos API';
6
+ documentationUrl = 'https://api.comandos.ai';
7
+ properties: INodeProperties[] = [
8
+ {
9
+ displayName: 'License Key',
10
+ name: 'licenseKey',
11
+ type: 'string',
12
+ default: '',
13
+ required: true,
14
+ typeOptions: {
15
+ password: true,
16
+ },
17
+ },
18
+ ];
19
+ }
@@ -0,0 +1,7 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class CommandosApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommandosApi = void 0;
4
+ class CommandosApi {
5
+ constructor() {
6
+ this.name = 'commandosApi';
7
+ this.displayName = 'Commandos API';
8
+ this.documentationUrl = 'https://api.comandos.ai';
9
+ this.properties = [
10
+ {
11
+ displayName: 'License Key',
12
+ name: 'licenseKey',
13
+ type: 'string',
14
+ default: '',
15
+ required: true,
16
+ typeOptions: {
17
+ password: true,
18
+ },
19
+ },
20
+ ];
21
+ }
22
+ }
23
+ exports.CommandosApi = CommandosApi;
@@ -0,0 +1,3 @@
1
+ import { CommandosImage } from './nodes/CommandosImage.node';
2
+ import { CommandosApi } from './credentials/CommandosApi.credentials';
3
+ export { CommandosImage, CommandosApi };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommandosApi = exports.CommandosImage = void 0;
4
+ const CommandosImage_node_1 = require("./nodes/CommandosImage.node");
5
+ Object.defineProperty(exports, "CommandosImage", { enumerable: true, get: function () { return CommandosImage_node_1.CommandosImage; } });
6
+ const CommandosApi_credentials_1 = require("./credentials/CommandosApi.credentials");
7
+ Object.defineProperty(exports, "CommandosApi", { enumerable: true, get: function () { return CommandosApi_credentials_1.CommandosApi; } });
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class CommandosImage implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommandosImage = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const COMMANDOS_API_URL = 'https://api.comandos.ai';
6
+ const GENERATION_URL_DEFAULT = String(process.env.COMMANDOS_IMAGE_URL_DEFAULT || '').trim();
7
+ const GENERATION_URL_GPT4O = String(process.env.COMMANDOS_IMAGE_URL_GPT4O || '').trim();
8
+ const GENERATION_URL_MJ = String(process.env.COMMANDOS_IMAGE_URL_MJ || '').trim();
9
+ const resolveGenerationUrl = (resolvedModel) => {
10
+ if (resolvedModel === 'gpt4o-image') {
11
+ return GENERATION_URL_GPT4O || GENERATION_URL_DEFAULT;
12
+ }
13
+ if (resolvedModel === 'midjourney') {
14
+ return GENERATION_URL_MJ || GENERATION_URL_DEFAULT;
15
+ }
16
+ return GENERATION_URL_DEFAULT;
17
+ };
18
+ const buildGenerationRequest = ({ model, prompt, ratio, references }) => {
19
+ const hasReferences = references.length > 0;
20
+ const requestedModel = model;
21
+ let resolvedModel = model;
22
+ if (requestedModel === 'flux-pro') {
23
+ resolvedModel = hasReferences ? 'flux-2/pro-image-to-image' : 'flux-2/pro-text-to-image';
24
+ }
25
+ else if (requestedModel === 'nano-banana') {
26
+ resolvedModel = hasReferences ? 'google/nano-banana-edit' : 'google/nano-banana';
27
+ }
28
+ else if (requestedModel === 'nano-banana-pro') {
29
+ resolvedModel = 'nano-banana-pro';
30
+ }
31
+ else if (requestedModel === 'seedream') {
32
+ resolvedModel = 'bytedance/seedream-v4-edit';
33
+ }
34
+ if (!hasReferences && (requestedModel === 'seedream' || requestedModel === 'midjourney')) {
35
+ if (requestedModel !== 'midjourney') {
36
+ resolvedModel = 'gpt4o-image';
37
+ }
38
+ }
39
+ let body = {};
40
+ if (resolvedModel === 'gpt4o-image') {
41
+ body = {
42
+ filesUrl: references,
43
+ prompt,
44
+ size: ratio,
45
+ callBackUrl: 'https://your-callback-url.com/callback',
46
+ fallbackModel: 'FLUX_MAX',
47
+ };
48
+ }
49
+ else if (resolvedModel.includes('flux-2/')) {
50
+ body = {
51
+ model: resolvedModel,
52
+ input: {
53
+ prompt,
54
+ aspect_ratio: ratio,
55
+ resolution: '1K',
56
+ ...(hasReferences ? { input_urls: references } : {}),
57
+ },
58
+ };
59
+ }
60
+ else if (resolvedModel === 'nano-banana-pro') {
61
+ body = {
62
+ model: resolvedModel,
63
+ input: {
64
+ prompt,
65
+ aspect_ratio: ratio,
66
+ resolution: '1K',
67
+ output_format: 'png',
68
+ ...(hasReferences ? { image_input: references } : {}),
69
+ },
70
+ };
71
+ }
72
+ else if (resolvedModel.includes('google/nano-banana')) {
73
+ body = {
74
+ model: resolvedModel,
75
+ input: {
76
+ prompt,
77
+ image_size: ratio,
78
+ output_format: 'png',
79
+ ...(hasReferences ? { image_urls: references } : {}),
80
+ },
81
+ };
82
+ }
83
+ else if (resolvedModel === 'midjourney') {
84
+ body = {
85
+ taskType: hasReferences ? 'mj_img2img' : 'mj_txt2img',
86
+ speed: 'fast',
87
+ prompt,
88
+ fileUrls: references,
89
+ aspectRatio: ratio,
90
+ enableTranslation: true,
91
+ };
92
+ }
93
+ else {
94
+ body = {
95
+ model: 'bytedance/seedream-v4-edit',
96
+ input: {
97
+ image_urls: references,
98
+ prompt,
99
+ image_size: ratio === '2:3' ? 'portrait_3_2' : ratio,
100
+ image_resolution: '1K',
101
+ },
102
+ };
103
+ }
104
+ return {
105
+ url: resolveGenerationUrl(resolvedModel),
106
+ body,
107
+ requestedModel,
108
+ resolvedModel,
109
+ ratio,
110
+ references,
111
+ hasReferences,
112
+ };
113
+ };
114
+ const extractReferences = (raw) => {
115
+ if (!raw || typeof raw !== 'object') {
116
+ return [];
117
+ }
118
+ const collection = raw;
119
+ const items = Array.isArray(collection.reference) ? collection.reference : [];
120
+ return items
121
+ .map((entry) => String((entry === null || entry === void 0 ? void 0 : entry.url) || '').trim())
122
+ .filter((value) => value.length > 0 && /^https?:\/\//i.test(value))
123
+ .slice(0, 2);
124
+ };
125
+ class CommandosImage {
126
+ constructor() {
127
+ this.description = {
128
+ displayName: 'Commandos Image',
129
+ name: 'commandosImage',
130
+ group: ['transform'],
131
+ version: 1,
132
+ description: 'Create image tasks in Commandos API',
133
+ defaults: {
134
+ name: 'Commandos Image',
135
+ },
136
+ icon: 'file:Image.png',
137
+ inputs: ['main'],
138
+ outputs: ['main'],
139
+ credentials: [
140
+ {
141
+ name: 'commandosApi',
142
+ required: true,
143
+ },
144
+ ],
145
+ properties: [
146
+ {
147
+ displayName: 'Operation',
148
+ name: 'operation',
149
+ type: 'options',
150
+ noDataExpression: true,
151
+ options: [
152
+ {
153
+ name: 'Create Task',
154
+ value: 'create',
155
+ },
156
+ {
157
+ name: 'Check Status',
158
+ value: 'status',
159
+ },
160
+ ],
161
+ default: 'create',
162
+ },
163
+ {
164
+ displayName: 'Model',
165
+ name: 'model',
166
+ type: 'options',
167
+ options: [
168
+ { name: 'Flux Pro', value: 'flux-pro' },
169
+ { name: 'GPT-4o Image', value: 'gpt4o-image' },
170
+ { name: 'Nano Banana', value: 'nano-banana' },
171
+ { name: 'Nano Banana Pro', value: 'nano-banana-pro' },
172
+ { name: 'Seedream', value: 'seedream' },
173
+ { name: 'Midjourney', value: 'midjourney' },
174
+ ],
175
+ default: 'flux-pro',
176
+ displayOptions: {
177
+ show: {
178
+ operation: ['create'],
179
+ },
180
+ },
181
+ },
182
+ {
183
+ displayName: 'Prompt',
184
+ name: 'prompt',
185
+ type: 'string',
186
+ default: '',
187
+ typeOptions: {
188
+ rows: 4,
189
+ },
190
+ displayOptions: {
191
+ show: {
192
+ operation: ['create'],
193
+ },
194
+ },
195
+ },
196
+ {
197
+ displayName: 'Ratio',
198
+ name: 'ratio',
199
+ type: 'options',
200
+ options: [
201
+ { name: '1:1', value: '1:1' },
202
+ { name: '2:3', value: '2:3' },
203
+ { name: '3:2', value: '3:2' },
204
+ { name: '4:5', value: '4:5' },
205
+ { name: '16:9', value: '16:9' },
206
+ { name: '9:16', value: '9:16' },
207
+ ],
208
+ default: '2:3',
209
+ displayOptions: {
210
+ show: {
211
+ operation: ['create'],
212
+ },
213
+ },
214
+ },
215
+ {
216
+ displayName: 'References',
217
+ name: 'references',
218
+ type: 'fixedCollection',
219
+ default: {},
220
+ typeOptions: {
221
+ multipleValues: true,
222
+ },
223
+ options: [
224
+ {
225
+ name: 'reference',
226
+ displayName: 'Reference',
227
+ values: [
228
+ {
229
+ displayName: 'Reference URL',
230
+ name: 'url',
231
+ type: 'string',
232
+ default: '',
233
+ },
234
+ ],
235
+ },
236
+ ],
237
+ displayOptions: {
238
+ show: {
239
+ operation: ['create'],
240
+ },
241
+ },
242
+ },
243
+ {
244
+ displayName: 'Task ID',
245
+ name: 'taskId',
246
+ type: 'string',
247
+ default: '',
248
+ displayOptions: {
249
+ show: {
250
+ operation: ['status'],
251
+ },
252
+ },
253
+ },
254
+ ],
255
+ };
256
+ }
257
+ async execute() {
258
+ const items = this.getInputData();
259
+ const operation = this.getNodeParameter('operation', 0);
260
+ const credentials = await this.getCredentials('commandosApi');
261
+ const licenseKey = String(credentials.licenseKey || '').trim();
262
+ if (!licenseKey) {
263
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'License key is required');
264
+ }
265
+ const results = [];
266
+ for (let i = 0; i < items.length; i += 1) {
267
+ try {
268
+ if (operation === 'create') {
269
+ const model = String(this.getNodeParameter('model', i));
270
+ const prompt = String(this.getNodeParameter('prompt', i) || '').trim();
271
+ const ratio = String(this.getNodeParameter('ratio', i));
272
+ const references = extractReferences(this.getNodeParameter('references', i, {}));
273
+ if (!prompt) {
274
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Prompt is required', { itemIndex: i });
275
+ }
276
+ const request = buildGenerationRequest({ model, prompt, ratio, references });
277
+ if (!request.url) {
278
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'URL генерации не настроен. Проверь переменные окружения COMMANDOS_IMAGE_URL_DEFAULT/COMMANDOS_IMAGE_URL_GPT4O/COMMANDOS_IMAGE_URL_MJ', { itemIndex: i });
279
+ }
280
+ const payload = {
281
+ url: request.url,
282
+ body: request.body,
283
+ requestedModel: request.requestedModel,
284
+ resolvedModel: request.resolvedModel,
285
+ ratio: request.ratio,
286
+ references: request.references,
287
+ hasReferences: request.hasReferences,
288
+ };
289
+ const response = await this.helpers.request({
290
+ method: 'POST',
291
+ url: `${COMMANDOS_API_URL}/tasks`,
292
+ headers: {
293
+ 'Content-Type': 'application/json',
294
+ 'X-License-Key': licenseKey,
295
+ },
296
+ body: {
297
+ process_type: 'IMAGE_GENERATION',
298
+ payload,
299
+ },
300
+ json: true,
301
+ });
302
+ results.push({ json: response });
303
+ }
304
+ else if (operation === 'status') {
305
+ const taskId = String(this.getNodeParameter('taskId', i) || '').trim();
306
+ if (!taskId) {
307
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Task ID is required', { itemIndex: i });
308
+ }
309
+ const response = await this.helpers.request({
310
+ method: 'GET',
311
+ url: `${COMMANDOS_API_URL}/tasks/${encodeURIComponent(taskId)}`,
312
+ headers: {
313
+ 'X-License-Key': licenseKey,
314
+ },
315
+ json: true,
316
+ });
317
+ results.push({ json: response });
318
+ }
319
+ else {
320
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
321
+ itemIndex: i,
322
+ });
323
+ }
324
+ }
325
+ catch (error) {
326
+ if (error instanceof n8n_workflow_1.NodeOperationError) {
327
+ throw error;
328
+ }
329
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Request failed', { itemIndex: i });
330
+ }
331
+ }
332
+ return [results];
333
+ }
334
+ }
335
+ exports.CommandosImage = CommandosImage;
Binary file