@lhi/n8m 0.1.1 → 0.1.3

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 CHANGED
@@ -235,6 +235,14 @@ npm run dev
235
235
 
236
236
  ---
237
237
 
238
+ ## Sponsors
239
+
240
+ ### Partially Sponsored By
241
+
242
+ [The Daily Caller](https://dailycaller.com)
243
+
244
+ ---
245
+
238
246
  ## Roadmap
239
247
 
240
248
  - [x] Agentic graph (Architect → Engineer → QA)
package/bin/dev.js CHANGED
@@ -1,5 +1,13 @@
1
1
  #!/usr/bin/env node --loader ts-node/esm --no-warnings=ExperimentalWarning
2
2
 
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { config } from 'dotenv';
6
+
7
+ // Load .env from the package root regardless of the user's cwd
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ config({ path: join(__dirname, '..', '.env') });
10
+
3
11
  import {execute} from '@oclif/core'
4
12
 
5
13
  await execute({development: true, dir: import.meta.url})
package/bin/run.js CHANGED
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import { config } from 'dotenv';
5
+
6
+ // Load .env from the package root regardless of the user's cwd
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ config({ path: join(__dirname, '..', '.env') });
3
9
 
4
10
  import {execute} from '@oclif/core'
5
11
 
@@ -12,6 +12,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
12
12
  strategies: any[];
13
13
  candidates: any[];
14
14
  customTools: Record<string, string>;
15
+ collaborationLog: string[];
15
16
  }, {
16
17
  userGoal?: string | undefined;
17
18
  spec?: any;
@@ -25,6 +26,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
25
26
  strategies?: any[] | undefined;
26
27
  candidates?: any[] | undefined;
27
28
  customTools?: Record<string, string> | undefined;
29
+ collaborationLog?: string[] | undefined;
28
30
  }, "__start__" | "architect" | "engineer" | "reviewer" | "supervisor" | "qa", {
29
31
  userGoal: {
30
32
  (): import("@langchain/langgraph").LastValue<string>;
@@ -74,6 +76,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
74
76
  };
75
77
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
76
78
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
79
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
77
80
  }, {
78
81
  userGoal: {
79
82
  (): import("@langchain/langgraph").LastValue<string>;
@@ -123,11 +126,25 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
123
126
  };
124
127
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
125
128
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
129
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
126
130
  }, import("@langchain/langgraph").StateDefinition, {
127
131
  architect: {
128
- spec: any;
129
- strategies: any[];
130
- needsClarification: any;
132
+ spec: import("../services/ai.service.js").WorkflowSpec;
133
+ strategies: {
134
+ strategyName: string;
135
+ aiModel: string;
136
+ suggestedName: string;
137
+ description: string;
138
+ nodes: {
139
+ type: string;
140
+ purpose: string;
141
+ config?: any;
142
+ }[];
143
+ questions?: string[];
144
+ aiProvider?: string;
145
+ }[];
146
+ needsClarification: boolean | undefined;
147
+ collaborationLog: string[];
131
148
  };
132
149
  engineer: {
133
150
  workflowJson: any;
@@ -136,8 +153,8 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
136
153
  workflowJson?: undefined;
137
154
  candidates?: undefined;
138
155
  } | {
139
- workflowJson: any;
140
156
  candidates: any[];
157
+ workflowJson?: undefined;
141
158
  };
142
159
  reviewer: import("@langchain/langgraph").UpdateType<{
143
160
  userGoal: {
@@ -188,11 +205,14 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
188
205
  };
189
206
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
190
207
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
208
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
191
209
  }>;
192
210
  supervisor: {
193
211
  workflowJson?: undefined;
212
+ collaborationLog?: undefined;
194
213
  } | {
195
214
  workflowJson: any;
215
+ collaborationLog: string[];
196
216
  };
197
217
  qa: import("@langchain/langgraph").UpdateType<{
198
218
  userGoal: {
@@ -243,6 +263,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
243
263
  };
244
264
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
245
265
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
266
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
246
267
  }>;
247
268
  }, unknown, unknown>;
