@n8n-as-code/n8nac 2026.3.1 → 2026.4.0-next.11
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 -2
- package/index.ts +40 -50
- package/openclaw.plugin.json +3 -0
- package/package.json +2 -1
- package/skills/n8n-architect/SKILL.md +72 -0
- package/src/child-env.ts +34 -0
- package/src/cli.ts +10 -4
- package/src/tool.ts +9 -3
package/README.md
CHANGED
|
@@ -37,8 +37,10 @@ Once setup is done, just talk to OpenClaw:
|
|
|
37
37
|
|
|
38
38
|
> "What operations does the Google Sheets node support?"
|
|
39
39
|
|
|
40
|
-
The plugin
|
|
41
|
-
|
|
40
|
+
The plugin now keeps its default prompt hook lightweight. OpenClaw can activate
|
|
41
|
+
the bundled `n8n-architect` skill for explicit n8n workflow sessions, and that
|
|
42
|
+
skill then reads the generated workspace `AGENTS.md` for the full workflow
|
|
43
|
+
engineering guidance.
|
|
42
44
|
|
|
43
45
|
## CLI commands
|
|
44
46
|
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
4
4
|
import { registerN8nAcCli } from "./src/cli.js";
|
|
@@ -6,7 +6,7 @@ import { createN8nAcTool } from "./src/tool.js";
|
|
|
6
6
|
import { getWorkspaceDir, isWorkspaceInitialized } from "./src/workspace.js";
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
-
//
|
|
9
|
+
// Lightweight prompt context
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
const BOOTSTRAP_CONTEXT = `\
|
|
@@ -21,20 +21,6 @@ Once you have both, call the \`n8nac\` tool with \`action: "init_auth"\`, then
|
|
|
21
21
|
\`action: "init_project"\` to finish setup.
|
|
22
22
|
`;
|
|
23
23
|
|
|
24
|
-
const MISSING_AGENTS_CONTEXT = `\
|
|
25
|
-
## n8n-as-code — AI Context Missing
|
|
26
|
-
|
|
27
|
-
The workspace is initialized, but the generated AI context file (\`AGENTS.md\`) is missing or unreadable.
|
|
28
|
-
|
|
29
|
-
**Tell the user:**
|
|
30
|
-
> "Your n8n-as-code workspace is connected, but the AI context needs to be regenerated before I can safely guide workflow changes."
|
|
31
|
-
|
|
32
|
-
Ask the user to run \`npx --yes n8nac update-ai\` in the OpenClaw workspace, or rerun
|
|
33
|
-
\`openclaw n8nac:setup\` if they want the setup wizard to repair it.
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
let agentsContext: string | null = null;
|
|
37
|
-
|
|
38
24
|
function readConfig(workspaceDir: string): Record<string, string> {
|
|
39
25
|
try {
|
|
40
26
|
const raw = readFileSync(join(workspaceDir, "n8nac-config.json"), "utf-8");
|
|
@@ -56,26 +42,46 @@ function buildStatusHeader(workspaceDir: string): string {
|
|
|
56
42
|
`- Workspace directory: \`${workspaceDir}\``,
|
|
57
43
|
`- n8n host: \`${host}\``,
|
|
58
44
|
`- Active project: \`${project}\``,
|
|
59
|
-
"",
|
|
60
|
-
"Skip the 'Workspace Bootstrap' section below — setup is complete.",
|
|
61
|
-
"Proceed directly to the user's request using the `n8nac` tool.",
|
|
62
|
-
"",
|
|
63
|
-
"---",
|
|
64
|
-
"",
|
|
65
45
|
].join("\n");
|
|
66
46
|
}
|
|
67
47
|
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
if (!existsSync(p)) return null;
|
|
48
|
+
function hasAgentsContext(workspaceDir: string): boolean {
|
|
49
|
+
const agentsPath = join(workspaceDir, "AGENTS.md");
|
|
71
50
|
try {
|
|
72
|
-
|
|
73
|
-
return
|
|
51
|
+
accessSync(agentsPath, constants.R_OK);
|
|
52
|
+
return true;
|
|
74
53
|
} catch {
|
|
75
|
-
return
|
|
54
|
+
return false;
|
|
76
55
|
}
|
|
77
56
|
}
|
|
78
57
|
|
|
58
|
+
export function buildPromptContext(workspaceDir: string): string {
|
|
59
|
+
if (!isWorkspaceInitialized(workspaceDir)) {
|
|
60
|
+
return BOOTSTRAP_CONTEXT;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const agentsPath = join(workspaceDir, "AGENTS.md");
|
|
64
|
+
const guidanceLines = hasAgentsContext(workspaceDir)
|
|
65
|
+
? [
|
|
66
|
+
"",
|
|
67
|
+
"Detailed workflow-authoring guidance is intentionally scoped to the `n8n-architect` skill.",
|
|
68
|
+
"Only use that deeper n8n workflow context when the request is clearly about n8n workflow work.",
|
|
69
|
+
`When that happens, read \`${agentsPath}\` for workspace-specific instructions.`,
|
|
70
|
+
]
|
|
71
|
+
: [
|
|
72
|
+
"",
|
|
73
|
+
"Detailed workflow-authoring guidance is intentionally scoped to the `n8n-architect` skill, but the generated workspace AI context file (`AGENTS.md`) is missing.",
|
|
74
|
+
"If the user starts explicit n8n workflow work, regenerate `AGENTS.md` with `npx --yes n8nac update-ai` or rerun `openclaw n8nac:setup` first.",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
buildStatusHeader(workspaceDir),
|
|
79
|
+
...guidanceLines,
|
|
80
|
+
"",
|
|
81
|
+
"For unrelated requests, ignore this plugin context.",
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
// ---------------------------------------------------------------------------
|
|
80
86
|
// Plugin
|
|
81
87
|
// ---------------------------------------------------------------------------
|
|
@@ -94,19 +100,10 @@ const n8nAcPlugin = {
|
|
|
94
100
|
mkdirSync(workspaceDir, { recursive: true });
|
|
95
101
|
|
|
96
102
|
// -- Context injection ---------------------------------------------------
|
|
97
|
-
//
|
|
103
|
+
// Keep default context lightweight; full workflow-authoring guidance lives
|
|
104
|
+
// in the bundled `n8n-architect` skill and the workspace AGENTS.md file.
|
|
98
105
|
api.on("before_prompt_build", () => {
|
|
99
|
-
|
|
100
|
-
// Lazy-load: setup may have run after the gateway started, so the
|
|
101
|
-
// service start() missed it. Re-attempt on every prompt until loaded.
|
|
102
|
-
// The status header embeds host + project, so re-read on every call
|
|
103
|
-
// when not yet cached to pick up fresh config after setup.
|
|
104
|
-
if (agentsContext === null && initialized) {
|
|
105
|
-
agentsContext = loadAgentsContext(workspaceDir);
|
|
106
|
-
}
|
|
107
|
-
const context = agentsContext ?? (initialized ? MISSING_AGENTS_CONTEXT : BOOTSTRAP_CONTEXT);
|
|
108
|
-
if (!context) return;
|
|
109
|
-
return { prependContext: context };
|
|
106
|
+
return { prependContext: buildPromptContext(workspaceDir) };
|
|
110
107
|
});
|
|
111
108
|
|
|
112
109
|
// -- Agent tool ----------------------------------------------------------
|
|
@@ -119,17 +116,12 @@ const n8nAcPlugin = {
|
|
|
119
116
|
);
|
|
120
117
|
|
|
121
118
|
// -- Service -------------------------------------------------------------
|
|
122
|
-
// On gateway start: refresh the AGENTS.md cache so the agent always has
|
|
123
|
-
// up-to-date node knowledge.
|
|
124
119
|
api.registerService({
|
|
125
120
|
id: "n8nac-context",
|
|
126
121
|
start: async () => {
|
|
127
|
-
// Invalidate so next before_prompt_build re-reads from disk.
|
|
128
|
-
agentsContext = null;
|
|
129
122
|
if (isWorkspaceInitialized(workspaceDir)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
api.logger.info("[n8nac] Workspace ready — AI context loaded.");
|
|
123
|
+
if (hasAgentsContext(workspaceDir)) {
|
|
124
|
+
api.logger.info("[n8nac] Workspace ready — lightweight prompt context enabled; n8n skill available.");
|
|
133
125
|
} else {
|
|
134
126
|
api.logger.warn("[n8nac] Workspace ready, but AGENTS.md is missing or unreadable.");
|
|
135
127
|
}
|
|
@@ -137,9 +129,7 @@ const n8nAcPlugin = {
|
|
|
137
129
|
api.logger.info("[n8nac] Workspace not initialized. Run `openclaw n8nac:setup`.");
|
|
138
130
|
}
|
|
139
131
|
},
|
|
140
|
-
stop: async () => {
|
|
141
|
-
agentsContext = null;
|
|
142
|
-
},
|
|
132
|
+
stop: async () => {},
|
|
143
133
|
});
|
|
144
134
|
},
|
|
145
135
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
"id": "n8nac",
|
|
3
3
|
"name": "n8n-as-code",
|
|
4
4
|
"description": "Create and manage n8n workflows from OpenClaw. Guides the AI through n8nac initialization and provides workflow tools.",
|
|
5
|
+
"skills": [
|
|
6
|
+
"skills"
|
|
7
|
+
],
|
|
5
8
|
"configSchema": {
|
|
6
9
|
"type": "object",
|
|
7
10
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n-as-code/n8nac",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.4.0-next.11",
|
|
4
4
|
"description": "OpenClaw plugin for n8n-as-code — create and manage n8n workflows from OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"README.md",
|
|
39
39
|
"index.ts",
|
|
40
40
|
"openclaw.plugin.json",
|
|
41
|
+
"skills/",
|
|
41
42
|
"src/",
|
|
42
43
|
"tsconfig.json"
|
|
43
44
|
],
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: n8n-architect
|
|
3
|
+
description: Use when the user explicitly wants to create, edit, validate, sync, or troubleshoot n8n workflows, asks about n8n nodes or automation, or wants to use the n8nac tool.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# n8n Architect
|
|
7
|
+
|
|
8
|
+
Use this skill only for explicit n8n workflow work.
|
|
9
|
+
|
|
10
|
+
## First steps
|
|
11
|
+
|
|
12
|
+
1. Check whether `n8nac-config.json` exists in the workspace root.
|
|
13
|
+
2. If the workspace is initialized, read `AGENTS.md` from the workspace root before making workflow changes. It is the detailed, workspace-specific source of truth generated by `n8nac update-ai`.
|
|
14
|
+
3. If `AGENTS.md` is missing or unreadable, regenerate it with `npx --yes n8nac update-ai` or run the `openclaw n8nac:setup` command before attempting workflow authoring.
|
|
15
|
+
4. If the workspace is not initialized, ask the user for the n8n host URL and API key, then use the `n8nac` tool with `action: "init_auth"` and `action: "init_project"` to complete setup yourself.
|
|
16
|
+
|
|
17
|
+
## Using the n8nac tool
|
|
18
|
+
|
|
19
|
+
- Use the `n8nac` tool for setup checks, workflow list/pull/push/verify, validation, and `skills` lookups.
|
|
20
|
+
- Use `action: "skills"` whenever you need node search or schema details.
|
|
21
|
+
- Never guess node parameters. The schema lookup is the source of truth.
|
|
22
|
+
- Treat `AGENTS.md` as the authoritative workflow-engineering protocol once this skill is active.
|
|
23
|
+
|
|
24
|
+
## Reading workflow files efficiently
|
|
25
|
+
|
|
26
|
+
Every `.workflow.ts` file starts with a `<workflow-map>` block — a compact index generated automatically at each sync. Always read this block first before opening the rest of the file.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
// <workflow-map>
|
|
30
|
+
// Workflow : My Workflow
|
|
31
|
+
// Nodes : 12 | Connections: 14
|
|
32
|
+
//
|
|
33
|
+
// NODE INDEX
|
|
34
|
+
// ──────────────────────────────────────────────────────────────────
|
|
35
|
+
// Property name Node type (short) Flags
|
|
36
|
+
// ScheduleTrigger scheduleTrigger
|
|
37
|
+
// AgentGenerateApplication agent [AI] [creds]
|
|
38
|
+
// OpenaiChatModel lmChatOpenAi [creds] [ai_languageModel]
|
|
39
|
+
// Memory memoryBufferWindow [ai_memory]
|
|
40
|
+
// GithubCheckBranchRef httpRequest [onError→out(1)]
|
|
41
|
+
//
|
|
42
|
+
// ROUTING MAP
|
|
43
|
+
// ──────────────────────────────────────────────────────────────────
|
|
44
|
+
// ⚠️ Nodes flagged [ai_*] are NOT in the → routing — they connect via .uses()
|
|
45
|
+
// ScheduleTrigger
|
|
46
|
+
// → Configuration1
|
|
47
|
+
// → BuildProfileSources → LoopOverProfileSources
|
|
48
|
+
// .out(1) → JinaReadProfileSource → LoopOverProfileSources (↩ loop)
|
|
49
|
+
//
|
|
50
|
+
// AI CONNECTIONS
|
|
51
|
+
// AgentGenerateApplication.uses({ ai_languageModel: OpenaiChatModel, ai_memory: Memory })
|
|
52
|
+
// </workflow-map>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### How to navigate a workflow as an agent
|
|
56
|
+
|
|
57
|
+
1. Read `<workflow-map>` only — locate the property name you need.
|
|
58
|
+
2. Search for that property name in the file (for example `AgentGenerateApplication =`).
|
|
59
|
+
3. Read only that section — do not load the entire file into context.
|
|
60
|
+
|
|
61
|
+
This avoids loading 1500+ lines when you only need to patch 10.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### AI tool nodes
|
|
65
|
+
|
|
66
|
+
When an AI agent uses tool nodes:
|
|
67
|
+
|
|
68
|
+
- ✅ Search for the exact tool node first.
|
|
69
|
+
- ✅ Run `npx --yes n8nac skills node-info <nodeName>` before writing parameters.
|
|
70
|
+
- ✅ Connect tool nodes as arrays: `this.Agent.uses({ ai_tool: [this.Tool.output] })`.
|
|
71
|
+
- ❌ Do not assume tool parameter names or reuse stale node-specific guidance.
|
|
72
|
+
|
package/src/child-env.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a minimal environment for child processes.
|
|
3
|
+
* Only passes the vars needed for npx/node to operate, deliberately excluding
|
|
4
|
+
* any sensitive credentials that the parent (agent host) may hold in its env
|
|
5
|
+
* (e.g. LLM API keys), preventing accidental credential forwarding.
|
|
6
|
+
*/
|
|
7
|
+
export function getChildEnv(): NodeJS.ProcessEnv {
|
|
8
|
+
const env: NodeJS.ProcessEnv = {};
|
|
9
|
+
|
|
10
|
+
// Matches var names that look like credentials anywhere in the name — these are never forwarded
|
|
11
|
+
// even if they match another allowlist prefix (e.g. NODE_AUTH_TOKEN, npm_config_*:_authToken,
|
|
12
|
+
// npm_config_authority is intentionally over-blocked since we prefer false-positives to leaks).
|
|
13
|
+
const secretPattern = /(?:auth|token|password|secret|apikey|api_key|_key)/i;
|
|
14
|
+
|
|
15
|
+
for (const key of Object.keys(process.env)) {
|
|
16
|
+
const upperKey = key.toUpperCase();
|
|
17
|
+
if (
|
|
18
|
+
// Basic system vars needed by node/npx (case-insensitive, including Windows-specific ones)
|
|
19
|
+
/^(PATH|HOME|USERPROFILE|HOMEDRIVE|HOMEPATH|TMPDIR|TMP|TEMP|LANG|LC_ALL|SHELL|TERM|TERM_PROGRAM|NODE_PATH|NODE_OPTIONS|SYSTEMROOT|COMSPEC|PATHEXT)$/.test(
|
|
20
|
+
upperKey,
|
|
21
|
+
) ||
|
|
22
|
+
// npm execution/config vars required by npx — but NOT auth/token vars
|
|
23
|
+
// (e.g. excludes npm_config_//registry.npmjs.org/:_authToken)
|
|
24
|
+
(key.startsWith("npm_") && !secretPattern.test(key)) ||
|
|
25
|
+
// Specific safe NODE_* vars (deliberately NOT a prefix match to exclude NODE_AUTH_TOKEN)
|
|
26
|
+
/^NODE_(ENV|NO_WARNINGS|ICU_DATA)$/.test(upperKey) ||
|
|
27
|
+
// n8n-as-code specific vars
|
|
28
|
+
key.startsWith("N8N_AS_CODE_")
|
|
29
|
+
) {
|
|
30
|
+
env[key] = process.env[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return env;
|
|
34
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import type { ChildProcess, ChildProcessWithoutNullStreams } from "node:child_process";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
6
|
+
import { getChildEnv } from "./child-env.js";
|
|
6
7
|
import { isWorkspaceInitialized } from "./workspace.js";
|
|
7
8
|
|
|
8
9
|
type CliProgram = Parameters<Parameters<OpenClawPluginApi["registerCli"]>[0]>[0]["program"];
|
|
@@ -24,14 +25,14 @@ function runN8nac(
|
|
|
24
25
|
opts: {
|
|
25
26
|
cwd: string;
|
|
26
27
|
timeout: number;
|
|
27
|
-
|
|
28
|
+
stdinInput?: string;
|
|
28
29
|
stdio?: "pipe" | "inherit";
|
|
29
30
|
},
|
|
30
31
|
): Promise<RunResult> {
|
|
31
32
|
return new Promise((resolve) => {
|
|
32
33
|
const baseOptions = {
|
|
33
34
|
cwd: opts.cwd,
|
|
34
|
-
env:
|
|
35
|
+
env: getChildEnv(),
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const child: ChildProcess | ChildProcessWithoutNullStreams =
|
|
@@ -65,6 +66,11 @@ function runN8nac(
|
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
if (opts.stdinInput !== undefined && "stdin" in child && child.stdin) {
|
|
70
|
+
child.stdin.write(`${opts.stdinInput}\n`);
|
|
71
|
+
child.stdin.end();
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
const timer = setTimeout(() => {
|
|
69
75
|
timedOut = true;
|
|
70
76
|
child.kill("SIGTERM");
|
|
@@ -154,10 +160,10 @@ export function registerN8nAcCli({ program, workspaceDir }: CliOpts): void {
|
|
|
154
160
|
const authSpinner = p.spinner();
|
|
155
161
|
authSpinner.start("Saving credentials…");
|
|
156
162
|
|
|
157
|
-
const authResult = await runN8nac(["init-auth", "--host", host], {
|
|
163
|
+
const authResult = await runN8nac(["init-auth", "--host", host, "--api-key-stdin"], {
|
|
158
164
|
cwd: workspaceDir,
|
|
159
165
|
timeout: 60_000,
|
|
160
|
-
|
|
166
|
+
stdinInput: apiKey,
|
|
161
167
|
});
|
|
162
168
|
|
|
163
169
|
if (authResult.exitCode !== 0) {
|
package/src/tool.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { getChildEnv } from "./child-env.js";
|
|
3
4
|
import { isWorkspaceInitialized } from "./workspace.js";
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -100,13 +101,13 @@ type RunResult = {
|
|
|
100
101
|
function runNpx(
|
|
101
102
|
args: string[],
|
|
102
103
|
cwd: string,
|
|
103
|
-
|
|
104
|
+
stdinInput?: string,
|
|
104
105
|
): Promise<RunResult> {
|
|
105
106
|
return new Promise((resolve) => {
|
|
106
107
|
const child = spawn("npx", ["--yes", "n8nac", ...args], {
|
|
107
108
|
cwd,
|
|
108
|
-
env: { ...process.env, ...env },
|
|
109
109
|
stdio: "pipe",
|
|
110
|
+
env: getChildEnv(),
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
let stdout = "";
|
|
@@ -131,6 +132,11 @@ function runNpx(
|
|
|
131
132
|
stderr += chunk.toString();
|
|
132
133
|
});
|
|
133
134
|
|
|
135
|
+
if (stdinInput !== undefined) {
|
|
136
|
+
child.stdin.write(`${stdinInput}\n`);
|
|
137
|
+
child.stdin.end();
|
|
138
|
+
}
|
|
139
|
+
|
|
134
140
|
const timer = setTimeout(() => {
|
|
135
141
|
timedOut = true;
|
|
136
142
|
child.kill("SIGTERM");
|
|
@@ -250,7 +256,7 @@ export function createN8nAcTool(opts: { workspaceDir: string }) {
|
|
|
250
256
|
if (!host || !key) {
|
|
251
257
|
return ok({ error: "n8nHost and n8nApiKey are required for init_auth" });
|
|
252
258
|
}
|
|
253
|
-
const r = await runNpx(["init-auth", "--host", host], workspaceDir,
|
|
259
|
+
const r = await runNpx(["init-auth", "--host", host, "--api-key-stdin"], workspaceDir, key);
|
|
254
260
|
if (r.exitCode !== 0) {
|
|
255
261
|
return ok({ error: r.stderr || r.stdout, exitCode: r.exitCode });
|
|
256
262
|
}
|