n8nac 2.4.0-next.150 → 2.4.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 +4 -24
- package/dist/commands/base.d.ts.map +1 -1
- package/dist/commands/base.js +36 -90
- package/dist/commands/base.js.map +1 -1
- package/dist/commands/promote.d.ts +7 -1
- package/dist/commands/promote.d.ts.map +1 -1
- package/dist/commands/promote.js +94 -19
- package/dist/commands/promote.js.map +1 -1
- package/dist/commands/update-ai.d.ts.map +1 -1
- package/dist/commands/update-ai.js +27 -12
- package/dist/commands/update-ai.js.map +1 -1
- package/dist/core/services/n8n-api-client.d.ts.map +1 -1
- package/dist/core/services/n8n-api-client.js +2 -0
- package/dist/core/services/n8n-api-client.js.map +1 -1
- package/dist/core/services/workflow-state-tracker.d.ts.map +1 -1
- package/dist/core/services/workflow-state-tracker.js +26 -4
- package/dist/core/services/workflow-state-tracker.js.map +1 -1
- package/dist/index.js +166 -273
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +0 -1
- package/dist/lib.js.map +1 -1
- package/dist/services/config-service.d.ts +25 -155
- package/dist/services/config-service.d.ts.map +1 -1
- package/dist/services/config-service.js +145 -991
- package/dist/services/config-service.js.map +1 -1
- package/package.json +5 -4
- package/dist/services/workspace-migration-facade.d.ts +0 -21
- package/dist/services/workspace-migration-facade.d.ts.map +0 -1
- package/dist/services/workspace-migration-facade.js +0 -28
- package/dist/services/workspace-migration-facade.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,6 @@ import { parsePositiveIntegerOption } from './utils/option-parsers.js';
|
|
|
19
19
|
import { spawn } from 'child_process';
|
|
20
20
|
import { createN8nManagerFacade } from '@n8n-as-code/manager-adapter';
|
|
21
21
|
import { ConfigService } from './services/config-service.js';
|
|
22
|
-
import { WorkspaceMigrationFacade } from './services/workspace-migration-facade.js';
|
|
23
22
|
import { N8N_FACADE_SETUP_MODES, isN8nFacadeSetupMode, } from '@n8n-as-code/workflow-core';
|
|
24
23
|
import { createTelemetryClient, getTelemetryStatus, setTelemetryEnabled, classifyTelemetryError, shouldShowTelemetryNotice, markTelemetryNoticeShown, } from '@n8n-as-code/telemetry';
|
|
25
24
|
async function readSecretFromStdin() {
|
|
@@ -35,6 +34,15 @@ async function hydrateApiKeyFromStdin(options) {
|
|
|
35
34
|
}
|
|
36
35
|
options.apiKey = await readSecretFromStdin();
|
|
37
36
|
}
|
|
37
|
+
async function hydrateNativeMcpTokenFromStdin(options) {
|
|
38
|
+
if (options.token || !options.tokenStdin) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
options.token = await readSecretFromStdin();
|
|
42
|
+
}
|
|
43
|
+
function defaultNativeMcpEndpointFromHost(host) {
|
|
44
|
+
return `${host.trim().replace(/\/+$/g, '')}/mcp-server/http`;
|
|
45
|
+
}
|
|
38
46
|
function createManagerFacadeFromOptions(options) {
|
|
39
47
|
return createN8nManagerFacade({
|
|
40
48
|
n8nHost: options.host || process.env.N8N_HOST,
|
|
@@ -77,88 +85,12 @@ function hideCommand(command) {
|
|
|
77
85
|
command.hidden = true;
|
|
78
86
|
return command;
|
|
79
87
|
}
|
|
80
|
-
function formatLegacyMigrationResult(result) {
|
|
81
|
-
if (result.status === 'not-needed') {
|
|
82
|
-
return chalk.green(`No legacy n8nac workspace config detected at ${result.configPath}.`);
|
|
83
|
-
}
|
|
84
|
-
const plan = result.plan;
|
|
85
|
-
const instanceLines = plan.instances.length > 0
|
|
86
|
-
? plan.instances.map((instance) => `- ${instance.name} (${instance.id})${instance.host ? ` -> ${instance.host}` : ''}${instance.hasApiKey ? ' [API key found]' : ''}`)
|
|
87
|
-
: ['- No instances found in the legacy config.'];
|
|
88
|
-
const workspaceLines = [
|
|
89
|
-
plan.workspace.syncFolder ? `- Sync folder: ${plan.workspace.syncFolder}` : undefined,
|
|
90
|
-
plan.workspace.projectName || plan.workspace.projectId ? `- Project: ${plan.workspace.projectName || plan.workspace.projectId}` : undefined,
|
|
91
|
-
plan.activeInstanceId ? `- Workspace-pinned instance: ${plan.activeInstanceId}` : undefined,
|
|
92
|
-
].filter(Boolean);
|
|
93
|
-
const header = result.status === 'dry-run'
|
|
94
|
-
? chalk.yellow('Legacy n8nac workspace config detected. No files changed.')
|
|
95
|
-
: chalk.green('Legacy n8nac workspace config migrated.');
|
|
96
|
-
const footer = result.status === 'dry-run'
|
|
97
|
-
? ['Run `n8nac workspace migrate-v1 --write` to migrate and create a backup first.']
|
|
98
|
-
: [`Backup: ${result.backupPath}`, 'Run `n8nac workspace status --json` to verify the resolved context.'];
|
|
99
|
-
return [
|
|
100
|
-
header,
|
|
101
|
-
`Config: ${plan.configPath}`,
|
|
102
|
-
plan.version ? `Legacy version: ${plan.version}` : undefined,
|
|
103
|
-
'',
|
|
104
|
-
'Instances:',
|
|
105
|
-
...instanceLines,
|
|
106
|
-
workspaceLines.length ? '' : undefined,
|
|
107
|
-
workspaceLines.length ? 'Workspace overrides:' : undefined,
|
|
108
|
-
...workspaceLines,
|
|
109
|
-
'',
|
|
110
|
-
'Notes:',
|
|
111
|
-
...plan.warnings.map((warning) => `- ${warning}`),
|
|
112
|
-
'',
|
|
113
|
-
...footer,
|
|
114
|
-
].filter(Boolean).join('\n');
|
|
115
|
-
}
|
|
116
|
-
function formatWorkspaceMigrationReport(report) {
|
|
117
|
-
if (report.status === 'not-needed') {
|
|
118
|
-
return chalk.green(`No n8n workspace migration required at ${report.configPath}.`);
|
|
119
|
-
}
|
|
120
|
-
const header = report.status === 'dry-run'
|
|
121
|
-
? chalk.yellow('n8n workspace migration required. No files changed.')
|
|
122
|
-
: chalk.green('n8n workspace migration completed.');
|
|
123
|
-
const footer = report.status === 'dry-run'
|
|
124
|
-
? ['Run `n8nac workspace migrate --write` to apply the migration.']
|
|
125
|
-
: [
|
|
126
|
-
report.backupPath ? `Backup: ${report.backupPath}` : undefined,
|
|
127
|
-
`Migrated environments: ${report.migratedEnvironmentIds?.length || 0}`,
|
|
128
|
-
`Removed global external instances: ${report.deletedGlobalInstanceIds?.length || 0}`,
|
|
129
|
-
'Run `n8nac workspace status --json` to verify the resolved context.',
|
|
130
|
-
].filter(Boolean);
|
|
131
|
-
return [
|
|
132
|
-
header,
|
|
133
|
-
`Config: ${report.configPath}`,
|
|
134
|
-
'',
|
|
135
|
-
'Migration operations:',
|
|
136
|
-
...report.operations.map((operation) => `- ${operation.label}: ${operation.instanceCount} instance${operation.instanceCount === 1 ? '' : 's'}`),
|
|
137
|
-
report.warnings.length ? '' : undefined,
|
|
138
|
-
report.warnings.length ? 'Notes:' : undefined,
|
|
139
|
-
...report.warnings.map((warning) => `- ${warning}`),
|
|
140
|
-
'',
|
|
141
|
-
...footer,
|
|
142
|
-
].filter(Boolean).join('\n');
|
|
143
|
-
}
|
|
144
88
|
function redactResolvedEnvironment(environment) {
|
|
145
89
|
if (!environment)
|
|
146
90
|
return environment;
|
|
147
91
|
const { apiKey: _apiKey, ...safeEnvironment } = environment;
|
|
148
92
|
return safeEnvironment;
|
|
149
93
|
}
|
|
150
|
-
function abortIfWorkspaceMigrationRequired(configService, options = {}) {
|
|
151
|
-
const payload = new WorkspaceMigrationFacade({ configService }).inspect();
|
|
152
|
-
if (!payload)
|
|
153
|
-
return;
|
|
154
|
-
printJsonOrText(options, payload, [
|
|
155
|
-
chalk.yellow('n8n workspace migration required before environment commands can run.'),
|
|
156
|
-
`Config: ${payload.configPath}`,
|
|
157
|
-
'Run `n8nac workspace migrate --json` to inspect it.',
|
|
158
|
-
'After explicit confirmation, run `n8nac workspace migrate --write` to apply it.',
|
|
159
|
-
].join('\n'));
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
94
|
/**
|
|
163
95
|
* Get version from package.json
|
|
164
96
|
*/
|
|
@@ -217,11 +149,11 @@ const getFirstPositionalToken = (args, startIndex = 0) => {
|
|
|
217
149
|
if (token === '--') {
|
|
218
150
|
return args[index + 1];
|
|
219
151
|
}
|
|
220
|
-
if (token === '--
|
|
152
|
+
if (token === '--env' || token === '--environment') {
|
|
221
153
|
index += 1;
|
|
222
154
|
continue;
|
|
223
155
|
}
|
|
224
|
-
if (token.startsWith('--
|
|
156
|
+
if (token.startsWith('--env=') || token.startsWith('--environment=')) {
|
|
225
157
|
continue;
|
|
226
158
|
}
|
|
227
159
|
if (token.startsWith('-')) {
|
|
@@ -252,16 +184,50 @@ const loadSkillsRegistrar = async () => {
|
|
|
252
184
|
return import(pathToFileURL(modulePath).href);
|
|
253
185
|
};
|
|
254
186
|
const getMcpEntry = () => {
|
|
187
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
188
|
+
const siblingEntry = join(__dirname, '..', '..', 'mcp', 'dist', 'cli.js');
|
|
189
|
+
if (existsSync(siblingEntry)) {
|
|
190
|
+
return siblingEntry;
|
|
191
|
+
}
|
|
255
192
|
try {
|
|
256
193
|
const require = createRequire(import.meta.url);
|
|
257
194
|
const mcpPkg = require.resolve('@n8n-as-code/mcp/package.json');
|
|
258
195
|
return join(dirname(mcpPkg), 'dist', 'cli.js');
|
|
259
196
|
}
|
|
260
197
|
catch {
|
|
261
|
-
|
|
262
|
-
return join(__dirname, '..', '..', 'mcp', 'dist', 'cli.js');
|
|
198
|
+
return siblingEntry;
|
|
263
199
|
}
|
|
264
200
|
};
|
|
201
|
+
async function runMcpDiagnosticCommand(kind, options = {}) {
|
|
202
|
+
const mcpEntry = getMcpEntry();
|
|
203
|
+
const args = [mcpEntry, `--native-mcp-${kind}`];
|
|
204
|
+
if (options.cwd)
|
|
205
|
+
args.push('--cwd', options.cwd);
|
|
206
|
+
if (options.json)
|
|
207
|
+
args.push('--json');
|
|
208
|
+
if (options.includeTools)
|
|
209
|
+
args.push('--include-tools');
|
|
210
|
+
const child = spawn(process.execPath, args, {
|
|
211
|
+
cwd: process.cwd(),
|
|
212
|
+
env: options.environmentNameOrId
|
|
213
|
+
? { ...process.env, N8NAC_ENVIRONMENT: options.environmentNameOrId }
|
|
214
|
+
: process.env,
|
|
215
|
+
stdio: 'inherit',
|
|
216
|
+
});
|
|
217
|
+
await new Promise((resolve, reject) => {
|
|
218
|
+
child.on('error', reject);
|
|
219
|
+
child.on('exit', async (code, signal) => {
|
|
220
|
+
if (signal) {
|
|
221
|
+
process.kill(process.pid, signal);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if ((code ?? 1) !== 0) {
|
|
225
|
+
await exitWithTelemetry(code ?? 1);
|
|
226
|
+
}
|
|
227
|
+
resolve();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
265
231
|
const program = new Command();
|
|
266
232
|
const telemetry = createTelemetryClient({ facade: 'cli', version: getVersion() });
|
|
267
233
|
const commandStartTimes = new WeakMap();
|
|
@@ -338,8 +304,7 @@ program
|
|
|
338
304
|
.description('N8N Sync Command Line Interface - Manage n8n workflows as code')
|
|
339
305
|
.version(getVersion())
|
|
340
306
|
.option('--env <name>', 'Target a specific workspace environment by name or ID')
|
|
341
|
-
.addOption(new Option('--environment <name>', 'Alias for --env').hideHelp())
|
|
342
|
-
.addOption(new Option('--instance <name>', 'Deprecated; use --env').hideHelp());
|
|
307
|
+
.addOption(new Option('--environment <name>', 'Alias for --env').hideHelp());
|
|
343
308
|
// Inject --env into the environment only for the lifetime of the command action
|
|
344
309
|
// so BaseCommand can pick it up without leaking process-wide state afterwards.
|
|
345
310
|
let previousEnvironmentEnv;
|
|
@@ -410,7 +375,7 @@ telemetryProgram.command('disable')
|
|
|
410
375
|
printJsonOrText(options, status, chalk.green('Anonymous telemetry disabled.'));
|
|
411
376
|
});
|
|
412
377
|
const workspaceProgram = program.command('workspace')
|
|
413
|
-
.description('Inspect
|
|
378
|
+
.description('Inspect n8n workspace configuration');
|
|
414
379
|
workspaceProgram.command('status')
|
|
415
380
|
.alias('get')
|
|
416
381
|
.description('Show the effective n8n workspace context resolved by the backend')
|
|
@@ -418,179 +383,24 @@ workspaceProgram.command('status')
|
|
|
418
383
|
.action(async (options) => {
|
|
419
384
|
const configService = new ConfigService();
|
|
420
385
|
const selectedEnvironment = process.env.N8NAC_ENVIRONMENT?.trim() || undefined;
|
|
421
|
-
const migration = new WorkspaceMigrationFacade({ configService }).inspect();
|
|
422
|
-
if (migration) {
|
|
423
|
-
printJsonOrText(options, migration, [
|
|
424
|
-
chalk.yellow('n8n workspace migration required.'),
|
|
425
|
-
`Config: ${migration.configPath}`,
|
|
426
|
-
'Run `n8nac workspace migrate --json` to inspect it.',
|
|
427
|
-
'After confirmation, run `n8nac workspace migrate --write` to apply it.',
|
|
428
|
-
].join('\n'));
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
386
|
const workspaceConfig = configService.getWorkspaceConfig();
|
|
432
387
|
const resolvedEnvironment = selectedEnvironment
|
|
433
388
|
? await configService.prepareEnvironment(selectedEnvironment)
|
|
434
|
-
:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
} })()
|
|
441
|
-
: undefined;
|
|
389
|
+
: await (async () => { try {
|
|
390
|
+
return await configService.prepareEnvironment();
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return undefined;
|
|
394
|
+
} })();
|
|
442
395
|
printJsonOrText(options, resolvedEnvironment ? { ...workspaceConfig, selectedEnvironment: redactResolvedEnvironment(resolvedEnvironment) } : workspaceConfig, [
|
|
443
396
|
chalk.cyan('\nEffective n8n workspace context:\n'),
|
|
444
397
|
workspaceConfig.activeEnvironmentId ? `Env : ${chalk.bold(resolvedEnvironment?.environmentName || workspaceConfig.activeEnvironment?.name || workspaceConfig.activeEnvironmentId)}` : undefined,
|
|
445
398
|
`Instance: ${chalk.bold(resolvedEnvironment?.activeInstanceName || workspaceConfig.activeInstanceId || '(none)')}`,
|
|
446
399
|
`Project : ${chalk.bold(resolvedEnvironment?.projectName || workspaceConfig.projectName || workspaceConfig.projectId || '(none)')}`,
|
|
447
|
-
`Workflows path: ${chalk.bold(resolvedEnvironment?.workflowsPath || workspaceConfig.workflowsPath ||
|
|
400
|
+
`Workflows path: ${chalk.bold(resolvedEnvironment?.workflowsPath || workspaceConfig.workflowsPath || '(none)')}`,
|
|
448
401
|
'',
|
|
449
402
|
].filter(Boolean).join('\n'));
|
|
450
403
|
});
|
|
451
|
-
workspaceProgram.command('migrate')
|
|
452
|
-
.description('Inspect or run required workspace migrations')
|
|
453
|
-
.option('--write', 'Apply the migration. Without this flag, the command only reports what would change.')
|
|
454
|
-
.option('--json', 'Output migration result as JSON')
|
|
455
|
-
.action(async (options) => {
|
|
456
|
-
const report = await new WorkspaceMigrationFacade().migrate({ write: Boolean(options.write) });
|
|
457
|
-
printJsonOrText(options, report, formatWorkspaceMigrationReport(report));
|
|
458
|
-
});
|
|
459
|
-
workspaceProgram.command('migrate-v1')
|
|
460
|
-
.description('Inspect or migrate a legacy v1/v2 n8nac-config.json into the v2 manager-backed storage model')
|
|
461
|
-
.option('--write', 'Apply the migration. Without this flag, the command only reports what would change.')
|
|
462
|
-
.option('--json', 'Output migration result as JSON')
|
|
463
|
-
.action((options) => {
|
|
464
|
-
const configService = new ConfigService();
|
|
465
|
-
const result = configService.migrateLegacyWorkspaceConfig({ write: Boolean(options.write) });
|
|
466
|
-
printJsonOrText(options, result, formatLegacyMigrationResult(result));
|
|
467
|
-
});
|
|
468
|
-
hideCommand(workspaceProgram.command('pin-instance'))
|
|
469
|
-
.description('Pin the effective n8n instance for this workspace')
|
|
470
|
-
.requiredOption('--instance-id <id>', 'Global n8n instance ID to pin')
|
|
471
|
-
.option('--json', 'Output workspace config as JSON')
|
|
472
|
-
.action((options) => {
|
|
473
|
-
const configService = new ConfigService();
|
|
474
|
-
const instance = configService.pinWorkspaceInstance(options.instanceId);
|
|
475
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
476
|
-
printJsonOrText(options, workspaceConfig, chalk.green(`✔ Workspace pinned to n8n instance: ${instance.name}`));
|
|
477
|
-
});
|
|
478
|
-
hideCommand(workspaceProgram.command('clear-instance'))
|
|
479
|
-
.description('Clear the workspace n8n instance pin and fall back to the global active instance')
|
|
480
|
-
.option('--json', 'Output workspace config as JSON')
|
|
481
|
-
.action((options) => {
|
|
482
|
-
const configService = new ConfigService();
|
|
483
|
-
configService.clearWorkspaceInstanceOverride();
|
|
484
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
485
|
-
printJsonOrText(options, workspaceConfig, chalk.green('✔ Workspace instance override cleared.'));
|
|
486
|
-
});
|
|
487
|
-
hideCommand(workspaceProgram.command('set-sync-folder'))
|
|
488
|
-
.description('Set the n8n sync folder override for this workspace')
|
|
489
|
-
.argument('<path>', 'Workspace sync folder path')
|
|
490
|
-
.option('--json', 'Output workspace config as JSON')
|
|
491
|
-
.action((syncFolder, options) => {
|
|
492
|
-
const configService = new ConfigService();
|
|
493
|
-
configService.setWorkspaceSyncFolder(syncFolder);
|
|
494
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
495
|
-
printJsonOrText(options, workspaceConfig, chalk.green(`✔ Workspace sync folder set to: ${syncFolder}`));
|
|
496
|
-
});
|
|
497
|
-
hideCommand(workspaceProgram.command('clear-sync-folder'))
|
|
498
|
-
.description('Clear the workspace n8n sync folder override and fall back to the global default')
|
|
499
|
-
.option('--json', 'Output workspace config as JSON')
|
|
500
|
-
.action((options) => {
|
|
501
|
-
const configService = new ConfigService();
|
|
502
|
-
configService.clearWorkspaceSyncFolderOverride();
|
|
503
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
504
|
-
printJsonOrText(options, workspaceConfig, chalk.green('✔ Workspace sync folder override cleared.'));
|
|
505
|
-
});
|
|
506
|
-
hideCommand(workspaceProgram.command('set-project'))
|
|
507
|
-
.description('Set the n8n project override for this workspace from known project values')
|
|
508
|
-
.requiredOption('--project-id <id>', 'n8n project ID to store in this workspace')
|
|
509
|
-
.requiredOption('--project-name <name>', 'n8n project display name to store in this workspace')
|
|
510
|
-
.option('--json', 'Output workspace config as JSON')
|
|
511
|
-
.action((options) => {
|
|
512
|
-
const configService = new ConfigService();
|
|
513
|
-
configService.setWorkspaceProject({
|
|
514
|
-
projectId: options.projectId,
|
|
515
|
-
projectName: options.projectName,
|
|
516
|
-
});
|
|
517
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
518
|
-
printJsonOrText(options, workspaceConfig, chalk.green(`✔ Workspace project set to: ${options.projectName}`));
|
|
519
|
-
});
|
|
520
|
-
hideCommand(workspaceProgram.command('clear-project'))
|
|
521
|
-
.description('Clear the workspace n8n project override and fall back to the instance default project')
|
|
522
|
-
.option('--json', 'Output workspace config as JSON')
|
|
523
|
-
.action((options) => {
|
|
524
|
-
const configService = new ConfigService();
|
|
525
|
-
configService.clearWorkspaceProjectOverride();
|
|
526
|
-
const workspaceConfig = configService.getWorkspaceConfig();
|
|
527
|
-
printJsonOrText(options, workspaceConfig, chalk.green('✔ Workspace project override cleared.'));
|
|
528
|
-
});
|
|
529
|
-
const environmentTargetProgram = hideCommand(program.command('instance-target')
|
|
530
|
-
.alias('target')
|
|
531
|
-
.description('Compatibility: manage low-level workspace targets used by environments'));
|
|
532
|
-
environmentTargetProgram.command('list')
|
|
533
|
-
.description('List workspace instance targets')
|
|
534
|
-
.option('--json', 'Output targets as JSON')
|
|
535
|
-
.action((options) => {
|
|
536
|
-
const configService = new ConfigService();
|
|
537
|
-
const targets = configService.listInstanceTargets();
|
|
538
|
-
printJsonOrText(options, targets, targets.length
|
|
539
|
-
? targets.map((target) => `${target.id}\t${target.name}\t${target.kind}\t${target.kind === 'managed-instance' ? target.managedInstanceId : target.url}`).join('\n')
|
|
540
|
-
: 'No workspace instance targets configured.');
|
|
541
|
-
});
|
|
542
|
-
environmentTargetProgram.command('add')
|
|
543
|
-
.description('Compatibility: add a low-level workspace target. Prefer `n8nac env add --base-url ...`.')
|
|
544
|
-
.argument('<name>', 'Target display name')
|
|
545
|
-
.option('--id <id>', 'Stable target ID')
|
|
546
|
-
.option('--instance-ref <id>', 'Global n8n-manager instance ID to reference')
|
|
547
|
-
.option('--base-url <url>', 'Public externalInstance n8n URL to embed without secrets')
|
|
548
|
-
.option('--description <text>', 'Target description')
|
|
549
|
-
.option('--json', 'Output target as JSON')
|
|
550
|
-
.action(async (name, options) => {
|
|
551
|
-
const configService = new ConfigService();
|
|
552
|
-
const managedInstanceId = options.managedInstanceId || options.instanceRef;
|
|
553
|
-
const url = options.url || options.baseUrl;
|
|
554
|
-
if (managedInstanceId) {
|
|
555
|
-
const instance = (await createManagerFacadeFromOptions({}).listInstances()).find((item) => item.id === managedInstanceId);
|
|
556
|
-
if (!instance)
|
|
557
|
-
throw new Error(`Unknown managed local n8n instance: ${managedInstanceId}`);
|
|
558
|
-
if (instance.mode !== 'managed-local-docker') {
|
|
559
|
-
throw new Error(`Instance "${instance.name || instance.id}" is not managed locally. Prefer \`n8nac env add <name> --base-url <url> --workflows-path workflows/<name>\` for remote n8n environments.`);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
const target = configService.addInstanceTarget({
|
|
563
|
-
name,
|
|
564
|
-
id: options.id,
|
|
565
|
-
managedInstanceId,
|
|
566
|
-
url,
|
|
567
|
-
description: options.description,
|
|
568
|
-
});
|
|
569
|
-
printJsonOrText(options, target, chalk.green(`✔ Workspace instance target added: ${target.name}`));
|
|
570
|
-
});
|
|
571
|
-
environmentTargetProgram.command('update')
|
|
572
|
-
.description('Update a workspace instance target')
|
|
573
|
-
.argument('<name-or-id>', 'Target name or ID')
|
|
574
|
-
.option('--name <name>', 'New display name')
|
|
575
|
-
.option('--instance-ref <id>', 'New global n8n-manager instance reference for managedInstance targets')
|
|
576
|
-
.option('--base-url <url>', 'New public URL for externalInstance targets')
|
|
577
|
-
.option('--description <text>', 'New description')
|
|
578
|
-
.option('--json', 'Output target as JSON')
|
|
579
|
-
.action((nameOrId, options) => {
|
|
580
|
-
const configService = new ConfigService();
|
|
581
|
-
const target = configService.updateInstanceTarget(nameOrId, options);
|
|
582
|
-
printJsonOrText(options, target, chalk.green(`✔ Workspace instance target updated: ${target.name}`));
|
|
583
|
-
});
|
|
584
|
-
environmentTargetProgram.command('remove')
|
|
585
|
-
.alias('rm')
|
|
586
|
-
.description('Remove an unused workspace instance target')
|
|
587
|
-
.argument('<name-or-id>', 'Target name or ID')
|
|
588
|
-
.option('--json', 'Output removed target as JSON')
|
|
589
|
-
.action((nameOrId, options) => {
|
|
590
|
-
const configService = new ConfigService();
|
|
591
|
-
const target = configService.removeInstanceTarget(nameOrId);
|
|
592
|
-
printJsonOrText(options, target, chalk.green(`✔ Workspace instance target removed: ${target.name}`));
|
|
593
|
-
});
|
|
594
404
|
const environmentProgram = program.command('env')
|
|
595
405
|
.alias('environment')
|
|
596
406
|
.description('Manage n8n workspace environments');
|
|
@@ -599,7 +409,6 @@ environmentProgram.command('list')
|
|
|
599
409
|
.option('--json', 'Output environments as JSON')
|
|
600
410
|
.action((options) => {
|
|
601
411
|
const configService = new ConfigService();
|
|
602
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
603
412
|
const config = configService.getWorkspaceConfig();
|
|
604
413
|
const environments = configService.listEnvironments().map((environment) => {
|
|
605
414
|
const resolved = (() => { try {
|
|
@@ -617,7 +426,7 @@ environmentProgram.command('list')
|
|
|
617
426
|
environment.name,
|
|
618
427
|
environment.resolved?.environmentTargetName || environment.environmentTargetId,
|
|
619
428
|
environment.projectName || environment.projectId || '(no project)',
|
|
620
|
-
environment.workflowsPath
|
|
429
|
+
environment.workflowsPath,
|
|
621
430
|
].join('\t')).join('\n')
|
|
622
431
|
: 'No workspace environments configured.');
|
|
623
432
|
});
|
|
@@ -626,14 +435,11 @@ environmentProgram.command('add')
|
|
|
626
435
|
.argument('<name>', 'Environment display name')
|
|
627
436
|
.option('--base-url <url>', 'Remote n8n URL to store in this workspace environment')
|
|
628
437
|
.option('--managed-instance <id>', 'Local managed n8n instance ID to reference')
|
|
629
|
-
.option('--instance-target <name-or-id>', 'Compatibility: workspace target name or ID')
|
|
630
438
|
.option('--api-key <key>', 'Store a local API key for --base-url without committing it')
|
|
631
439
|
.option('--api-key-stdin', 'Read the local API key for --base-url from stdin')
|
|
632
440
|
.option('--project-id <id>', 'n8n project ID')
|
|
633
441
|
.option('--project-name <name>', 'n8n project display name')
|
|
634
442
|
.option('--workflows-path <path>', 'Directory that contains this environment workflows')
|
|
635
|
-
.option('--workflow-dir <path>', 'Compatibility alias for --workflows-path')
|
|
636
|
-
.option('--sync-folder <path>', 'Compatibility alias for --workflows-path')
|
|
637
443
|
.option('--id <id>', 'Stable environment ID')
|
|
638
444
|
.option('--folder-sync', 'Enable folder sync for this environment')
|
|
639
445
|
.option('--custom-nodes-path <path>', 'Custom nodes path for this environment')
|
|
@@ -642,17 +448,15 @@ environmentProgram.command('add')
|
|
|
642
448
|
.action(async (name, options) => {
|
|
643
449
|
await hydrateApiKeyFromStdin(options);
|
|
644
450
|
const configService = new ConfigService();
|
|
645
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
646
|
-
const environmentTargetOption = options.environmentTarget || options.instanceTarget;
|
|
647
451
|
const urlOption = options.url || options.baseUrl;
|
|
648
|
-
const selectors = [
|
|
452
|
+
const selectors = [urlOption, options.managedInstance].filter(Boolean);
|
|
649
453
|
if (selectors.length !== 1) {
|
|
650
|
-
throw new Error('Provide exactly one of --base-url
|
|
454
|
+
throw new Error('Provide exactly one of --base-url or --managed-instance.');
|
|
651
455
|
}
|
|
652
456
|
if ((options.projectId && !options.projectName) || (!options.projectId && options.projectName)) {
|
|
653
457
|
throw new Error('Provide both --project-id and --project-name, or omit both.');
|
|
654
458
|
}
|
|
655
|
-
let environmentTarget
|
|
459
|
+
let environmentTarget;
|
|
656
460
|
if (urlOption) {
|
|
657
461
|
const target = configService.ensureEmbeddedInstanceTarget({
|
|
658
462
|
name,
|
|
@@ -676,8 +480,6 @@ environmentProgram.command('add')
|
|
|
676
480
|
projectId: options.projectId || (urlOption ? 'personal' : undefined),
|
|
677
481
|
projectName: options.projectName || (urlOption ? 'Personal' : undefined),
|
|
678
482
|
workflowsPath: options.workflowsPath,
|
|
679
|
-
workflowDir: options.workflowDir,
|
|
680
|
-
syncFolder: options.syncFolder,
|
|
681
483
|
folderSync: Boolean(options.folderSync),
|
|
682
484
|
customNodesPath: options.customNodesPath,
|
|
683
485
|
description: options.description,
|
|
@@ -690,14 +492,11 @@ environmentProgram.command('update')
|
|
|
690
492
|
.option('--name <name>', 'New display name')
|
|
691
493
|
.option('--base-url <url>', 'Move this environment to a remote n8n URL')
|
|
692
494
|
.option('--managed-instance <id>', 'Move this environment to a local managed n8n instance')
|
|
693
|
-
.option('--instance-target <name-or-id>', 'Workspace instance target name or ID')
|
|
694
495
|
.option('--api-key <key>', 'Store a local API key for --base-url without committing it')
|
|
695
496
|
.option('--api-key-stdin', 'Read the local API key for --base-url from stdin')
|
|
696
497
|
.option('--project-id <id>', 'n8n project ID')
|
|
697
498
|
.option('--project-name <name>', 'n8n project display name')
|
|
698
499
|
.option('--workflows-path <path>', 'Directory that contains this environment workflows')
|
|
699
|
-
.option('--workflow-dir <path>', 'Compatibility alias for --workflows-path')
|
|
700
|
-
.option('--sync-folder <path>', 'Compatibility alias for --workflows-path')
|
|
701
500
|
.option('--folder-sync', 'Enable folder sync for this environment')
|
|
702
501
|
.option('--no-folder-sync', 'Disable folder sync for this environment')
|
|
703
502
|
.option('--custom-nodes-path <path>', 'Custom nodes path for this environment')
|
|
@@ -706,14 +505,12 @@ environmentProgram.command('update')
|
|
|
706
505
|
.action(async (nameOrId, options) => {
|
|
707
506
|
await hydrateApiKeyFromStdin(options);
|
|
708
507
|
const configService = new ConfigService();
|
|
709
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
710
|
-
const environmentTargetOption = options.environmentTarget || options.instanceTarget;
|
|
711
508
|
const urlOption = options.url || options.baseUrl;
|
|
712
|
-
const selectors = [
|
|
509
|
+
const selectors = [urlOption, options.managedInstance].filter(Boolean);
|
|
713
510
|
if (selectors.length > 1) {
|
|
714
|
-
throw new Error('Provide at most one of --base-url
|
|
511
|
+
throw new Error('Provide at most one of --base-url or --managed-instance.');
|
|
715
512
|
}
|
|
716
|
-
let environmentTarget
|
|
513
|
+
let environmentTarget;
|
|
717
514
|
if (urlOption) {
|
|
718
515
|
const target = configService.ensureEmbeddedInstanceTarget({
|
|
719
516
|
name: options.name || nameOrId,
|
|
@@ -733,8 +530,6 @@ environmentProgram.command('update')
|
|
|
733
530
|
projectId: options.projectId,
|
|
734
531
|
projectName: options.projectName,
|
|
735
532
|
workflowsPath: options.workflowsPath,
|
|
736
|
-
workflowDir: options.workflowDir,
|
|
737
|
-
syncFolder: options.syncFolder,
|
|
738
533
|
folderSync: typeof options.folderSync === 'boolean' ? options.folderSync : undefined,
|
|
739
534
|
customNodesPath: options.customNodesPath,
|
|
740
535
|
description: options.description,
|
|
@@ -749,7 +544,6 @@ environmentProgram.command('pin')
|
|
|
749
544
|
.option('--json', 'Output environment as JSON')
|
|
750
545
|
.action((nameOrId, options) => {
|
|
751
546
|
const configService = new ConfigService();
|
|
752
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
753
547
|
const environment = configService.pinEnvironment(nameOrId);
|
|
754
548
|
printJsonOrText(options, environment, chalk.green(`✔ Workspace environment pinned: ${environment.name}`));
|
|
755
549
|
});
|
|
@@ -761,7 +555,6 @@ environmentProgram.command('remove')
|
|
|
761
555
|
.option('--json', 'Output removed environment as JSON')
|
|
762
556
|
.action((nameOrId, options) => {
|
|
763
557
|
const configService = new ConfigService();
|
|
764
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
765
558
|
const environment = configService.removeEnvironment(nameOrId, { force: Boolean(options.force) });
|
|
766
559
|
printJsonOrText(options, environment, chalk.green(`✔ Workspace environment removed: ${environment.name}`));
|
|
767
560
|
});
|
|
@@ -778,7 +571,6 @@ environmentAuthProgram.command('set')
|
|
|
778
571
|
throw new Error('Provide --api-key or --api-key-stdin.');
|
|
779
572
|
}
|
|
780
573
|
const configService = new ConfigService();
|
|
781
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
782
574
|
const environment = configService.resolveEnvironment(nameOrId);
|
|
783
575
|
if (environment.sourceKind === 'managed-instance') {
|
|
784
576
|
throw new Error(`Environment "${environment.environmentName}" uses managed instance "${environment.managedInstanceId}". Configure credentials through the managed n8n instance instead.`);
|
|
@@ -797,7 +589,6 @@ environmentProgram.command('status')
|
|
|
797
589
|
.option('--json', 'Output resolved environment as JSON')
|
|
798
590
|
.action((nameOrId, options) => {
|
|
799
591
|
const configService = new ConfigService();
|
|
800
|
-
abortIfWorkspaceMigrationRequired(configService, options);
|
|
801
592
|
const environment = configService.resolveEnvironment(nameOrId || process.env.N8NAC_ENVIRONMENT?.trim() || undefined);
|
|
802
593
|
printJsonOrText(options, redactResolvedEnvironment(environment), [
|
|
803
594
|
chalk.cyan(`\nWorkspace environment: ${environment.environmentName}\n`),
|
|
@@ -805,7 +596,7 @@ environmentProgram.command('status')
|
|
|
805
596
|
`Instance: ${chalk.bold(environment.activeInstanceName || environment.managedInstanceId || '(externalInstance)')}`,
|
|
806
597
|
`Host : ${chalk.bold(environment.host)}`,
|
|
807
598
|
`Project : ${chalk.bold(environment.projectName || environment.projectId || '(none)')}`,
|
|
808
|
-
`Workflows path: ${chalk.bold(environment.workflowsPath ||
|
|
599
|
+
`Workflows path: ${chalk.bold(environment.workflowsPath || '(unresolved)')}`,
|
|
809
600
|
`API key : ${chalk.bold(environment.apiKeyAvailable ? environment.apiKeySource : 'missing')}`,
|
|
810
601
|
'',
|
|
811
602
|
].join('\n'));
|
|
@@ -1160,6 +951,108 @@ program.command('convert-batch')
|
|
|
1160
951
|
}
|
|
1161
952
|
await new ConvertCommand().batch(directory, options);
|
|
1162
953
|
});
|
|
954
|
+
const nativeMcpCmd = program.command('native-mcp')
|
|
955
|
+
.description('Inspect optional native n8n MCP assist configuration and capabilities');
|
|
956
|
+
nativeMcpCmd
|
|
957
|
+
.command('configure')
|
|
958
|
+
.description('Configure optional native n8n MCP assist for a workspace environment without committing secrets')
|
|
959
|
+
.argument('[name-or-id]', 'Environment name or ID; defaults to pinned environment or --env')
|
|
960
|
+
.option('--url <url>', 'Native n8n MCP HTTP endpoint; defaults to <environment-url>/mcp-server/http')
|
|
961
|
+
.option('--token <token>', 'Native n8n MCP bearer token to store locally')
|
|
962
|
+
.option('--token-stdin', 'Read the native n8n MCP bearer token from stdin')
|
|
963
|
+
.option('--timeout-ms <ms>', 'Native MCP request timeout in milliseconds', (value) => parsePositiveIntegerOption(value, '--timeout-ms'))
|
|
964
|
+
.option('--allow-execution-data', 'Allow full live execution payloads when explicitly requested')
|
|
965
|
+
.option('--deny-execution-data', 'Disallow full live execution payloads')
|
|
966
|
+
.option('--allow-remote', 'Allow exposing native wrappers through non-loopback HTTP/SSE broker transports')
|
|
967
|
+
.option('--deny-remote', 'Disallow exposing native wrappers through non-loopback HTTP/SSE broker transports')
|
|
968
|
+
.option('--json', 'Output environment as JSON')
|
|
969
|
+
.action(async (nameOrId, options) => {
|
|
970
|
+
await hydrateNativeMcpTokenFromStdin(options);
|
|
971
|
+
const configService = new ConfigService();
|
|
972
|
+
const selectedEnvironment = nameOrId || process.env.N8NAC_ENVIRONMENT?.trim() || undefined;
|
|
973
|
+
const resolved = configService.resolveEnvironment(selectedEnvironment);
|
|
974
|
+
const existing = resolved.environment.nativeMcp;
|
|
975
|
+
const nativeMcp = {
|
|
976
|
+
...existing,
|
|
977
|
+
enabled: true,
|
|
978
|
+
mode: 'assist',
|
|
979
|
+
url: options.url || existing?.url || defaultNativeMcpEndpointFromHost(resolved.host),
|
|
980
|
+
timeoutMs: options.timeoutMs ?? existing?.timeoutMs,
|
|
981
|
+
allowExecutionData: options.denyExecutionData ? false : options.allowExecutionData ? true : existing?.allowExecutionData,
|
|
982
|
+
allowRemoteExposure: options.denyRemote ? false : options.allowRemote ? true : existing?.allowRemoteExposure,
|
|
983
|
+
requireSyncBack: existing?.requireSyncBack ?? true,
|
|
984
|
+
};
|
|
985
|
+
const environment = configService.updateEnvironment(resolved.environmentId, { nativeMcp });
|
|
986
|
+
if (options.token) {
|
|
987
|
+
configService.saveNativeMcpToken(environment.id, options.token);
|
|
988
|
+
}
|
|
989
|
+
const snapshot = configService.getWorkspaceConfig().environments?.find((item) => item.id === environment.id) || environment;
|
|
990
|
+
printJsonOrText(options, snapshot, [
|
|
991
|
+
chalk.green(`✔ Native n8n MCP assist configured for environment: ${environment.name}`),
|
|
992
|
+
`Endpoint: ${snapshot.nativeMcp?.url || nativeMcp.url}`,
|
|
993
|
+
`Token : ${snapshot.nativeMcp?.tokenConfigured ? 'stored locally' : 'not configured'}`,
|
|
994
|
+
].join('\n'));
|
|
995
|
+
});
|
|
996
|
+
nativeMcpCmd
|
|
997
|
+
.command('disable')
|
|
998
|
+
.description('Disable native n8n MCP assist for a workspace environment and remove its stored token')
|
|
999
|
+
.argument('[name-or-id]', 'Environment name or ID; defaults to pinned environment or --env')
|
|
1000
|
+
.option('--json', 'Output environment as JSON')
|
|
1001
|
+
.action((nameOrId, options) => {
|
|
1002
|
+
const configService = new ConfigService();
|
|
1003
|
+
const selectedEnvironment = nameOrId || process.env.N8NAC_ENVIRONMENT?.trim() || undefined;
|
|
1004
|
+
const resolved = configService.resolveEnvironment(selectedEnvironment);
|
|
1005
|
+
configService.deleteNativeMcpToken(resolved.environmentId);
|
|
1006
|
+
const environment = configService.updateEnvironment(resolved.environmentId, {
|
|
1007
|
+
nativeMcp: {
|
|
1008
|
+
...resolved.environment.nativeMcp,
|
|
1009
|
+
enabled: false,
|
|
1010
|
+
},
|
|
1011
|
+
});
|
|
1012
|
+
const snapshot = configService.getWorkspaceConfig().environments?.find((item) => item.id === environment.id) || environment;
|
|
1013
|
+
printJsonOrText(options, snapshot, chalk.green(`✔ Native n8n MCP assist disabled for environment: ${environment.name}`));
|
|
1014
|
+
});
|
|
1015
|
+
nativeMcpCmd
|
|
1016
|
+
.command('status')
|
|
1017
|
+
.description('Show native n8n MCP assist configuration status without mutating n8n')
|
|
1018
|
+
.argument('[name-or-id]', 'Environment name or ID; defaults to pinned environment or --env')
|
|
1019
|
+
.option('--cwd <path>', 'Project directory used to resolve n8n-as-code context', process.env.N8N_AS_CODE_PROJECT_DIR)
|
|
1020
|
+
.option('--include-tools', 'Connect to the native n8n MCP server and include discovered tools')
|
|
1021
|
+
.option('--json', 'Output status as JSON')
|
|
1022
|
+
.action(async (nameOrId, options) => {
|
|
1023
|
+
await runMcpDiagnosticCommand('status', {
|
|
1024
|
+
cwd: options.cwd,
|
|
1025
|
+
json: options.json,
|
|
1026
|
+
includeTools: options.includeTools,
|
|
1027
|
+
environmentNameOrId: nameOrId,
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
nativeMcpCmd
|
|
1031
|
+
.command('tools')
|
|
1032
|
+
.description('List tools exposed by the configured native n8n MCP server')
|
|
1033
|
+
.argument('[name-or-id]', 'Environment name or ID; defaults to pinned environment or --env')
|
|
1034
|
+
.option('--cwd <path>', 'Project directory used to resolve n8n-as-code context', process.env.N8N_AS_CODE_PROJECT_DIR)
|
|
1035
|
+
.option('--json', 'Output status and tool list as JSON')
|
|
1036
|
+
.action(async (nameOrId, options) => {
|
|
1037
|
+
await runMcpDiagnosticCommand('tools', {
|
|
1038
|
+
cwd: options.cwd,
|
|
1039
|
+
json: options.json,
|
|
1040
|
+
environmentNameOrId: nameOrId,
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
nativeMcpCmd
|
|
1044
|
+
.command('doctor')
|
|
1045
|
+
.description('Check whether native n8n MCP assist is enabled, configured, reachable, and tool-discoverable')
|
|
1046
|
+
.argument('[name-or-id]', 'Environment name or ID; defaults to pinned environment or --env')
|
|
1047
|
+
.option('--cwd <path>', 'Project directory used to resolve n8n-as-code context', process.env.N8N_AS_CODE_PROJECT_DIR)
|
|
1048
|
+
.option('--json', 'Output status as JSON')
|
|
1049
|
+
.action(async (nameOrId, options) => {
|
|
1050
|
+
await runMcpDiagnosticCommand('doctor', {
|
|
1051
|
+
cwd: options.cwd,
|
|
1052
|
+
json: options.json,
|
|
1053
|
+
environmentNameOrId: nameOrId,
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1163
1056
|
program.command('mcp')
|
|
1164
1057
|
.description('Start the dedicated n8n-as-code MCP server')
|
|
1165
1058
|
.option('--cwd <path>', 'Project directory used to resolve n8nac-config.json and n8nac-custom-nodes.json', process.env.N8N_AS_CODE_PROJECT_DIR)
|