@gangtiser/md2wechat 0.1.0
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/LICENSE +5 -0
- package/README.md +105 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +251 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/config/config.d.ts +9 -0
- package/dist/src/config/config.js +11 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/core/article.d.ts +20 -0
- package/dist/src/core/article.js +81 -0
- package/dist/src/core/article.js.map +1 -0
- package/dist/src/core/envelope.d.ts +14 -0
- package/dist/src/core/envelope.js +34 -0
- package/dist/src/core/envelope.js.map +1 -0
- package/dist/src/core/errors.d.ts +7 -0
- package/dist/src/core/errors.js +22 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/html.d.ts +2 -0
- package/dist/src/core/html.js +18 -0
- package/dist/src/core/html.js.map +1 -0
- package/dist/src/core/io.d.ts +11 -0
- package/dist/src/core/io.js +28 -0
- package/dist/src/core/io.js.map +1 -0
- package/dist/src/images/generate.d.ts +17 -0
- package/dist/src/images/generate.js +28 -0
- package/dist/src/images/generate.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inspect/inspect.d.ts +18 -0
- package/dist/src/inspect/inspect.js +31 -0
- package/dist/src/inspect/inspect.js.map +1 -0
- package/dist/src/mcp/server.d.ts +1 -0
- package/dist/src/mcp/server.js +25 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +19 -0
- package/dist/src/mcp/tools.js +190 -0
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/openai/client.d.ts +34 -0
- package/dist/src/openai/client.js +101 -0
- package/dist/src/openai/client.js.map +1 -0
- package/dist/src/preview/preview.d.ts +2 -0
- package/dist/src/preview/preview.js +21 -0
- package/dist/src/preview/preview.js.map +1 -0
- package/dist/src/renderer/render.d.ts +19 -0
- package/dist/src/renderer/render.js +36 -0
- package/dist/src/renderer/render.js.map +1 -0
- package/dist/src/themes/builtin.d.ts +2 -0
- package/dist/src/themes/builtin.js +21 -0
- package/dist/src/themes/builtin.js.map +1 -0
- package/dist/src/themes/registry.d.ts +18 -0
- package/dist/src/themes/registry.js +130 -0
- package/dist/src/themes/registry.js.map +1 -0
- package/package.json +55 -0
- package/skills/md2wechat/SKILL.md +52 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AppError } from "./errors.js";
|
|
4
|
+
export async function loadInput(options) {
|
|
5
|
+
if (options.content && options.content.trim() !== "") {
|
|
6
|
+
return { content: options.content, source: "content" };
|
|
7
|
+
}
|
|
8
|
+
if (options.file) {
|
|
9
|
+
const absolute = path.resolve(options.file);
|
|
10
|
+
return {
|
|
11
|
+
content: await readFile(absolute, "utf8"),
|
|
12
|
+
baseDir: path.dirname(absolute),
|
|
13
|
+
source: "file"
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (options.contentUrl) {
|
|
17
|
+
const response = await fetch(options.contentUrl, { signal: AbortSignal.timeout(30_000) });
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new AppError("INPUT_URL_FAILED", `failed to fetch input URL: HTTP ${response.status}`, true);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
content: await response.text(),
|
|
23
|
+
source: "url"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
throw new AppError("INPUT_MISSING", "missing input content, file, or content URL");
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../../../src/core/io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAcvC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAyB;IACvD,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACzD,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YACzC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC/B,MAAM,EAAE,MAAM;SACf,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,mCAAmC,QAAQ,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;QACrG,CAAC;QACD,OAAO;YACL,OAAO,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE;YAC9B,MAAM,EAAE,KAAK;SACd,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,QAAQ,CAAC,eAAe,EAAE,6CAA6C,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OpenAIClient } from "../openai/client.js";
|
|
2
|
+
export interface GenerateImageInput {
|
|
3
|
+
prompt: string;
|
|
4
|
+
articleContext?: string;
|
|
5
|
+
output: string;
|
|
6
|
+
size?: string;
|
|
7
|
+
quality?: string;
|
|
8
|
+
client: OpenAIClient;
|
|
9
|
+
}
|
|
10
|
+
export interface GeneratedImage {
|
|
11
|
+
outputPath: string;
|
|
12
|
+
model: string;
|
|
13
|
+
size?: string;
|
|
14
|
+
quality?: string;
|
|
15
|
+
revisedPrompt?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function generateImage(input: GenerateImageInput): Promise<GeneratedImage>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function generateImage(input) {
|
|
4
|
+
const model = input.client.imageModel;
|
|
5
|
+
const image = await input.client.imagesGenerate({
|
|
6
|
+
model,
|
|
7
|
+
prompt: finalPrompt(input.prompt, input.articleContext),
|
|
8
|
+
size: input.size,
|
|
9
|
+
quality: input.quality
|
|
10
|
+
});
|
|
11
|
+
const outputPath = path.resolve(input.output);
|
|
12
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
13
|
+
await writeFile(outputPath, Buffer.from(image.b64Json, "base64"));
|
|
14
|
+
return {
|
|
15
|
+
outputPath,
|
|
16
|
+
model: image.model,
|
|
17
|
+
size: input.size,
|
|
18
|
+
quality: input.quality,
|
|
19
|
+
revisedPrompt: image.revisedPrompt
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function finalPrompt(prompt, articleContext) {
|
|
23
|
+
if (!articleContext?.trim()) {
|
|
24
|
+
return prompt;
|
|
25
|
+
}
|
|
26
|
+
return `${prompt}\n\nArticle context:\n${articleContext}`;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/images/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAoB7B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAyB;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;IACtC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC;QAC9C,KAAK;QACL,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC;QACvD,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAE9C,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElE,OAAO;QACL,UAAU;QACV,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,aAAa,EAAE,KAAK,CAAC,aAAa;KACnC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,MAAc,EAAE,cAAkC;IACrE,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,MAAM,yBAAyB,cAAc,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ArticleMetadata, type ImageReference } from "../core/article.js";
|
|
2
|
+
export type InspectCheckStatus = "pass" | "warn";
|
|
3
|
+
export interface InspectCheck {
|
|
4
|
+
code: string;
|
|
5
|
+
status: InspectCheckStatus;
|
|
6
|
+
message: string;
|
|
7
|
+
count?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface InspectResult {
|
|
10
|
+
metadata: ArticleMetadata;
|
|
11
|
+
images: ImageReference[];
|
|
12
|
+
checks: InspectCheck[];
|
|
13
|
+
baseDir?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface InspectOptions {
|
|
16
|
+
baseDir?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function inspectArticle(markdown: string, options?: InspectOptions): InspectResult;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseArticle } from "../core/article.js";
|
|
2
|
+
export function inspectArticle(markdown, options = {}) {
|
|
3
|
+
const article = parseArticle(markdown);
|
|
4
|
+
const checks = [
|
|
5
|
+
article.metadata.title
|
|
6
|
+
? { code: "TITLE_PRESENT", status: "pass", message: "title resolved" }
|
|
7
|
+
: { code: "TITLE_PRESENT", status: "warn", message: "title is missing" },
|
|
8
|
+
{
|
|
9
|
+
code: "IMAGE_REFERENCES",
|
|
10
|
+
status: "pass",
|
|
11
|
+
message: `${article.images.length} image references found`,
|
|
12
|
+
count: article.images.length
|
|
13
|
+
}
|
|
14
|
+
];
|
|
15
|
+
for (const image of article.images) {
|
|
16
|
+
if (image.type === "local") {
|
|
17
|
+
checks.push({
|
|
18
|
+
code: "LOCAL_IMAGE",
|
|
19
|
+
status: "warn",
|
|
20
|
+
message: `local image reference: ${image.src}`
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
metadata: article.metadata,
|
|
26
|
+
images: article.images,
|
|
27
|
+
checks,
|
|
28
|
+
baseDir: options.baseDir
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../../src/inspect/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,oBAAoB,CAAC;AAsB7F,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,UAA0B,EAAE;IAC3E,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,MAAM,GAAmB;QAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE;YACtE,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE;QAC1E;YACE,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,yBAAyB;YAC1D,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;SAC7B;KACF,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,aAAa;gBACnB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,0BAA0B,KAAK,CAAC,GAAG,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM;QACN,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMcpServer(): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { handleToolCall, listToolSchemas } from "./tools.js";
|
|
5
|
+
export async function startMcpServer() {
|
|
6
|
+
const server = new Server({
|
|
7
|
+
name: "md2wechat",
|
|
8
|
+
version: "0.1.0"
|
|
9
|
+
}, {
|
|
10
|
+
capabilities: {
|
|
11
|
+
tools: {}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
15
|
+
tools: listToolSchemas()
|
|
16
|
+
}));
|
|
17
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
18
|
+
console.error(`[md2wechat mcp] call ${request.params.name}`);
|
|
19
|
+
return await handleToolCall(request.params.name, request.params.arguments || {});
|
|
20
|
+
});
|
|
21
|
+
const transport = new StdioServerTransport();
|
|
22
|
+
await server.connect(transport);
|
|
23
|
+
console.error("[md2wechat mcp] server started");
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;KACF,CACF,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,eAAe,EAAE;KACzB,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,OAAO,CAAC,KAAK,CAAC,wBAAwB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO,MAAM,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAQ,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface McpToolSchema {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface McpToolResult {
|
|
11
|
+
content: Array<{
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
type ToolArgs = Record<string, unknown>;
|
|
17
|
+
export declare function listToolSchemas(): McpToolSchema[];
|
|
18
|
+
export declare function handleToolCall(name: string, args?: ToolArgs): Promise<McpToolResult>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { parseArticle } from "../core/article.js";
|
|
2
|
+
import { AppError, toAppError } from "../core/errors.js";
|
|
3
|
+
import { loadInput } from "../core/io.js";
|
|
4
|
+
import { loadConfig } from "../config/config.js";
|
|
5
|
+
import { generateImage } from "../images/generate.js";
|
|
6
|
+
import { createOpenAIClient } from "../openai/client.js";
|
|
7
|
+
import { buildPreviewDocument } from "../preview/preview.js";
|
|
8
|
+
import { renderArticle } from "../renderer/render.js";
|
|
9
|
+
import { listThemes, registerTheme, removeTheme, resolveTheme } from "../themes/registry.js";
|
|
10
|
+
export function listToolSchemas() {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "convert_article",
|
|
14
|
+
description: "Convert Markdown to WeChat Official Account inline-style HTML.",
|
|
15
|
+
inputSchema: articleInputSchema({ includeOutput: true })
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "preview_article",
|
|
19
|
+
description: "Render Markdown and write a local preview HTML document.",
|
|
20
|
+
inputSchema: articleInputSchema({ includeOutput: true })
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "generate_image",
|
|
24
|
+
description: "Generate an image with OpenAI image generation and write it to disk.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
prompt: { type: "string" },
|
|
29
|
+
article: { type: "string" },
|
|
30
|
+
output: { type: "string" },
|
|
31
|
+
size: { type: "string" },
|
|
32
|
+
quality: { type: "string" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "list_themes",
|
|
38
|
+
description: "List built-in and custom themes.",
|
|
39
|
+
inputSchema: { type: "object", properties: {} }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "register_theme",
|
|
43
|
+
description: "Register a custom theme from a local JSON theme file.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
name: { type: "string" },
|
|
48
|
+
path: { type: "string" }
|
|
49
|
+
},
|
|
50
|
+
required: ["name", "path"]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "remove_theme",
|
|
55
|
+
description: "Remove a custom theme by name.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
name: { type: "string" }
|
|
60
|
+
},
|
|
61
|
+
required: ["name"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
export async function handleToolCall(name, args = {}) {
|
|
67
|
+
try {
|
|
68
|
+
switch (name) {
|
|
69
|
+
case "convert_article":
|
|
70
|
+
return textResult(JSON.stringify(await convertArticle(args), null, 2));
|
|
71
|
+
case "preview_article":
|
|
72
|
+
return textResult(JSON.stringify(await previewArticle(args), null, 2));
|
|
73
|
+
case "generate_image":
|
|
74
|
+
return textResult(JSON.stringify(await generateImageTool(args), null, 2));
|
|
75
|
+
case "list_themes":
|
|
76
|
+
return textResult(JSON.stringify({ themes: await listThemes(themeOptions()) }, null, 2));
|
|
77
|
+
case "register_theme":
|
|
78
|
+
return textResult(JSON.stringify({ theme: await registerTheme(requiredString(args, "name"), requiredString(args, "path"), themeOptions()) }, null, 2));
|
|
79
|
+
case "remove_theme": {
|
|
80
|
+
const id = requiredString(args, "name");
|
|
81
|
+
await removeTheme(id, themeOptions());
|
|
82
|
+
return textResult(JSON.stringify({ id }, null, 2));
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
throw new AppError("MCP_TOOL_UNKNOWN", `Unknown tool: ${name}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const appError = toAppError(error);
|
|
90
|
+
return textResult(`执行工具失败: ${appError.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function convertArticle(args) {
|
|
94
|
+
const config = loadConfig();
|
|
95
|
+
const input = await loadInput({
|
|
96
|
+
content: optionalString(args, "content"),
|
|
97
|
+
file: optionalString(args, "file"),
|
|
98
|
+
contentUrl: optionalString(args, "content_url")
|
|
99
|
+
});
|
|
100
|
+
const article = parseArticle(input.content);
|
|
101
|
+
const theme = await resolveTheme(optionalString(args, "theme_id") || "default", themeOptions());
|
|
102
|
+
const client = createOpenAIClient({
|
|
103
|
+
apiKey: requireOpenAIKey(config.openaiApiKey),
|
|
104
|
+
baseUrl: config.openaiBaseUrl,
|
|
105
|
+
textModel: config.openaiTextModel,
|
|
106
|
+
imageModel: config.openaiImageModel
|
|
107
|
+
});
|
|
108
|
+
const rendered = await renderArticle({
|
|
109
|
+
markdown: article.body,
|
|
110
|
+
metadata: article.metadata,
|
|
111
|
+
theme,
|
|
112
|
+
client
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
html: rendered.html,
|
|
116
|
+
model: rendered.model,
|
|
117
|
+
theme_id: rendered.themeId,
|
|
118
|
+
metadata: article.metadata
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function previewArticle(args) {
|
|
122
|
+
const converted = await convertArticle(args);
|
|
123
|
+
if (!isRecord(converted) || typeof converted.html !== "string") {
|
|
124
|
+
throw new AppError("MCP_PREVIEW_FAILED", "Unable to render article preview");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...converted,
|
|
128
|
+
preview_html: buildPreviewDocument(converted.html, isRecord(converted.metadata) ? converted.metadata : {})
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function generateImageTool(args) {
|
|
132
|
+
const config = loadConfig();
|
|
133
|
+
const client = createOpenAIClient({
|
|
134
|
+
apiKey: requireOpenAIKey(config.openaiApiKey),
|
|
135
|
+
baseUrl: config.openaiBaseUrl,
|
|
136
|
+
textModel: config.openaiTextModel,
|
|
137
|
+
imageModel: config.openaiImageModel
|
|
138
|
+
});
|
|
139
|
+
return generateImage({
|
|
140
|
+
prompt: optionalString(args, "prompt") || "WeChat article cover image",
|
|
141
|
+
articleContext: optionalString(args, "article"),
|
|
142
|
+
output: optionalString(args, "output") || "image.png",
|
|
143
|
+
size: optionalString(args, "size"),
|
|
144
|
+
quality: optionalString(args, "quality"),
|
|
145
|
+
client
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function articleInputSchema(options) {
|
|
149
|
+
return {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
content: { type: "string" },
|
|
153
|
+
content_url: { type: "string" },
|
|
154
|
+
file: { type: "string" },
|
|
155
|
+
theme_id: { type: "string" },
|
|
156
|
+
...(options.includeOutput ? { output: { type: "string" } } : {})
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function textResult(text) {
|
|
161
|
+
return { content: [{ type: "text", text }] };
|
|
162
|
+
}
|
|
163
|
+
function optionalString(args, key) {
|
|
164
|
+
const value = args[key];
|
|
165
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
166
|
+
}
|
|
167
|
+
function requiredString(args, key) {
|
|
168
|
+
const value = optionalString(args, key);
|
|
169
|
+
if (!value) {
|
|
170
|
+
throw new AppError("MCP_INPUT_INVALID", `${key} is required`);
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
function requireOpenAIKey(value) {
|
|
175
|
+
if (!value) {
|
|
176
|
+
throw new AppError("OPENAI_KEY_MISSING", "OPENAI_API_KEY is required");
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
function themeOptions() {
|
|
181
|
+
const config = loadConfig();
|
|
182
|
+
return {
|
|
183
|
+
registryPath: config.themeRegistryPath,
|
|
184
|
+
projectThemesDir: config.themesDir
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isRecord(value) {
|
|
188
|
+
return typeof value === "object" && value !== null;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/mcp/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface OpenAIClientOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
textModel?: string;
|
|
5
|
+
imageModel?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResponsesCreateInput {
|
|
8
|
+
model?: string;
|
|
9
|
+
input: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ResponsesCreateResult {
|
|
12
|
+
model: string;
|
|
13
|
+
outputText: string;
|
|
14
|
+
raw: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface ImagesGenerateInput {
|
|
17
|
+
model?: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
size?: string;
|
|
20
|
+
quality?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ImagesGenerateResult {
|
|
23
|
+
model: string;
|
|
24
|
+
b64Json: string;
|
|
25
|
+
revisedPrompt?: string;
|
|
26
|
+
raw: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface OpenAIClient {
|
|
29
|
+
textModel: string;
|
|
30
|
+
imageModel: string;
|
|
31
|
+
responsesCreate(input: ResponsesCreateInput): Promise<ResponsesCreateResult>;
|
|
32
|
+
imagesGenerate(input: ImagesGenerateInput): Promise<ImagesGenerateResult>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createOpenAIClient(options: OpenAIClientOptions): OpenAIClient;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const defaultBaseUrl = "https://api.openai.com/v1";
|
|
2
|
+
const defaultTextModel = "gpt-5.5";
|
|
3
|
+
const defaultImageModel = "gpt-image-2";
|
|
4
|
+
export function createOpenAIClient(options) {
|
|
5
|
+
const baseUrl = (options.baseUrl || defaultBaseUrl).replace(/\/+$/, "");
|
|
6
|
+
const textModel = options.textModel || defaultTextModel;
|
|
7
|
+
const imageModel = options.imageModel || defaultImageModel;
|
|
8
|
+
return {
|
|
9
|
+
textModel,
|
|
10
|
+
imageModel,
|
|
11
|
+
async responsesCreate(input) {
|
|
12
|
+
const model = input.model || textModel;
|
|
13
|
+
const json = await postJson(`${baseUrl}/responses`, options.apiKey, {
|
|
14
|
+
model,
|
|
15
|
+
input: input.input
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
model,
|
|
19
|
+
outputText: extractOutputText(json),
|
|
20
|
+
raw: json
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
async imagesGenerate(input) {
|
|
24
|
+
const model = input.model || imageModel;
|
|
25
|
+
const json = await postJson(`${baseUrl}/images/generations`, options.apiKey, {
|
|
26
|
+
model,
|
|
27
|
+
prompt: input.prompt,
|
|
28
|
+
...(input.size ? { size: input.size } : {}),
|
|
29
|
+
...(input.quality ? { quality: input.quality } : {})
|
|
30
|
+
});
|
|
31
|
+
const firstImage = extractFirstImage(json);
|
|
32
|
+
return {
|
|
33
|
+
model,
|
|
34
|
+
b64Json: firstImage.b64Json,
|
|
35
|
+
revisedPrompt: firstImage.revisedPrompt,
|
|
36
|
+
raw: json
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function postJson(url, apiKey, body) {
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
authorization: `Bearer ${apiKey}`,
|
|
46
|
+
"content-type": "application/json"
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
signal: AbortSignal.timeout(120000)
|
|
50
|
+
});
|
|
51
|
+
const json = await response.json().catch(() => undefined);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(openAIErrorMessage(json, response.status));
|
|
54
|
+
}
|
|
55
|
+
return json;
|
|
56
|
+
}
|
|
57
|
+
function extractOutputText(json) {
|
|
58
|
+
if (isRecord(json) && typeof json.output_text === "string") {
|
|
59
|
+
return json.output_text;
|
|
60
|
+
}
|
|
61
|
+
if (isRecord(json) && Array.isArray(json.output)) {
|
|
62
|
+
const parts = [];
|
|
63
|
+
for (const item of json.output) {
|
|
64
|
+
if (!isRecord(item) || !Array.isArray(item.content)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const content of item.content) {
|
|
68
|
+
if (isRecord(content) && typeof content.text === "string") {
|
|
69
|
+
parts.push(content.text);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (parts.length > 0) {
|
|
74
|
+
return parts.join("");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw new Error("OpenAI response did not include output text");
|
|
78
|
+
}
|
|
79
|
+
function extractFirstImage(json) {
|
|
80
|
+
if (!isRecord(json) || !Array.isArray(json.data) || json.data.length === 0) {
|
|
81
|
+
throw new Error("OpenAI image response did not include image data");
|
|
82
|
+
}
|
|
83
|
+
const first = json.data[0];
|
|
84
|
+
if (!isRecord(first) || typeof first.b64_json !== "string") {
|
|
85
|
+
throw new Error("OpenAI image response did not include b64_json");
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
b64Json: first.b64_json,
|
|
89
|
+
revisedPrompt: typeof first.revised_prompt === "string" ? first.revised_prompt : undefined
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function openAIErrorMessage(json, status) {
|
|
93
|
+
if (isRecord(json) && isRecord(json.error) && typeof json.error.message === "string") {
|
|
94
|
+
return `OpenAI request failed (${status}): ${json.error.message}`;
|
|
95
|
+
}
|
|
96
|
+
return `OpenAI request failed (${status})`;
|
|
97
|
+
}
|
|
98
|
+
function isRecord(value) {
|
|
99
|
+
return typeof value === "object" && value !== null;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/openai/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { escapeHtml, sanitizeWechatHtml } from "../core/html.js";
|
|
2
|
+
export function buildPreviewDocument(html, metadata = {}) {
|
|
3
|
+
const title = escapeHtml(metadata.title || "md2wechat preview");
|
|
4
|
+
const body = sanitizeWechatHtml(html);
|
|
5
|
+
return `<!doctype html>
|
|
6
|
+
<html lang="zh-CN">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<title>${title}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body style="margin:0;background:#f6f7f9;color:#1f2933;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;">
|
|
13
|
+
<main style="max-width:780px;margin:0 auto;padding:32px 18px;">
|
|
14
|
+
<article style="background:#fff;padding:28px 24px;border:1px solid #e5e7eb;">
|
|
15
|
+
${body}
|
|
16
|
+
</article>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>`;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/preview/preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEjE,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,WAA4B,EAAE;IAC/E,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAEtC,OAAO;;;;;WAKE,KAAK;;;;;EAKd,IAAI;;;;QAIE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ArticleMetadata } from "../core/article.js";
|
|
2
|
+
import type { OpenAIClient } from "../openai/client.js";
|
|
3
|
+
export interface RenderTheme {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
prompt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RenderArticleInput {
|
|
9
|
+
markdown: string;
|
|
10
|
+
metadata?: ArticleMetadata;
|
|
11
|
+
theme: RenderTheme;
|
|
12
|
+
client: OpenAIClient;
|
|
13
|
+
}
|
|
14
|
+
export interface RenderedArticle {
|
|
15
|
+
html: string;
|
|
16
|
+
model: string;
|
|
17
|
+
themeId: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function renderArticle(input: RenderArticleInput): Promise<RenderedArticle>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sanitizeWechatHtml } from "../core/html.js";
|
|
2
|
+
export async function renderArticle(input) {
|
|
3
|
+
const model = input.client.textModel;
|
|
4
|
+
const response = await input.client.responsesCreate({
|
|
5
|
+
model,
|
|
6
|
+
input: buildRenderPrompt(input)
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
html: sanitizeWechatHtml(response.outputText),
|
|
10
|
+
model: response.model,
|
|
11
|
+
themeId: input.theme.id
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function buildRenderPrompt(input) {
|
|
15
|
+
const metadata = [
|
|
16
|
+
input.metadata?.title ? `Title: ${input.metadata.title}` : undefined,
|
|
17
|
+
input.metadata?.author ? `Author: ${input.metadata.author}` : undefined,
|
|
18
|
+
input.metadata?.digest ? `Digest: ${input.metadata.digest}` : undefined
|
|
19
|
+
]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join("\n");
|
|
22
|
+
return [
|
|
23
|
+
"Convert the Markdown article to WeChat Official Account HTML.",
|
|
24
|
+
"Requirements:",
|
|
25
|
+
"- Return HTML only, with no markdown fences or explanatory text.",
|
|
26
|
+
"- Use inline styles compatible with WeChat.",
|
|
27
|
+
"- Do not include <style>, <script>, event handler attributes, or javascript: URLs.",
|
|
28
|
+
"- Use safe structural tags such as section, p, h1, h2, h3, blockquote, ul, ol, li, strong, em, span, a, and img.",
|
|
29
|
+
`Theme (${input.theme.name}): ${input.theme.prompt}`,
|
|
30
|
+
metadata ? `Metadata:\n${metadata}` : undefined,
|
|
31
|
+
`Markdown:\n${input.markdown}`
|
|
32
|
+
]
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join("\n\n");
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/renderer/render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAsBrD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAyB;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC;QAClD,KAAK;QACL,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC;KAChC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,kBAAkB,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC7C,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAyB;IAClD,MAAM,QAAQ,GAAG;QACf,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS;QACpE,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;QACvE,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;KACxE;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,+DAA+D;QAC/D,eAAe;QACf,kEAAkE;QAClE,6CAA6C;QAC7C,oFAAoF;QACpF,kHAAkH;QAClH,UAAU,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE;QACpD,QAAQ,CAAC,CAAC,CAAC,cAAc,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;QAC/C,cAAc,KAAK,CAAC,QAAQ,EAAE;KAC/B;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC"}
|