@lhi/n8m 0.1.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.js +6 -0
  5. package/dist/agentic/checkpointer.d.ts +2 -0
  6. package/dist/agentic/checkpointer.js +14 -0
  7. package/dist/agentic/graph.d.ts +483 -0
  8. package/dist/agentic/graph.js +100 -0
  9. package/dist/agentic/nodes/architect.d.ts +6 -0
  10. package/dist/agentic/nodes/architect.js +51 -0
  11. package/dist/agentic/nodes/engineer.d.ts +11 -0
  12. package/dist/agentic/nodes/engineer.js +182 -0
  13. package/dist/agentic/nodes/qa.d.ts +5 -0
  14. package/dist/agentic/nodes/qa.js +151 -0
  15. package/dist/agentic/nodes/reviewer.d.ts +5 -0
  16. package/dist/agentic/nodes/reviewer.js +111 -0
  17. package/dist/agentic/nodes/supervisor.d.ts +6 -0
  18. package/dist/agentic/nodes/supervisor.js +18 -0
  19. package/dist/agentic/state.d.ts +51 -0
  20. package/dist/agentic/state.js +26 -0
  21. package/dist/commands/config.d.ts +13 -0
  22. package/dist/commands/config.js +47 -0
  23. package/dist/commands/create.d.ts +14 -0
  24. package/dist/commands/create.js +182 -0
  25. package/dist/commands/deploy.d.ts +13 -0
  26. package/dist/commands/deploy.js +68 -0
  27. package/dist/commands/modify.d.ts +13 -0
  28. package/dist/commands/modify.js +276 -0
  29. package/dist/commands/prune.d.ts +9 -0
  30. package/dist/commands/prune.js +98 -0
  31. package/dist/commands/resume.d.ts +8 -0
  32. package/dist/commands/resume.js +39 -0
  33. package/dist/commands/test.d.ts +27 -0
  34. package/dist/commands/test.js +619 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/services/ai.service.d.ts +51 -0
  38. package/dist/services/ai.service.js +421 -0
  39. package/dist/services/n8n.service.d.ts +17 -0
  40. package/dist/services/n8n.service.js +81 -0
  41. package/dist/services/node-definitions.service.d.ts +36 -0
  42. package/dist/services/node-definitions.service.js +102 -0
  43. package/dist/utils/config.d.ts +15 -0
  44. package/dist/utils/config.js +25 -0
  45. package/dist/utils/multilinePrompt.d.ts +1 -0
  46. package/dist/utils/multilinePrompt.js +52 -0
  47. package/dist/utils/n8nClient.d.ts +97 -0
  48. package/dist/utils/n8nClient.js +440 -0
  49. package/dist/utils/sandbox.d.ts +13 -0
  50. package/dist/utils/sandbox.js +34 -0
  51. package/dist/utils/theme.d.ts +23 -0
  52. package/dist/utils/theme.js +92 -0
  53. package/oclif.manifest.json +331 -0
  54. package/package.json +95 -0
