@lhi/n8m 0.2.4 → 0.3.1

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.
@@ -0,0 +1,598 @@
1
+ # n8m Developer Guide
2
+
3
+ > A deep-dive into the internals of `n8m` for contributors and developers who
4
+ > want to understand, extend, or build on the project.
5
+
6
+ ## Table of Contents
7
+
8
+ - [Project Structure](#project-structure)
9
+ - [Architecture Overview](#architecture-overview)
10
+ - [The Agentic Graph](#the-agentic-graph)
11
+ - [TeamState](#teamstate)
12
+ - [Agent Nodes](#agent-nodes)
13
+ - [Graph Edges & Control Flow](#graph-edges--control-flow)
14
+ - [AI Service](#ai-service)
15
+ - [Node Definitions & RAG](#node-definitions--rag)
16
+ - [CLI Commands](#cli-commands)
17
+ - [Testing Infrastructure](#testing-infrastructure)
18
+ - [Extending n8m](#extending-n8m)
19
+ - [Environment Variables](#environment-variables)
20
+ - [Local Development](#local-development)
21
+
22
+ ---
23
+
24
+ ## Project Structure
25
+
26
+ ```
27
+ n8m/
28
+ ├── bin/ # CLI entry points
29
+ ├── docs/ # Documentation (you are here)
30
+ ├── src/
31
+ │ ├── agentic/ # LangGraph multi-agent system
32
+ │ │ ├── graph.ts # Graph definition, edges, and exports
33
+ │ │ ├── state.ts # TeamState (shared agent memory)
34
+ │ │ ├── checkpointer.ts # SQLite persistence for sessions
35
+ │ │ └── nodes/ # Individual agent node implementations
36
+ │ │ ├── architect.ts # Blueprint designer
37
+ │ │ ├── engineer.ts # Workflow JSON generator
38
+ │ │ ├── reviewer.ts # Static structural validator
39
+ │ │ ├── supervisor.ts # Candidate selector (fan-in)
40
+ │ │ └── qa.ts # Live ephemeral tester
41
+ │ ├── commands/ # oclif CLI command handlers
42
+ │ │ ├── create.ts
43
+ │ │ ├── modify.ts
44
+ │ │ ├── test.ts
45
+ │ │ ├── deploy.ts
46
+ │ │ ├── doc.ts
47
+ │ │ ├── fixture.ts # capture/init sub-commands for offline fixtures
48
+ │ │ ├── learn.ts # extract pattern knowledge from validated workflows
49
+ │ │ ├── mcp.ts # MCP server entry point
50
+ │ │ ├── resume.ts
51
+ │ │ ├── prune.ts
52
+ │ │ └── config.ts
53
+ │ ├── services/ # Core business logic services
54
+ │ │ ├── ai.service.ts # LLM abstraction layer
55
+ │ │ ├── doc.service.ts # Documentation generation
56
+ │ │ ├── n8n.service.ts # n8n API helpers
57
+ │ │ ├── mcp.service.ts # MCP server integration
58
+ │ │ └── node-definitions.service.ts # RAG for n8n node schemas
59
+ │ ├── utils/
60
+ │ │ ├── n8nClient.ts # n8n REST API client
61
+ │ │ ├── config.ts # Config file management
62
+ │ │ ├── theme.ts # CLI formatting/theming
63
+ │ │ ├── fixtureManager.ts # Read/write .n8m/fixtures/ (single-file + directory)
64
+ │ │ └── sandbox.ts # Isolated script runner for custom QA tools
65
+ │ └── resources/
66
+ │ └── node-definitions-fallback.json # Static node schema fallback
67
+ ├── docs/
68
+ │ └── N8N_NODE_REFERENCE.md # Human-readable node reference (for LLM context)
69
+ ├── test/ # Mocha unit tests
70
+ └── workflows/ # Local workflow project folders
71
+ └── <slug>/
72
+ ├── workflow.json
73
+ └── README.md
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Architecture Overview
79
+
80
+ `n8m` uses a **multi-agent LangGraph pipeline** to translate a natural-language
81
+ goal into a validated n8n workflow JSON. The pipeline is composed of several
82
+ specialized AI agents, each with a distinct role:
83
+
84
+ ```
85
+ Developer → n8m create "Send daily Slack digest"
86
+
87
+
88
+ ┌───────────────┐
89
+ │ Architect │ Generates 2 strategies (Primary + Alternative)
90
+ └───────┬───────┘
91
+ │ Send() fan-out
92
+ ┌────────┴────────┐
93
+ ▼ ▼
94
+ ┌──────────┐ ┌──────────┐ (Parallel Engineers — each works on one strategy)
95
+ │ Engineer │ │ Engineer │
96
+ └────┬─────┘ └────┬─────┘
97
+ └────────┬────────┘
98
+ │ candidates[]
99
+
100
+ ┌──────────────┐
101
+ │ Supervisor │ AI picks the best candidate
102
+ └──────┬───────┘
103
+
104
+
105
+ ┌───────────┐
106
+ │ Reviewer │ Static structural validation (node types, orphans, connections)
107
+ └──────┬────┘
108
+ pass │ fail
109
+ ┌─────┴─────┐
110
+ ▼ ▼
111
+ ┌─────┐ ┌──────────┐
112
+ │ QA │ │ Engineer │◄─ repair loop
113
+ └──┬──┘ └──────────┘
114
+ pass │ fail
115
+ │ ┌──────────┐
116
+ │ │ Engineer │◄─ self-correction loop
117
+
118
+ END
119
+ ./workflows/<slug>/
120
+ ├── workflow.json
121
+ └── README.md
122
+ ```
123
+
124
+ The pipeline leverages [LangGraph](https://github.com/langchain-ai/langgraphjs)
125
+ for orchestration, with SQLite-backed checkpointing for session persistence and
126
+ HITL (Human-in-the-Loop) interrupts.
127
+
128
+ ---
129
+
130
+ ## The Agentic Graph
131
+
132
+ ### TeamState
133
+
134
+ Defined in `src/agentic/state.ts`. This is the **shared memory** of the entire
135
+ pipeline — all agents read from and write to this object.
136
+
137
+ ```typescript
138
+ // src/agentic/state.ts
139
+ export const TeamState = Annotation.Root({
140
+ userGoal: Annotation<string>, // The original user prompt
141
+ spec: Annotation<any>, // Workflow spec from Architect
142
+ workflowJson: Annotation<any>, // The generated/fixed workflow
143
+ validationErrors: Annotation<string[]>, // Errors from Reviewer or QA
144
+ validationStatus: Annotation<"passed" | "failed">,
145
+ availableNodeTypes: Annotation<string[]>, // Node types from live n8n instance
146
+ revisionCount: Annotation<number>, // How many repair loops have run
147
+ strategies: Annotation<any[]>, // Multiple Architect strategies (for parallel Engineers)
148
+ candidates: Annotation<any[]>, // Each Engineer pushes here (fan-in reducer)
149
+ collaborationLog: Annotation<string[]>, // Agent audit trail
150
+ userFeedback: Annotation<string>, // HITL feedback from user
151
+ testScenarios: Annotation<any[]>, // AI-generated test input payloads
152
+ customTools: Annotation<Record<string, string>>, // Dynamic scripts for QA sandbox
153
+ });
154
+ ```
155
+
156
+ **Key design note**: `candidates` uses a custom `reducer` that concatenates
157
+ incoming arrays. This is what enables the parallel fan-out pattern — each
158
+ Engineer pushes one candidate, and LangGraph merges them for the Supervisor to
159
+ evaluate.
160
+
161
+ ### Agent Nodes
162
+
163
+ Each node is a plain `async function(state) => Partial<TeamState>`. They live in
164
+ `src/agentic/nodes/`.
165
+
166
+ | Node | File | Role |
167
+ | ------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
168
+ | `architect` | `architect.ts` | Calls `AIService.generateSpec()` twice (primary + alternative) to produce two strategies. Returns `{ strategies, spec }`. |
169
+ | `engineer` | `engineer.ts` | Receives one strategy via `Send()`. Performs RAG lookup for relevant node schemas, then calls `AIService` to generate full workflow JSON. Returns `{ candidates: [result] }` or repairs an existing `workflowJson`. |
170
+ | `supervisor` | `supervisor.ts` | Receives all candidates from Engineers. Calls `AIService.evaluateCandidates()` to have an AI pick the best one. Sets `workflowJson`. |
171
+ | `reviewer` | `reviewer.ts` | Performs **pure static validation** (no AI, no network). Detects hallucinated node types, orphaned nodes, and missing sub-workflow IDs. |
172
+ | `qa` | `qa.ts` | Deploys the workflow ephemerally to your n8n instance, runs test scenarios via webhook, verifies execution results, and cleans up. |
173
+
174
+ ### Graph Edges & Control Flow
175
+
176
+ ```typescript
177
+ // src/agentic/graph.ts (simplified)
178
+ workflow
179
+ .addEdge(START, "architect")
180
+ // Fan-out: One Engineer for each Architect strategy
181
+ .addConditionalEdges("architect", (state) => {
182
+ if (state.strategies?.length > 0) {
183
+ return state.strategies.map((s) =>
184
+ new Send("engineer", { spec: s })
185
+ );
186
+ }
187
+ return "engineer"; // fallback
188
+ }, ["engineer"])
189
+ // Route: if repairing (errors present) → skip Supervisor → go to Reviewer
190
+ .addConditionalEdges("engineer", (state) => {
191
+ return state.validationErrors?.length > 0 ? "reviewer" : "supervisor";
192
+ }, ["supervisor", "reviewer"])
193
+ .addEdge("supervisor", "reviewer")
194
+ // Reviewer: pass → QA, fail → back to Engineer
195
+ .addConditionalEdges(
196
+ "reviewer",
197
+ (state) => state.validationStatus === "passed" ? "passed" : "failed",
198
+ { passed: "qa", failed: "engineer" },
199
+ )
200
+ // QA: pass → END, fail → back to Engineer (self-correction loop)
201
+ .addConditionalEdges(
202
+ "qa",
203
+ (state) => state.validationStatus === "passed" ? "passed" : "failed",
204
+ { passed: END, failed: "engineer" },
205
+ );
206
+
207
+ // HITL interrupts fire before these nodes, pausing for user review
208
+ export const graph = workflow.compile({
209
+ checkpointer,
210
+ interruptBefore: ["engineer", "qa"],
211
+ });
212
+ ```
213
+
214
+ **HITL (Human-in-the-Loop)**: The graph pauses execution before `engineer` and
215
+ `qa`. The CLI commands (`create.ts`, `test.ts`) detect this pause by checking
216
+ `graph.getState()`, prompt the user for input, then call
217
+ `graph.stream(null, ...)` to resume.
218
+
219
+ ---
220
+
221
+ ## AI Service
222
+
223
+ `src/services/ai.service.ts` is the **single abstraction layer** for all LLM
224
+ calls. It supports OpenAI, Anthropic (Claude), Google Gemini, and any
225
+ OpenAI-compatible API (Ollama, Groq, etc.).
226
+
227
+ ### Key Methods
228
+
229
+ | Method | Description |
230
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
231
+ | `generateContent(prompt, options?)` | Low-level LLM call with retry logic (3 attempts, exponential backoff). |
232
+ | `generateSpec(goal)` | Produces a `WorkflowSpec` (blueprint) from a user goal. |
233
+ | `generateAlternativeSpec(goal, primarySpec)` | Generates a second, different strategy — uses the "alternative model" for diversity. |
234
+ | `generateWorkflowFix(workflow, errors, model?)` | Sends a failing workflow + error list to the LLM for repair. |
235
+ | `evaluateCandidates(goal, candidates)` | AI picks the best candidate workflow from the list. |
236
+ | `generateTestScenarios(workflowJson, goal)` | Returns 3 test payloads: happy path, edge case, error case. |
237
+ | `evaluateTestError(error, nodes, failingNode)` | Classifies a live test failure and returns a `TestErrorEvaluation` describing what action to take. Used by the self-healing loop in `test.ts` and `qa.ts`. |
238
+ | `inferBinaryFieldName(predecessorNode)` | Given a Code node that produces binary output, reads its `jsCode` and asks the AI what the binary field name is. Returns `string \| null`. |
239
+ | `fixHallucinatedNodes(workflow)` | Corrects known-bad node type strings (e.g. `rssFeed` → `rssFeedRead`). |
240
+ | `validateAndShim(workflow, validNodeTypes)` | Replaces truly unknown node types with safe shims (`n8n-nodes-base.set`). |
241
+
242
+ ### Provider Configuration
243
+
244
+ The service reads credentials from (in priority order):
245
+
246
+ 1. Environment variables (`AI_API_KEY`, `AI_PROVIDER`, `AI_MODEL`,
247
+ `AI_BASE_URL`)
248
+ 2. `~/.n8m/config.json` (written by `n8m config`)
249
+
250
+ Anthropic is called via its native `/messages` REST API because it doesn't fully
251
+ conform to the OpenAI SDK. All others use the OpenAI SDK with a custom
252
+ `baseURL`.
253
+
254
+ ### Parallel Strategies & Model Diversity
255
+
256
+ The Architect generates a primary strategy using the default model, and an
257
+ alternative strategy using `getAlternativeModel()`. This method returns a
258
+ different model tier from the same provider (e.g., `claude-haiku` if using
259
+ `claude-sonnet`), ensuring genuine architectural diversity in the two candidates
260
+ before the Supervisor picks the winner.
261
+
262
+ ---
263
+
264
+ ## Node Definitions & RAG
265
+
266
+ `src/services/node-definitions.service.ts` provides **Retrieval-Augmented
267
+ Generation** for n8n node schemas, helping the Engineer produce accurate node
268
+ configurations.
269
+
270
+ ### Loading Strategy (with Fallback)
271
+
272
+ ```
273
+ 1. Fetch live node types from n8n instance via /nodes endpoint
274
+ ↓ (on failure)
275
+ 2. Load from src/resources/node-definitions-fallback.json (static snapshot)
276
+ ↓ (on failure)
277
+ 3. Empty — RAG disabled, Engineer uses base knowledge only
278
+ ```
279
+
280
+ ### How RAG Works in the Engineer Node
281
+
282
+ ```typescript
283
+ // src/agentic/nodes/engineer.ts
284
+ const nodeService = NodeDefinitionsService.getInstance();
285
+ await nodeService.loadDefinitions();
286
+
287
+ // Build a query from goal + spec description
288
+ const queryText = state.userGoal + " " + state.spec.suggestedName;
289
+
290
+ // Keyword search — returns up to 8 reduced definitions
291
+ const relevantDefs = nodeService.search(queryText, 8);
292
+
293
+ // Static markdown reference (loaded from docs/N8N_NODE_REFERENCE.md)
294
+ const staticRef = nodeService.getStaticReference();
295
+
296
+ // Both are injected into the Engineer's LLM prompt
297
+ const ragContext =
298
+ `[N8N NODE REFERENCE GUIDE]\n${staticRef}\n\n[AVAILABLE NODE SCHEMAS]\n${
299
+ nodeService.formatForLLM(relevantDefs)
300
+ }`;
301
+ ```
302
+
303
+ ### Updating the Fallback / Reference
304
+
305
+ - **`src/resources/node-definitions-fallback.json`**: A JSON snapshot of n8n
306
+ node type definitions. Update this periodically from a live n8n instance.
307
+ - **`docs/N8N_NODE_REFERENCE.md`**: A human-readable markdown reference injected
308
+ into the Architect and Engineer prompts. Editable manually. This is the
309
+ primary guide for the AI when choosing node types and parameters.
310
+
311
+ ---
312
+
313
+ ## CLI Commands
314
+
315
+ All commands are built with [oclif](https://oclif.io/) and live in
316
+ `src/commands/`. They handle user I/O and then delegate to the agentic graph or
317
+ services.
318
+
319
+ | Command | File | Description |
320
+ | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------- |
321
+ | `create` | `create.ts` | Runs `runAgenticWorkflowStream()`, handles HITL prompts, organizes output into project folders, auto-generates docs. |
322
+ | `modify` | `modify.ts` | Loads an existing workflow, builds a modification goal, passes to `runAgenticWorkflow()`. |
323
+ | `test` | `test.ts` | Resolves sub-workflow dependencies, runs the agentic validator/repairer, handles ephemeral deploy/cleanup. Also drives the offline fixture replay loop. |
324
+ | `deploy` | `deploy.ts` | Directly pushes a local JSON to the n8n instance. |
325
+ | `doc` | `doc.ts` | Uses `DocService` to generate Mermaid diagrams + AI summaries, organizes loose files into project folders. |
326
+ | `fixture` | `fixture.ts` | Two sub-commands: `capture` (pull real execution data from n8n → named fixture) and `init` (scaffold empty template). Fixtures stored in `.n8m/fixtures/<workflowId>/<name>.json`. |
327
+ | `learn` | `learn.ts` | Extracts reusable patterns from validated workflow JSON and writes `.md` pattern files to `.n8m/patterns/`. Also supports `--github owner/repo` to import patterns from a public GitHub archive. |
328
+ | `mcp` | `mcp.ts` | Launches the MCP (Model Context Protocol) server over stdio, exposing `create_workflow` and `test_workflow` as tools for Claude Desktop and other MCP clients. |
329
+ | `resume` | `resume.ts` | Resumes a paused graph session by thread ID from the SQLite checkpointer. |
330
+ | `prune` | `prune.ts` | Deletes `[n8m:test:*]` prefixed workflows from the n8n instance. |
331
+ | `config` | `config.ts` | Reads/writes `~/.n8m/config.json`. |
332
+
333
+ ### Project Folder Output
334
+
335
+ When a workflow is created or saved, it is organized into a slug-named folder:
336
+
337
+ ```
338
+ ./workflows/
339
+ └── send-daily-slack-digest/
340
+ ├── workflow.json ← the n8n workflow
341
+ └── README.md ← AI-generated doc with Mermaid diagram
342
+ ```
343
+
344
+ The slug is generated by `DocService.generateSlug()`, which lowercases the name
345
+ and replaces non-alphanumeric characters with hyphens.
346
+
347
+ ---
348
+
349
+ ## Testing Infrastructure
350
+
351
+ Tests live in `test/` and run with [Mocha](https://mochajs.org/) +
352
+ [Sinon](https://sinonjs.org/).
353
+
354
+ ```bash
355
+ # Run all tests
356
+ npm test
357
+
358
+ # Watch mode
359
+ npm run dev
360
+ ```
361
+
362
+ ### Key Testing Principles
363
+
364
+ - **No live AI calls in tests**: `process.env.NODE_ENV=test` is set by
365
+ `.mocharc.json`. The `AIService` and `N8nClient` must be fully mocked in test
366
+ files via `sinon.stub()` before calling any tested code. Importing them causes
367
+ the singleton to initialize — ensure stubs are applied first.
368
+ - **Ephemeral test workflows**: `n8m test` deploys workflows to n8n with a
369
+ `[n8m:test]` name prefix and deletes them in the `finally` block — even on
370
+ failure.
371
+ - **AI Scenario Generation**: Use `--ai-scenarios` flag to have the QA node
372
+ generate 3 diverse test payloads automatically (happy path, edge case, error).
373
+
374
+ ---
375
+
376
+ ## Extending n8m
377
+
378
+ ### Adding a New Agent Node
379
+
380
+ 1. Create a new file in `src/agentic/nodes/my-node.ts`:
381
+ ```typescript
382
+ import { TeamState } from "../state.js";
383
+
384
+ export const myNode = async (state: typeof TeamState.State) => {
385
+ // Read from state, do work, return partial state
386
+ return {
387
+ collaborationLog: ["myNode: did something"],
388
+ };
389
+ };
390
+ ```
391
+
392
+ 2. Register it in `src/agentic/graph.ts`:
393
+ ```typescript
394
+ import { myNode } from "./nodes/my-node.js";
395
+
396
+ const workflow = new StateGraph(TeamState)
397
+ .addNode("myNode", myNode);
398
+ // ... add edges
399
+ ```
400
+
401
+ 3. Add any new state fields to `src/agentic/state.ts`.
402
+
403
+ ### Adding a New CLI Command
404
+
405
+ 1. Create `src/commands/my-command.ts` extending `Command` from `@oclif/core`.
406
+ 2. Register it in `package.json` under the `oclif.commands` field (or in the
407
+ commands directory manifest).
408
+
409
+ ### Adding a New AI Provider
410
+
411
+ `AIService` wraps the OpenAI SDK with custom `baseURL`. For any
412
+ OpenAI-compatible API:
413
+
414
+ ```bash
415
+ n8m config --ai-base-url https://api.my-provider.com/v1 --ai-key <key> --ai-model my-model
416
+ ```
417
+
418
+ For non-compatible APIs, implement a new private call method in `AIService`
419
+ (similar to `callAnthropicNative`) and route to it in `generateContent()`.
420
+
421
+ ---
422
+
423
+ ## Self-Healing Test Loop
424
+
425
+ Both `src/commands/test.ts` (`testRemoteWorkflowDirectly`) and
426
+ `src/agentic/nodes/qa.ts` implement the same AI-powered repair cycle:
427
+
428
+ ```
429
+ 1. Fire webhook / execute workflow
430
+ 2. Poll for execution result
431
+ 3. On failure → call AIService.evaluateTestError(error, nodes, failingNodeName)
432
+ 4. Dispatch on returned action:
433
+ ├── fix_node/code_node_js → patch JS syntax in the Code node's jsCode
434
+ ├── fix_node/execute_command → patch shell script in Execute Command node
435
+ ├── fix_node/binary_field → correct a wrong binary field name (see below)
436
+ ├── regenerate_payload → ask AI to produce a new test input payload
437
+ ├── structural_pass → test environment limitation; mark as pass
438
+ └── escalate → fundamental design flaw; abort with message
439
+ 5. Apply fix, redeploy, retry
440
+ ```
441
+
442
+ ### `TestErrorEvaluation` interface
443
+
444
+ ```typescript
445
+ // src/services/ai.service.ts
446
+ export interface TestErrorEvaluation {
447
+ action: 'fix_node' | 'regenerate_payload' | 'structural_pass' | 'escalate';
448
+ nodeFixType?: 'code_node_js' | 'execute_command' | 'binary_field';
449
+ targetNodeName?: string;
450
+ suggestedBinaryField?: string;
451
+ reason: string;
452
+ }
453
+ ```
454
+
455
+ ### Binary field fix flow
456
+
457
+ When `nodeFixType === 'binary_field'` (error: `"has no binary field 'X'"`):
458
+
459
+ 1. Find the predecessor node via the workflow's `connections` map.
460
+ 2. If predecessor is an **HTTP Request** node → use `'data'` (always correct).
461
+ 3. If predecessor is a **Code node** → call `AIService.inferBinaryFieldName(node)`,
462
+ which reads `jsCode` and asks the AI what the binary field is called.
463
+ 4. Any other predecessor type → `structural_pass` (can't determine field).
464
+ 5. After **any** `binary_field` fix attempt, a subsequent binary error on the
465
+ same run → `structural_pass` (avoids infinite loops in test environments
466
+ that don't support binary pin injection).
467
+
468
+ ---
469
+
470
+ ## Fixture Infrastructure
471
+
472
+ Fixtures are managed by `src/utils/fixtureManager.ts` (`FixtureManager` class).
473
+
474
+ ### Storage format
475
+
476
+ ```
477
+ .n8m/fixtures/
478
+ <workflowId>/ ← new multi-fixture directory (one file per scenario)
479
+ happy-path.json
480
+ error-case.json
481
+ <workflowId>.json ← legacy single-file format (still supported for reads)
482
+ ```
483
+
484
+ `FixtureManager.loadAll(workflowId)` prefers the directory format, falling back
485
+ to the single legacy file if no directory exists.
486
+
487
+ ### `WorkflowFixture` schema
488
+
489
+ ```typescript
490
+ interface WorkflowFixture {
491
+ version: '1.0';
492
+ capturedAt: string; // ISO timestamp
493
+ workflowId: string;
494
+ workflowName: string;
495
+ description?: string; // human label, e.g. "happy-path"
496
+ expectedOutcome?: 'pass' | 'fail'; // default: 'pass'
497
+ workflow: any; // full workflow JSON
498
+ execution: {
499
+ id?: string;
500
+ status: string;
501
+ startedAt?: string;
502
+ data: {
503
+ resultData: {
504
+ error?: any;
505
+ runData: Record<string, any[]>; // keyed by exact node name
506
+ };
507
+ };
508
+ };
509
+ }
510
+ ```
511
+
512
+ ### Key methods
513
+
514
+ | Method | Description |
515
+ |---|---|
516
+ | `exists(workflowId)` | Returns `true` if any fixture (directory or legacy file) exists for the workflow. |
517
+ | `loadAll(workflowId)` | Returns all fixtures for a workflow as an array; used by `test.ts` to run every scenario. |
518
+ | `load(workflowId)` | Legacy: loads the single `.n8m/fixtures/<workflowId>.json` file. |
519
+ | `loadFromPath(filePath)` | Loads a fixture from an explicit path (used with `--fixture` flag). |
520
+ | `saveNamed(fixture, name)` | Saves to the per-workflow directory (new multi-fixture format). |
521
+ | `save(fixture)` | Legacy single-file save; used by `offerSaveFixture` after live test runs. |
522
+ | `getCapturedDate(workflowId)` | Returns the most recent `capturedAt` date across all fixtures. |
523
+
524
+ ---
525
+
526
+ ## MCP Server
527
+
528
+ `src/services/mcp.service.ts` implements an MCP server using the
529
+ `@modelcontextprotocol/sdk` package with a **stdio transport**.
530
+
531
+ ```
532
+ Claude Desktop / Cursor / other MCP client
533
+ │ stdio
534
+
535
+ MCPService (n8m-agent)
536
+ ├── create_workflow(goal) → runAgenticWorkflow(goal)
537
+ └── test_workflow(workflowJson, goal) → deploys + validates ephemerally
538
+ ```
539
+
540
+ The server runs as a long-lived process started by `n8m mcp`. It does not use
541
+ the HITL interrupt mechanism (no interactive prompts), so workflow generation
542
+ runs fully autonomously.
543
+
544
+ To integrate with Claude Desktop, add to `claude_desktop_config.json`:
545
+
546
+ ```json
547
+ {
548
+ "mcpServers": {
549
+ "n8m": { "command": "npx", "args": ["n8m", "mcp"] }
550
+ }
551
+ }
552
+ ```
553
+
554
+ ---
555
+
556
+ ## Environment Variables
557
+
558
+ | Variable | Description | Priority |
559
+ | ---------------- | ------------------------------------------ | ----------------- |
560
+ | `AI_API_KEY` | API key for the AI provider | Env > Config file |
561
+ | `AI_PROVIDER` | `openai`, `anthropic`, or `gemini` | Env > Config file |
562
+ | `AI_MODEL` | Override the default model | Env > Config file |
563
+ | `AI_BASE_URL` | Base URL for any OpenAI-compatible API | Env > Config file |
564
+ | `N8N_API_URL` | URL of your n8n instance | Env > Config file |
565
+ | `N8N_API_KEY` | n8n API key | Env > Config file |
566
+ | `GEMINI_API_KEY` | Alias for `AI_API_KEY` when using Gemini | Env only |
567
+ | `NODE_ENV` | Set to `test` to prevent live AI/n8n calls | Env only |
568
+
569
+ ---
570
+
571
+ ## Local Development
572
+
573
+ ```bash
574
+ git clone https://github.com/lcanady/n8m.git
575
+ cd n8m
576
+ npm install
577
+
578
+ # Watch mode (recompiles on change)
579
+ npm run dev
580
+
581
+ # Run the CLI directly from source
582
+ ./bin/run.js help
583
+ ./bin/run.js create "Send a Slack message every morning"
584
+
585
+ # Run tests
586
+ npm test
587
+ ```
588
+
589
+ ### Build
590
+
591
+ ```bash
592
+ npm run build # Compiles TypeScript to dist/
593
+ ```
594
+
595
+ The project uses `tsconfig.json` with `"module": "NodeNext"` and
596
+ `"moduleResolution": "NodeNext"`. All imports in source files must include the
597
+ `.js` extension, even for TypeScript files (this is resolved to `.ts` by the
598
+ TypeScript compiler at build time but must be explicit).