@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 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 injects the full n8n-architect instructions into every conversation
41
- so the AI knows the exact `n8nac` workflow (init-check pull edit push → verify).
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
- // AGENTS.md context injection — populated once on service start
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 loadAgentsContext(workspaceDir: string): string | null {
69
- const p = join(workspaceDir, "AGENTS.md");
70
- if (!existsSync(p)) return null;
48
+ function hasAgentsContext(workspaceDir: string): boolean {
49
+ const agentsPath = join(workspaceDir, "AGENTS.md");
71
50
  try {
72
- const raw = readFileSync(p, "utf-8");
73
- return buildStatusHeader(workspaceDir) + raw;
51
+ accessSync(agentsPath, constants.R_OK);
52
+ return true;
74
53
  } catch {
75
- return null;
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
- // Prepend n8n-architect instructions to every prompt build.
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
- const initialized = isWorkspaceInitialized(workspaceDir);
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
- agentsContext = loadAgentsContext(workspaceDir);
131
- if (agentsContext) {
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
  };
@@ -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.1",
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
+
@@ -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
- env?: NodeJS.ProcessEnv;
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: { ...process.env, ...opts.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
- env: { N8N_API_KEY: apiKey },
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
- env?: NodeJS.ProcessEnv,
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, { N8N_API_KEY: key });
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
  }