claude-codex-wechat 0.1.57 → 0.1.59
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/mcp/mediaServer.js +7 -1
- package/dist/mcp/scripts/douyin-download.mjs +181 -74
- package/dist/server/cli.js +136 -107
- package/package.json +1 -1
package/dist/mcp/mediaServer.js
CHANGED
|
@@ -142,6 +142,12 @@ function locateDouyinScript(input) {
|
|
|
142
142
|
return void 0;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// src/shared/platform.ts
|
|
146
|
+
var isWindows = process.platform === "win32";
|
|
147
|
+
function nodeExecutable() {
|
|
148
|
+
return process.execPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
145
151
|
// src/mcp/tools/douyinDownload.ts
|
|
146
152
|
function findDouyinScript() {
|
|
147
153
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -152,7 +158,7 @@ function findDouyinScript() {
|
|
|
152
158
|
function runScript(args) {
|
|
153
159
|
const script = findDouyinScript();
|
|
154
160
|
return new Promise((resolve2, reject) => {
|
|
155
|
-
execFile(
|
|
161
|
+
execFile(nodeExecutable(), [script, ...args], { timeout: 18e4 }, (err, stdout, stderr) => {
|
|
156
162
|
if (err) reject(new Error(stderr || err.message));
|
|
157
163
|
else resolve2({ stdout, stderr });
|
|
158
164
|
});
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 抖音无水印视频下载脚本(自包含,无下载器依赖)。
|
|
5
5
|
*
|
|
6
6
|
* 用法:
|
|
7
7
|
* node douyin-download.mjs "<抖音链接>"
|
|
8
8
|
* node douyin-download.mjs "<抖音链接>" --output ./videos/
|
|
9
9
|
* node douyin-download.mjs "<分享文案含链接>"
|
|
10
10
|
*
|
|
11
|
-
* 要求:Node.js
|
|
11
|
+
* 要求:Node.js 20+。新版抖音页面回退路径需要本机安装 Chrome/Chromium。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { execFile } from "node:child_process";
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
15
17
|
import { resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { promisify } from "node:util";
|
|
20
|
+
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
16
22
|
|
|
17
23
|
// ─── 常量 ───────────────────────────────────────────────────────────────────
|
|
18
24
|
|
|
@@ -20,34 +26,38 @@ const MOBILE_UA =
|
|
|
20
26
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1";
|
|
21
27
|
const DESKTOP_UA =
|
|
22
28
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
29
|
+
const PLAYER_LOAD_TIMEOUT_MS = 35_000;
|
|
30
|
+
const PLAYER_VIRTUAL_TIME_MS = 15_000;
|
|
31
|
+
const PLAYER_ATTEMPTS = 2;
|
|
32
|
+
const MAX_DOM_BYTES = 2 * 1024 * 1024;
|
|
33
|
+
const ALLOWED_VIDEO_HOST_SUFFIXES = [".douyinvod.com", ".douyin.com", ".snssdk.com"];
|
|
23
34
|
|
|
24
35
|
// ─── 带重试/超时的 fetch ─────────────────────────────────────────────────────
|
|
25
36
|
|
|
26
|
-
const sleep = (ms) => new Promise((
|
|
37
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
27
38
|
|
|
28
39
|
/**
|
|
29
40
|
* fetch 包一层重试与超时,抵御瞬时网络抖动("fetch failed" 之类的底层网络错误)。
|
|
30
41
|
* 仅对网络层错误和 5xx/429 重试;4xx(除 429)视为确定性失败,不重试。
|
|
31
42
|
*/
|
|
32
43
|
async function fetchWithRetry(url, options = {}, { retries = 3, timeoutMs = 20_000 } = {}) {
|
|
33
|
-
let
|
|
44
|
+
let lastError;
|
|
34
45
|
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
|
35
46
|
const controller = new AbortController();
|
|
36
47
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
37
48
|
try {
|
|
38
|
-
const
|
|
49
|
+
const response = await fetch(url, { ...options, signal: controller.signal });
|
|
39
50
|
clearTimeout(timer);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
lastErr = new Error(`HTTP ${resp.status}`);
|
|
51
|
+
if ((response.status >= 500 || response.status === 429) && attempt < retries) {
|
|
52
|
+
lastError = new Error(`HTTP ${response.status}`);
|
|
43
53
|
await sleep(attempt * 800);
|
|
44
54
|
continue;
|
|
45
55
|
}
|
|
46
|
-
return
|
|
47
|
-
} catch (
|
|
56
|
+
return response;
|
|
57
|
+
} catch (error) {
|
|
48
58
|
clearTimeout(timer);
|
|
49
|
-
|
|
50
|
-
const reason =
|
|
59
|
+
lastError = error;
|
|
60
|
+
const reason = error?.name === "AbortError" ? `请求超时(>${timeoutMs}ms)` : error?.message || String(error);
|
|
51
61
|
if (attempt < retries) {
|
|
52
62
|
await sleep(attempt * 800);
|
|
53
63
|
continue;
|
|
@@ -55,26 +65,25 @@ async function fetchWithRetry(url, options = {}, { retries = 3, timeoutMs = 20_0
|
|
|
55
65
|
throw new Error(`网络请求失败(已重试${retries}次):${reason}`);
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
|
-
throw
|
|
68
|
+
throw lastError ?? new Error("网络请求失败");
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
// ─── 解析逻辑 ────────────────────────────────────────────────────────────────
|
|
62
72
|
|
|
63
|
-
/** 从任意文本里提取第一个抖音 URL */
|
|
64
|
-
function extractDouyinUrl(text) {
|
|
65
|
-
const
|
|
66
|
-
/https?:\/\/(?:v\.douyin\.com|www\.douyin\.com|www\.iesdouyin\.com|iesdouyin\.com)\/[^\s"'))】]+/i
|
|
73
|
+
/** 从任意文本里提取第一个抖音 URL。 */
|
|
74
|
+
export function extractDouyinUrl(text) {
|
|
75
|
+
const matched = text.match(
|
|
76
|
+
/https?:\/\/(?:v\.douyin\.com|www\.douyin\.com|www\.iesdouyin\.com|iesdouyin\.com)\/[^\s"'))】]+/i,
|
|
67
77
|
);
|
|
68
|
-
return
|
|
78
|
+
return matched ? matched[0] : null;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
/** 把任意输入解析成 aweme_id */
|
|
72
|
-
async function resolveAwemeId(input) {
|
|
81
|
+
/** 把任意输入解析成 aweme_id。 */
|
|
82
|
+
export async function resolveAwemeId(input) {
|
|
73
83
|
const trimmed = input.trim();
|
|
74
84
|
if (/^\d{15,}$/.test(trimmed)) return trimmed;
|
|
75
85
|
|
|
76
86
|
const url = extractDouyinUrl(trimmed) ?? trimmed;
|
|
77
|
-
|
|
78
87
|
const direct = url.match(/(?:share\/)?video\/(\d{15,})/);
|
|
79
88
|
if (direct) return direct[1];
|
|
80
89
|
|
|
@@ -82,96 +91,194 @@ async function resolveAwemeId(input) {
|
|
|
82
91
|
throw new Error(`无法识别的输入(既不是抖音链接,也不是 aweme_id):${trimmed}`);
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
const
|
|
94
|
+
const response = await fetchWithRetry(url, {
|
|
86
95
|
method: "GET",
|
|
87
96
|
headers: { "User-Agent": MOBILE_UA },
|
|
88
97
|
redirect: "follow",
|
|
89
98
|
});
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
92
|
-
return
|
|
99
|
+
const matched = response.url.match(/(\d{15,})/);
|
|
100
|
+
if (!matched) throw new Error(`短链重定向后未找到 aweme_id,最终地址:${response.url}`);
|
|
101
|
+
return matched[1];
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
/**
|
|
96
|
-
async function resolveVideoId(awemeId) {
|
|
97
|
-
const shareUrl = `https://www.iesdouyin.com/share/video/${awemeId}/`;
|
|
98
|
-
const resp = await fetchWithRetry(shareUrl, { headers: { "User-Agent": MOBILE_UA } });
|
|
99
|
-
if (!resp.ok) throw new Error(`分享页请求失败:HTTP ${resp.status}(aweme_id=${awemeId})`);
|
|
100
|
-
const html = await resp.text();
|
|
101
|
-
const m =
|
|
102
|
-
html.match(/video_id=([a-z0-9]+)/i) ??
|
|
103
|
-
html.match(/"video_id"\s*:\s*"([a-z0-9]+)"/i) ??
|
|
104
|
-
html.match(/playwm\/\?[^"'\\]*?video_id=([a-z0-9]+)/i);
|
|
105
|
-
if (!m) throw new Error(`分享页里未找到 video_id(aweme_id=${awemeId})`);
|
|
106
|
-
return m[1];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** 拼无水印播放入口(play 而非 playwm) */
|
|
104
|
+
/** 拼无水印播放入口(play 而非 playwm)。 */
|
|
110
105
|
function buildPlayUrlNoWatermark(videoId, ratio = "720p") {
|
|
111
106
|
return `https://aweme.snssdk.com/aweme/v1/play/?ratio=${ratio}&video_id=${videoId}`;
|
|
112
107
|
}
|
|
113
108
|
|
|
109
|
+
/** 查找用户机器上已有的 Chrome/Chromium,不下载浏览器。 */
|
|
110
|
+
export function findChromeExecutable({ platform = process.platform, env = process.env, exists = existsSync } = {}) {
|
|
111
|
+
const explicit = [env.DOUYIN_CHROME_EXECUTABLE, env.CHROME_PATH, env.GOOGLE_CHROME_BIN].filter(Boolean);
|
|
112
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
113
|
+
const candidates = [...explicit];
|
|
114
|
+
|
|
115
|
+
if (platform === "darwin") {
|
|
116
|
+
candidates.push(
|
|
117
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
118
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
119
|
+
resolve(home, "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
|
|
120
|
+
);
|
|
121
|
+
} else if (platform === "win32") {
|
|
122
|
+
for (const root of [env.PROGRAMFILES, env["PROGRAMFILES(X86)"], env.LOCALAPPDATA].filter(Boolean)) {
|
|
123
|
+
candidates.push(resolve(root, "Google/Chrome/Application/chrome.exe"), resolve(root, "Chromium/Application/chrome.exe"));
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
candidates.push(
|
|
127
|
+
"/usr/bin/google-chrome-stable",
|
|
128
|
+
"/usr/bin/google-chrome",
|
|
129
|
+
"/usr/bin/chromium",
|
|
130
|
+
"/usr/bin/chromium-browser",
|
|
131
|
+
"/snap/bin/chromium",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return candidates.find((candidate) => exists(candidate)) ?? "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function decodeHtmlAttribute(value) {
|
|
139
|
+
return value
|
|
140
|
+
.replaceAll("&", "&")
|
|
141
|
+
.replaceAll(""", '"')
|
|
142
|
+
.replaceAll("'", "'")
|
|
143
|
+
.replaceAll("<", "<")
|
|
144
|
+
.replaceAll(">", ">");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 从官方播放器渲染后的 DOM 中提取并校验视频 CDN 地址。 */
|
|
148
|
+
export function parseOfficialVideoUrl(dom) {
|
|
149
|
+
const videoTag = dom.match(/<video\b[^>]*>/i)?.[0];
|
|
150
|
+
const rawSource = videoTag?.match(/\bsrc=["']([^"']+)["']/i)?.[1];
|
|
151
|
+
if (!rawSource) throw new Error("抖音官方播放器未返回视频地址,作品可能已删除、设为私密或触发风控");
|
|
152
|
+
|
|
153
|
+
const normalized = decodeHtmlAttribute(rawSource);
|
|
154
|
+
let url;
|
|
155
|
+
try {
|
|
156
|
+
url = new URL(normalized.startsWith("//") ? `https:${normalized}` : normalized);
|
|
157
|
+
} catch {
|
|
158
|
+
throw new Error("抖音官方播放器返回了无效的视频地址");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const allowed =
|
|
162
|
+
url.protocol === "https:" && ALLOWED_VIDEO_HOST_SUFFIXES.some((suffix) => url.hostname.endsWith(suffix));
|
|
163
|
+
if (!allowed) throw new Error(`抖音官方播放器返回了非预期的视频域名:${url.hostname}`);
|
|
164
|
+
return url.toString();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function renderOfficialPlayerDom(chrome, playerUrl) {
|
|
168
|
+
const { stdout } = await execFileAsync(
|
|
169
|
+
chrome,
|
|
170
|
+
[
|
|
171
|
+
"--headless=new",
|
|
172
|
+
"--no-sandbox",
|
|
173
|
+
"--disable-gpu",
|
|
174
|
+
`--virtual-time-budget=${PLAYER_VIRTUAL_TIME_MS}`,
|
|
175
|
+
"--dump-dom",
|
|
176
|
+
playerUrl,
|
|
177
|
+
],
|
|
178
|
+
{ timeout: PLAYER_LOAD_TIMEOUT_MS, maxBuffer: MAX_DOM_BYTES },
|
|
179
|
+
);
|
|
180
|
+
return stdout;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 由抖音官方播放器完成访客 Cookie 与动态签名,再读取真实 CDN 地址。 */
|
|
184
|
+
export async function resolveOfficialDouyinVideoUrl(
|
|
185
|
+
awemeId,
|
|
186
|
+
{ chrome = findChromeExecutable(), renderDom = renderOfficialPlayerDom } = {},
|
|
187
|
+
) {
|
|
188
|
+
if (!/^\d{15,}$/.test(awemeId)) throw new Error(`无效的抖音 aweme_id:${awemeId}`);
|
|
189
|
+
if (!chrome) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"旧分享页已不再提供 video_id,且未找到 Chrome/Chromium。请安装 Chrome,或设置 DOUYIN_CHROME_EXECUTABLE 指向浏览器可执行文件。",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const playerUrl = `https://open.douyin.com/player/video?vid=${awemeId}&autoplay=0`;
|
|
196
|
+
let lastError;
|
|
197
|
+
for (let attempt = 1; attempt <= PLAYER_ATTEMPTS; attempt += 1) {
|
|
198
|
+
try {
|
|
199
|
+
return parseOfficialVideoUrl(await renderDom(chrome, playerUrl));
|
|
200
|
+
} catch (error) {
|
|
201
|
+
lastError = error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
205
|
+
}
|
|
206
|
+
|
|
114
207
|
// ─── 下载逻辑 ────────────────────────────────────────────────────────────────
|
|
115
208
|
|
|
116
|
-
async function
|
|
209
|
+
async function fetchVideoSource(url, referer) {
|
|
210
|
+
return fetchWithRetry(
|
|
211
|
+
url,
|
|
212
|
+
{
|
|
213
|
+
headers: { "User-Agent": DESKTOP_UA, Referer: referer },
|
|
214
|
+
redirect: "follow",
|
|
215
|
+
},
|
|
216
|
+
{ timeoutMs: 60_000 },
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function download(input, outputDir) {
|
|
117
221
|
console.log("正在解析...");
|
|
118
222
|
|
|
119
223
|
const trimmed = input.trim();
|
|
120
224
|
let awemeId = "";
|
|
121
225
|
let videoId = "";
|
|
226
|
+
let playUrl;
|
|
227
|
+
let referer = "https://open.douyin.com/";
|
|
122
228
|
|
|
123
229
|
if (/^v0[a-z0-9]{10,}$/i.test(trimmed)) {
|
|
124
230
|
videoId = trimmed;
|
|
231
|
+
playUrl = buildPlayUrlNoWatermark(videoId);
|
|
232
|
+
referer = "https://www.douyin.com/";
|
|
125
233
|
} else {
|
|
126
234
|
awemeId = await resolveAwemeId(trimmed);
|
|
127
|
-
|
|
235
|
+
playUrl = await resolveOfficialDouyinVideoUrl(awemeId);
|
|
128
236
|
}
|
|
129
237
|
|
|
130
|
-
console.log(`aweme_id: ${awemeId || "-"} video_id: ${videoId}`);
|
|
131
|
-
|
|
132
|
-
const playUrl = buildPlayUrlNoWatermark(videoId);
|
|
238
|
+
console.log(`aweme_id: ${awemeId || "-"} video_id: ${videoId || "-"}`);
|
|
133
239
|
console.log("正在下载无水印视频...");
|
|
134
240
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
redirect: "follow",
|
|
138
|
-
}, { timeoutMs: 60_000 });
|
|
139
|
-
|
|
140
|
-
if (!resp.ok) {
|
|
141
|
-
throw new Error(`下载失败: HTTP ${resp.status}`);
|
|
142
|
-
}
|
|
241
|
+
const response = await fetchVideoSource(playUrl, referer);
|
|
242
|
+
if (!response.ok) throw new Error(`下载失败: HTTP ${response.status}`);
|
|
143
243
|
|
|
144
|
-
const
|
|
244
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
145
245
|
const filename = `douyin_${awemeId || videoId}.mp4`;
|
|
146
|
-
|
|
147
246
|
const dir = outputDir ? resolve(outputDir) : process.cwd();
|
|
148
247
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
149
248
|
|
|
150
249
|
const filepath = resolve(dir, filename);
|
|
151
|
-
writeFileSync(filepath,
|
|
250
|
+
writeFileSync(filepath, buffer);
|
|
152
251
|
|
|
153
|
-
const sizeMB = (
|
|
252
|
+
const sizeMB = (buffer.length / 1024 / 1024).toFixed(2);
|
|
154
253
|
console.log(`✅ 已保存: ${filepath} (${sizeMB} MB)`);
|
|
155
254
|
return filepath;
|
|
156
255
|
}
|
|
157
256
|
|
|
158
|
-
// ─── CLI 入口
|
|
257
|
+
// ─── CLI 入口 ────────────────────────────────────────────────────────────────
|
|
159
258
|
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
259
|
+
async function main() {
|
|
260
|
+
const argv = process.argv.slice(2);
|
|
261
|
+
const outputIndex = argv.indexOf("--output");
|
|
262
|
+
let outputDir;
|
|
263
|
+
if (outputIndex !== -1) {
|
|
264
|
+
outputDir = argv[outputIndex + 1];
|
|
265
|
+
argv.splice(outputIndex, 2);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const input = argv.join(" ").trim();
|
|
269
|
+
if (!input) {
|
|
270
|
+
console.error('用法: node douyin-download.mjs "<抖音链接>" [--output <目录>]');
|
|
271
|
+
process.exitCode = 2;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
167
274
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
275
|
+
try {
|
|
276
|
+
await download(input, outputDir);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error("❌ 错误:", error.message || error);
|
|
279
|
+
process.exitCode = 1;
|
|
280
|
+
}
|
|
172
281
|
}
|
|
173
282
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
283
|
+
const entryPath = process.argv[1] ? resolve(process.argv[1]) : "";
|
|
284
|
+
if (entryPath === fileURLToPath(import.meta.url)) await main();
|
package/dist/server/cli.js
CHANGED
|
@@ -15,6 +15,106 @@ import { networkInterfaces } from "node:os";
|
|
|
15
15
|
import { mkdtempSync, existsSync as existsSync10, writeFileSync as writeFileSync2 } from "node:fs";
|
|
16
16
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17
17
|
import { dirname as dirname8, isAbsolute, join as join13 } from "node:path";
|
|
18
|
+
|
|
19
|
+
// src/daemon/mcpConfig.ts
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
|
|
22
|
+
// src/shared/platform.ts
|
|
23
|
+
import { access, constants } from "node:fs/promises";
|
|
24
|
+
import { delimiter, join } from "node:path";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { spawn } from "node:child_process";
|
|
27
|
+
var isWindows = process.platform === "win32";
|
|
28
|
+
async function isExecutableFile(candidate) {
|
|
29
|
+
try {
|
|
30
|
+
await access(candidate, isWindows ? constants.F_OK : constants.X_OK);
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function executableExtensions() {
|
|
37
|
+
if (!isWindows) return [""];
|
|
38
|
+
const pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
39
|
+
return ["", ...pathext.split(";").filter(Boolean)];
|
|
40
|
+
}
|
|
41
|
+
function isTemporaryShim(candidate) {
|
|
42
|
+
const tempDir = tmpdir();
|
|
43
|
+
return candidate.startsWith(tempDir);
|
|
44
|
+
}
|
|
45
|
+
async function findExecutable(command, pathEnv) {
|
|
46
|
+
const extensions = executableExtensions();
|
|
47
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
48
|
+
for (const ext of extensions) {
|
|
49
|
+
if (await isExecutableFile(command + ext)) return command + ext;
|
|
50
|
+
}
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
const dirs = (pathEnv ?? "").split(delimiter).filter(Boolean);
|
|
54
|
+
let fallback;
|
|
55
|
+
for (const dir of dirs) {
|
|
56
|
+
for (const ext of extensions) {
|
|
57
|
+
const candidate = join(dir, command + ext);
|
|
58
|
+
if (await isExecutableFile(candidate)) {
|
|
59
|
+
if (isTemporaryShim(candidate)) {
|
|
60
|
+
fallback ??= candidate;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return fallback;
|
|
68
|
+
}
|
|
69
|
+
function terminateChild(child, signal = "SIGTERM") {
|
|
70
|
+
if (child.pid === void 0 || child.exitCode !== null || child.signalCode !== null) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (isWindows) {
|
|
74
|
+
try {
|
|
75
|
+
spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
76
|
+
return;
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
child.kill(signal);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function defaultWorkspaceDir() {
|
|
86
|
+
return join(tmpdir(), "project");
|
|
87
|
+
}
|
|
88
|
+
function useShellForCli() {
|
|
89
|
+
return isWindows;
|
|
90
|
+
}
|
|
91
|
+
function statePath(filename) {
|
|
92
|
+
return join(tmpdir(), filename);
|
|
93
|
+
}
|
|
94
|
+
function nodeExecutable() {
|
|
95
|
+
return process.execPath;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/daemon/mcpConfig.ts
|
|
99
|
+
function buildMcpServerCommand(input) {
|
|
100
|
+
if (input.exists(input.jsEntry)) {
|
|
101
|
+
return { command: nodeExecutable(), args: [input.jsEntry] };
|
|
102
|
+
}
|
|
103
|
+
const tsxCli = (input.resolveTsxCli ?? defaultResolveTsxCli)();
|
|
104
|
+
if (tsxCli) {
|
|
105
|
+
return { command: nodeExecutable(), args: [tsxCli, input.tsEntry] };
|
|
106
|
+
}
|
|
107
|
+
return { command: "tsx", args: [input.tsEntry] };
|
|
108
|
+
}
|
|
109
|
+
function defaultResolveTsxCli() {
|
|
110
|
+
try {
|
|
111
|
+
return createRequire(import.meta.url).resolve("tsx/dist/cli.mjs");
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/daemon/server.ts
|
|
18
118
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19
119
|
import Fastify from "fastify";
|
|
20
120
|
|
|
@@ -69,9 +169,9 @@ var WeixinDirectLoginClient = class {
|
|
|
69
169
|
// src/daemon/config.ts
|
|
70
170
|
import { existsSync, readFileSync } from "node:fs";
|
|
71
171
|
import { homedir } from "node:os";
|
|
72
|
-
import { join } from "node:path";
|
|
172
|
+
import { join as join2 } from "node:path";
|
|
73
173
|
function defaultConfigPath() {
|
|
74
|
-
return
|
|
174
|
+
return join2(homedir(), ".claude-codex-wechat", "config.json");
|
|
75
175
|
}
|
|
76
176
|
function loadBridgeConfig(path = process.env.BRIDGE_CONFIG ?? defaultConfigPath()) {
|
|
77
177
|
if (!existsSync(path)) return normalizeBridgeConfig({}, process.env, path);
|
|
@@ -162,10 +262,10 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:f
|
|
|
162
262
|
import { closeSync, fsyncSync, mkdirSync, openSync, renameSync, unlinkSync, writeSync } from "node:fs";
|
|
163
263
|
import { mkdir, open, readdir, rename, stat, unlink } from "node:fs/promises";
|
|
164
264
|
import { randomBytes } from "node:crypto";
|
|
165
|
-
import { basename, dirname, join as
|
|
265
|
+
import { basename, dirname, join as join3 } from "node:path";
|
|
166
266
|
var FILE_MODE = 384;
|
|
167
267
|
function tempPathFor(path) {
|
|
168
|
-
return
|
|
268
|
+
return join3(dirname(path), `.${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
169
269
|
}
|
|
170
270
|
var RENAME_RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
171
271
|
var MAX_RENAME_RETRIES = 10;
|
|
@@ -247,7 +347,7 @@ async function cleanupStaleTempFiles(targetPath, maxAgeMs = 36e5) {
|
|
|
247
347
|
const now = Date.now();
|
|
248
348
|
await Promise.all(entries.map(async (name) => {
|
|
249
349
|
if (!name.startsWith(prefix) || !name.endsWith(".tmp")) return;
|
|
250
|
-
const full =
|
|
350
|
+
const full = join3(dir, name);
|
|
251
351
|
try {
|
|
252
352
|
const info = await stat(full);
|
|
253
353
|
if (now - info.mtimeMs >= maxAgeMs) await unlink(full);
|
|
@@ -501,7 +601,7 @@ var FileWeixinStateStore = class {
|
|
|
501
601
|
// src/providers/claude-code/nativeSessions.ts
|
|
502
602
|
import { appendFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, stat as stat2, unlink as unlink2, writeFile } from "node:fs/promises";
|
|
503
603
|
import { homedir as homedir2 } from "node:os";
|
|
504
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
604
|
+
import { basename as basename2, dirname as dirname2, join as join4 } from "node:path";
|
|
505
605
|
|
|
506
606
|
// src/session/sessionBridgeTag.ts
|
|
507
607
|
function buildSessionBridgeName(input) {
|
|
@@ -528,7 +628,7 @@ function parseSessionBridgeName(value) {
|
|
|
528
628
|
|
|
529
629
|
// src/providers/claude-code/nativeSessions.ts
|
|
530
630
|
function resolveClaudeConfigDir(env = process.env) {
|
|
531
|
-
return
|
|
631
|
+
return join4(env.HOME || homedir2(), ".claude");
|
|
532
632
|
}
|
|
533
633
|
async function prepareClaudeSessionForResume(input) {
|
|
534
634
|
const env = input.env ?? process.env;
|
|
@@ -544,20 +644,20 @@ async function prepareClaudeSessionForResume(input) {
|
|
|
544
644
|
return { ok: true, adoptedBackground: true };
|
|
545
645
|
}
|
|
546
646
|
async function listRecoverableClaudeSessions(env = process.env) {
|
|
547
|
-
const projectsDir =
|
|
647
|
+
const projectsDir = join4(resolveClaudeConfigDir(env), "projects");
|
|
548
648
|
const historyIndex = await readClaudeHistoryIndex(env);
|
|
549
649
|
const candidates = [];
|
|
550
650
|
try {
|
|
551
651
|
const projectEntries = await readdir2(projectsDir, { withFileTypes: true });
|
|
552
652
|
for (const entry of projectEntries) {
|
|
553
653
|
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
554
|
-
const projectDir =
|
|
654
|
+
const projectDir = join4(projectsDir, String(entry.name));
|
|
555
655
|
const sessionEntries = await readdir2(projectDir, { withFileTypes: true }).catch(() => []);
|
|
556
656
|
for (const sessionEntry of sessionEntries) {
|
|
557
657
|
if (!sessionEntry.isFile() || sessionEntry.isSymbolicLink()) continue;
|
|
558
658
|
const fileName = String(sessionEntry.name);
|
|
559
659
|
if (!fileName.endsWith(".jsonl")) continue;
|
|
560
|
-
const filePath =
|
|
660
|
+
const filePath = join4(projectDir, fileName);
|
|
561
661
|
const metadata = await stat2(filePath).catch(() => null);
|
|
562
662
|
const parsedMeta = await readClaudeSessionMetadata(filePath).catch(() => null);
|
|
563
663
|
const historyMeta = historyIndex.get(basename2(fileName, ".jsonl"));
|
|
@@ -583,12 +683,12 @@ async function listRecoverableClaudeSessions(env = process.env) {
|
|
|
583
683
|
async function findRecoverableClaudeSessionPath(sessionId, env = process.env) {
|
|
584
684
|
const trimmed = sessionId.trim();
|
|
585
685
|
if (!trimmed || trimmed.includes("/") || trimmed.includes("\\")) return null;
|
|
586
|
-
const projectsDir =
|
|
686
|
+
const projectsDir = join4(resolveClaudeConfigDir(env), "projects");
|
|
587
687
|
try {
|
|
588
688
|
const projectEntries = await readdir2(projectsDir, { withFileTypes: true });
|
|
589
689
|
for (const entry of projectEntries) {
|
|
590
690
|
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
591
|
-
const sessionPath =
|
|
691
|
+
const sessionPath = join4(projectsDir, String(entry.name), `${trimmed}.jsonl`);
|
|
592
692
|
try {
|
|
593
693
|
const metadata = await stat2(sessionPath);
|
|
594
694
|
if (metadata.isFile()) return sessionPath;
|
|
@@ -636,7 +736,7 @@ async function hasClaudeSessionBridgeMetadata(input) {
|
|
|
636
736
|
}
|
|
637
737
|
async function hasClaudeHistoryDisplay(input) {
|
|
638
738
|
const env = input.env ?? process.env;
|
|
639
|
-
const historyPath =
|
|
739
|
+
const historyPath = join4(resolveClaudeConfigDir(env), "history.jsonl");
|
|
640
740
|
try {
|
|
641
741
|
const content = await readFile2(historyPath, "utf8");
|
|
642
742
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -701,13 +801,13 @@ async function readClaudeSessionMetadata(filePath) {
|
|
|
701
801
|
return { aiTitle, lastPrompt, sessionName, cwd };
|
|
702
802
|
}
|
|
703
803
|
async function readClaudeRuntimeSessionIndex(env) {
|
|
704
|
-
const sessionsDir =
|
|
804
|
+
const sessionsDir = join4(resolveClaudeConfigDir(env), "sessions");
|
|
705
805
|
const index = /* @__PURE__ */ new Map();
|
|
706
806
|
try {
|
|
707
807
|
const entries = await readdir2(sessionsDir, { withFileTypes: true });
|
|
708
808
|
for (const entry of entries) {
|
|
709
809
|
if (!entry.isFile() || entry.isSymbolicLink() || !String(entry.name).endsWith(".json")) continue;
|
|
710
|
-
const path =
|
|
810
|
+
const path = join4(sessionsDir, String(entry.name));
|
|
711
811
|
try {
|
|
712
812
|
const raw = await readFile2(path, "utf8");
|
|
713
813
|
const record = JSON.parse(raw);
|
|
@@ -725,7 +825,7 @@ async function readClaudeRuntimeSessionIndex(env) {
|
|
|
725
825
|
return index;
|
|
726
826
|
}
|
|
727
827
|
async function readClaudeHistoryIndex(env) {
|
|
728
|
-
const historyPath =
|
|
828
|
+
const historyPath = join4(resolveClaudeConfigDir(env), "history.jsonl");
|
|
729
829
|
const index = /* @__PURE__ */ new Map();
|
|
730
830
|
try {
|
|
731
831
|
const content = await readFile2(historyPath, "utf8");
|
|
@@ -750,7 +850,7 @@ async function readClaudeHistoryIndex(env) {
|
|
|
750
850
|
return index;
|
|
751
851
|
}
|
|
752
852
|
async function upsertClaudeHistoryDisplay(input) {
|
|
753
|
-
const historyPath =
|
|
853
|
+
const historyPath = join4(resolveClaudeConfigDir(input.env), "history.jsonl");
|
|
754
854
|
const lines = [];
|
|
755
855
|
let replaced = false;
|
|
756
856
|
let changed = false;
|
|
@@ -840,9 +940,9 @@ import Database from "better-sqlite3";
|
|
|
840
940
|
import { readdir as readdir3, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
841
941
|
import { existsSync as existsSync4 } from "node:fs";
|
|
842
942
|
import { homedir as homedir3 } from "node:os";
|
|
843
|
-
import { basename as basename3, join as
|
|
943
|
+
import { basename as basename3, join as join5 } from "node:path";
|
|
844
944
|
function resolveCodexSessionsRoot(env = process.env) {
|
|
845
|
-
return
|
|
945
|
+
return join5(env.CODEX_HOME || join5(env.HOME || homedir3(), ".codex"), "sessions");
|
|
846
946
|
}
|
|
847
947
|
function isMatchingCodexRolloutFile(name) {
|
|
848
948
|
return name.startsWith("rollout-") && name.endsWith(".jsonl");
|
|
@@ -865,7 +965,7 @@ async function listRecoverableCodexSessions(env = process.env) {
|
|
|
865
965
|
for (const entry of entries) {
|
|
866
966
|
if (entry.isSymbolicLink()) continue;
|
|
867
967
|
const name = String(entry.name);
|
|
868
|
-
const path =
|
|
968
|
+
const path = join5(currentDir, name);
|
|
869
969
|
if (entry.isDirectory()) {
|
|
870
970
|
await walk(path, depth + 1);
|
|
871
971
|
continue;
|
|
@@ -901,7 +1001,7 @@ async function findRecoverableCodexSessionPath(sessionId, env = process.env) {
|
|
|
901
1001
|
for (const entry of entries) {
|
|
902
1002
|
if (entry.isSymbolicLink()) continue;
|
|
903
1003
|
const name = String(entry.name);
|
|
904
|
-
const path =
|
|
1004
|
+
const path = join5(currentDir, name);
|
|
905
1005
|
if (entry.isDirectory()) {
|
|
906
1006
|
const found = await walk(path, depth + 1);
|
|
907
1007
|
if (found) return found;
|
|
@@ -925,7 +1025,7 @@ async function getCodexNativeVersion(input) {
|
|
|
925
1025
|
};
|
|
926
1026
|
}
|
|
927
1027
|
async function readCodexSessionIndex(env) {
|
|
928
|
-
const indexPath =
|
|
1028
|
+
const indexPath = join5(env.CODEX_HOME || join5(env.HOME || homedir3(), ".codex"), "session_index.jsonl");
|
|
929
1029
|
const index = /* @__PURE__ */ new Map();
|
|
930
1030
|
try {
|
|
931
1031
|
const content = await readFile3(indexPath, "utf8");
|
|
@@ -948,7 +1048,7 @@ async function readCodexSessionIndex(env) {
|
|
|
948
1048
|
return index;
|
|
949
1049
|
}
|
|
950
1050
|
function readCodexThreadMetadata(env) {
|
|
951
|
-
const dbPath =
|
|
1051
|
+
const dbPath = join5(env.CODEX_HOME || join5(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
|
|
952
1052
|
const metadata = /* @__PURE__ */ new Map();
|
|
953
1053
|
if (!existsSync4(dbPath)) return metadata;
|
|
954
1054
|
let db = null;
|
|
@@ -1009,9 +1109,9 @@ import { nanoid } from "nanoid";
|
|
|
1009
1109
|
// src/providers/codex/sessionIndex.ts
|
|
1010
1110
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
1011
1111
|
import { homedir as homedir4 } from "node:os";
|
|
1012
|
-
import { dirname as dirname3, join as
|
|
1112
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
1013
1113
|
function resolveCodexIndexPath(env = process.env) {
|
|
1014
|
-
return
|
|
1114
|
+
return join6(env.CODEX_HOME || join6(env.HOME || homedir4(), ".codex"), "session_index.jsonl");
|
|
1015
1115
|
}
|
|
1016
1116
|
async function upsertCodexSessionIndexEntry(input, env = process.env) {
|
|
1017
1117
|
const indexPath = resolveCodexIndexPath(env);
|
|
@@ -1057,9 +1157,9 @@ import Database2 from "better-sqlite3";
|
|
|
1057
1157
|
import { existsSync as existsSync5 } from "node:fs";
|
|
1058
1158
|
import { readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
1059
1159
|
import { homedir as homedir5 } from "node:os";
|
|
1060
|
-
import { join as
|
|
1160
|
+
import { join as join7 } from "node:path";
|
|
1061
1161
|
function resolveCodexStateDbPath(env = process.env) {
|
|
1062
|
-
return
|
|
1162
|
+
return join7(env.CODEX_HOME || join7(env.HOME || homedir5(), ".codex"), "state_5.sqlite");
|
|
1063
1163
|
}
|
|
1064
1164
|
async function syncCodexThreadForResume(input) {
|
|
1065
1165
|
const env = input.env ?? process.env;
|
|
@@ -1741,7 +1841,7 @@ function normalizeSettings(input, defaultWorkspace) {
|
|
|
1741
1841
|
// src/admin/fsBrowseRoutes.ts
|
|
1742
1842
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
1743
1843
|
import { homedir as homedir7 } from "node:os";
|
|
1744
|
-
import { dirname as dirname4, join as
|
|
1844
|
+
import { dirname as dirname4, join as join8, resolve } from "node:path";
|
|
1745
1845
|
|
|
1746
1846
|
// src/shared/expandTilde.ts
|
|
1747
1847
|
import { homedir as homedir6 } from "node:os";
|
|
@@ -1767,7 +1867,7 @@ function registerFsBrowseRoutes(input) {
|
|
|
1767
1867
|
reply.code(400);
|
|
1768
1868
|
return { error: "cannot_read_directory", path: target };
|
|
1769
1869
|
}
|
|
1770
|
-
const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => ({ name: dirent.name, path:
|
|
1870
|
+
const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => ({ name: dirent.name, path: join8(target, dirent.name), isDirectory: true })).filter((entry) => !entry.name.startsWith(".") || isOnKeepChain(entry.path)).sort((a, b) => a.name.localeCompare(b.name));
|
|
1771
1871
|
const parent = dirname4(target);
|
|
1772
1872
|
const isRoot = parent === target;
|
|
1773
1873
|
const listing = {
|
|
@@ -2048,7 +2148,7 @@ var WeixinDirectApiClient = class {
|
|
|
2048
2148
|
|
|
2049
2149
|
// src/channels/weixin-direct/adapter.ts
|
|
2050
2150
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
2051
|
-
import { join as
|
|
2151
|
+
import { join as join9 } from "node:path";
|
|
2052
2152
|
|
|
2053
2153
|
// src/channels/weixin-direct/typingController.ts
|
|
2054
2154
|
var CONFIG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2344,7 +2444,7 @@ var WeixinDirectAdapter = class _WeixinDirectAdapter {
|
|
|
2344
2444
|
continue;
|
|
2345
2445
|
}
|
|
2346
2446
|
const { ext, mimeType } = mediaExtAndMime(meta);
|
|
2347
|
-
const destPath =
|
|
2447
|
+
const destPath = join9(mediaDir, `${idPrefix}_${i}${ext}`);
|
|
2348
2448
|
const maxBytes = meta.kind === "video" ? VIDEO_MAX_BYTES : void 0;
|
|
2349
2449
|
const result = await downloader.download(meta.media ?? {}, { destPath, aeskeyOverride: meta.aeskey, maxBytes });
|
|
2350
2450
|
if (result.ok) {
|
|
@@ -2875,79 +2975,6 @@ function stamp(item) {
|
|
|
2875
2975
|
import { spawn as spawn3 } from "node:child_process";
|
|
2876
2976
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2877
2977
|
|
|
2878
|
-
// src/shared/platform.ts
|
|
2879
|
-
import { access, constants } from "node:fs/promises";
|
|
2880
|
-
import { delimiter, join as join9 } from "node:path";
|
|
2881
|
-
import { tmpdir } from "node:os";
|
|
2882
|
-
import { spawn } from "node:child_process";
|
|
2883
|
-
var isWindows = process.platform === "win32";
|
|
2884
|
-
async function isExecutableFile(candidate) {
|
|
2885
|
-
try {
|
|
2886
|
-
await access(candidate, isWindows ? constants.F_OK : constants.X_OK);
|
|
2887
|
-
return true;
|
|
2888
|
-
} catch {
|
|
2889
|
-
return false;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
function executableExtensions() {
|
|
2893
|
-
if (!isWindows) return [""];
|
|
2894
|
-
const pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
2895
|
-
return ["", ...pathext.split(";").filter(Boolean)];
|
|
2896
|
-
}
|
|
2897
|
-
function isTemporaryShim(candidate) {
|
|
2898
|
-
const tempDir = tmpdir();
|
|
2899
|
-
return candidate.startsWith(tempDir);
|
|
2900
|
-
}
|
|
2901
|
-
async function findExecutable(command, pathEnv) {
|
|
2902
|
-
const extensions = executableExtensions();
|
|
2903
|
-
if (command.includes("/") || command.includes("\\")) {
|
|
2904
|
-
for (const ext of extensions) {
|
|
2905
|
-
if (await isExecutableFile(command + ext)) return command + ext;
|
|
2906
|
-
}
|
|
2907
|
-
return void 0;
|
|
2908
|
-
}
|
|
2909
|
-
const dirs = (pathEnv ?? "").split(delimiter).filter(Boolean);
|
|
2910
|
-
let fallback;
|
|
2911
|
-
for (const dir of dirs) {
|
|
2912
|
-
for (const ext of extensions) {
|
|
2913
|
-
const candidate = join9(dir, command + ext);
|
|
2914
|
-
if (await isExecutableFile(candidate)) {
|
|
2915
|
-
if (isTemporaryShim(candidate)) {
|
|
2916
|
-
fallback ??= candidate;
|
|
2917
|
-
continue;
|
|
2918
|
-
}
|
|
2919
|
-
return candidate;
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
return fallback;
|
|
2924
|
-
}
|
|
2925
|
-
function terminateChild(child, signal = "SIGTERM") {
|
|
2926
|
-
if (child.pid === void 0 || child.exitCode !== null || child.signalCode !== null) {
|
|
2927
|
-
return;
|
|
2928
|
-
}
|
|
2929
|
-
if (isWindows) {
|
|
2930
|
-
try {
|
|
2931
|
-
spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
2932
|
-
return;
|
|
2933
|
-
} catch {
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
try {
|
|
2937
|
-
child.kill(signal);
|
|
2938
|
-
} catch {
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
function defaultWorkspaceDir() {
|
|
2942
|
-
return join9(tmpdir(), "project");
|
|
2943
|
-
}
|
|
2944
|
-
function useShellForCli() {
|
|
2945
|
-
return isWindows;
|
|
2946
|
-
}
|
|
2947
|
-
function statePath(filename) {
|
|
2948
|
-
return join9(tmpdir(), filename);
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
2978
|
// src/shared/loginShellEnv.ts
|
|
2952
2979
|
import { spawnSync } from "node:child_process";
|
|
2953
2980
|
import { delimiter as delimiter2 } from "node:path";
|
|
@@ -5608,9 +5635,11 @@ function createDaemonServer(options = {}) {
|
|
|
5608
5635
|
const serverDir = dirname8(fileURLToPath3(import.meta.url));
|
|
5609
5636
|
const mcpMediaServerTs = join13(serverDir, "..", "mcp", "mediaServer.ts");
|
|
5610
5637
|
const mcpMediaServerJs = join13(serverDir, "..", "mcp", "mediaServer.js");
|
|
5611
|
-
const
|
|
5612
|
-
|
|
5613
|
-
|
|
5638
|
+
const { command: mcpCommand, args: mcpArgs } = buildMcpServerCommand({
|
|
5639
|
+
tsEntry: mcpMediaServerTs,
|
|
5640
|
+
jsEntry: mcpMediaServerJs,
|
|
5641
|
+
exists: existsSync10
|
|
5642
|
+
});
|
|
5614
5643
|
writeFileSync2(mcpConfigPath, JSON.stringify({
|
|
5615
5644
|
mcpServers: {
|
|
5616
5645
|
"wechat-media": {
|