@lhi/n8m 0.3.2 → 1.0.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.
Files changed (48) hide show
  1. package/README.md +50 -10
  2. package/banner.txt +9 -0
  3. package/dist/agentic/graph.d.ts +11 -1
  4. package/dist/agentic/graph.js +2 -1
  5. package/dist/agentic/nodes/architect.js +3 -2
  6. package/dist/agentic/nodes/engineer.js +4 -2
  7. package/dist/agentic/state.d.ts +2 -0
  8. package/dist/agentic/state.js +4 -0
  9. package/dist/commands/create.js +15 -2
  10. package/dist/commands/deploy.d.ts +2 -0
  11. package/dist/commands/deploy.js +25 -8
  12. package/dist/commands/fixture.js +1 -0
  13. package/dist/commands/learn.js +1 -1
  14. package/dist/commands/modify.js +13 -1
  15. package/dist/commands/rollback.d.ts +31 -0
  16. package/dist/commands/rollback.js +201 -0
  17. package/dist/fixture-schema.json +162 -0
  18. package/dist/help.d.ts +6 -0
  19. package/dist/help.js +12 -0
  20. package/dist/resources/node-definitions-fallback.json +390 -0
  21. package/dist/resources/node-test-hints.json +188 -0
  22. package/dist/resources/workflow-test-fixtures.json +42 -0
  23. package/dist/services/ai.service.d.ts +9 -2
  24. package/dist/services/ai.service.js +27 -6
  25. package/dist/services/git.service.d.ts +52 -0
  26. package/dist/services/git.service.js +110 -0
  27. package/dist/services/mcp.service.d.ts +1 -0
  28. package/dist/services/mcp.service.js +201 -0
  29. package/dist/services/node-definitions.service.js +1 -1
  30. package/dist/utils/config.js +3 -1
  31. package/dist/utils/n8nClient.d.ts +11 -0
  32. package/dist/utils/n8nClient.js +39 -0
  33. package/docs/.nojekyll +0 -0
  34. package/docs/CNAME +1 -0
  35. package/docs/DEVELOPER_GUIDE.md +12 -2
  36. package/docs/apple-touch-icon.png +0 -0
  37. package/docs/favicon-16x16.png +0 -0
  38. package/docs/favicon-192x192.png +0 -0
  39. package/docs/favicon-32x32.png +0 -0
  40. package/docs/favicon.svg +4 -0
  41. package/docs/index.html +1577 -0
  42. package/docs/social-card.html +237 -0
  43. package/n8m-cover.png +0 -0
  44. package/n8m-logo-light.png +0 -0
  45. package/n8m-logo-mono.png +0 -0
  46. package/n8m-logo-v2.png +0 -0
  47. package/oclif.manifest.json +68 -1
  48. package/package.json +11 -1
package/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # n8m: The Agentic CLI for n8n
1
+ <div align="center">
2
+ <img src="n8m-cover.png" alt="n8m — agentic n8n workflow generator" width="100%" />
3
+ </div>
2
4
 
3
5
  > Generate, modify, test, and deploy n8n workflows from the command line using
4
6
  > AI.
5
7
 
