@lhi/n8m 0.3.3 → 1.0.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.
Files changed (49) hide show
  1. package/README.md +27 -9
  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 +1580 -0
  42. package/docs/social-card.html +237 -0
  43. package/docs/social-card.png +0 -0
  44. package/n8m-cover.png +0 -0
  45. package/n8m-logo-light.png +0 -0
  46. package/n8m-logo-mono.png +0 -0
  47. package/n8m-logo-v2.png +0 -0
  48. package/oclif.manifest.json +68 -1
  49. package/package.json +12 -1
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # n8m: The Agentic CLI for n8n
1
+ <div align="center">
2
+ <img src="https://n8m.run/social-card.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.
@@ -10,6 +12,8 @@
10
12
  [![oclif](https://badgen.net/badge/CLI/oclif/purple)](https://oclif.io/)
11
13
  [![n8n](https://badgen.net/badge/n8n/Compatible/orange)](https://n8n.io)
12
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)
13
17
 
14
18
  **Stop clicking. Start shipping.** `n8m` is an open-source CLI that wraps your
15
19
  n8n instance with an agentic AI layer. Describe what you want in plain English —
@@ -50,8 +54,8 @@ npx n8m config --ai-provider gemini --ai-key AIza...
50
54
  npx n8m config --ai-base-url http://localhost:11434/v1 --ai-key ollama --ai-model llama3
51
55
  ```
52
56
 
53
- You can also use environment variables or a `.env` file — env vars take priority
54
- 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:
55
59
 
56
60
  | Variable | Description |
57
61
  | ------------- | -------------------------------------------------------- |
@@ -285,6 +289,12 @@ n8m deploy ./workflows/my-flow.json
285
289
 
286
290
  # Activate the workflow immediately after deployment
287
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
288
298
  ```
289
299
 
290
300
  ---
@@ -378,12 +388,18 @@ n8m mcp
378
388
  # → Starting n8m MCP Server...
379
389
  ```
380
390
 
381
- The server runs over stdio and registers two tools:
391
+ The server runs over stdio and registers the following tools:
382
392
 
383
393
  | Tool | Description |
384
394
  |---|---|
385
395
  | `create_workflow` | Generate an n8n workflow from a natural-language goal |
396
+ | `modify_workflow` | Modify an existing workflow JSON using natural language instructions |
386
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 |
387
403
 
388
404
  Add it to your MCP client config (e.g. Claude Desktop's `claude_desktop_config.json`):
389
405
 
@@ -507,7 +523,7 @@ Developer → n8m learn <workflow.json>
507
523
  ## Local Development
508
524
 
509
525
  ```bash
510
- git clone https://github.com/lcanady/n8m.git
526
+ git clone https://github.com/Lee-Holdings-International/n8m.git
511
527
  cd n8m
512
528
  npm install
513
529
 
@@ -550,7 +566,7 @@ evolving node ecosystem.
550
566
 
551
567
  ### Shipped
552
568
 
553
- - [x] Agentic graph (Architect → Engineer → QA)
569
+ - [x] Agentic graph (Architect → Engineer → Supervisor → Reviewer → QA)
554
570
  - [x] SQLite session persistence
555
571
  - [x] HITL interrupts and resume
556
572
  - [x] Sub-workflow dependency resolution in tests
@@ -566,13 +582,15 @@ evolving node ecosystem.
566
582
  - [x] Pattern library — extract & reuse knowledge from validated workflows (`n8m learn`)
567
583
  - [x] GitHub pattern archive import (`n8m learn --github owner/repo`)
568
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
569
589
 
570
590
  ### Near-term
571
591
 
572
592
  - [ ] **`n8m watch`** — file-system watcher that auto-deploys on save with live reload in the n8n editor
573
- - [ ] **`n8m diff`** — human-readable structural diff between two versions of a workflow (nodes added/removed/changed)
574
- - [ ] **`n8m rollback`** — restore a workflow to a previous git-tracked version with a single command
575
- - [ ] **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)
576
594
  - [ ] **Parallel test runs** — fire multiple fixture payloads concurrently and report aggregate pass/fail
577
595
 
578
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
+ }