@lhi/n8m 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -21
- package/bin/dev.js +8 -0
- package/bin/run.js +7 -1
- package/dist/agentic/graph.d.ts +116 -8
- package/dist/agentic/graph.js +1 -1
- package/dist/agentic/nodes/architect.d.ts +15 -3
- package/dist/agentic/nodes/architect.js +11 -2
- package/dist/agentic/nodes/engineer.d.ts +1 -1
- package/dist/agentic/nodes/engineer.js +19 -91
- package/dist/agentic/nodes/qa.js +66 -55
- package/dist/agentic/nodes/reviewer.js +5 -3
- package/dist/agentic/state.d.ts +10 -0
- package/dist/agentic/state.js +2 -0
- package/dist/commands/create.js +115 -48
- package/dist/commands/doc.d.ts +11 -0
- package/dist/commands/doc.js +136 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +25 -0
- package/dist/commands/modify.js +1 -1
- package/dist/commands/resume.js +40 -1
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +36 -4
- package/dist/resources/node-definitions-fallback.json +213 -0
- package/dist/services/ai.service.d.ts +38 -47
- package/dist/services/ai.service.js +302 -350
- package/dist/services/doc.service.d.ts +26 -0
- package/dist/services/doc.service.js +92 -0
- package/dist/services/mcp.service.d.ts +9 -0
- package/dist/services/mcp.service.js +110 -0
- package/dist/services/node-definitions.service.d.ts +5 -0
- package/dist/services/node-definitions.service.js +65 -9
- package/dist/utils/n8nClient.d.ts +10 -10
- package/dist/utils/n8nClient.js +80 -145
- package/oclif.manifest.json +67 -3
- package/package.json +7 -4
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { theme } from '../utils/theme.js';
|
|
3
|
+
import { N8nClient } from '../utils/n8nClient.js';
|
|
4
|
+
import { ConfigManager } from '../utils/config.js';
|
|
5
|
+
import { DocService } from '../services/doc.service.js';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
export default class Doc extends Command {
|
|
11
|
+
static args = {
|
|
12
|
+
workflow: Args.string({
|
|
13
|
+
description: 'Path or Name of the workflow to document',
|
|
14
|
+
required: false,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
static description = 'Generate visual and text documentation for n8n workflows';
|
|
18
|
+
static flags = {
|
|
19
|
+
output: Flags.string({
|
|
20
|
+
char: 'o',
|
|
21
|
+
description: 'Output directory for documentation (defaults to ./docs)',
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args, flags } = await this.parse(Doc);
|
|
26
|
+
this.log(theme.brand());
|
|
27
|
+
this.log(theme.header('WORKFLOW DOCUMENTATION'));
|
|
28
|
+
// 1. Load Credentials & Client
|
|
29
|
+
const config = await ConfigManager.load();
|
|
30
|
+
const n8nUrl = config.n8nUrl || process.env.N8N_API_URL;
|
|
31
|
+
const n8nKey = config.n8nKey || process.env.N8N_API_KEY;
|
|
32
|
+
if (!n8nUrl || !n8nKey) {
|
|
33
|
+
this.error('Credentials missing. Configure environment via \'n8m config\'.');
|
|
34
|
+
}
|
|
35
|
+
const client = new N8nClient({ apiUrl: n8nUrl, apiKey: n8nKey });
|
|
36
|
+
const docService = DocService.getInstance();
|
|
37
|
+
// 2. Resolve Workflow
|
|
38
|
+
let workflowData;
|
|
39
|
+
let workflowName = 'Untitled';
|
|
40
|
+
let localPath = null;
|
|
41
|
+
if (args.workflow && existsSync(args.workflow)) {
|
|
42
|
+
const content = await fs.readFile(args.workflow, 'utf-8');
|
|
43
|
+
workflowData = JSON.parse(content);
|
|
44
|
+
workflowName = workflowData.name || path.basename(args.workflow, '.json');
|
|
45
|
+
localPath = path.resolve(args.workflow);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.log(theme.info('Searching for local and remote workflows...'));
|
|
49
|
+
const localChoices = [];
|
|
50
|
+
const workflowsDir = path.join(process.cwd(), 'workflows');
|
|
51
|
+
if (existsSync(workflowsDir)) {
|
|
52
|
+
// Scan for loose files AND directory-based workflows
|
|
53
|
+
const entries = await fs.readdir(workflowsDir, { withFileTypes: true });
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
56
|
+
localChoices.push({
|
|
57
|
+
name: `${theme.value('[LOCAL]')} ${entry.name}`,
|
|
58
|
+
value: { type: 'local', path: path.join(workflowsDir, entry.name) }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (entry.isDirectory()) {
|
|
62
|
+
const subPath = path.join(workflowsDir, entry.name, 'workflow.json');
|
|
63
|
+
if (existsSync(subPath)) {
|
|
64
|
+
localChoices.push({
|
|
65
|
+
name: `${theme.value('[LOCAL]')} ${entry.name}/workflow.json`,
|
|
66
|
+
value: { type: 'local', path: subPath }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const remoteWorkflows = await client.getWorkflows();
|
|
73
|
+
const remoteChoices = remoteWorkflows
|
|
74
|
+
.map(w => ({
|
|
75
|
+
name: `${theme.info('[n8n]')} ${w.name} (${w.id}) ${w.active ? '[Active]' : ''}`,
|
|
76
|
+
value: { type: 'remote', id: w.id, data: w }
|
|
77
|
+
}));
|
|
78
|
+
const choices = [
|
|
79
|
+
...(localChoices.length > 0 ? [new inquirer.Separator('--- Local Files ---'), ...localChoices] : []),
|
|
80
|
+
...(remoteChoices.length > 0 ? [new inquirer.Separator('--- n8n Instance ---'), ...remoteChoices] : []),
|
|
81
|
+
];
|
|
82
|
+
if (choices.length === 0)
|
|
83
|
+
this.error('No workflows found sequence.');
|
|
84
|
+
const { selection } = await inquirer.prompt([{
|
|
85
|
+
type: 'select',
|
|
86
|
+
name: 'selection',
|
|
87
|
+
message: 'Select a workflow to document:',
|
|
88
|
+
choices,
|
|
89
|
+
pageSize: 15
|
|
90
|
+
}]);
|
|
91
|
+
if (selection.type === 'local') {
|
|
92
|
+
const content = await fs.readFile(selection.path, 'utf-8');
|
|
93
|
+
workflowData = JSON.parse(content);
|
|
94
|
+
workflowName = workflowData.name || path.basename(selection.path, '.json');
|
|
95
|
+
localPath = selection.path;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
workflowData = await client.getWorkflow(selection.id);
|
|
99
|
+
workflowName = workflowData.name || 'Remote Workflow';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 3. Prepare Folder Structure
|
|
103
|
+
const slug = docService.generateSlug(workflowName);
|
|
104
|
+
const workflowsDir = path.join(process.cwd(), 'workflows');
|
|
105
|
+
const targetDir = path.join(workflowsDir, slug);
|
|
106
|
+
const targetJsonPath = path.join(targetDir, 'workflow.json');
|
|
107
|
+
const targetReadmePath = path.join(targetDir, 'README.md');
|
|
108
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
109
|
+
// Move or Save JSON
|
|
110
|
+
if (localPath) {
|
|
111
|
+
if (path.resolve(localPath) !== path.resolve(targetJsonPath)) {
|
|
112
|
+
this.log(theme.info(`Moving workflow to: ${theme.value(targetJsonPath)}`));
|
|
113
|
+
await fs.writeFile(targetJsonPath, JSON.stringify(workflowData, null, 2));
|
|
114
|
+
// Only delete if it's a loose file in workflows/ or provided path
|
|
115
|
+
if (path.resolve(localPath) !== path.resolve(targetJsonPath)) {
|
|
116
|
+
await fs.unlink(localPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.log(theme.info(`Saving remote workflow to: ${theme.value(targetJsonPath)}`));
|
|
122
|
+
await fs.writeFile(targetJsonPath, JSON.stringify(workflowData, null, 2));
|
|
123
|
+
}
|
|
124
|
+
// 4. Generate & Save Documentation
|
|
125
|
+
this.log(theme.agent(`Generating diagrams and summary for: "${workflowName}"...`));
|
|
126
|
+
const mermaid = docService.generateMermaid(workflowData);
|
|
127
|
+
const readme = await docService.generateReadme(workflowData);
|
|
128
|
+
const fullDoc = `# ${workflowName}\n\n## Visual Flow\n\n\`\`\`mermaid\n${mermaid}\`\`\`\n\n${readme}`;
|
|
129
|
+
await fs.writeFile(targetReadmePath, fullDoc);
|
|
130
|
+
this.log(theme.success(`✔ Documentation Generated & Organized.`));
|
|
131
|
+
this.log(`${theme.label('Folder')} ${theme.value(targetDir)}`);
|
|
132
|
+
this.log(theme.divider());
|
|
133
|
+
this.log(theme.subHeader('Mermaid Diagram Preview:'));
|
|
134
|
+
this.log(theme.muted(mermaid));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { theme } from '../utils/theme.js';
|
|
3
|
+
import { MCPService } from '../services/mcp.service.js';
|
|
4
|
+
export default class MCP extends Command {
|
|
5
|
+
static description = 'Launch the n8m MCP (Model Context Protocol) server';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
];
|
|
9
|
+
async run() {
|
|
10
|
+
this.log(theme.brand());
|
|
11
|
+
this.log(theme.info('Starting n8m MCP Server...'));
|
|
12
|
+
try {
|
|
13
|
+
const mcpService = new MCPService();
|
|
14
|
+
await mcpService.start();
|
|
15
|
+
// Wait for interrupt (Process will stay alive due to stdio transport)
|
|
16
|
+
process.on('SIGINT', () => {
|
|
17
|
+
this.log(theme.info('\nStopping MCP Server...'));
|
|
18
|
+
process.exit(0);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
this.error(`Failed to start MCP Server: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/commands/modify.js
CHANGED
|
@@ -20,7 +20,7 @@ export default class Modify extends Command {
|
|
|
20
20
|
required: false,
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
|
-
static description = 'Modify existing n8n workflows using
|
|
23
|
+
static description = 'Modify existing n8n workflows using an AI Agent';
|
|
24
24
|
static flags = {
|
|
25
25
|
multiline: Flags.boolean({
|
|
26
26
|
char: 'm',
|
package/dist/commands/resume.js
CHANGED
|
@@ -21,8 +21,47 @@ export default class Resume extends Command {
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
this.log(theme.info(`Workflow is paused at: ${next.join(', ')}`));
|
|
24
|
-
this.log(theme.agent("Resuming..."));
|
|
25
24
|
try {
|
|
25
|
+
if (next.includes('engineer')) {
|
|
26
|
+
const state = snapshot.values;
|
|
27
|
+
if (state.strategies && state.strategies.length > 0) {
|
|
28
|
+
this.log(theme.header('\nPAUSED STRATEGIES:'));
|
|
29
|
+
state.strategies.forEach((s, i) => {
|
|
30
|
+
this.log(`${i === 0 ? theme.success(' [Primary]') : theme.info(' [Alternative]')} ${theme.value(s.suggestedName)}`);
|
|
31
|
+
this.log(` Description: ${s.description}`);
|
|
32
|
+
if (s.nodes && s.nodes.length > 0) {
|
|
33
|
+
this.log(` Proposed Nodes: ${s.nodes.map((n) => n.type.split('.').pop()).join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
this.log('');
|
|
36
|
+
});
|
|
37
|
+
const { action } = await (await import('inquirer')).default.prompt([{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'action',
|
|
40
|
+
message: 'How would you like to proceed?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'Approve and Generate Workflow', value: 'approve' },
|
|
43
|
+
{ name: 'Provide Feedback / Refine Strategy', value: 'feedback' },
|
|
44
|
+
{ name: 'Exit', value: 'exit' }
|
|
45
|
+
]
|
|
46
|
+
}]);
|
|
47
|
+
if (action === 'approve') {
|
|
48
|
+
this.log(theme.agent("Approve! Resuming..."));
|
|
49
|
+
await graph.updateState({ configurable: { thread_id: threadId } }, { userFeedback: undefined }, 'engineer');
|
|
50
|
+
}
|
|
51
|
+
else if (action === 'feedback') {
|
|
52
|
+
const { feedback } = await (await import('inquirer')).default.prompt([{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'feedback',
|
|
55
|
+
message: 'Enter your feedback/instructions:',
|
|
56
|
+
}]);
|
|
57
|
+
await graph.updateState({ configurable: { thread_id: threadId } }, { userFeedback: feedback }, 'engineer');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.log(theme.agent("Resuming..."));
|
|
26
65
|
const result = await resumeAgenticWorkflow(threadId);
|
|
27
66
|
if (result.validationStatus === 'passed') {
|
|
28
67
|
this.log(theme.success("Workflow completed successfully!"));
|
package/dist/commands/test.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export default class Test extends Command {
|
|
|
9
9
|
'keep-on-fail': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
'no-brand': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
'validate-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'ai-scenarios': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
};
|
|
13
14
|
run(): Promise<void>;
|
|
14
15
|
/**
|
package/dist/commands/test.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
3
3
|
import { theme } from '../utils/theme.js';
|
|
4
4
|
import { N8nClient } from '../utils/n8nClient.js';
|
|
5
5
|
import { ConfigManager } from '../utils/config.js';
|
|
6
|
+
import { AIService } from '../services/ai.service.js';
|
|
7
|
+
import { DocService } from '../services/doc.service.js';
|
|
6
8
|
import { runAgenticWorkflow, graph, resumeAgenticWorkflow } from '../agentic/graph.js';
|
|
7
9
|
import * as path from 'path';
|
|
8
10
|
import * as fs from 'fs/promises';
|
|
@@ -35,6 +37,10 @@ export default class Test extends Command {
|
|
|
35
37
|
hidden: true,
|
|
36
38
|
description: 'Execute test but do not prompt for deploy/save actions',
|
|
37
39
|
}),
|
|
40
|
+
'ai-scenarios': Flags.boolean({
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Generate 3 diverse AI test scenarios (happy path, edge case, error)',
|
|
43
|
+
}),
|
|
38
44
|
};
|
|
39
45
|
async run() {
|
|
40
46
|
const { args, flags } = await this.parse(Test);
|
|
@@ -50,6 +56,7 @@ export default class Test extends Command {
|
|
|
50
56
|
this.error('Credentials missing. Configure environment via \'n8m config\'.');
|
|
51
57
|
}
|
|
52
58
|
const client = new N8nClient({ apiUrl: n8nUrl, apiKey: n8nKey });
|
|
59
|
+
const aiService = AIService.getInstance();
|
|
53
60
|
// 1a. Fetch Valid Node Types (New)
|
|
54
61
|
let validNodeTypes = [];
|
|
55
62
|
try {
|
|
@@ -354,12 +361,19 @@ export default class Test extends Command {
|
|
|
354
361
|
this.log(theme.subHeader('AGENTIC VALIDATION'));
|
|
355
362
|
this.log(theme.agent("Initializing Agentic Workflow to validate/repair this workflow..."));
|
|
356
363
|
const goal = `Validate and fix the workflow named "${workflowName}"`;
|
|
364
|
+
let testScenarios = [];
|
|
365
|
+
if (flags['ai-scenarios']) {
|
|
366
|
+
this.log(theme.agent("Generating AI test scenarios..."));
|
|
367
|
+
testScenarios = await aiService.generateTestScenarios(workflowData, goal);
|
|
368
|
+
this.log(theme.muted(`Generated ${testScenarios.length} scenarios.`));
|
|
369
|
+
}
|
|
357
370
|
const initialState = {
|
|
358
371
|
userGoal: goal,
|
|
359
372
|
messages: [],
|
|
360
373
|
validationErrors: [],
|
|
361
374
|
workflowJson: workflowData,
|
|
362
|
-
availableNodeTypes: validNodeTypes
|
|
375
|
+
availableNodeTypes: validNodeTypes,
|
|
376
|
+
testScenarios: testScenarios
|
|
363
377
|
};
|
|
364
378
|
// We need to route the graph logger to our CLI logger if possible, or just let it print to stdout
|
|
365
379
|
// The graph uses console.log currently, which is fine.
|
|
@@ -540,20 +554,38 @@ export default class Test extends Command {
|
|
|
540
554
|
}]);
|
|
541
555
|
if (!save)
|
|
542
556
|
return;
|
|
557
|
+
const docService = DocService.getInstance();
|
|
543
558
|
for (const [, def] of deployedDefinitions.entries()) {
|
|
544
559
|
const cleanData = this.sanitizeWorkflow(this.stripShim(def.data));
|
|
545
|
-
|
|
546
|
-
|
|
560
|
+
// Use AI to suggest title if it looks like a temporary name
|
|
561
|
+
let workflowName = def.name;
|
|
562
|
+
if (workflowName.startsWith('[n8m:test]') || workflowName.includes('Agentic_Test')) {
|
|
563
|
+
this.log(theme.agent("Suggesting professional project title..."));
|
|
564
|
+
workflowName = await docService.generateProjectTitle(cleanData);
|
|
565
|
+
}
|
|
566
|
+
cleanData.name = workflowName;
|
|
567
|
+
const slug = docService.generateSlug(workflowName);
|
|
568
|
+
const targetDir = path.join(process.cwd(), 'workflows', slug);
|
|
569
|
+
const targetPath = path.join(targetDir, 'workflow.json');
|
|
547
570
|
const { confirmPath } = await inquirer.prompt([{
|
|
548
571
|
type: 'input',
|
|
549
572
|
name: 'confirmPath',
|
|
550
|
-
message: `Save '${
|
|
573
|
+
message: `Save '${workflowName}' to:`,
|
|
551
574
|
default: targetPath
|
|
552
575
|
}]);
|
|
553
576
|
try {
|
|
554
577
|
await fs.mkdir(path.dirname(confirmPath), { recursive: true });
|
|
555
578
|
await fs.writeFile(confirmPath, JSON.stringify(cleanData, null, 2));
|
|
556
579
|
this.log(theme.success(`Saved to ${confirmPath}`));
|
|
580
|
+
// Optionally generate doc if it's a new directory
|
|
581
|
+
const readmePath = path.join(path.dirname(confirmPath), 'README.md');
|
|
582
|
+
if (!existsSync(readmePath)) {
|
|
583
|
+
this.log(theme.agent("Generating initial documentation..."));
|
|
584
|
+
const mermaid = docService.generateMermaid(cleanData);
|
|
585
|
+
const readmeContent = await docService.generateReadme(cleanData);
|
|
586
|
+
const fullDoc = `# ${workflowName}\n\n## Visual Flow\n\n\`\`\`mermaid\n${mermaid}\`\`\`\n\n${readmeContent}`;
|
|
587
|
+
await fs.writeFile(readmePath, fullDoc);
|
|
588
|
+
}
|
|
557
589
|
}
|
|
558
590
|
catch (e) {
|
|
559
591
|
this.log(theme.fail(`Failed to save: ${e.message}`));
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "n8n-nodes-base.start",
|
|
4
|
+
"displayName": "Manual Trigger",
|
|
5
|
+
"description": "The starting point for manual execution.",
|
|
6
|
+
"properties": []
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "n8n-nodes-base.httpRequest",
|
|
10
|
+
"displayName": "HTTP Request",
|
|
11
|
+
"description": "Send HTTP requests to any API",
|
|
12
|
+
"properties": [
|
|
13
|
+
{
|
|
14
|
+
"name": "method",
|
|
15
|
+
"displayName": "Method",
|
|
16
|
+
"type": "options",
|
|
17
|
+
"default": "GET",
|
|
18
|
+
"options": [
|
|
19
|
+
{ "name": "GET", "value": "GET" },
|
|
20
|
+
{ "name": "POST", "value": "POST" },
|
|
21
|
+
{ "name": "PUT", "value": "PUT" },
|
|
22
|
+
{ "name": "DELETE", "value": "DELETE" }
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "url",
|
|
27
|
+
"displayName": "URL",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"default": "",
|
|
30
|
+
"description": "The URL to send the request to"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "authentication",
|
|
34
|
+
"displayName": "Authentication",
|
|
35
|
+
"type": "options",
|
|
36
|
+
"default": "none",
|
|
37
|
+
"options": [
|
|
38
|
+
{ "name": "None", "value": "none" },
|
|
39
|
+
{ "name": "Predefined Credential Type", "value": "predefinedCredentialType" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "sendBody",
|
|
44
|
+
"displayName": "Send Body",
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"default": false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "body",
|
|
50
|
+
"displayName": "Body",
|
|
51
|
+
"type": "json",
|
|
52
|
+
"default": ""
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "n8n-nodes-base.slack",
|
|
58
|
+
"displayName": "Slack",
|
|
59
|
+
"description": "Interact with Slack",
|
|
60
|
+
"properties": [
|
|
61
|
+
{
|
|
62
|
+
"name": "resource",
|
|
63
|
+
"displayName": "Resource",
|
|
64
|
+
"type": "options",
|
|
65
|
+
"default": "message",
|
|
66
|
+
"options": [
|
|
67
|
+
{ "name": "Message", "value": "message" },
|
|
68
|
+
{ "name": "Channel", "value": "channel" }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "operation",
|
|
73
|
+
"displayName": "Operation",
|
|
74
|
+
"type": "options",
|
|
75
|
+
"default": "post",
|
|
76
|
+
"options": [
|
|
77
|
+
{ "name": "Post", "value": "post" },
|
|
78
|
+
{ "name": "Update", "value": "update" }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "channel",
|
|
83
|
+
"displayName": "Channel",
|
|
84
|
+
"type": "string",
|
|
85
|
+
"default": ""
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "text",
|
|
89
|
+
"displayName": "Text",
|
|
90
|
+
"type": "string",
|
|
91
|
+
"default": ""
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "n8n-nodes-base.googleSheets",
|
|
97
|
+
"displayName": "Google Sheets",
|
|
98
|
+
"description": "Interact with Google Sheets",
|
|
99
|
+
"properties": [
|
|
100
|
+
{
|
|
101
|
+
"name": "resource",
|
|
102
|
+
"displayName": "Resource",
|
|
103
|
+
"type": "options",
|
|
104
|
+
"default": "sheet",
|
|
105
|
+
"options": [
|
|
106
|
+
{ "name": "Sheet", "value": "sheet" }
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "operation",
|
|
111
|
+
"displayName": "Operation",
|
|
112
|
+
"type": "options",
|
|
113
|
+
"default": "append",
|
|
114
|
+
"options": [
|
|
115
|
+
{ "name": "Append", "value": "append" },
|
|
116
|
+
{ "name": "Read", "value": "read" },
|
|
117
|
+
{ "name": "Update", "value": "update" }
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "sheetId",
|
|
122
|
+
"displayName": "Sheet ID",
|
|
123
|
+
"type": "string",
|
|
124
|
+
"default": ""
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "range",
|
|
128
|
+
"displayName": "Range",
|
|
129
|
+
"type": "string",
|
|
130
|
+
"default": "Sheet1!A:Z"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "n8n-nodes-base.if",
|
|
136
|
+
"displayName": "IF",
|
|
137
|
+
"description": "Conditional logic: True/False",
|
|
138
|
+
"properties": [
|
|
139
|
+
{
|
|
140
|
+
"name": "conditions",
|
|
141
|
+
"displayName": "Conditions",
|
|
142
|
+
"type": "json",
|
|
143
|
+
"default": {}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "n8n-nodes-base.set",
|
|
149
|
+
"displayName": "Set",
|
|
150
|
+
"description": "Set variables or values",
|
|
151
|
+
"properties": [
|
|
152
|
+
{
|
|
153
|
+
"name": "values",
|
|
154
|
+
"displayName": "Values",
|
|
155
|
+
"type": "fixedCollection",
|
|
156
|
+
"default": {}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "n8n-nodes-base.webhook",
|
|
162
|
+
"displayName": "Webhook",
|
|
163
|
+
"description": "Receive HTTP requests",
|
|
164
|
+
"properties": [
|
|
165
|
+
{
|
|
166
|
+
"name": "httpMethod",
|
|
167
|
+
"displayName": "HTTP Method",
|
|
168
|
+
"type": "options",
|
|
169
|
+
"default": "GET",
|
|
170
|
+
"options": [
|
|
171
|
+
{ "name": "GET", "value": "GET" },
|
|
172
|
+
{ "name": "POST", "value": "POST" }
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "path",
|
|
177
|
+
"displayName": "Path",
|
|
178
|
+
"type": "string",
|
|
179
|
+
"default": ""
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "n8n-nodes-base.code",
|
|
185
|
+
"displayName": "Code",
|
|
186
|
+
"description": "Run JavaScript/TypeScript",
|
|
187
|
+
"properties": [
|
|
188
|
+
{
|
|
189
|
+
"name": "jsCode",
|
|
190
|
+
"displayName": "JS Code",
|
|
191
|
+
"type": "string",
|
|
192
|
+
"default": "// Your code here\nreturn items;"
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "n8n-nodes-base.merge",
|
|
198
|
+
"displayName": "Merge",
|
|
199
|
+
"description": "Merge data from multiple inputs",
|
|
200
|
+
"properties": [
|
|
201
|
+
{
|
|
202
|
+
"name": "mode",
|
|
203
|
+
"displayName": "Mode",
|
|
204
|
+
"type": "options",
|
|
205
|
+
"default": "append",
|
|
206
|
+
"options": [
|
|
207
|
+
{ "name": "Append", "value": "append" },
|
|
208
|
+
{ "name": "Merge by Index", "value": "mergeByIndex" }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
]
|
|
@@ -1,64 +1,55 @@
|
|
|
1
1
|
export interface GenerateOptions {
|
|
2
2
|
model?: string;
|
|
3
|
+
provider?: string;
|
|
3
4
|
temperature?: number;
|
|
4
5
|
}
|
|
6
|
+
export interface WorkflowSpec {
|
|
7
|
+
suggestedName: string;
|
|
8
|
+
description: string;
|
|
9
|
+
nodes: {
|
|
10
|
+
type: string;
|
|
11
|
+
purpose: string;
|
|
12
|
+
config?: any;
|
|
13
|
+
}[];
|
|
14
|
+
questions?: string[];
|
|
15
|
+
strategyName?: string;
|
|
16
|
+
aiModel?: string;
|
|
17
|
+
aiProvider?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const PROVIDER_PRESETS: Record<string, {
|
|
20
|
+
baseURL?: string;
|
|
21
|
+
defaultModel: string;
|
|
22
|
+
models: string[];
|
|
23
|
+
}>;
|
|
5
24
|
export declare class AIService {
|
|
6
25
|
private static instance;
|
|
7
|
-
private
|
|
26
|
+
private clients;
|
|
27
|
+
private defaultProvider;
|
|
8
28
|
private model;
|
|
29
|
+
private apiKey;
|
|
30
|
+
private baseURL?;
|
|
9
31
|
private constructor();
|
|
32
|
+
private getClient;
|
|
10
33
|
static getInstance(): AIService;
|
|
11
|
-
|
|
12
|
-
* Core generation method — works with any OpenAI-compatible API
|
|
13
|
-
*/
|
|
34
|
+
private callAnthropicNative;
|
|
14
35
|
generateContent(prompt: string, options?: GenerateOptions): Promise<string>;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
refineSpec(spec: any, feedback: string): Promise<any>;
|
|
27
|
-
/**
|
|
28
|
-
* Generate workflow JSONs from an approved Specification
|
|
29
|
-
*/
|
|
30
|
-
generateWorkflowFromSpec(spec: any): Promise<any>;
|
|
31
|
-
/**
|
|
32
|
-
* Generate mock data for a workflow execution
|
|
33
|
-
*/
|
|
34
|
-
generateMockData(context: string, previousFailures?: string[]): Promise<any>;
|
|
35
|
-
/**
|
|
36
|
-
* Diagnostic Repair: Fix a workflow based on execution error
|
|
37
|
-
*/
|
|
38
|
-
generateWorkflowFix(workflowJson: any, errorContext: string, model?: string, _useSearch?: boolean, validNodeTypes?: string[]): Promise<any>;
|
|
39
|
-
/**
|
|
40
|
-
* Auto-correct common n8n node type hallucinations
|
|
41
|
-
*/
|
|
42
|
-
private fixHallucinatedNodes;
|
|
43
|
-
/**
|
|
44
|
-
* Force-fix connection structure to prevent "object is not iterable" errors
|
|
45
|
-
*/
|
|
46
|
-
private fixN8nConnections;
|
|
47
|
-
/**
|
|
48
|
-
* Generate an alternative workflow specification with a different approach to the same goal.
|
|
49
|
-
* Used by the Architect node to produce a second strategy for parallel Engineer execution.
|
|
50
|
-
*/
|
|
51
|
-
generateAlternativeSpec(goal: string, primarySpec: any): Promise<any>;
|
|
52
|
-
/**
|
|
53
|
-
* Evaluate multiple workflow candidates and select the best one for the given goal.
|
|
54
|
-
* Used by the Supervisor node to choose between parallel Engineer outputs.
|
|
55
|
-
*/
|
|
36
|
+
getAlternativeModel(): string;
|
|
37
|
+
getDefaultModel(): string;
|
|
38
|
+
getDefaultProvider(): string;
|
|
39
|
+
generateSpec(goal: string): Promise<WorkflowSpec>;
|
|
40
|
+
generateWorkflow(goal: string): Promise<any>;
|
|
41
|
+
generateAlternativeSpec(goal: string, primarySpec: WorkflowSpec): Promise<WorkflowSpec>;
|
|
42
|
+
generateWorkflowFix(workflow: any, error: string, model?: string, _stream?: boolean, validNodeTypes?: string[]): Promise<any>;
|
|
43
|
+
validateAndShim(workflow: any, validNodeTypes?: string[], explicitlyInvalid?: string[]): any;
|
|
44
|
+
fixHallucinatedNodes(workflow: any): any;
|
|
45
|
+
fixN8nConnections(workflow: any): any;
|
|
46
|
+
generateMockData(context: string): Promise<any>;
|
|
56
47
|
evaluateCandidates(goal: string, candidates: any[]): Promise<{
|
|
57
48
|
selectedIndex: number;
|
|
58
49
|
reason: string;
|
|
59
50
|
}>;
|
|
60
51
|
/**
|
|
61
|
-
*
|
|
52
|
+
* Generates 3-5 diverse test scenarios (input payloads) for a workflow.
|
|
62
53
|
*/
|
|
63
|
-
|
|
54
|
+
generateTestScenarios(workflowJson: any, goal: string): Promise<any[]>;
|
|
64
55
|
}
|