wechat-to-anything 0.5.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.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * OpenAI Codex Agent — codex exec 登录模式,支持图片
3
+ *
4
+ * 前置:npm install -g @openai/codex && codex login
5
+ * 用法:node server.mjs
6
+ */
7
+
8
+ import { createServer } from "node:http";
9
+ import { execFile } from "node:child_process";
10
+ import { writeFile, readFile, unlink, mkdir } from "node:fs/promises";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { randomBytes } from "node:crypto";
14
+
15
+ const PORT = 3001;
16
+ const TMP_DIR = join(tmpdir(), "wechat-codex");
17
+
18
+ function runCodex(prompt, imagePaths = []) {
19
+ return new Promise(async (resolve, reject) => {
20
+ await mkdir(TMP_DIR, { recursive: true });
21
+ const outFile = join(TMP_DIR, `out-${randomBytes(4).toString("hex")}.txt`);
22
+
23
+ const args = [
24
+ "exec",
25
+ "--skip-git-repo-check",
26
+ "--ephemeral",
27
+ "-o", outFile, // 把回复写到文件,避免 stdout 被 progress 污染
28
+ ];
29
+ for (const img of imagePaths) args.push("-i", img);
30
+ args.push("--", prompt);
31
+
32
+ execFile("codex", args, {
33
+ timeout: 300_000,
34
+ maxBuffer: 2 * 1024 * 1024,
35
+ cwd: tmpdir(),
36
+ }, async (err, stdout, stderr) => {
37
+ try {
38
+ // 优先从输出文件读取
39
+ const reply = await readFile(outFile, "utf-8").catch(() => "");
40
+ await unlink(outFile).catch(() => {});
41
+
42
+ if (reply.trim()) {
43
+ resolve(reply.trim());
44
+ } else if (stdout.trim()) {
45
+ resolve(stdout.trim());
46
+ } else if (err) {
47
+ reject(new Error((stderr || err.message).trim().slice(0, 300)));
48
+ } else {
49
+ resolve("(empty response)");
50
+ }
51
+ } catch (e) {
52
+ reject(e);
53
+ }
54
+ });
55
+ });
56
+ }
57
+
58
+ const server = createServer(async (req, res) => {
59
+ if (req.method === "POST" && req.url.endsWith("/chat/completions")) {
60
+ let body = "";
61
+ for await (const chunk of req) body += chunk;
62
+ const tmpFiles = [];
63
+
64
+ try {
65
+ const { messages } = JSON.parse(body);
66
+ const lastMsg = messages[messages.length - 1];
67
+ let prompt = "";
68
+ const imagePaths = [];
69
+
70
+ if (typeof lastMsg.content === "string") {
71
+ prompt = lastMsg.content;
72
+ } else if (Array.isArray(lastMsg.content)) {
73
+ await mkdir(TMP_DIR, { recursive: true });
74
+ for (const part of lastMsg.content) {
75
+ if (part.type === "text") {
76
+ prompt += (prompt ? "\n" : "") + part.text;
77
+ } else if (part.type === "image_url" && part.image_url?.url) {
78
+ const url = part.image_url.url;
79
+ const tmpPath = join(TMP_DIR, `img-${randomBytes(4).toString("hex")}.jpg`);
80
+ if (url.startsWith("data:")) {
81
+ await writeFile(tmpPath, Buffer.from(url.replace(/^data:[^;]+;base64,/, ""), "base64"));
82
+ } else {
83
+ const r = await fetch(url);
84
+ if (r.ok) await writeFile(tmpPath, Buffer.from(await r.arrayBuffer()));
85
+ }
86
+ imagePaths.push(tmpPath);
87
+ tmpFiles.push(tmpPath);
88
+ }
89
+ }
90
+ }
91
+
92
+ if (!prompt && imagePaths.length > 0) prompt = "请描述这张图片";
93
+ if (!prompt) { res.writeHead(400); res.end('{"error":"empty"}'); return; }
94
+
95
+ const label = imagePaths.length > 0 ? ` +${imagePaths.length}图` : "";
96
+ console.log(`← ${prompt.slice(0, 80)}${label}`);
97
+
98
+ const reply = await runCodex(prompt, imagePaths);
99
+ console.log(`→ ${reply.slice(0, 80)}`);
100
+
101
+ res.writeHead(200, { "Content-Type": "application/json" });
102
+ res.end(JSON.stringify({ choices: [{ message: { role: "assistant", content: reply } }] }));
103
+ } catch (err) {
104
+ console.error(`❌ ${err.message.slice(0, 200)}`);
105
+ res.writeHead(500, { "Content-Type": "application/json" });
106
+ res.end(JSON.stringify({ error: err.message }));
107
+ } finally {
108
+ for (const f of tmpFiles) unlink(f).catch(() => {});
109
+ }
110
+ } else {
111
+ res.writeHead(200, { "Content-Type": "application/json" });
112
+ res.end('{"status":"ok","agent":"codex"}');
113
+ }
114
+ });
115
+
116
+ server.listen(PORT, () => {
117
+ console.log(`🤖 Codex Agent 运行在 http://localhost:${PORT}/v1`);
118
+ console.log(` 登录模式(codex exec -o),支持图片`);
119
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "wechat-to-anything",
3
+ "version": "0.5.0",
4
+ "description": "一条命令,把微信变成任何 AI Agent 的入口",
5
+ "type": "module",
6
+ "bin": {
7
+ "wechat-to-anything": "./bin/cli.mjs",
8
+ "wxta": "./bin/cli.mjs"
9
+ },
10
+ "scripts": {
11
+ "dev": "node bin/cli.mjs"
12
+ },
13
+ "keywords": [
14
+ "wechat",
15
+ "ai-agent",
16
+ "bridge",
17
+ "gateway",
18
+ "multimodal",
19
+ "image",
20
+ "claude-code",
21
+ "openai",
22
+ "codex",
23
+ "anthropic"
24
+ ],
25
+ "author": "kellyvv",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/kellyvv/wechat-to-anything.git"
30
+ },
31
+ "homepage": "https://github.com/kellyvv/wechat-to-anything",
32
+ "engines": {
33
+ "node": ">=22"
34
+ },
35
+ "dependencies": {
36
+ "picocolors": "^1.1.0",
37
+ "qrcode-terminal": "^0.12.0"
38
+ }
39
+ }