Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/cli/src/commands/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ import inquirer from 'inquirer';

export class SyncCommand extends BaseCommand {

/**
* Guards the level-0 validation notice: `push --verify` runs pushOne and
* verifyRemote on the same instance, and printing the same warning twice
* reads as two distinct problems (agents have tried to "fix" the second
* one by reconfiguring MCP mid-task). One notice per process is enough.
*/
private levelNoticeShown = false;

private printLevelZeroNoticeOnce(): void {
if (this.levelNoticeShown) return;
this.levelNoticeShown = true;
console.log(chalk.dim(` Validated against the bundled schema only (native MCP level 0 — discouraged, the instance may differ). Connect the instance MCP for instance-exact validation: n8nac native-mcp configure --level 1.`));
}

async pullOne(workflowId: string): Promise<void> {
const syncConfig = await this.getSyncConfig();
const syncManager = new SyncManager(this.client, syncConfig);
Expand Down Expand Up @@ -152,7 +166,7 @@ export class SyncCommand extends BaseCommand {
const finalWorkflowId = await syncManager.push(filename, { draft: options?.draft === true });
spinner.succeed(chalk.green(`✔ Pushed workflow ${filename}.`));
if (level === 0) {
console.log(chalk.dim(` Validated against the bundled schema only (native MCP level 0 — discouraged, the instance may differ). Connect the instance MCP for instance-exact validation: n8nac native-mcp configure --level 1.`));
this.printLevelZeroNoticeOnce();
}
this.reportPublishState(publishReport, finalWorkflowId);
return finalWorkflowId;
Expand Down Expand Up @@ -321,7 +335,7 @@ export class SyncCommand extends BaseCommand {
console.log(chalk.dim(' Fix the issues locally, then push again.'));
}
if (effectiveNativeMcpLevel(this.activeEnvironment?.nativeMcp, process.env.N8NAC_NATIVE_MCP_LEVEL) === 0) {
console.log(chalk.dim(' Validated against the bundled schema only (native MCP level 0 — discouraged, the instance may differ). Connect the instance MCP for instance-exact validation: n8nac native-mcp configure --level 1.'));
this.printLevelZeroNoticeOnce();
}

return result.valid;
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/core/services/n8n-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class N8nApiClient {

this.client = axios.create({
baseURL: host,
// Fail-closed default: no API call may hang indefinitely on a
// stalled network (setup, push, verify). Calls that need longer
// pass their own explicit timeout, which takes precedence.
timeout: 30_000,
headers: {
'X-N8N-API-KEY': this.apiKey,
'Content-Type': 'application/json',
Expand Down
36 changes: 34 additions & 2 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ environmentProgram.command('add')
.option('--folder-sync', 'Enable folder sync for this environment')
.option('--custom-nodes-path <path>', 'Custom nodes path for this environment')
.option('--description <text>', 'Environment description')
.option('--pin', 'Pin this environment as the workspace default in the same process (saves one cold start)')
.option('--json', 'Output environment as JSON')
.action(async (name, options) => {
await hydrateApiKeyFromStdin(options);
Expand Down Expand Up @@ -579,7 +580,8 @@ environmentProgram.command('add')
// so copying a per-environment credential into it lets one environment silently
// authenticate as another (and the last `env add --api-key` would repoint them all).
if (options.apiKey && urlOption) configService.saveWorkspaceEnvironmentApiKey(environment.id, options.apiKey);
printJsonOrText(options, environment, chalk.green(`✔ Workspace environment added: ${environment.name}`));
const added = options.pin ? configService.pinEnvironment(environment.id) : environment;
printJsonOrText(options, added, chalk.green(`✔ Workspace environment added: ${added.name}`));
});

environmentProgram.command('update')
Expand Down Expand Up @@ -856,13 +858,43 @@ hideCommand(program.command('setup'))
throw error;
}

// Bridge the facade/workspace gap: `setup` configures the runtime
// facade but creates no workspace environment, which strands fresh
// agents and users (setup exits 0, yet `env status` is empty). When
// nothing is configured, point at the single command that finishes
// the job instead of leaving a silent dead end.
let setupNextSteps: string[] = [];
try {
if (new ConfigService().listEnvironments().length === 0) {
// Managed local instances attach via --managed-instance and
// reject API keys — printing the base-url variant there sends
// agents into a guaranteed validation error.
const setupInstance = instance as { mode?: string; id?: string };
if (setupInstance?.mode === 'managed-local-docker' && setupInstance?.id) {
setupNextSteps = [
'No workspace environment configured yet — attach this managed instance:',
`n8nac env add Local --managed-instance ${setupInstance.id} --workflows-path workflows/local --pin`,
];
} else {
setupNextSteps = [
'No workspace environment configured yet — create one to sync workflows:',
'n8nac env add <name> --base-url <url> --workflows-path workflows/<name> --api-key-stdin --pin',
];
}
}
} catch {
// Never break setup output on environment inspection failure.
}
printJsonOrText(
options,
{ instance, modes: facade.listSetupModes() },
setupNextSteps.length > 0
? { instance, modes: facade.listSetupModes(), nextSteps: setupNextSteps }
: { instance, modes: facade.listSetupModes() },
[
chalk.green('✅ n8n facade setup mode saved.'),
`Mode: ${instance.mode}`,
instance.baseUrl ? `n8n host: ${instance.baseUrl}` : undefined,
setupNextSteps.length > 0 ? chalk.yellow(`\n${setupNextSteps.join('\n')}`) : undefined,
].filter(Boolean).join('\n'),
);
});
Expand Down
8 changes: 6 additions & 2 deletions packages/skills/src/agent-skills/n8n-architect/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Use `{{N8NAC_CMD}} env ...` for workspace environments, remote URLs, active envi
```

- Prefer `--api-key-stdin` for API keys.
- Prefer `env add --pin` to create and pin the default environment in one process instead of a separate `env use`.
- Do not pass secrets inline in shell arguments.
- `env auth set` binds the key to one environment, so several environments may share a base URL with one key each. Run it once per environment; `apiKeySource` in `env status --json` is `workspace-environment` when the environment uses its own key.
- Do not ask for host/API key when the user wants a managed local Docker instance.
Expand Down Expand Up @@ -255,7 +256,10 @@ Use these commands instead of guessing:
{{N8NAC_SKILLS_CMD}} examples download <id>
```

- Prefer `--compact` on `search`, `node-info`, and `node-schema`: same schemas, bounded output (required params + snippet + gating flags).
- Prefer one `batch --compact` over N separate lookups: one process parses the ontology once. Pass `--calls '<json>'`, `--calls-file <path>` (file avoids shell-quoting), or pipe JSON via stdin. `--compact` applies to `search`, `node-info`, `node-schema`; `examples-search` and `examples-info` always return full workflow data. Example: `batch --compact --calls '[{"cmd":"search","query":"gmail"},{"cmd":"node-info","name":"gmailTool"}]'`.
- Start with `examples search` when the user asks for a common automation pattern.
- Fetch community examples only when you do not know how to wire something, when the workflow is unusually complex, or when the user explicitly asks. Each download costs a full roundtrip: for routine tasks, local knowledge (`search`, `node-info`, `batch`) is faster and authoritative. Skip examples otherwise.
- Use examples to learn patterns, not as authority over current node schemas.
- If a command or flag is unfamiliar, run `{{N8NAC_CMD}} <subcommand> --help`; do not invent flags.

Expand Down Expand Up @@ -396,10 +400,10 @@ defineRouting() {

## Verify, Test, And Present

After pushing:
Prefer `push --verify`: it fetches the pushed workflow and validates it in the same process. A standalone `verify` right after `push --verify` re-checks the same state — skip it unless you pushed without `--verify`.

```bash
{{N8NAC_CMD}} verify <workflowId>
{{N8NAC_CMD}} push <path> --verify
{{N8NAC_CMD}} test-plan <workflowId> --json
```

Expand Down
Loading
Loading