8
+ [![npm](https://img.shields.io/npm/v/@lhi/n8m.svg)](https://www.npmjs.com/package/@lhi/n8m)
9
+ [![npm downloads](https://img.shields.io/npm/dm/@lhi/n8m.svg)](https://www.npmjs.com/package/@lhi/n8m)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
11
  [![TypeScript](https://badgen.net/badge/Built%20with/TypeScript/blue)](https://typescriptlang.org/)
7
12
  [![oclif](https://badgen.net/badge/CLI/oclif/purple)](https://oclif.io/)
8
13
  [![n8n](https://badgen.net/badge/n8n/Compatible/orange)](https://n8n.io)
14
+ [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?logo=github)](https://github.com/Lee-Holdings-International/n8m#sponsors)
15
+ [![Discussions](https://img.shields.io/badge/Discussions-Join-blue?logo=github)](https://github.com/Lee-Holdings-International/n8m/discussions)
16
+ [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
9
17
 
10
18
  **Stop clicking. Start shipping.** `n8m` is an open-source CLI that wraps your
11
19
  n8n instance with an agentic AI layer. Describe what you want in plain English —
@@ -46,8 +54,8 @@ npx n8m config --ai-provider gemini --ai-key AIza...
46
54
  npx n8m config --ai-base-url http://localhost:11434/v1 --ai-key ollama --ai-model llama3
47
55
  ```
48
56
 
49
- You can also use environment variables or a `.env` file — env vars take priority
50
- over stored config:
57
+ You can also use environment variables or a `.env` file — stored config takes
58
+ priority; env vars are used as fallback when no config file value is set:
51
59
 
52
60
  | Variable | Description |
53
61
  | ------------- | -------------------------------------------------------- |
@@ -281,6 +289,12 @@ n8m deploy ./workflows/my-flow.json
281
289
 
282
290
  # Activate the workflow immediately after deployment
283
291
  n8m deploy ./workflows/my-flow.json --activate
292
+
293
+ # Non-interactive: always update the existing workflow (useful in CI)
294
+ n8m deploy ./workflows/my-flow.json --update
295
+
296
+ # Non-interactive: always create a new workflow, ignoring any existing ID
297
+ n8m deploy ./workflows/my-flow.json --force-create
284
298
  ```
285
299
 
286
300
  ---
@@ -374,12 +388,18 @@ n8m mcp
374
388
  # → Starting n8m MCP Server...
375
389
  ```
376
390
 
377
- The server runs over stdio and registers two tools:
391
+ The server runs over stdio and registers the following tools:
378
392
 
379
393
  | Tool | Description |
380
394
  |---|---|
381
395
  | `create_workflow` | Generate an n8n workflow from a natural-language goal |
396
+ | `modify_workflow` | Modify an existing workflow JSON using natural language instructions |
382
397
  | `test_workflow` | Deploy a workflow ephemerally to n8n and validate it |
398
+ | `deploy_workflow` | Create or update a workflow on the configured n8n instance |
399
+ | `get_workflow` | Fetch a workflow from n8n by ID |
400
+ | `list_workflows` | List all workflows on the n8n instance |
401
+ | `delete_workflow` | Delete a workflow from n8n by ID |
402
+ | `generate_docs` | Generate a Mermaid diagram and README for a workflow JSON |
383
403
 
384
404
  Add it to your MCP client config (e.g. Claude Desktop's `claude_desktop_config.json`):
385
405
 
@@ -503,7 +523,7 @@ Developer → n8m learn <workflow.json>
503
523
  ## Local Development
504
524
 
505
525
  ```bash
506
- git clone https://github.com/lcanady/n8m.git
526
+ git clone https://github.com/Lee-Holdings-International/n8m.git
507
527
  cd n8m
508
528
  npm install
509
529
 
@@ -518,7 +538,25 @@ npm run dev
518
538
 
519
539
  ## Sponsors
520
540
 
521
- ### Partially Sponsored By
541
+ n8m is MIT-licensed and free forever. Sponsorship directly funds AI API costs,
542
+ continued development, and the time it takes to keep up with n8n's rapidly
543
+ evolving node ecosystem.
544
+
545
+ ### Sponsor tiers
546
+
547
+ | Tier | Amount | What you get |
548
+ |------|--------|--------------|
549
+ | **Supporter** | $5 / mo | Name in the README sponsors list |
550
+ | **Studio** | $25 / mo | Logo in the README + priority issue responses |
551
+ | **Enterprise** | $100 / mo | Logo + a direct line for feature requests and integration questions |
552
+
553
+ [Become a sponsor on Ko-fi](https://ko-fi.com/lcanady) · [GitHub Sponsors](https://github.com/sponsors/lcanady)
554
+
555
+ ---
556
+
557
+ ### Current Sponsors
558
+
559
+ #### Partial Sponsors
522
560
 
523
561
  [The Daily Caller](https://dailycaller.com)
524
562
 
@@ -528,7 +566,7 @@ npm run dev
528
566
 
529
567
  ### Shipped
530
568
 
531
- - [x] Agentic graph (Architect → Engineer → QA)
569
+ - [x] Agentic graph (Architect → Engineer → Supervisor → Reviewer → QA)
532
570
  - [x] SQLite session persistence
533
571
  - [x] HITL interrupts and resume
534
572
  - [x] Sub-workflow dependency resolution in tests
@@ -544,13 +582,15 @@ npm run dev
544
582
  - [x] Pattern library — extract & reuse knowledge from validated workflows (`n8m learn`)
545
583
  - [x] GitHub pattern archive import (`n8m learn --github owner/repo`)
546
584
  - [x] MCP server — expose n8m as tools for Claude Desktop and other MCP clients
585
+ - [x] **`n8m rollback`** — restore a workflow to a previous git-tracked version with a single command
586
+ - [x] **Credential awareness** — AI consults available credential types on the target instance so it stops generating nodes it can't authenticate
587
+ - [x] **MCP server expanded to 8 tools** — full workflow lifecycle from any MCP client: create, modify, test, deploy, get, list, delete, generate docs
588
+ - [x] **Non-interactive deploy flags** — `--update` and `--force-create` for CI/scripted use without interactive prompts
547
589
 
548
590
  ### Near-term
549
591
 
550
592
  - [ ] **`n8m watch`** — file-system watcher that auto-deploys on save with live reload in the n8n editor
551
- - [ ] **`n8m diff`** — human-readable structural diff between two versions of a workflow (nodes added/removed/changed)
552
- - [ ] **`n8m rollback`** — restore a workflow to a previous git-tracked version with a single command
553
- - [ ] **Credential awareness** — AI consults available credential types on the target instance so it stops generating nodes it can't authenticate
593
+ - [ ] **`n8m diff`** — human-readable structural diff between two workflow versions (nodes added/removed/changed)
554
594
  - [ ] **Parallel test runs** — fire multiple fixture payloads concurrently and report aggregate pass/fail
555
595
 
556
596
  ### Medium-term
package/banner.txt ADDED
@@ -0,0 +1,9 @@
1
+
2
+ $$\ $$\ $$$$$$\ $$\ $$\
3
+ $$$\ $$ |$$ __$$\ $$$\ $$$ |
4
+ $$$$\ $$ |$$ / $$ |$$$$\ $$$$ |
5
+ $$ $$\$$ | $$$$$$ |$$\$$\$$ $$ |
6
+ $$ \$$$$ |$$ __$$< $$ \$$$ $$ |
7
+ $$ |\$$$ |$$ / $$ |$$ |\$ /$$ |
8
+ $$ | \$$ |\$$$$$$ |$$ | \_/ $$ |
9
+ \__| \__| \______/ \__| \__|
@@ -9,6 +9,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
9
9
  validationStatus: "passed" | "failed";
10
10
  availableNodeTypes: string[];
11
11
  revisionCount: number;
12
+ availableCredentials: import("../utils/n8nClient.js").N8nCredential[];
12
13
  strategies: any[];
13
14
  candidates: any[];
14
15
  customTools: Record<string, string>;
@@ -26,6 +27,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
26
27
  validationStatus?: "passed" | "failed" | undefined;
27
28
  availableNodeTypes?: string[] | undefined;
28
29
  revisionCount?: number | undefined;
30
+ availableCredentials?: import("../utils/n8nClient.js").N8nCredential[] | undefined;
29
31
  strategies?: any[] | undefined;
30
32
  candidates?: any[] | undefined;
31
33
  customTools?: Record<string, string> | undefined;
@@ -75,6 +77,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
75
77
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
76
78
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
77
79
  };
80
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
78
81
  strategies: {
79
82
  (): import("@langchain/langgraph").LastValue<any[]>;
80
83
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -136,6 +139,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
136
139
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
137
140
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
138
141
  };
142
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
139
143
  strategies: {
140
144
  (): import("@langchain/langgraph").LastValue<any[]>;
141
145
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -252,6 +256,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
252
256
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
253
257
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
254
258
  };
259
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
255
260
  strategies: {
256
261
  (): import("@langchain/langgraph").LastValue<any[]>;
257
262
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -321,6 +326,7 @@ export declare const graph: import("@langchain/langgraph").CompiledStateGraph<{
321
326
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
322
327
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
323
328
  };
329
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
324
330
  strategies: {
325
331
  (): import("@langchain/langgraph").LastValue<any[]>;
326
332
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -390,6 +396,7 @@ export declare const runAgenticWorkflow: (goal: string, initialState?: Partial<t
390
396
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
391
397
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
392
398
  };
399
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
393
400
  strategies: {
394
401
  (): import("@langchain/langgraph").LastValue<any[]>;
395
402
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -415,7 +422,7 @@ export declare const runAgenticWorkflow: (goal: string, initialState?: Partial<t
415
422
  * @param goal The user's goal string
416
423
  * @returns AsyncIterable for events
417
424
  */
418
- export declare const runAgenticWorkflowStream: (goal: string, threadId?: string) => Promise<import("@langchain/core/utils/stream").IterableReadableStream<{
425
+ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string, initialState?: Partial<typeof TeamState.State>) => Promise<import("@langchain/core/utils/stream").IterableReadableStream<{
419
426
  architect?: {
420
427
  spec: any;
421
428
  collaborationLog: string[];
@@ -512,6 +519,7 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
512
519
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
513
520
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
514
521
  };
522
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
515
523
  strategies: {
516
524
  (): import("@langchain/langgraph").LastValue<any[]>;
517
525
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -581,6 +589,7 @@ export declare const runAgenticWorkflowStream: (goal: string, threadId?: string)
581
589
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
582
590
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
583
591
  };
592
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
584
593
  strategies: {
585
594
  (): import("@langchain/langgraph").LastValue<any[]>;
586
595
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -647,6 +656,7 @@ export declare const resumeAgenticWorkflow: (threadId: string, input?: any) => P
647
656
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
648
657
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
649
658
  };
659
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<import("../utils/n8nClient.js").N8nCredential[], import("../utils/n8nClient.js").N8nCredential[]>;
650
660
  strategies: {
651
661
  (): import("@langchain/langgraph").LastValue<any[]>;
652
662
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -72,12 +72,13 @@ export const runAgenticWorkflow = async (goal, initialState = {}, threadId = "de
72
72
  * @param goal The user's goal string
73
73
  * @returns AsyncIterable for events
74
74
  */
75
- export const runAgenticWorkflowStream = async (goal, threadId = "default_session") => {
75
+ export const runAgenticWorkflowStream = async (goal, threadId = "default_session", initialState = {}) => {
76
76
  return await graph.stream({
77
77
  userGoal: goal,
78
78
  messages: [],
79
79
  validationErrors: [],
80
80
  revisionCount: 0,
81
+ ...initialState,
81
82
  }, {
82
83
  configurable: { thread_id: threadId }
83
84
  });
@@ -17,13 +17,14 @@ export const architectNode = async (state) => {
17
17
  };
18
18
  }
19
19
  try {
20
- const spec = await aiService.generateSpec(state.userGoal);
20
+ const credentials = state.availableCredentials ?? [];
21
+ const spec = await aiService.generateSpec(state.userGoal, credentials);
21
22
  // Check if the spec requires clarification
22
23
  const questions = spec.questions;
23
24
  const needsClarification = questions && questions.length > 0;
24
25
  // Multi-agent collaboration: generate an alternative strategy in parallel with the primary.
25
26
  // Both are handed off to separate Engineer agents that run concurrently.
26
- const alternativeSpec = await aiService.generateAlternativeSpec(state.userGoal, spec);
27
+ const alternativeSpec = await aiService.generateAlternativeSpec(state.userGoal, spec, credentials);
27
28
  const alternativeModel = aiService.getAlternativeModel();
28
29
  const strategies = [
29
30
  {
@@ -1,4 +1,4 @@
1
- import { AIService } from "../../services/ai.service.js";
1
+ import { AIService, buildCredentialContext } from "../../services/ai.service.js";
2
2
  import { NodeDefinitionsService } from "../../services/node-definitions.service.js";
3
3
  import { jsonrepair } from "jsonrepair";
4
4
  import { theme } from "../../utils/theme.js";
@@ -75,13 +75,15 @@ export const engineerNode = async (state) => {
75
75
  if (!state.spec) {
76
76
  throw new Error("Workflow specification is missing.");
77
77
  }
78
+ const credentialContext = buildCredentialContext(state.availableCredentials ?? []);
78
79
  try {
79
80
  const prompt = `You are an n8n Workflow Engineer.
80
81
  Generate the valid n8n workflow JSON(s) based on the following approved Specification.
81
-
82
+
82
83
  Specification:
83
84
  ${JSON.stringify(state.spec, null, 2)}
84
85
  ${ragContext}
86
+ ${credentialContext}
85
87
  ${state.userFeedback ? `\n\nUSER FEEDBACK / REFINEMENTS:\n${state.userFeedback}\n(Incorporate this feedback into the generation process)` : ""}
86
88
 
87
89
  IMPORTANT:
@@ -1,4 +1,5 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
+ import type { N8nCredential } from '../utils/n8nClient.js';
2
3
  export declare const TeamState: import("@langchain/langgraph").AnnotationRoot<{
3
4
  userGoal: {
4
5
  (): import("@langchain/langgraph").LastValue<string>;
@@ -41,6 +42,7 @@ export declare const TeamState: import("@langchain/langgraph").AnnotationRoot<{
41
42
  (annotation: import("@langchain/langgraph").SingleReducer<number, number>): import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
42
43
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
43
44
  };
45
+ availableCredentials: import("@langchain/langgraph").BinaryOperatorAggregate<N8nCredential[], N8nCredential[]>;
44
46
  strategies: {
45
47
  (): import("@langchain/langgraph").LastValue<any[]>;
46
48
  (annotation: import("@langchain/langgraph").SingleReducer<any[], any[]>): import("@langchain/langgraph").BinaryOperatorAggregate<any[], any[]>;
@@ -12,6 +12,10 @@ export const TeamState = Annotation.Root({
12
12
  validationStatus: (Annotation),
13
13
  availableNodeTypes: (Annotation),
14
14
  revisionCount: (Annotation),
15
+ availableCredentials: Annotation({
16
+ value: (_x, y) => y ?? [],
17
+ default: () => [],
18
+ }),
15
19
  // Parallel Execution Support
16
20
  strategies: (Annotation),
17
21
  candidates: Annotation({
@@ -78,9 +78,23 @@ export default class Create extends Command {
78
78
  // 2. AGENTIC EXECUTION
79
79
  const threadId = randomUUID();
80
80
  this.log(theme.info(`\nInitializing Agentic Workflow for: "${description}" (Session: ${threadId})`));
81
+ // Fetch available credentials for AI guidance (gracefully skipped if n8n not configured)
82
+ let availableCredentials = [];
83
+ {
84
+ const preConfig = await ConfigManager.load();
85
+ const preUrl = process.env.N8N_API_URL || preConfig.n8nUrl;
86
+ const preKey = process.env.N8N_API_KEY || preConfig.n8nKey;
87
+ if (preUrl && preKey) {
88
+ const preClient = new N8nClient({ apiUrl: preUrl, apiKey: preKey });
89
+ availableCredentials = await preClient.getCredentials();
90
+ if (availableCredentials.length > 0) {
91
+ this.log(theme.muted(` Found ${availableCredentials.length} credential(s) on n8n instance — AI will use these for node selection.`));
92
+ }
93
+ }
94
+ }
81
95
  let lastWorkflowJson = null;
82
96
  try {
83
- const stream = await runAgenticWorkflowStream(description, threadId);
97
+ const stream = await runAgenticWorkflowStream(description, threadId, { availableCredentials });
84
98
  for await (const event of stream) {
85
99
  // event keys correspond to node names that just finished
86
100
  const nodeName = Object.keys(event)[0];
@@ -184,7 +198,6 @@ export default class Create extends Command {
184
198
  this.log(theme.header('\nCHATTING WITH THE ENGINEER'));
185
199
  this.log(theme.muted(` Plan: ${currentSpec?.suggestedName}`));
186
200
  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
201
  while (true) {
189
202
  const { message } = await inquirer.prompt([{
190
203
  type: 'input',
@@ -9,6 +9,8 @@ export default class Deploy extends Command {
9
9
  instance: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  activate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ update: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ 'force-create': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
14
  };
13
15
  run(): Promise<void>;
14
16
  }
@@ -64,6 +64,14 @@ export default class Deploy extends Command {
64
64
  char: 'd',
65
65
  description: 'Directory to scan for workflows (default: ./workflows)',
66
66
  }),
67
+ update: Flags.boolean({
68
+ description: 'Update existing workflow without prompting (non-interactive)',
69
+ default: false,
70
+ }),
71
+ 'force-create': Flags.boolean({
72
+ description: 'Always create as a new workflow, ignoring any existing ID (non-interactive)',
73
+ default: false,
74
+ }),
67
75
  };
68
76
  async run() {
69
77
  this.log(theme.brand());
@@ -126,14 +134,23 @@ export default class Deploy extends Command {
126
134
  // not found — will create
127
135
  }
128
136
  if (existsRemotely) {
129
- const { default: select } = await import('@inquirer/select');
130
- const action = await select({
131
- message: `Workflow "${workflowData.name}" already exists in n8n (ID: ${workflowData.id}). What would you like to do?`,
132
- choices: [
133
- { name: 'Update existing workflow', value: 'update' },
134
- { name: 'Create as new workflow', value: 'create' },
135
- ],
136
- });
137
+ let action;
138
+ if (flags.update) {
139
+ action = 'update';
140
+ }
141
+ else if (flags['force-create']) {
142
+ action = 'create';
143
+ }
144
+ else {
145
+ const { default: select } = await import('@inquirer/select');
146
+ action = await select({
147
+ message: `Workflow "${workflowData.name}" already exists in n8n (ID: ${workflowData.id}). What would you like to do?`,
148
+ choices: [
149
+ { name: 'Update existing workflow', value: 'update' },
150
+ { name: 'Create as new workflow', value: 'create' },
151
+ ],
152
+ });
153
+ }
137
154
  if (action === 'update') {
138
155
  await client.updateWorkflow(workflowData.id, workflowData);
139
156
  deployedId = workflowData.id;
@@ -26,6 +26,7 @@ export default class Fixture extends Command {
26
26
  '<%= config.bin %> fixture capture abc123 # pull latest real execution for a specific workflow ID',
27
27
  ];
28
28
  async run() {
29
+ this.log(theme.brand());
29
30
  const { args } = await this.parse(Fixture);
30
31
  if (args.action === 'init') {
31
32
  await this.initFixture(args.workflowId);
@@ -162,7 +162,7 @@ export default class Learn extends Command {
162
162
  return;
163
163
  }
164
164
  this.log(theme.info(`Found ${mdFiles.length} pattern(s).`));
165
- const { checkbox } = await import('inquirer');
165
+ const { default: checkbox } = await import('@inquirer/checkbox');
166
166
  const selected = await checkbox({
167
167
  message: 'Select patterns to import:',
168
168
  choices: mdFiles.map(f => ({ name: f.path, value: f.path, checked: true })),
@@ -55,6 +55,17 @@ export default class Modify extends Command {
55
55
  catch (e) {
56
56
  this.log(theme.warn(`⚠ Failed to fetch node types: ${e.message}`));
57
57
  }
58
+ // 1b. Fetch available credentials for AI guidance
59
+ let availableCredentials = [];
60
+ try {
61
+ availableCredentials = await client.getCredentials();
62
+ if (availableCredentials.length > 0) {
63
+ this.log(theme.muted(` Found ${availableCredentials.length} credential(s) — AI will use these for node selection.`));
64
+ }
65
+ }
66
+ catch {
67
+ // Non-fatal — proceed without credential context
68
+ }
58
69
  // 2. Resolve Workflow
59
70
  let workflowData;
60
71
  let workflowName = 'Untitled';
@@ -152,7 +163,8 @@ export default class Modify extends Command {
152
163
  messages: [],
153
164
  validationErrors: [],
154
165
  workflowJson: workflowData,
155
- availableNodeTypes: validNodeTypes
166
+ availableNodeTypes: validNodeTypes,
167
+ availableCredentials,
156
168
  };
157
169
  try {
158
170
  const stream = await graph.stream({
@@ -0,0 +1,31 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { GitCommit } from '../services/git.service.js';
3
+ export interface RollbackChoice {
4
+ name: string;
5
+ value: string;
6
+ }
7
+ /**
8
+ * Build the interactive select choices from a list of git commits.
9
+ * The most-recent commit is labelled "(HEAD)" to make it easy to identify.
10
+ * Pure function — safe to unit test.
11
+ */
12
+ export declare function buildRollbackChoices(commits: GitCommit[]): RollbackChoice[];
13
+ /**
14
+ * Build a human-readable diff preview comparing the current on-disk version
15
+ * of a workflow against the version about to be restored.
16
+ * Pure function — safe to unit test.
17
+ */
18
+ export declare function buildRollbackPreview(currentJson: any, targetJson: any): string;
19
+ export default class Rollback extends Command {
20
+ static args: {
21
+ workflow: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
22
+ };
23
+ static description: string;
24
+ static examples: string[];
25
+ static flags: {
26
+ instance: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
27
+ deploy: import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
+ };
30
+ run(): Promise<void>;
31
+ }