@@ -0,0 +1,98 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { theme } from '../utils/theme.js';
3
+ import { ConfigManager } from '../utils/config.js';
4
+ import { N8nClient } from '../utils/n8nClient.js';
5
+ import inquirer from 'inquirer';
6
+ export default class Prune extends Command {
7
+ static description = 'Deduplicate workflows on the instance';
8
+ static flags = {
9
+ force: Flags.boolean({ char: 'f', description: 'Force delete without confirmation' }),
10
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be deleted', default: false }),
11
+ };
12
+ async run() {
13
+ this.log(theme.brand());
14
+ const { flags } = await this.parse(Prune);
15
+ this.log(theme.header('WORKFLOW PRUNING'));
16
+ const config = await ConfigManager.load();
17
+ const n8nUrl = config.n8nUrl || process.env.N8N_API_URL;
18
+ const n8nKey = config.n8nKey || process.env.N8N_API_KEY;
19
+ if (!n8nUrl || !n8nKey) {
20
+ this.error('Credentials missing. Configure environment via \'n8m config\'.');
21
+ }
22
+ const client = new N8nClient({ apiUrl: n8nUrl, apiKey: n8nKey });
23
+ try {
24
+ this.log(theme.info('Fetching workflows...'));
25
+ const workflows = await client.getWorkflows();
26
+ // Group by name
27
+ const grouped = new Map();
28
+ for (const wf of workflows) {
29
+ const list = grouped.get(wf.name) || [];
30
+ list.push(wf);
31
+ grouped.set(wf.name, list);
32
+ }
33
+ const toDelete = [];
34
+ const toKeep = [];
35
+ for (const [name, list] of grouped.entries()) {
36
+ // Logic 1: Remove Test Artifacts via Regex
37
+ if (/^\[n8m:.*\]/i.test(name) || /^\[test/i.test(name) || /^My Workflow/i.test(name)) {
38
+ toDelete.push(...list);
39
+ continue;
40
+ }
41
+ // Logic 2: Deduplicate by Name
42
+ if (list.length > 1) {
43
+ // Sort by updatedAt descending (newest first)
44
+ list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
45
+ // Keep index 0, delete the rest
46
+ toKeep.push(list[0]);
47
+ toDelete.push(...list.slice(1));
48
+ }
49
+ }
50
+ if (toDelete.length === 0) {
51
+ this.log(theme.done('No duplicates found. Instance is clean.'));
52
+ return;
53
+ }
54
+ this.log(theme.subHeader('Analysis Result'));
55
+ this.log(`${theme.label('Total Workflows')} ${theme.value(workflows.length)}`);
56
+ this.log(`${theme.label('Duplicates Found')} ${theme.error(toDelete.length.toString())}`);
57
+ this.log(theme.divider(20));
58
+ this.log(theme.info('Duplicates to be removed:'));
59
+ for (const wf of toDelete) {
60
+ const keeper = grouped.get(wf.name)?.[0];
61
+ this.log(`${theme.muted('[DELETE]')} ${wf.name} (${wf.id}) ${theme.muted(`< updated ${wf.updatedAt}`)}`);
62
+ this.log(` ${theme.success('KEEPing')} ${keeper?.id} (updated ${keeper?.updatedAt})`);
63
+ }
64
+ if (flags['dry-run']) {
65
+ this.log(theme.divider(20));
66
+ this.log(theme.warn('DRY RUN: No actual changes made.'));
67
+ return;
68
+ }
69
+ if (!flags.force) {
70
+ const { confirm } = await inquirer.prompt([{
71
+ type: 'confirm',
72
+ name: 'confirm',
73
+ message: `Are you sure you want to delete ${toDelete.length} workflows? This cannot be undone.`,
74
+ default: false
75
+ }]);
76
+ if (!confirm) {
77
+ this.log(theme.info('Aborted.'));
78
+ return;
79
+ }
80
+ }
81
+ this.log(theme.agent('Executing purge...'));
82
+ for (const wf of toDelete) {
83
+ try {
84
+ process.stdout.write(`Deleting ${wf.id}... `);
85
+ await client.deleteWorkflow(wf.id);
86
+ console.log(theme.done('Deleted'));
87
+ }
88
+ catch {
89
+ console.log(theme.fail('Failed'));
90
+ }
91
+ }
92
+ this.log(theme.done('Pruning complete.'));
93
+ }
94
+ catch (error) {
95
+ this.error(`Pruning failed: ${error.message}`);
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,8 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Resume extends Command {
3
+ static args: {
4
+ threadId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,39 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { theme } from '../utils/theme.js';
3
+ import { resumeAgenticWorkflow, graph } from '../agentic/graph.js';
4
+ export default class Resume extends Command {
5
+ static args = {
6
+ threadId: Args.string({
7
+ description: 'The Thread ID of the session to resume',
8
+ required: true,
9
+ }),
10
+ };
11
+ static description = 'Resume a paused/interrupted agentic workflow';
12
+ async run() {
13
+ const { args } = await this.parse(Resume);
14
+ const threadId = args.threadId;
15
+ this.log(theme.brand());
16
+ this.log(theme.header(`RESUMING SESSION: ${threadId}`));
17
+ const snapshot = await graph.getState({ configurable: { thread_id: threadId } });
18
+ const next = snapshot.next;
19
+ if (next.length === 0) {
20
+ this.log(theme.warn("This session is already completed or does not exist."));
21
+ return;
22
+ }
23
+ this.log(theme.info(`Workflow is paused at: ${next.join(', ')}`));
24
+ this.log(theme.agent("Resuming..."));
25
+ try {
26
+ const result = await resumeAgenticWorkflow(threadId);
27
+ if (result.validationStatus === 'passed') {
28
+ this.log(theme.success("Workflow completed successfully!"));
29
+ }
30
+ else {
31
+ this.log(theme.warn(`Workflow finished with status: ${result.validationStatus}`));
32
+ }
33
+ process.exit(0);
34
+ }
35
+ catch (error) {
36
+ this.error(`Failed to resume: ${error.message}`);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,27 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Test extends Command {
3
+ static args: {
4
+ workflow: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static flags: {
8
+ headless: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ 'keep-on-fail': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ 'no-brand': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'validate-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ /**
15
+ * Extract clean error message from n8n API responses
16
+ */
17
+ private cleanErrorMsg;
18
+ /**
19
+ * Normalize error messages to catch "similar" errors (masking IDs/numbers)
20
+ */
21
+ private normalizeError;
22
+ private sanitizeWorkflow;
23
+ private stripShim;
24
+ private saveWorkflows;
25
+ private handlePostTestActions;
26
+ private deployWorkflows;
27
+ }