@ahmedrowaihi/8n 0.5.17 → 0.5.19
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/dist/index.mjs +129 -113
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import path, { basename, delimiter, dirname, extname, join, normalize, relative,
|
|
|
5
5
|
import fs, { appendFileSync, cpSync, existsSync, mkdirSync, promises, readFileSync, readdirSync, realpathSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
6
6
|
import process$1, { cwd } from "node:process";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import {
|
|
8
|
+
import { readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { parseTarGzip } from "nanotar";
|
|
11
11
|
import { PassThrough } from "node:stream";
|
|
@@ -16,7 +16,7 @@ import v8 from "node:v8";
|
|
|
16
16
|
import { format, inspect } from "node:util";
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { createInterface } from "node:readline/promises";
|
|
19
|
-
import { McpServer
|
|
19
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
20
20
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
21
|
|
|
22
22
|
//#region \0rolldown/runtime.js
|
|
@@ -11600,7 +11600,7 @@ async function checkSelfUpdate() {
|
|
|
11600
11600
|
});
|
|
11601
11601
|
if (!res.ok) return;
|
|
11602
11602
|
const { version: latest } = await res.json();
|
|
11603
|
-
const current = "0.5.
|
|
11603
|
+
const current = "0.5.19";
|
|
11604
11604
|
if (latest !== current) console.log(import_picocolors.default.yellow("⚠") + import_picocolors.default.dim(` new CLI version available: `) + import_picocolors.default.cyan(latest) + import_picocolors.default.dim(` (current: ${current}) — run `) + import_picocolors.default.cyan("npm i -g @ahmedrowaihi/8n") + import_picocolors.default.dim(" to update"));
|
|
11605
11605
|
} catch {}
|
|
11606
11606
|
}
|
|
@@ -11973,133 +11973,149 @@ async function prune() {
|
|
|
11973
11973
|
|
|
11974
11974
|
//#endregion
|
|
11975
11975
|
//#region src/commands/mcp.ts
|
|
11976
|
-
async function
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
} catch {
|
|
11983
|
-
return;
|
|
11984
|
-
}
|
|
11985
|
-
for (const e of entries) {
|
|
11986
|
-
const full = join(d, e.name);
|
|
11987
|
-
if (e.isDirectory()) await walk(full);
|
|
11988
|
-
else if (e.name.endsWith(".mdx") || e.name.endsWith(".md")) files.push(full);
|
|
11989
|
-
}
|
|
11976
|
+
async function listMdFiles(dir) {
|
|
11977
|
+
let entries;
|
|
11978
|
+
try {
|
|
11979
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
11980
|
+
} catch {
|
|
11981
|
+
return [];
|
|
11990
11982
|
}
|
|
11991
|
-
|
|
11992
|
-
return files;
|
|
11993
|
-
}
|
|
11994
|
-
function extractTitle(raw, fallback) {
|
|
11995
|
-
const m = raw.match(/^---[\s\S]*?\ntitle:\s*(.+)/m);
|
|
11996
|
-
return m ? m[1].trim().replace(/^["']|["']$/g, "") : fallback;
|
|
11997
|
-
}
|
|
11998
|
-
function safeAbsPath(base, rel) {
|
|
11999
|
-
const abs = join(base, rel);
|
|
12000
|
-
return abs.startsWith(base) ? abs : null;
|
|
11983
|
+
return entries.filter((e) => !e.isDirectory() && (e.name.endsWith(".mdx") || e.name.endsWith(".md"))).map((e) => e.name);
|
|
12001
11984
|
}
|
|
12002
11985
|
async function mcp() {
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
contentDir = (await resolveProject()).contentDir;
|
|
12006
|
-
} catch {}
|
|
11986
|
+
const starterDir = findAnyCachedStarterDir();
|
|
11987
|
+
const mcpDir = starterDir ? join(starterDir, "content", "mcp", "en") : null;
|
|
12007
11988
|
const server = new McpServer({
|
|
12008
11989
|
name: "8n",
|
|
12009
|
-
version: "0.5.
|
|
11990
|
+
version: "0.5.19"
|
|
12010
11991
|
});
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
11992
|
+
server.registerTool("read_me", {
|
|
11993
|
+
description: "Returns how to use the 8n MCP tools. Call this BEFORE documenting anything with 8n.",
|
|
11994
|
+
inputSchema: {}
|
|
11995
|
+
}, async () => ({ content: [{
|
|
11996
|
+
type: "text",
|
|
11997
|
+
text: `# 8n MCP
|
|
11998
|
+
|
|
11999
|
+
Use these tools to learn how to write documentation for an 8n project before creating any content.
|
|
12000
|
+
|
|
12001
|
+
## Tools
|
|
12002
|
+
|
|
12003
|
+
### list_components
|
|
12004
|
+
Lists all available reference topics (config, structure, frontmatter, components).
|
|
12005
|
+
Call this first to discover what topics are available.
|
|
12006
|
+
|
|
12007
|
+
### get_component
|
|
12008
|
+
Reads a specific topic by name (without extension).
|
|
12009
|
+
Example: get_component({ name: "components" }) returns all available MDX components with usage examples.
|
|
12010
|
+
|
|
12011
|
+
## Workflow
|
|
12012
|
+
1. Call list_components to see available topics
|
|
12013
|
+
2. Call get_component for the topics relevant to your task
|
|
12014
|
+
3. Use that knowledge to write correct MDX content for the user's project
|
|
12015
|
+
`
|
|
12016
|
+
}] }));
|
|
12017
|
+
server.registerTool("list_components", {
|
|
12018
|
+
description: "Lists all available 8n reference topics (config, structure, frontmatter, MDX components).",
|
|
12019
|
+
inputSchema: {}
|
|
12029
12020
|
}, async () => {
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
text: existsSync(configPath) ? await readFile(configPath, "utf-8") : "No 8n.config.ts found in current directory.",
|
|
12034
|
-
mimeType: "text/plain"
|
|
12021
|
+
if (!mcpDir || !existsSync(mcpDir)) return { content: [{
|
|
12022
|
+
type: "text",
|
|
12023
|
+
text: "8n starter not cached. Run `8n dev` first."
|
|
12035
12024
|
}] };
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
return { resources: await Promise.all(pageFiles.map(async (f) => {
|
|
12040
|
-
const rel = relative(contentDir, f);
|
|
12041
|
-
const raw = await readFile(f, "utf-8");
|
|
12042
|
-
return {
|
|
12043
|
-
uri: `8n://pages/${rel}`,
|
|
12044
|
-
name: extractTitle(raw, rel),
|
|
12045
|
-
mimeType: "text/markdown"
|
|
12046
|
-
};
|
|
12047
|
-
})) };
|
|
12048
|
-
} });
|
|
12049
|
-
server.registerResource("pages", pagesTemplate, {
|
|
12050
|
-
title: "Project pages",
|
|
12051
|
-
mimeType: "text/markdown"
|
|
12052
|
-
}, async (uri, { path }) => {
|
|
12053
|
-
if (!path) return { contents: [{
|
|
12054
|
-
uri: uri.href,
|
|
12055
|
-
text: "Missing path.",
|
|
12056
|
-
mimeType: "text/plain"
|
|
12025
|
+
return { content: [{
|
|
12026
|
+
type: "text",
|
|
12027
|
+
text: (await listMdFiles(mcpDir)).map((f) => basename(f, extname(f))).join("\n")
|
|
12057
12028
|
}] };
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12029
|
+
});
|
|
12030
|
+
server.registerTool("get_component", {
|
|
12031
|
+
description: "Reads a specific 8n reference topic by name. Use list_components first to discover available names.",
|
|
12032
|
+
inputSchema: { name: z.string().describe("Topic name without extension, e.g. components, config, structure, frontmatter") }
|
|
12033
|
+
}, async ({ name }) => {
|
|
12034
|
+
if (!mcpDir || !existsSync(mcpDir)) return { content: [{
|
|
12035
|
+
type: "text",
|
|
12036
|
+
text: "8n starter not cached. Run `8n dev` first."
|
|
12064
12037
|
}] };
|
|
12065
|
-
|
|
12066
|
-
const
|
|
12067
|
-
return {
|
|
12068
|
-
|
|
12069
|
-
text,
|
|
12070
|
-
mimeType: "text/markdown"
|
|
12071
|
-
}] };
|
|
12072
|
-
} catch {
|
|
12073
|
-
return { contents: [{
|
|
12074
|
-
uri: uri.href,
|
|
12075
|
-
text: `File not found: ${rel}`,
|
|
12076
|
-
mimeType: "text/plain"
|
|
12038
|
+
for (const ext of [".mdx", ".md"]) {
|
|
12039
|
+
const file = join(mcpDir, `${name}${ext}`);
|
|
12040
|
+
if (existsSync(file)) return { content: [{
|
|
12041
|
+
type: "text",
|
|
12042
|
+
text: await readFile(file, "utf-8")
|
|
12077
12043
|
}] };
|
|
12078
12044
|
}
|
|
12079
|
-
|
|
12080
|
-
server.tool("create_page", "Create a new MDX page in the content directory", {
|
|
12081
|
-
path: z.string().describe("Path relative to content dir, e.g. docs/en/my-page.mdx"),
|
|
12082
|
-
title: z.string().describe("Page title (used in frontmatter and sidebar)"),
|
|
12083
|
-
description: z.string().optional().describe("Short description shown below the heading"),
|
|
12084
|
-
body: z.string().optional().describe("MDX body content (everything after the frontmatter)")
|
|
12085
|
-
}, async ({ path: p, title, description, body }) => {
|
|
12086
|
-
const abs = safeAbsPath(contentDir, p);
|
|
12087
|
-
if (!abs) return { content: [{
|
|
12045
|
+
return { content: [{
|
|
12088
12046
|
type: "text",
|
|
12089
|
-
text:
|
|
12047
|
+
text: `Unknown topic: ${name}. Call list_components to see available topics.`
|
|
12090
12048
|
}] };
|
|
12091
|
-
|
|
12049
|
+
});
|
|
12050
|
+
server.registerTool("analyze", {
|
|
12051
|
+
description: "Runs static analysis on the user's content directory and reports issues that may break the documentation site.",
|
|
12052
|
+
inputSchema: {}
|
|
12053
|
+
}, async () => {
|
|
12054
|
+
let contentDir = join(process.cwd(), "content");
|
|
12055
|
+
let locales = ["en"];
|
|
12056
|
+
try {
|
|
12057
|
+
const project = await resolveProject();
|
|
12058
|
+
contentDir = project.contentDir;
|
|
12059
|
+
locales = project.config.locales ?? ["en"];
|
|
12060
|
+
} catch {}
|
|
12061
|
+
const issues = [];
|
|
12062
|
+
async function walkAll(dir) {
|
|
12063
|
+
const results = [];
|
|
12064
|
+
let entries;
|
|
12065
|
+
try {
|
|
12066
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
12067
|
+
} catch {
|
|
12068
|
+
return results;
|
|
12069
|
+
}
|
|
12070
|
+
for (const e of entries) {
|
|
12071
|
+
const full = join(dir, e.name);
|
|
12072
|
+
if (e.isDirectory()) results.push(...await walkAll(full));
|
|
12073
|
+
else results.push(full);
|
|
12074
|
+
}
|
|
12075
|
+
return results;
|
|
12076
|
+
}
|
|
12077
|
+
const allFiles = await walkAll(contentDir);
|
|
12078
|
+
const mdxFiles = allFiles.filter((f) => f.endsWith(".mdx") || f.endsWith(".md"));
|
|
12079
|
+
const metaFiles = allFiles.filter((f) => f.endsWith("meta.json"));
|
|
12080
|
+
for (const file of mdxFiles) {
|
|
12081
|
+
const raw = await readFile(file, "utf-8").catch(() => "");
|
|
12082
|
+
const rel = relative(contentDir, file);
|
|
12083
|
+
if (!raw.match(/^---[\s\S]*?\ntitle:/m)) issues.push(`Missing title in frontmatter: ${rel}`);
|
|
12084
|
+
if (rel.startsWith("changelog/")) {
|
|
12085
|
+
if (!raw.match(/\nversion:/)) issues.push(`Changelog page missing version: ${rel}`);
|
|
12086
|
+
if (!raw.match(/\ndate:/)) issues.push(`Changelog page missing date: ${rel}`);
|
|
12087
|
+
}
|
|
12088
|
+
const bodyMatch = raw.match(/^---[\s\S]*?---\s*([\s\S]*)$/m);
|
|
12089
|
+
if (bodyMatch && bodyMatch[1].trim() === "") issues.push(`Empty page body: ${rel}`);
|
|
12090
|
+
}
|
|
12091
|
+
for (const file of metaFiles) {
|
|
12092
|
+
const rel = relative(contentDir, file);
|
|
12093
|
+
const raw = await readFile(file, "utf-8").catch(() => "");
|
|
12094
|
+
let meta;
|
|
12095
|
+
try {
|
|
12096
|
+
meta = JSON.parse(raw);
|
|
12097
|
+
} catch {
|
|
12098
|
+
issues.push(`Malformed JSON: ${rel}`);
|
|
12099
|
+
continue;
|
|
12100
|
+
}
|
|
12101
|
+
if (meta.pages) {
|
|
12102
|
+
const dir = join(file, "..");
|
|
12103
|
+
for (const page of meta.pages) if (![".mdx", ".md"].some((ext) => existsSync(join(dir, `${page}${ext}`)))) issues.push(`meta.json references missing page "${page}": ${rel}`);
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
const defaultLocale = locales[0];
|
|
12107
|
+
const otherLocales = locales.slice(1);
|
|
12108
|
+
for (const locale of otherLocales) {
|
|
12109
|
+
const defaultFiles = mdxFiles.filter((f) => f.includes(`/${defaultLocale}/`)).map((f) => relative(contentDir, f).replace(`${defaultLocale}/`, `${locale}/`));
|
|
12110
|
+
for (const expected of defaultFiles) if (!existsSync(join(contentDir, expected))) issues.push(`Missing ${locale} translation: ${expected}`);
|
|
12111
|
+
}
|
|
12112
|
+
if (issues.length === 0) return { content: [{
|
|
12092
12113
|
type: "text",
|
|
12093
|
-
text:
|
|
12114
|
+
text: "✓ No issues found."
|
|
12094
12115
|
}] };
|
|
12095
|
-
const fm = ["---", `title: ${title}`];
|
|
12096
|
-
if (description) fm.push(`description: ${description}`);
|
|
12097
|
-
fm.push("---", "", body ?? "");
|
|
12098
|
-
await mkdir(dirname(abs), { recursive: true });
|
|
12099
|
-
await writeFile(abs, fm.join("\n"));
|
|
12100
12116
|
return { content: [{
|
|
12101
12117
|
type: "text",
|
|
12102
|
-
text: `
|
|
12118
|
+
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}:\n\n${issues.map((i) => `- ${i}`).join("\n")}`
|
|
12103
12119
|
}] };
|
|
12104
12120
|
});
|
|
12105
12121
|
const transport = new StdioServerTransport();
|
|
@@ -12108,7 +12124,7 @@ async function mcp() {
|
|
|
12108
12124
|
|
|
12109
12125
|
//#endregion
|
|
12110
12126
|
//#region src/index.ts
|
|
12111
|
-
const program = new Command().name("8n").description("Run your 8n docs site").version("0.5.
|
|
12127
|
+
const program = new Command().name("8n").description("Run your 8n docs site").version("0.5.19").addOption(new Option("--debug").hideHelp()).hook("preAction", (cmd) => {
|
|
12112
12128
|
if (cmd.opts().debug) process.env.DEBUG_8N = "1";
|
|
12113
12129
|
});
|
|
12114
12130
|
program.command("init").description("Scaffold a new docs project in the current directory").action(init);
|