248
269
  /**
@@ -300,6 +321,7 @@ export declare const runAgenticWorkflow: (goal: string, initialState?: Partial<t
300
321
  };
301
322
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
302
323
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
324
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
303
325
  }>>;
304
326
  /**
305
327
  * Run the Agentic Workflow with Streaming
@@ -308,9 +330,22 @@ export declare const runAgenticWorkflow: (goal: string, initialState?: Partial<t
308
330
  */
309
331
  export declare const runAgenticWorkflowStream: (goal: string, threadId?: string) => Promise<import("@langchain/core/utils/stream").IterableReadableStream<{
310
332
  architect?: {
311
- spec: any;
312
- strategies: any[];
313
- needsClarification: any;
333
+ spec: import("../services/ai.service.js").WorkflowSpec;
334
+ strategies: {
335
+ strategyName: string;
336
+ aiModel: string;
337
+ suggestedName: string;
338
+ description: string;
339
+ nodes: {
340
+ type: string;
341
+ purpose: string;
342
+ config?: any;
343
+ }[];
344
+ questions?: string[];
345
+ aiProvider?: string;
346
+ }[];
347
+ needsClarification: boolean | undefined;
348
+ collaborationLog: string[];
314
349
  } | undefined;
315
350
  engineer?: {
316
351
  workflowJson: any;
@@ -319,8 +354,8 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
319
354
  workflowJson?: undefined;
320
355
  candidates?: undefined;
321
356
  } | {
322
- workflowJson: any;
323
357
  candidates: any[];
358
+ workflowJson?: undefined;
324
359
  } | undefined;
325
360
  reviewer?: import("@langchain/langgraph").UpdateType<{
326
361
  userGoal: {
@@ -371,11 +406,14 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
371
406
  };
372
407
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
373
408
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
409
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
374
410
  }> | undefined;
375
411
  supervisor?: {
376
412
  workflowJson?: undefined;
413
+ collaborationLog?: undefined;
377
414
  } | {
378
415
  workflowJson: any;
416
+ collaborationLog: string[];
379
417
  } | undefined;
380
418
  qa?: import("@langchain/langgraph").UpdateType<{
381
419
  userGoal: {
@@ -426,6 +464,7 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
426
464
  };
427
465
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
428
466
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
467
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
429
468
  }> | undefined;
430
469
  }>>;
