Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
25 changes: 23 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,32 @@ 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) {
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',
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
];
}
} 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
47 changes: 45 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,6 +256,8 @@ 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>'` or `--calls-file <path>` (file avoids shell-quoting). Supported: `search`, `node-info`, `node-schema`, `examples-search`, `examples-info`. Example: `batch --compact --calls '[{"cmd":"search","query":"gmail"},{"cmd":"node-info","name":"gmailTool"}]'`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
- Start with `examples search` when the user asks for a common automation pattern.
- 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 @@ -373,6 +376,46 @@ defineRouting() {

- Use `.uses()` for language models, memory, tools, parsers, embeddings, vector stores, retrievers, and other AI sub-nodes.
- Never connect AI sub-nodes with `.out().to()`.
- Start agentic workflows from this server-validated skeleton (adapt names/params, keep the shapes):

```typescript
@node({ name: 'Daily Schedule', type: 'n8n-nodes-base.scheduleTrigger', version: 1.3 })
DailySchedule = { rule: { interval: [{ field: 'days', triggerAtHour: 7 }] } };

@node({ name: 'Orchestrator', type: '@n8n/n8n-nodes-langchain.agent', version: 3.1 })
Orchestrator = {
text: '=Summarize: {{ $json }}',
promptType: 'define', // required whenever `text` is set
options: { systemMessage: 'You are a briefing assistant.' },
};

@node({ name: 'Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', version: 1.3 })
ChatModel = { model: { __rl: true, mode: 'list', value: 'gpt-4.1-mini' }, options: {} };

@node({ name: 'Window Memory', type: '@n8n/n8n-nodes-langchain.memoryBufferWindow', version: 1.4 })
WindowMemory = { sessionIdType: 'fromInput', contextWindowLength: 5 }; // no sessionKey unless sessionIdType is 'customKey'

@node({ name: 'Gmail Inbox', type: 'n8n-nodes-base.gmailTool', version: 2.2 })
GmailInbox = {
resource: 'message', operation: 'getAll', limit: 20,
descriptionType: 'manual', // required on tools, with a toolDescription
toolDescription: 'Read recent Gmail messages.',
};

@node({ name: 'Briefing Dashboard', type: 'n8n-nodes-base.html', version: 1.2 })
BriefingDashboard = { operation: 'generateHtmlTemplate', html: '<h1>Briefing</h1><pre>{{ $json }}</pre>' };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

@links()
defineRouting() {
this.DailySchedule.out(0).to(this.Orchestrator.in(0));
this.Orchestrator.out(0).to(this.BriefingDashboard.in(0));
this.Orchestrator.uses({
ai_languageModel: this.ChatModel.output,
ai_memory: this.WindowMemory.output,
ai_tool: [this.GmailInbox.output],
});
}
```
- `ai_tool` and `ai_document` must be arrays; every entry lands on input index 0.
- Most other AI connection types are single refs, or an array when the node exposes several inputs of the same type — position = input index.
- `needsFallback: true` (Agent, Basic LLM Chain) needs a second model on input 1: `ai_languageModel: [this.Model.output, this.FallbackModel.output]`. Same for the Model Selector node.
Expand All @@ -396,10 +439,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