@lhi/n8m 0.2.3 → 0.3.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 +149 -11
- package/dist/agentic/graph.d.ts +16 -4
- package/dist/agentic/nodes/architect.d.ts +2 -2
- package/dist/agentic/nodes/architect.js +5 -1
- package/dist/agentic/nodes/engineer.d.ts +6 -0
- package/dist/agentic/nodes/engineer.js +39 -5
- package/dist/commands/create.js +43 -4
- package/dist/commands/deploy.d.ts +2 -1
- package/dist/commands/deploy.js +119 -19
- package/dist/commands/fixture.js +31 -8
- package/dist/commands/learn.d.ts +19 -0
- package/dist/commands/learn.js +277 -0
- package/dist/commands/modify.js +210 -68
- package/dist/commands/test.d.ts +4 -0
- package/dist/commands/test.js +118 -14
- package/dist/services/ai.service.d.ts +33 -0
- package/dist/services/ai.service.js +337 -2
- package/dist/services/node-definitions.service.d.ts +8 -0
- package/dist/services/node-definitions.service.js +45 -0
- package/dist/utils/fixtureManager.d.ts +10 -0
- package/dist/utils/fixtureManager.js +43 -4
- package/dist/utils/multilinePrompt.js +33 -47
- package/dist/utils/n8nClient.js +60 -11
- package/docs/DEVELOPER_GUIDE.md +598 -0
- package/docs/N8N_NODE_REFERENCE.md +369 -0
- package/docs/patterns/bigquery-via-http.md +110 -0
- package/oclif.manifest.json +82 -3
- package/package.json +3 -1
- package/dist/fixture-schema.json +0 -162
- package/dist/resources/node-definitions-fallback.json +0 -390
- package/dist/resources/node-test-hints.json +0 -188
- package/dist/resources/workflow-test-fixtures.json +0 -42
package/README.md
CHANGED
|
@@ -198,15 +198,18 @@ Two ways to create a fixture:
|
|
|
198
198
|
# Pull real execution data from n8n (no test run required)
|
|
199
199
|
n8m fixture capture <workflowId>
|
|
200
200
|
|
|
201
|
+
# Browse local files + remote instance interactively
|
|
202
|
+
n8m fixture capture
|
|
203
|
+
|
|
201
204
|
# Scaffold an empty template to fill in by hand
|
|
202
205
|
n8m fixture init <workflowId>
|
|
203
206
|
```
|
|
204
207
|
|
|
205
|
-
**`capture`** connects to your n8n instance, fetches the
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
**`capture`** connects to your n8n instance, fetches the most recent executions
|
|
209
|
+
for the workflow, and presents an interactive menu to pick which one to save.
|
|
210
|
+
You'll be prompted for a fixture name (e.g. `happy-path`, `missing-field`) and
|
|
211
|
+
expected outcome (`pass` or `fail`). Fixtures are saved to
|
|
212
|
+
`.n8m/fixtures/<workflowId>/<name>.json`.
|
|
210
213
|
|
|
211
214
|
```bash
|
|
212
215
|
n8m fixture capture abc123
|
|
@@ -215,12 +218,21 @@ n8m fixture capture abc123
|
|
|
215
218
|
# → #177916 success 3/4/2026, 10:48:47 AM
|
|
216
219
|
# → #177914 success 3/4/2026, 10:48:23 AM
|
|
217
220
|
# → ❯ #177913 error 3/4/2026, 10:47:59 AM
|
|
218
|
-
# →
|
|
219
|
-
# →
|
|
221
|
+
# → Name this fixture (e.g. happy-path, missing-field, bad-auth): error-case
|
|
222
|
+
# → Expected test outcome: fail
|
|
223
|
+
# → Fixture saved to .n8m/fixtures/abc123/error-case.json
|
|
220
224
|
# → Workflow: My Workflow
|
|
221
225
|
# → Execution: error · 5 node(s) captured
|
|
222
226
|
```
|
|
223
227
|
|
|
228
|
+
Multiple named fixtures per workflow let you test different scenarios (happy
|
|
229
|
+
path, edge cases, error handling) with one command:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
n8m test --fixture .n8m/fixtures/abc123 # run ALL fixtures for a workflow
|
|
233
|
+
n8m test --fixture .n8m/fixtures/abc123/error-case.json # run one specific fixture
|
|
234
|
+
```
|
|
235
|
+
|
|
224
236
|
**`init`** creates an empty template when you want to define the fixture data
|
|
225
237
|
yourself, without needing a live execution first.
|
|
226
238
|
|
|
@@ -230,6 +242,8 @@ yourself, without needing a live execution first.
|
|
|
230
242
|
"version": "1.0",
|
|
231
243
|
"workflowId": "abc123",
|
|
232
244
|
"workflowName": "My Workflow",
|
|
245
|
+
"description": "happy-path",
|
|
246
|
+
"expectedOutcome": "pass",
|
|
233
247
|
"workflow": { "name": "My Workflow", "nodes": [], "connections": {} },
|
|
234
248
|
"execution": {
|
|
235
249
|
"status": "success",
|
|
@@ -249,7 +263,7 @@ Fill in `execution.data.resultData.runData` with the actual output of each node
|
|
|
249
263
|
(keyed by exact node name). Then test against it:
|
|
250
264
|
|
|
251
265
|
```bash
|
|
252
|
-
n8m test --fixture .n8m/fixtures/abc123.json
|
|
266
|
+
n8m test --fixture .n8m/fixtures/abc123/happy-path.json
|
|
253
267
|
```
|
|
254
268
|
|
|
255
269
|
Fixture files are project-local (`.n8m/fixtures/`) and should be committed to
|
|
@@ -271,6 +285,117 @@ n8m deploy ./workflows/my-flow.json --activate
|
|
|
271
285
|
|
|
272
286
|
---
|
|
273
287
|
|
|
288
|
+
### `n8m learn` — Build the pattern library
|
|
289
|
+
|
|
290
|
+
Extract reusable engineering knowledge from validated workflows and store it so
|
|
291
|
+
the AI engineer applies the same proven techniques in future generations.
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Interactive: pick a workflow from ./workflows/
|
|
295
|
+
n8m learn
|
|
296
|
+
|
|
297
|
+
# Learn from a specific workflow
|
|
298
|
+
n8m learn ./workflows/my-flow/workflow.json
|
|
299
|
+
|
|
300
|
+
# Batch-generate patterns for every workflow in the directory
|
|
301
|
+
n8m learn --all
|
|
302
|
+
|
|
303
|
+
# Import patterns from a GitHub archive (awesome-n8n style)
|
|
304
|
+
n8m learn --github owner/repo
|
|
305
|
+
n8m learn --github owner/repo@main --github-path patterns/google
|
|
306
|
+
n8m learn --github owner/repo --token ghp_xxx # or set GITHUB_TOKEN env var
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**How it works:**
|
|
310
|
+
|
|
311
|
+
- **Local mode**: The AI reads a validated workflow JSON, identifies the
|
|
312
|
+
techniques it demonstrates, extracts critical rules (including gotchas to
|
|
313
|
+
avoid), and writes a `.md` pattern file to `.n8m/patterns/`.
|
|
314
|
+
- **GitHub mode**: Fetches `.md` pattern files from any public GitHub repo via
|
|
315
|
+
the GitHub API. Shows a checkbox menu so you choose exactly which patterns to
|
|
316
|
+
import. Recurses into subdirectories automatically.
|
|
317
|
+
|
|
318
|
+
Patterns are Markdown files with a `<!-- keywords: ... -->` comment. When you
|
|
319
|
+
run `n8m create`, the engineer searches all pattern files and injects any that
|
|
320
|
+
match the workflow's goal into the prompt — teaching it to reproduce proven
|
|
321
|
+
approaches exactly.
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
.n8m/patterns/ ← your project-local library (user patterns)
|
|
325
|
+
docs/patterns/ ← built-in patterns shipped with n8m
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
User patterns in `.n8m/patterns/` take priority over built-in patterns. A
|
|
329
|
+
pattern file with the same filename overrides the built-in version, so you can
|
|
330
|
+
customize any default.
|
|
331
|
+
|
|
332
|
+
**Contributing default patterns** (for n8m maintainers):
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Generate docs/patterns/ from every workflow in ./workflows/
|
|
336
|
+
npm run generate-patterns
|
|
337
|
+
|
|
338
|
+
# Overwrite any already-existing pattern files
|
|
339
|
+
npm run generate-patterns -- --overwrite
|
|
340
|
+
|
|
341
|
+
# Preview what would be written without touching the filesystem
|
|
342
|
+
npm run generate-patterns -- --dry-run
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The script scans `workflows/` recursively, calls the AI on each `workflow.json`,
|
|
346
|
+
and writes the result to `docs/patterns/<slug>.md`. Patterns in `docs/patterns/`
|
|
347
|
+
are included in the npm package and available to all users automatically.
|
|
348
|
+
|
|
349
|
+
**Pattern file format:**
|
|
350
|
+
|
|
351
|
+
```markdown
|
|
352
|
+
<!-- keywords: bigquery, google bigquery, sql, merge, http request -->
|
|
353
|
+
|
|
354
|
+
# Pattern: BigQuery Operations via HTTP Request
|
|
355
|
+
|
|
356
|
+
## Critical Rules
|
|
357
|
+
- NEVER use n8n-nodes-base.googleBigQuery — it returns no output for DDL/DML.
|
|
358
|
+
Always use n8n-nodes-base.httpRequest with the BigQuery REST API.
|
|
359
|
+
|
|
360
|
+
## DDL / DML Queries
|
|
361
|
+
...
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
### `n8m mcp` — Launch the MCP server
|
|
367
|
+
|
|
368
|
+
Expose `n8m`'s agentic capabilities as tools to any
|
|
369
|
+
[Model Context Protocol](https://modelcontextprotocol.io/) client (Claude
|
|
370
|
+
Desktop, Cursor, etc.).
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
n8m mcp
|
|
374
|
+
# → Starting n8m MCP Server...
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The server runs over stdio and registers two tools:
|
|
378
|
+
|
|
379
|
+
| Tool | Description |
|
|
380
|
+
|---|---|
|
|
381
|
+
| `create_workflow` | Generate an n8n workflow from a natural-language goal |
|
|
382
|
+
| `test_workflow` | Deploy a workflow ephemerally to n8n and validate it |
|
|
383
|
+
|
|
384
|
+
Add it to your MCP client config (e.g. Claude Desktop's `claude_desktop_config.json`):
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"mcpServers": {
|
|
389
|
+
"n8m": {
|
|
390
|
+
"command": "npx",
|
|
391
|
+
"args": ["n8m", "mcp"]
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
274
399
|
### `n8m resume` — Resume a paused session
|
|
275
400
|
|
|
276
401
|
The agent can pause mid-run for human review (HITL). Resume it with its thread
|
|
@@ -330,8 +455,8 @@ n8m config
|
|
|
330
455
|
Developer → n8m create "..."
|
|
331
456
|
│
|
|
332
457
|
▼
|
|
333
|
-
┌─────────────┐
|
|
334
|
-
│ Architect │
|
|
458
|
+
┌─────────────┐ .n8m/patterns/ docs/patterns/
|
|
459
|
+
│ Architect │ ◄── RAG: node defs + matched patterns
|
|
335
460
|
└──────┬──────┘
|
|
336
461
|
│
|
|
337
462
|
▼
|
|
@@ -345,10 +470,18 @@ Developer → n8m create "..."
|
|
|
345
470
|
└──────┬──────┘ └─────────────┘
|
|
346
471
|
│ passed
|
|
347
472
|
▼
|
|
348
|
-
▼
|
|
349
473
|
./workflows/<slug>/
|
|
350
474
|
├── workflow.json
|
|
351
475
|
└── README.md (with Mermaid diagram)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
Developer → n8m learn <workflow.json>
|
|
479
|
+
│
|
|
480
|
+
▼
|
|
481
|
+
AI analyzes validated workflow
|
|
482
|
+
│
|
|
483
|
+
▼
|
|
484
|
+
.n8m/patterns/<slug>.md ← feeds back into Engineer on next create
|
|
352
485
|
```
|
|
353
486
|
|
|
354
487
|
- **Local first**: credentials and workflow files live on your machine
|
|
@@ -358,6 +491,8 @@ Developer → n8m create "..."
|
|
|
358
491
|
- **HITL pauses**: the agent stops for your review before committing
|
|
359
492
|
- **Bring your own AI**: works with OpenAI, Claude, Gemini, Ollama, or any
|
|
360
493
|
OpenAI-compatible API
|
|
494
|
+
- **Self-improving**: every validated workflow you `learn` from strengthens
|
|
495
|
+
future generations
|
|
361
496
|
|
|
362
497
|
> **For developers**: See the [Developer Guide](docs/DEVELOPER_GUIDE.md) for a
|
|
363
498
|
> deep-dive into the agentic graph internals, RAG implementation, how to add new
|
|
@@ -405,5 +540,8 @@ npm run dev
|
|
|
405
540
|
- [x] Multi-workflow project generation support
|
|
406
541
|
- [x] Fixture record & replay — offline testing with real execution data
|
|
407
542
|
- [x] Hand-crafted fixture scaffolding (`n8m fixture init`) with JSON Schema
|
|
543
|
+
- [x] Pattern library — extract & reuse knowledge from validated workflows (`n8m learn`)
|
|
544
|
+
- [x] GitHub pattern archive import (`n8m learn --github owner/repo`)
|
|
545
|
+
- [x] MCP server — expose n8m as tools for Claude Desktop and other MCP clients
|
|
408
546
|
- [ ] Native n8n canvas integration
|
|
409
547
|
- [ ] Multi-agent collaboration on a single goal
|
package/dist/agentic/graph.d.ts
CHANGED
|
@@ -157,10 +157,10 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
|
|
|
157
157
|
maxRevisions: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
|
158
158
|
}, import("@langchain/langgraph").StateDefinition, {
|
|
159
159
|
architect: {
|
|
160
|
-
spec
|
|
160
|
+
spec: any;
|
|
161
|
+
collaborationLog: string[];
|
|
161
162
|
strategies?: undefined;
|
|
162
163
|
needsClarification?: undefined;
|
|
163
|
-
collaborationLog?: undefined;
|
|
164
164
|
} | {
|
|
165
165
|
spec: import("../services/ai.service.js").WorkflowSpec;
|
|
166
166
|
strategies: {
|
|
@@ -197,6 +197,12 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
|
|
|
197
197
|
validationErrors?: undefined;
|
|
198
198
|
workflowJson?: undefined;
|
|
199
199
|
candidates?: undefined;
|
|
200
|
+
} | {
|
|
201
|
+
workflowJson: any;
|
|
202
|
+
revisionCount?: undefined;
|
|
203
|
+
validationStatus?: undefined;
|
|
204
|
+
validationErrors?: undefined;
|
|
205
|
+
candidates?: undefined;
|
|
200
206
|
} | {
|
|
201
207
|
candidates: any[];
|
|
202
208
|
revisionCount?: undefined;
|
|
@@ -411,10 +417,10 @@ export declare const runAgenticWorkflow: (goal: string, initialState?: Partial<t
|
|
|
411
417
|
*/
|
|
412
418
|
export declare const runAgenticWorkflowStream: (goal: string, threadId?: string) => Promise<import("@langchain/core/utils/stream").IterableReadableStream<{
|
|
413
419
|
architect?: {
|
|
414
|
-
spec
|
|
420
|
+
spec: any;
|
|
421
|
+
collaborationLog: string[];
|
|
415
422
|
strategies?: undefined;
|
|
416
423
|
needsClarification?: undefined;
|
|
417
|
-
collaborationLog?: undefined;
|
|
418
424
|
} | {
|
|
419
425
|
spec: import("../services/ai.service.js").WorkflowSpec;
|
|
420
426
|
strategies: {
|
|
@@ -451,6 +457,12 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
|
|
|
451
457
|
validationErrors?: undefined;
|
|
452
458
|
workflowJson?: undefined;
|
|
453
459
|
candidates?: undefined;
|
|
460
|
+
} | {
|
|
461
|
+
workflowJson: any;
|
|
462
|
+
revisionCount?: undefined;
|
|
463
|
+
validationStatus?: undefined;
|
|
464
|
+
validationErrors?: undefined;
|
|
465
|
+
candidates?: undefined;
|
|
454
466
|
} | {
|
|
455
467
|
candidates: any[];
|
|
456
468
|
revisionCount?: undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { TeamState } from "../state.js";
|
|
2
2
|
export declare const architectNode: (state: typeof TeamState.State) => Promise<{
|
|
3
|
-
spec
|
|
3
|
+
spec: any;
|
|
4
|
+
collaborationLog: string[];
|
|
4
5
|
strategies?: undefined;
|
|
5
6
|
needsClarification?: undefined;
|
|
6
|
-
collaborationLog?: undefined;
|
|
7
7
|
} | {
|
|
8
8
|
spec: import("../../services/ai.service.js").WorkflowSpec;
|
|
9
9
|
strategies: {
|
|
@@ -10,7 +10,11 @@ export const architectNode = async (state) => {
|
|
|
10
10
|
// parallel engineers (via Send) to rebuild the workflow from scratch, which
|
|
11
11
|
// produces very large JSON that is error-prone and throws away the user's work.
|
|
12
12
|
if (state.workflowJson) {
|
|
13
|
-
|
|
13
|
+
const plan = await aiService.generateModificationPlan(state.userGoal, state.workflowJson);
|
|
14
|
+
return {
|
|
15
|
+
spec: plan,
|
|
16
|
+
collaborationLog: [`Architect: Modification plan — ${plan.description}`],
|
|
17
|
+
};
|
|
14
18
|
}
|
|
15
19
|
try {
|
|
16
20
|
const spec = await aiService.generateSpec(state.userGoal);
|
|
@@ -17,6 +17,12 @@ export declare const engineerNode: (state: typeof TeamState.State) => Promise<{
|
|
|
17
17
|
validationErrors?: undefined;
|
|
18
18
|
workflowJson?: undefined;
|
|
19
19
|
candidates?: undefined;
|
|
20
|
+
} | {
|
|
21
|
+
workflowJson: any;
|
|
22
|
+
revisionCount?: undefined;
|
|
23
|
+
validationStatus?: undefined;
|
|
24
|
+
validationErrors?: undefined;
|
|
25
|
+
candidates?: undefined;
|
|
20
26
|
} | {
|
|
21
27
|
candidates: any[];
|
|
22
28
|
revisionCount?: undefined;
|
|
@@ -12,8 +12,13 @@ export const engineerNode = async (state) => {
|
|
|
12
12
|
// Search for relevant nodes (limit 8 to save context)
|
|
13
13
|
const relevantDefs = nodeService.search(queryText, 8);
|
|
14
14
|
const staticRef = nodeService.getStaticReference();
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Search pattern library for proven working examples
|
|
16
|
+
const matchedPatterns = nodeService.searchPatterns(queryText);
|
|
17
|
+
const patternsContext = matchedPatterns.length > 0
|
|
18
|
+
? `\n\n[PROVEN WORKFLOW PATTERNS - FOLLOW THESE EXACTLY]\n${matchedPatterns.join('\n\n---\n\n')}`
|
|
19
|
+
: "";
|
|
20
|
+
const ragContext = (relevantDefs.length > 0 || staticRef || matchedPatterns.length > 0)
|
|
21
|
+
? `\n\n[N8N NODE REFERENCE GUIDE]\n${staticRef}\n\n[AVAILABLE NODE SCHEMAS - USE THESE EXACT PARAMETERS]\n${nodeService.formatForLLM(relevantDefs)}${patternsContext}`
|
|
17
22
|
: "";
|
|
18
23
|
// Self-Correction Loop Check
|
|
19
24
|
if (state.validationErrors && state.validationErrors.length > 0) {
|
|
@@ -57,9 +62,13 @@ export const engineerNode = async (state) => {
|
|
|
57
62
|
throw error;
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
|
-
//
|
|
65
|
+
// Modification mode: existing workflow + spec from architect
|
|
61
66
|
if (state.workflowJson) {
|
|
62
|
-
|
|
67
|
+
if (!state.spec) {
|
|
68
|
+
return {}; // No plan — nothing to do
|
|
69
|
+
}
|
|
70
|
+
const modifiedWorkflow = await aiService.applyModification(state.workflowJson, state.userGoal, state.spec, state.userFeedback, state.availableNodeTypes || []);
|
|
71
|
+
return { workflowJson: modifiedWorkflow };
|
|
63
72
|
}
|
|
64
73
|
// Standard Creation Flow
|
|
65
74
|
// console.log("⚙️ Engineer is building the workflow...");
|
|
@@ -88,6 +97,31 @@ export const engineerNode = async (state) => {
|
|
|
88
97
|
- Use "n8n-nodes-base.htmlExtract" for HTML/Cheerio extraction.
|
|
89
98
|
6. Connections Structure: The "connections" object keys MUST BE THE SOURCE NODE NAME. The "node" field inside the connection array MUST BE THE TARGET NODE NAME.
|
|
90
99
|
7. Connection Nesting: Ensure the correct n8n connection structure: "SourceNodeName": { "main": [ [ { "node": "TargetNodeName", "type": "main", "index": 0 } ] ] }.
|
|
100
|
+
8. Error Connections: In addition to "main", nodes support an "error" output that fires when a node fails. Use this for error handling and cleanup flows. Example:
|
|
101
|
+
"NodeThatMightFail": {
|
|
102
|
+
"main": [ [ { "node": "NextNode", "type": "main", "index": 0 } ] ],
|
|
103
|
+
"error": [ [ { "node": "CleanupOrErrorHandler", "type": "main", "index": 0 } ] ]
|
|
104
|
+
}
|
|
105
|
+
Any node that could fail mid-workflow AND where partial execution would leave side effects (e.g. temporary DB tables, uploaded files, open transactions) MUST have an error connection to a cleanup node.
|
|
106
|
+
9. HTTP Request Configuration: The method determines required fields.
|
|
107
|
+
- GET/DELETE: only "url" (and optional "sendQuery"/"sendHeaders") are needed — do NOT include "sendBody".
|
|
108
|
+
- POST/PUT/PATCH: MUST include "sendBody": true AND a "body" object:
|
|
109
|
+
{ "method": "POST", "url": "...", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify($json) }}" }
|
|
110
|
+
- Authentication: use "authentication": "predefinedCredentialType" + "nodeCredentialType": "<CredentialTypeName>" for service credentials.
|
|
111
|
+
- Minimal config: only include fields relevant to the method. Do not add empty optional fields.
|
|
112
|
+
10. Resource/Operation Nodes (Slack, Google Sheets, Airtable, Gmail, etc.): The "resource" + "operation" pair together determine which parameters are required. Different operations need different fields:
|
|
113
|
+
- post/create operations typically need target identifiers (channel, spreadsheetId, etc.) and content fields.
|
|
114
|
+
- update/patch operations typically need a record ID (messageId, rowId, etc.) and the fields to update.
|
|
115
|
+
- get/list operations typically need filter/search parameters, not content fields.
|
|
116
|
+
Always set both "resource" and "operation" first, then configure only the fields that operation requires.
|
|
117
|
+
11. Credentials Format: Credential references must follow this structure:
|
|
118
|
+
"credentials": { "<credentialTypeName>": { "id": "CREDENTIAL_ID", "name": "Human Readable Name" } }
|
|
119
|
+
Use the exact credential type name that matches the node (e.g. "slackApi", "googleSheetsOAuth2Api", "googleBigQueryOAuth2Api").
|
|
120
|
+
For Google services, prefer service account credentials over OAuth2 when available:
|
|
121
|
+
- BigQuery: use "googleApi" (service account) instead of "googleBigQueryOAuth2Api"
|
|
122
|
+
- Google Sheets: use "googleSheetsServiceAccountApi" instead of "googleSheetsOAuth2Api"
|
|
123
|
+
- Google Drive: use "googleDriveServiceAccountApi" instead of "googleDriveOAuth2Api"
|
|
124
|
+
- Other Google nodes: check if a service account variant exists (typically named "<serviceName>ServiceAccountApi")
|
|
91
125
|
|
|
92
126
|
Output a JSON object with this structure:
|
|
93
127
|
{
|
|
@@ -114,7 +148,7 @@ export const engineerNode = async (state) => {
|
|
|
114
148
|
throw new Error("AI generated invalid JSON for workflow from spec");
|
|
115
149
|
}
|
|
116
150
|
if (result.workflows && Array.isArray(result.workflows)) {
|
|
117
|
-
result.workflows = result.workflows.map((wf) => aiService.fixHallucinatedNodes(wf));
|
|
151
|
+
result.workflows = result.workflows.map((wf) => aiService.wireOrphanedErrorHandlers(aiService.fixHallucinatedNodes(wf)));
|
|
118
152
|
}
|
|
119
153
|
return {
|
|
120
154
|
// Only push to candidates — the Supervisor sets workflowJson after fan-in.
|
package/dist/commands/create.js
CHANGED
|
@@ -10,6 +10,7 @@ import { promptMultiline } from '../utils/multilinePrompt.js';
|
|
|
10
10
|
import { DocService } from '../services/doc.service.js';
|
|
11
11
|
import { ConfigManager } from '../utils/config.js';
|
|
12
12
|
import { N8nClient } from '../utils/n8nClient.js';
|
|
13
|
+
import { AIService } from '../services/ai.service.js';
|
|
13
14
|
export default class Create extends Command {
|
|
14
15
|
static args = {
|
|
15
16
|
description: Args.string({
|
|
@@ -147,6 +148,11 @@ export default class Create extends Command {
|
|
|
147
148
|
});
|
|
148
149
|
}
|
|
149
150
|
choices.push(new inquirer.Separator());
|
|
151
|
+
choices.push({
|
|
152
|
+
name: 'Discuss details with the Engineer',
|
|
153
|
+
value: { type: 'chat' },
|
|
154
|
+
short: 'Discuss',
|
|
155
|
+
});
|
|
150
156
|
choices.push({
|
|
151
157
|
name: 'Add feedback before building',
|
|
152
158
|
value: { type: 'feedback' },
|
|
@@ -171,7 +177,38 @@ export default class Create extends Command {
|
|
|
171
177
|
}
|
|
172
178
|
let chosenSpec = choice.strategy ?? spec;
|
|
173
179
|
let stateUpdate = { spec: chosenSpec, userFeedback: undefined };
|
|
174
|
-
if (choice.type === '
|
|
180
|
+
if (choice.type === 'chat') {
|
|
181
|
+
const aiService = AIService.getInstance();
|
|
182
|
+
const chatHistory = [];
|
|
183
|
+
let currentSpec = chosenSpec ?? strategies[0] ?? spec;
|
|
184
|
+
this.log(theme.header('\nCHATTING WITH THE ENGINEER'));
|
|
185
|
+
this.log(theme.muted(` Plan: ${currentSpec?.suggestedName}`));
|
|
186
|
+
this.log(theme.muted(` Type your question or request. Enter "done" when ready to build.\n`));
|
|
187
|
+
// eslint-disable-next-line no-constant-condition
|
|
188
|
+
while (true) {
|
|
189
|
+
const { message } = await inquirer.prompt([{
|
|
190
|
+
type: 'input',
|
|
191
|
+
name: 'message',
|
|
192
|
+
message: 'You:',
|
|
193
|
+
}]);
|
|
194
|
+
const trimmed = message.trim();
|
|
195
|
+
if (!trimmed || /^(done|build|approve|go|ok|yes)$/i.test(trimmed)) {
|
|
196
|
+
this.log(theme.agent(`Understood. Building "${currentSpec?.suggestedName}"...\n`));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const { reply, updatedSpec } = await aiService.chatAboutSpec(currentSpec, chatHistory, trimmed);
|
|
200
|
+
chatHistory.push({ role: 'user', content: trimmed });
|
|
201
|
+
chatHistory.push({ role: 'assistant', content: reply });
|
|
202
|
+
currentSpec = updatedSpec;
|
|
203
|
+
this.log(`\n${theme.agent('Engineer:')} ${reply}\n`);
|
|
204
|
+
if (updatedSpec.suggestedName !== (chosenSpec ?? spec)?.suggestedName) {
|
|
205
|
+
this.log(theme.muted(` (Plan updated: ${updatedSpec.suggestedName})`));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
chosenSpec = currentSpec;
|
|
209
|
+
stateUpdate = { spec: chosenSpec, userFeedback: undefined };
|
|
210
|
+
}
|
|
211
|
+
else if (choice.type === 'feedback') {
|
|
175
212
|
const { feedback } = await inquirer.prompt([{
|
|
176
213
|
type: 'input',
|
|
177
214
|
name: 'feedback',
|
|
@@ -184,7 +221,7 @@ export default class Create extends Command {
|
|
|
184
221
|
else {
|
|
185
222
|
this.log(theme.agent(`Building "${chosenSpec?.suggestedName}"...`));
|
|
186
223
|
}
|
|
187
|
-
await graph.updateState({ configurable: { thread_id: threadId } }, stateUpdate
|
|
224
|
+
await graph.updateState({ configurable: { thread_id: threadId } }, stateUpdate);
|
|
188
225
|
const buildStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
189
226
|
for await (const event of buildStream) {
|
|
190
227
|
const n = Object.keys(event)[0];
|
|
@@ -258,8 +295,10 @@ export default class Create extends Command {
|
|
|
258
295
|
const savedResources = [];
|
|
259
296
|
const docService = DocService.getInstance();
|
|
260
297
|
for (const workflow of workflows) {
|
|
261
|
-
|
|
262
|
-
|
|
298
|
+
// Use the workflow's own name (set by the Engineer from the spec's suggestedName).
|
|
299
|
+
// Only call generateProjectTitle as a fallback when the name is missing or generic.
|
|
300
|
+
const projectTitle = workflow.name || await docService.generateProjectTitle(workflow);
|
|
301
|
+
workflow.name = projectTitle;
|
|
263
302
|
const slug = docService.generateSlug(projectTitle);
|
|
264
303
|
const workflowsDir = path.join(process.cwd(), 'workflows');
|
|
265
304
|
const targetDir = path.join(workflowsDir, slug);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class Deploy extends Command {
|
|
3
3
|
static args: {
|
|
4
|
-
workflow: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
4
|
+
workflow: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
5
|
};
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
9
|
instance: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
activate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<void>;
|
|
13
14
|
}
|