431
470
  /**
@@ -480,4 +519,5 @@ export declare const resumeAgenticWorkflow: (threadId: string, input?: any) => P
480
519
  };
481
520
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
482
521
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
522
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
483
523
  }>>;
@@ -1,6 +1,19 @@
1
1
  import { TeamState } from "../state.js";
2
2
  export declare const architectNode: (state: typeof TeamState.State) => Promise<{
3
- spec: any;
4
- strategies: any[];
5
- needsClarification: any;
3
+ spec: import("../../services/ai.service.js").WorkflowSpec;
4
+ strategies: {
5
+ strategyName: string;
6
+ aiModel: string;
7
+ suggestedName: string;
8
+ description: string;
9
+ nodes: {
10
+ type: string;
11
+ purpose: string;
12
+ config?: any;
13
+ }[];
14
+ questions?: string[];
15
+ aiProvider?: string;
16
+ }[];
17
+ needsClarification: boolean | undefined;
18
+ collaborationLog: string[];
6
19
  }>;
@@ -30,18 +30,29 @@ export const architectNode = async (state) => {
30
30
  // Check if the spec requires clarification
31
31
  const questions = spec.questions;
32
32
  const needsClarification = questions && questions.length > 0;
33
- // For parallelism, we can create a secondary "Alternative" strategy
34
- // In a real scenario, the LLM would generate these explicitly.
35
- // Here we simulate it by wrapping the single spec into a strategy list.
33
+ // Multi-agent collaboration: generate an alternative strategy in parallel with the primary.
34
+ // Both are handed off to separate Engineer agents that run concurrently.
35
+ const alternativeSpec = await aiService.generateAlternativeSpec(state.userGoal, spec);
36
+ const alternativeModel = aiService.getAlternativeModel();
36
37
  const strategies = [
37
- { ...spec, name: "Primary Strategy" },
38
- // We could ask AI for an alternative here, but for now let's keep it simple to save tokens
39
- // { ...spec, name: "Alternative Strategy (Robust)" }
38
+ {
39
+ ...spec,
40
+ strategyName: "Primary Strategy",
41
+ aiModel: aiService.getDefaultModel()
42
+ },
43
+ {
44
+ ...alternativeSpec,
45
+ strategyName: "Alternative Strategy",
46
+ aiModel: alternativeModel
47
+ },
40
48
  ];
49
+ const logEntry = `Architect: Generated 2 strategies — "${strategies[0].suggestedName}" (primary) and "${strategies[1].suggestedName}" (alternative)`;
50
+ console.log(`[Architect] ${logEntry}`);
41
51
  return {
42
- spec, // Keep backward compatibility for single-path
52
+ spec,
43
53
  strategies,
44
54
  needsClarification,
55
+ collaborationLog: [logEntry],
45
56
  };
46
57
  }
47
58
  catch (error) {
@@ -6,6 +6,6 @@ export declare const engineerNode: (state: typeof TeamState.State) => Promise<{
6
6
  workflowJson?: undefined;
7
7
  candidates?: undefined;
8
8
  } | {
9
- workflowJson: any;
10
9
  candidates: any[];
10
+ workflowJson?: undefined;
11
11
  }>;
@@ -1,5 +1,6 @@
1
1
  import { AIService } from "../../services/ai.service.js";
2
2
  import { NodeDefinitionsService } from "../../services/node-definitions.service.js";
3
+ import { jsonrepair } from "jsonrepair";
3
4
  export const engineerNode = async (state) => {
4
5
  const aiService = AIService.getInstance();
5
6
  // RAG: Load and Search Node Definitions
@@ -9,8 +10,9 @@ export const engineerNode = async (state) => {
9
10
  const queryText = (state.userGoal + (state.spec ? ` ${state.spec.suggestedName} ${state.spec.description}` : "")).replace(/\n/g, " ");
10
11
  // Search for relevant nodes (limit 8 to save context)
11
12
  const relevantDefs = nodeService.search(queryText, 8);
12
- const ragContext = relevantDefs.length > 0
13
- ? `\n\n[AVAILABLE NODE SCHEMAS - USE THESE EXACT PARAMETERS]\n${nodeService.formatForLLM(relevantDefs)}`
13
+ const staticRef = nodeService.getStaticReference();
14
+ const ragContext = (relevantDefs.length > 0 || staticRef)
15
+ ? `\n\n[N8N NODE REFERENCE GUIDE]\n${staticRef}\n\n[AVAILABLE NODE SCHEMAS - USE THESE EXACT PARAMETERS]\n${nodeService.formatForLLM(relevantDefs)}`
14
16
  : "";
15
17
  if (relevantDefs.length > 0) {
16
18
  console.log(`[Engineer] RAG: Found ${relevantDefs.length} relevant node schemas.`);
@@ -22,7 +24,8 @@ export const engineerNode = async (state) => {
22
24
  // We pass the entire list of errors as context
23
25
  const errorContext = state.validationErrors.join('\n');
24
26
  // Use the robust fix logic from AIService
25
- const fixedWorkflow = await aiService.generateWorkflowFix(state.workflowJson, errorContext, undefined, false, state.availableNodeTypes || []);
27
+ const fixedWorkflow = await aiService.generateWorkflowFix(state.workflowJson, errorContext, state.spec?.aiModel, // Pass model if available
28
+ false, state.availableNodeTypes || []);
26
29
  return {
27
30
  workflowJson: fixedWorkflow,
28
31
  // validationErrors will be overwritten by next QA run
@@ -74,23 +77,27 @@ export const engineerNode = async (state) => {
74
77
  Output ONLY valid JSON. No commentary. No markdown.
75
78
  `;
76
79
  // Using AIService just for the LLM call to keep auth logic dry
77
- const response = await aiService.generateContent(prompt);
80
+ const response = await aiService.generateContent(prompt, {
81
+ provider: state.spec.aiProvider,
82
+ model: state.spec.aiModel
83
+ });
78
84
  let cleanJson = response || "{}";
79
85
  cleanJson = cleanJson.replace(/```json\n?|\n?```/g, "").trim();
80
86
  let result;
81
87
  try {
82
- result = JSON.parse(cleanJson);
88
+ result = JSON.parse(jsonrepair(cleanJson));
83
89
  }
84
- catch (e) {
85
- console.error("Failed to parse workflow JSON from spec", e);
90
+ catch (e2) {
91
+ console.error("Failed to parse workflow JSON from spec", e2);
86
92
  throw new Error("AI generated invalid JSON for workflow from spec");
87
93
  }
88
94
  if (result.workflows && Array.isArray(result.workflows)) {
89
95
  result.workflows = result.workflows.map((wf) => fixHallucinatedNodes(wf));
90
96
  }
91
97
  return {
92
- workflowJson: result,
93
- // For parallel execution, push to candidates
98
+ // Only push to candidates — the Supervisor sets workflowJson after fan-in.
99
+ // Writing workflowJson here would cause a LastValue conflict when two
100
+ // Engineers run in parallel via Send().
94
101
  candidates: [result],
95
102
  };
96
103
  }
@@ -10,8 +10,9 @@ export const qaNode = async (state) => {
10
10
  }
11
11
  // 1. Load Credentials
12
12
  const config = await ConfigManager.load();
13
- const n8nUrl = config.n8nUrl || process.env.N8N_API_URL;
14
- const n8nKey = config.n8nKey || process.env.N8N_API_KEY;
13
+ // Env vars take priority over stored config so a fresh key in .env is always used
14
+ const n8nUrl = process.env.N8N_API_URL || config.n8nUrl;
15
+ const n8nKey = process.env.N8N_API_KEY || config.n8nKey;
15
16
  if (!n8nUrl || !n8nKey) {
16
17
  throw new Error('Credentials missing. Configure environment via \'n8m config\'.');
17
18
  }
@@ -28,10 +29,21 @@ export const qaNode = async (state) => {
28
29
  targetWorkflow = workflowJson.workflows[0];
29
30
  }
30
31
  const workflowName = targetWorkflow.name || 'Agentic_Test_Workflow';
32
+ // Drop timezone — sanitizeSettings in N8nClient strips it unconditionally
33
+ const rawSettings = { ...(targetWorkflow.settings || {}) };
34
+ delete rawSettings.timezone;
35
+ // Strip credentials from all nodes — n8n 2.x refuses to activate ("publish")
36
+ // a workflow that references credentials that don't exist on the instance.
37
+ // Structural validation can still run; only live execution of credentialed
38
+ // nodes will be skipped/fail, which is expected for an ephemeral test.
39
+ const strippedNodes = targetWorkflow.nodes.map((node) => {
40
+ const { credentials: _creds, ...rest } = node;
41
+ return rest;
42
+ });
31
43
  const rootPayload = {
32
- nodes: targetWorkflow.nodes,
44
+ nodes: strippedNodes,
33
45
  connections: targetWorkflow.connections,
34
- settings: targetWorkflow.settings || {},
46
+ settings: rawSettings,
35
47
  staticData: targetWorkflow.staticData || {},
36
48
  name: `[n8m:test] ${workflowName}`,
37
49
  };
@@ -66,6 +66,8 @@ export const reviewerNode = async (state) => {
66
66
  // Triggers usually have no input.
67
67
  // We use a broader check: any node with "trigger" or "webhook" in the name, plus generic start types.
68
68
  const isTrigger = (type) => {
69
+ if (!type)
70
+ return false;
69
71
  const lower = type.toLowerCase();
70
72
  return lower.includes('trigger') ||
71
73
  lower.includes('webhook') ||
@@ -79,9 +81,9 @@ export const reviewerNode = async (state) => {
79
81
  // Sticky notes and Merge nodes can be tricky, but generally Merge needs input.
80
82
  if (!node.type.includes('StickyNote')) {
81
83
  // Double check for "On Execution" (custom trigger name sometimes used)
82
- if (!node.name.toLowerCase().includes('trigger') && !node.name.toLowerCase().includes('webhook')) {
83
- console.log(theme.warn(`[Reviewer] Validated disconnection: Node "${node.name}" has no incoming connections.`));
84
- validationErrors.push(`Node "${node.name}" (${node.type}) is disconnected (orphaned). Connect it or remove it.`);
84
+ if (!node.name || (!node.name.toLowerCase().includes('trigger') && !node.name.toLowerCase().includes('webhook'))) {
85
+ console.log(theme.warn(`[Reviewer] Validated disconnection: Node "${node.name || 'Unnamed'}" has no incoming connections.`));
86
+ validationErrors.push(`Node "${node.name || 'Unnamed'}" (${node.type || 'unknown type'}) is disconnected (orphaned). Connect it or remove it.`);
85
87
  }
86
88
  }
87
89
  }
@@ -1,6 +1,8 @@
1
1
  import { TeamState } from "../state.js";
2
2
  export declare const supervisorNode: (state: typeof TeamState.State) => Promise<{
3
3
  workflowJson?: undefined;
4
+ collaborationLog?: undefined;
4
5
  } | {
5
6
  workflowJson: any;
7
+ collaborationLog: string[];
6
8
  }>;
@@ -1,3 +1,4 @@
1
+ import { AIService } from "../../services/ai.service.js";
1
2
  import { theme } from "../../utils/theme.js";
2
3
  export const supervisorNode = async (state) => {
3
4
  const candidates = state.candidates;
@@ -5,14 +6,15 @@ export const supervisorNode = async (state) => {
5
6
  // Fallback: use existing workflowJson if available
6
7
  return {};
7
8
  }
8
- console.log(theme.agent(`Supervisor found ${candidates.length} candidates.`));
9
- // In a real agentic system, we would have an LLM evaluate them.
10
- // For now, we'll pick the first one (or the one with the most nodes? or custom logic?).
11
- // Let's simulate "Selection":
12
- const bestCandidate = candidates[0];
13
- console.log(theme.success(`Supervisor selected: ${bestCandidate.name || "Unnamed Workflow"}`));
14
- // We set the chosen one as the canonical 'workflowJson' for the rest of the flow (QA, etc)
9
+ console.log(theme.agent(`Supervisor evaluating ${candidates.length} candidate(s)...`));
10
+ const aiService = AIService.getInstance();
11
+ const evaluation = await aiService.evaluateCandidates(state.userGoal, candidates);
12
+ const bestCandidate = candidates[evaluation.selectedIndex] ?? candidates[0];
13
+ const logEntry = `Supervisor: Selected candidate ${evaluation.selectedIndex + 1}/${candidates.length} ("${bestCandidate.name || 'Unnamed'}"). Reason: ${evaluation.reason}`;
14
+ console.log(theme.success(`Supervisor selected: ${bestCandidate.name || "Unnamed Workflow"} (candidate ${evaluation.selectedIndex + 1})`));
15
+ console.log(theme.agent(` → ${evaluation.reason}`));
15
16
  return {
16
- workflowJson: bestCandidate
17
+ workflowJson: bestCandidate,
18
+ collaborationLog: [logEntry],
17
19
  };
18
20
  };
@@ -48,4 +48,5 @@ export declare const TeamState: import("@langchain/langgraph").AnnotationRoot<{
48
48
  };
49
49
  candidates: import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
50
50
  customTools: import("@langchain/langgraph").BinaryOperatorAggregate<Record<string, string>, Record<string, string>>;
51
+ collaborationLog: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
51
52
  }>;
@@ -23,4 +23,9 @@ export const TeamState = Annotation.Root({
23
23
  reducer: (x, y) => ({ ...x, ...y }),
24
24
  default: () => ({}),
25
25
  }),
26
+ // Collaboration Log: agents record their reasoning for visibility
27
+ collaborationLog: Annotation({
28
+ reducer: (x, y) => x.concat(y),
29
+ default: () => [],
30
+ }),
26
31
  });
@@ -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
+ ]