@szc-ft/mcp-szcd-client 0.32.1 → 0.34.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@szc-ft/mcp-szcd-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "MCP client for szcd component library - auto-configures AI coding tools with MCP server, skills, agents and commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"bin": {
|
|
23
23
|
"szcd-mcp-proxy": "mcp-proxy.js",
|
|
24
24
|
"szcd-mcp-setup": "scripts/postinstall.js",
|
|
25
|
+
"szcd-mcp-install-shortcuts": "scripts/install-dev-shortcuts.js",
|
|
25
26
|
"szcd-mcp-update-url": "scripts/update-mcp-url.js",
|
|
26
27
|
"szcd-mcp-coding-config": "scripts/update-coding-config.js",
|
|
27
28
|
"szcd-mcp-api-config": "scripts/update-api-config.js",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"local-browser-executor.js",
|
|
33
34
|
"lib/",
|
|
34
35
|
"scripts/postinstall.js",
|
|
36
|
+
"scripts/install-dev-shortcuts.js",
|
|
35
37
|
"scripts/update-mcp-url.js",
|
|
36
38
|
"scripts/update-coding-config.js",
|
|
37
39
|
"scripts/update-api-config.js",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* install-dev-shortcuts.js — 手动重跑 dev-browser 快捷方式安装的 CLI
|
|
4
|
+
*
|
|
5
|
+
* 用途:postinstall 静默失败时(特别是 Windows 中文系统、AV 拦截、ExecutionPolicy 等)
|
|
6
|
+
* 用户跑 `npx szcd-mcp-install-shortcuts` 单独重试并看到完整错误信息。
|
|
7
|
+
*
|
|
8
|
+
* 与 postinstall 调用同一个 setupDevBrowserShortcut;本 CLI 区别在于:
|
|
9
|
+
* 1. 进程退出码:失败时 exit 1(postinstall 是 exit 0 不阻塞 npm install)
|
|
10
|
+
* 2. 输出更详细:直接传入 process 的 stdout / stderr,不被 npm install 折叠
|
|
11
|
+
* 3. 文末汇总:成功/失败计数 + 每条失败的完整错误,便于贴回 issue
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { setupDevBrowserShortcut } from "./lib/dev-browser-shortcut.js";
|
|
15
|
+
|
|
16
|
+
console.log("============================================================");
|
|
17
|
+
console.log(" szcd-mcp dev-browser DevSession shortcut installer");
|
|
18
|
+
console.log("============================================================");
|
|
19
|
+
console.log(`Platform: ${process.platform}`);
|
|
20
|
+
console.log(`Node: ${process.version}`);
|
|
21
|
+
console.log("");
|
|
22
|
+
|
|
23
|
+
const result = setupDevBrowserShortcut({});
|
|
24
|
+
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("============================================================");
|
|
27
|
+
console.log(" Summary");
|
|
28
|
+
console.log("============================================================");
|
|
29
|
+
if (result.skipped) {
|
|
30
|
+
console.log("⚠️ No Chrome/Edge detected on this machine.");
|
|
31
|
+
console.log(" Install Google Chrome or Microsoft Edge, then re-run this command.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const okCount = result.results.filter(r => r.ok).length;
|
|
36
|
+
const total = result.results.length;
|
|
37
|
+
console.log(`Installed: ${okCount}/${total}`);
|
|
38
|
+
for (const r of result.results) {
|
|
39
|
+
if (r.ok) {
|
|
40
|
+
console.log(` ✓ ${r.name}: ${r.path}`);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(` ✗ ${r.name}:`);
|
|
43
|
+
for (const line of String(r.error || "(unknown error)").split("\n")) {
|
|
44
|
+
console.log(` ${line}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log("");
|
|
49
|
+
|
|
50
|
+
process.exit(result.ok ? 0 : 1);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codex.js — OpenAI Codex CLI 兼容逻辑
|
|
3
|
+
*
|
|
4
|
+
* 导出 setupCodex(deps) 供 postinstall.js 调用。
|
|
5
|
+
*
|
|
6
|
+
* Codex CLI 关键事实(来源:github.com/openai/codex codex-rs/cli/src/mcp_cmd.rs +
|
|
7
|
+
* developers.openai.ac.cn/codex/skills + 本机 codex-cli 0.142.0 实测):
|
|
8
|
+
* - 安装:npm i -g @openai/codex(提供 `codex` 命令)
|
|
9
|
+
* - 配置文件:$CODEX_HOME/config.toml(默认 ~/.codex/config.toml),TOML 格式
|
|
10
|
+
* - MCP server 节段:[mcp_servers.<NAME>],扁平字段,无 transport 子表、无 type/enabled:
|
|
11
|
+
* · stdio:command = "...",可选 args/env
|
|
12
|
+
* · streamable_http:url = "...",可选 bearer_token_env_var
|
|
13
|
+
* CLI 通过是否存在 url/command 自动识别 transport 类型
|
|
14
|
+
* - 管理子命令:codex mcp { list | get | add | remove }
|
|
15
|
+
* - Skill 目录(用户级):$HOME/.agents/skills/<skill-name>/SKILL.md(不是 ~/.codex/skills/,
|
|
16
|
+
* 与 Codex CLI 自己的 home 分离;同一 .agents/skills/ 也被其它走 "open agent skills 规范"
|
|
17
|
+
* 的客户端共享)。Codex 还会读仓库级 .agents/skills,但 postinstall 不应碰用户仓库。
|
|
18
|
+
* - Skill 格式:目录形式,必含 SKILL.md(YAML frontmatter: name/description + Markdown 正文),
|
|
19
|
+
* 可选 scripts/、references/、assets/、agents/openai.yaml 等子目录。无需 CLI 注册,
|
|
20
|
+
* 放文件即生效;变更后重启 Codex。
|
|
21
|
+
*
|
|
22
|
+
* 本实现策略:
|
|
23
|
+
* 1. MCP:优先 spawn `codex mcp remove` 再 `codex mcp add`(CLI 自己做 TOML 合并 + 幂等)
|
|
24
|
+
* 2. MCP fallback:找不到 `codex` 时直接写 config.toml(扁平 TOML,与 CLI 序列化一致)
|
|
25
|
+
* 3. Skill:调用 deps.copySkill(...) 把 standard-skill/* 整目录拷到 ~/.agents/skills/
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import os from "node:os";
|
|
31
|
+
import { execSync } from "node:child_process";
|
|
32
|
+
import { getSketchMcpServerUrl, ADDITIONAL_MCP_SERVERS } from "./common.js";
|
|
33
|
+
|
|
34
|
+
// ==================== 路径工具 ====================
|
|
35
|
+
|
|
36
|
+
function getHomeDir() {
|
|
37
|
+
if (os.platform() === "win32") {
|
|
38
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
39
|
+
}
|
|
40
|
+
return process.env.HOME || os.homedir();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getCodexHome() {
|
|
44
|
+
return process.env.CODEX_HOME || path.join(getHomeDir(), ".codex");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getCodexConfigPath() {
|
|
48
|
+
return path.join(getCodexHome(), "config.toml");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isCodexCliAvailable() {
|
|
52
|
+
try {
|
|
53
|
+
execSync("codex --version", { stdio: "pipe", timeout: 5000 });
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ==================== TOML 序列化(最小子集,够本场景用) ====================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 把单个 mcp server 渲染成 TOML(与 codex CLI 实际序列化格式一致:扁平、无 transport 子表)。
|
|
64
|
+
*
|
|
65
|
+
* stdio:
|
|
66
|
+
* [mcp_servers.NAME]
|
|
67
|
+
* command = "..."
|
|
68
|
+
* args = ["...", "..."]
|
|
69
|
+
*
|
|
70
|
+
* streamable_http:
|
|
71
|
+
* [mcp_servers.NAME]
|
|
72
|
+
* url = "https://..."
|
|
73
|
+
*
|
|
74
|
+
* 注:codex CLI 0.142 实测产物不写 `type`、`enabled`、`transport`,
|
|
75
|
+
* 由它根据是否有 url/command 字段自动识别。手写 TOML 必须与 CLI 一致,
|
|
76
|
+
* 否则 CLI 下次 `mcp add/remove` 重写时会丢失字段。
|
|
77
|
+
*/
|
|
78
|
+
function renderStdioServerToml(name, command, args = []) {
|
|
79
|
+
const lines = [
|
|
80
|
+
`[mcp_servers.${name}]`,
|
|
81
|
+
`command = ${tomlString(command)}`,
|
|
82
|
+
];
|
|
83
|
+
if (args.length) {
|
|
84
|
+
lines.push(`args = [${args.map(tomlString).join(", ")}]`);
|
|
85
|
+
}
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderHttpServerToml(name, url, bearerTokenEnvVar) {
|
|
90
|
+
const lines = [
|
|
91
|
+
`[mcp_servers.${name}]`,
|
|
92
|
+
`url = ${tomlString(url)}`,
|
|
93
|
+
];
|
|
94
|
+
if (bearerTokenEnvVar) {
|
|
95
|
+
lines.push(`bearer_token_env_var = ${tomlString(bearerTokenEnvVar)}`);
|
|
96
|
+
}
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function tomlString(s) {
|
|
101
|
+
// basic TOML string: 双引号 + 转义反斜杠/双引号/换行
|
|
102
|
+
return '"' + String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n") + '"';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ==================== 文件级合并(CLI 不可用时回退) ====================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 读取已有 config.toml,移除本包负责的 [mcp_servers.<name>] / [mcp_servers.<name>.transport]
|
|
109
|
+
* 整段(直到下一个 [...]),然后追加新的渲染结果。
|
|
110
|
+
*
|
|
111
|
+
* 不引入完整 TOML 解析器(用户其它 server / 顶层 settings 用纯文本保留,最稳)。
|
|
112
|
+
*/
|
|
113
|
+
function upsertCodexConfig(configPath, ourServers) {
|
|
114
|
+
let existing = "";
|
|
115
|
+
if (fs.existsSync(configPath)) {
|
|
116
|
+
existing = fs.readFileSync(configPath, "utf8");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 移除我们要写的每个 server 的旧 [mcp_servers.NAME] 整段(直到下一个 [...] 或文件末)
|
|
120
|
+
for (const s of ourServers) {
|
|
121
|
+
const sectionRegex = new RegExp(
|
|
122
|
+
`(^|\\n)\\[mcp_servers\\.${escapeRegex(s.name)}\\][^\\n]*\\n` +
|
|
123
|
+
`(?:(?!\\n\\[)[\\s\\S])*?(?=\\n\\[|$)`,
|
|
124
|
+
"g"
|
|
125
|
+
);
|
|
126
|
+
existing = existing.replace(sectionRegex, "");
|
|
127
|
+
}
|
|
128
|
+
// 收尾:折叠多余空行 / 保证末尾有换行
|
|
129
|
+
existing = existing.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").trimEnd();
|
|
130
|
+
|
|
131
|
+
const newBlocks = ourServers.map(s => {
|
|
132
|
+
if (s.kind === "stdio") return renderStdioServerToml(s.name, s.command, s.args);
|
|
133
|
+
return renderHttpServerToml(s.name, s.url, s.bearerTokenEnvVar);
|
|
134
|
+
}).join("\n\n");
|
|
135
|
+
|
|
136
|
+
const sep = existing.length ? "\n\n" : "";
|
|
137
|
+
return existing + sep + newBlocks + "\n";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function escapeRegex(s) {
|
|
141
|
+
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ==================== CLI 优先路径 ====================
|
|
145
|
+
|
|
146
|
+
function addServerViaCli(server) {
|
|
147
|
+
// codex mcp add NAME --url URL (HTTP)
|
|
148
|
+
// codex mcp add NAME [--env K=V]... -- COMMAND ARGS (stdio)
|
|
149
|
+
let cmd;
|
|
150
|
+
if (server.kind === "http") {
|
|
151
|
+
cmd = `codex mcp add ${shellEscape(server.name)} --url ${shellEscape(server.url)}`;
|
|
152
|
+
} else {
|
|
153
|
+
const args = (server.args || []).map(shellEscape).join(" ");
|
|
154
|
+
cmd = `codex mcp add ${shellEscape(server.name)} -- ${shellEscape(server.command)}${args ? " " + args : ""}`;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
// 先 remove 再 add,保证幂等(add 在已存在时会报错)
|
|
158
|
+
execSync(`codex mcp remove ${shellEscape(server.name)}`, { stdio: "pipe", timeout: 8000 });
|
|
159
|
+
} catch { /* 没有就忽略 */ }
|
|
160
|
+
try {
|
|
161
|
+
execSync(cmd, { stdio: "pipe", timeout: 10000 });
|
|
162
|
+
return { ok: true, via: "cli" };
|
|
163
|
+
} catch (e) {
|
|
164
|
+
return { ok: false, via: "cli", error: String(e.stderr || e.message).slice(0, 300) };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function shellEscape(s) {
|
|
169
|
+
// 简单版:含空格/特殊字符的串用单引号包,单引号本身转义为 '"'"'
|
|
170
|
+
if (/^[A-Za-z0-9_\-./:@=]+$/.test(s)) return s;
|
|
171
|
+
return `'${String(s).replace(/'/g, `'"'"'`)}'`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ==================== Skill 复制 ====================
|
|
175
|
+
|
|
176
|
+
function getCodexSkillsDirectory() {
|
|
177
|
+
return path.join(getHomeDir(), ".agents", "skills");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function copySkillsToCodex(deps) {
|
|
181
|
+
if (typeof deps.discoverSkills !== "function" || typeof deps.copySkill !== "function") {
|
|
182
|
+
console.log("ℹ️ deps.discoverSkills / deps.copySkill not provided — skipping Codex skill copy");
|
|
183
|
+
return { ok: false, skipped: true };
|
|
184
|
+
}
|
|
185
|
+
const skillsRoot = getCodexSkillsDirectory();
|
|
186
|
+
const skills = deps.discoverSkills();
|
|
187
|
+
const results = [];
|
|
188
|
+
for (const skill of skills) {
|
|
189
|
+
const skillDir = path.join(skillsRoot, skill.name);
|
|
190
|
+
try {
|
|
191
|
+
deps.ensureDirectory?.(skillDir) ?? fs.mkdirSync(skillDir, { recursive: true });
|
|
192
|
+
const dest = path.join(skillDir, "SKILL.md");
|
|
193
|
+
deps.copySkill(skill.sourcePath, dest);
|
|
194
|
+
console.log(`✓ Copied skill "${skill.name}" to Codex (${skillDir})`);
|
|
195
|
+
results.push({ name: skill.name, ok: true, path: skillDir });
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.warn(`⚠️ Failed to copy skill "${skill.name}" to Codex: ${e.message}`);
|
|
198
|
+
results.push({ name: skill.name, ok: false, error: e.message });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { ok: results.every(r => r.ok), results, skillsRoot };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ==================== 主入口 ====================
|
|
205
|
+
|
|
206
|
+
export function setupCodex(deps) {
|
|
207
|
+
// 1) 构造我们要注册的两个 server
|
|
208
|
+
const httpUrl = (deps.getMcpServerUrl?.() || "http://localhost:3456") + "/mcp";
|
|
209
|
+
const serverName = deps.getMcpServerName?.() || "szcd-component-helper";
|
|
210
|
+
|
|
211
|
+
const ourServers = [
|
|
212
|
+
// szcd 主 server:HTTP(与其它 IDE 一致;本地 server 无鉴权,故不带 bearer)
|
|
213
|
+
{ kind: "http", name: serverName, url: httpUrl },
|
|
214
|
+
// sketch-mcp-server:stdio
|
|
215
|
+
...ADDITIONAL_MCP_SERVERS.filter(s => s.type === "stdio").map(s => ({
|
|
216
|
+
kind: "stdio", name: s.name, command: s.command, args: s.args || [],
|
|
217
|
+
})),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
// 2) MCP 注册
|
|
221
|
+
const mcpResult = registerMcpServers(ourServers);
|
|
222
|
+
|
|
223
|
+
// 3) Skill 复制(独立于 MCP 注册成败,文件操作本身基本不会失败)
|
|
224
|
+
const skillResult = copySkillsToCodex(deps);
|
|
225
|
+
|
|
226
|
+
return { mcp: mcpResult, skill: skillResult };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function registerMcpServers(ourServers) {
|
|
230
|
+
const cliAvailable = isCodexCliAvailable();
|
|
231
|
+
if (cliAvailable) {
|
|
232
|
+
console.log("✓ codex CLI detected — registering MCP servers via `codex mcp add`");
|
|
233
|
+
const cliResults = [];
|
|
234
|
+
for (const s of ourServers) {
|
|
235
|
+
const r = addServerViaCli(s);
|
|
236
|
+
cliResults.push({ name: s.name, ...r });
|
|
237
|
+
if (r.ok) console.log(` ✓ codex mcp add ${s.name} (${s.kind})`);
|
|
238
|
+
else console.warn(` ⚠️ codex mcp add ${s.name} failed: ${r.error}`);
|
|
239
|
+
}
|
|
240
|
+
const allOk = cliResults.every(r => r.ok);
|
|
241
|
+
if (allOk) return { ok: true, via: "cli", servers: cliResults };
|
|
242
|
+
console.log("→ CLI partial failure, falling back to direct config.toml edit");
|
|
243
|
+
} else {
|
|
244
|
+
console.log("ℹ️ codex CLI not installed (you can install via `npm i -g @openai/codex`)");
|
|
245
|
+
console.log(" Writing config.toml directly so it works once Codex is installed.");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 回退:直接写 config.toml
|
|
249
|
+
const configPath = getCodexConfigPath();
|
|
250
|
+
try {
|
|
251
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
252
|
+
const merged = upsertCodexConfig(configPath, ourServers);
|
|
253
|
+
fs.writeFileSync(configPath, merged);
|
|
254
|
+
console.log(`✓ Wrote Codex MCP config: ${configPath}`);
|
|
255
|
+
return { ok: true, via: "config-toml", configPath, servers: ourServers.map(s => s.name) };
|
|
256
|
+
} catch (e) {
|
|
257
|
+
console.warn(`⚠️ Failed to write ${configPath}: ${e.message}`);
|
|
258
|
+
return { ok: false, error: e.message };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import os from "node:os";
|
|
22
|
-
import { execSync } from "node:child_process";
|
|
22
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
23
23
|
|
|
24
24
|
// 与 SKILL.md 第 186-191 行保持一致
|
|
25
25
|
const CDP_PORT = 9222;
|
|
@@ -117,12 +117,40 @@ function installWindows(browser) {
|
|
|
117
117
|
].join("; ");
|
|
118
118
|
// -EncodedCommand 要求脚本块本身是 UTF-16LE base64
|
|
119
119
|
const encoded = Buffer.from(psScript, "utf16le").toString("base64");
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
|
|
121
|
+
// 反馈 #18(2026-06-24, accuracy=1):原先用
|
|
122
|
+
// execSync('powershell ... -EncodedCommand ...', { stdio: 'ignore' })
|
|
123
|
+
// 在中文 Windows + 全局 npm install 场景下静默失败:execSync 经 cmd.exe /d /s /c
|
|
124
|
+
// 调用 PowerShell,中文 Windows 下 PowerShell 启动期向 stderr 写 CLIXML
|
|
125
|
+
// (progress/information record),stdio:'ignore' 导致 stderr 写入失败,
|
|
126
|
+
// PowerShell 以 exit code 1 终止,CreateShortcut COM 调用未执行;
|
|
127
|
+
// 外层 try/catch 仅 console.warn,postinstall 整体仍成功,用户无法察觉。
|
|
128
|
+
//
|
|
129
|
+
// 修复:用 spawnSync 直接调 powershell.exe,绕开 cmd.exe;stderr/stdout 用 pipe
|
|
130
|
+
// 而不是 ignore,捕获真实失败信息以便排查。
|
|
131
|
+
const result = spawnSync(
|
|
132
|
+
"powershell.exe",
|
|
133
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded],
|
|
134
|
+
{ stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
135
|
+
);
|
|
136
|
+
if (result.error) {
|
|
137
|
+
return { ok: false, error: `spawn failed: ${result.error.message}` };
|
|
125
138
|
}
|
|
139
|
+
if (result.status !== 0) {
|
|
140
|
+
const stderr = (result.stderr?.toString() || "").trim();
|
|
141
|
+
const stdout = (result.stdout?.toString() || "").trim();
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
error: `powershell exit code ${result.status}` +
|
|
145
|
+
(stderr ? `\n stderr: ${stderr.slice(0, 400)}` : "") +
|
|
146
|
+
(stdout ? `\n stdout: ${stdout.slice(0, 200)}` : ""),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// 二次校验:文件实际存在再算成功
|
|
150
|
+
if (!fs.existsSync(lnkPath)) {
|
|
151
|
+
return { ok: false, error: `powershell exit 0 but ${lnkPath} not created` };
|
|
152
|
+
}
|
|
153
|
+
return { ok: true, path: lnkPath };
|
|
126
154
|
}
|
|
127
155
|
|
|
128
156
|
// 字符串 → UTF-8 bytes → base64,给 PowerShell 内联反序列化用
|
|
@@ -216,14 +244,15 @@ export function setupDevBrowserShortcut(deps = {}) {
|
|
|
216
244
|
installLinux;
|
|
217
245
|
for (const b of browsers) {
|
|
218
246
|
const r = install(b);
|
|
219
|
-
results.push({ browser: b.id, ...r });
|
|
247
|
+
results.push({ browser: b.id, name: b.name, ...r });
|
|
220
248
|
if (r.ok) {
|
|
221
249
|
console.log(`✓ Created ${b.name} DevSession shortcut: ${r.path}`);
|
|
222
250
|
} else {
|
|
223
|
-
console.warn(`⚠️ Failed to create ${b.name} DevSession shortcut
|
|
251
|
+
console.warn(`⚠️ Failed to create ${b.name} DevSession shortcut:\n ${r.error}`);
|
|
224
252
|
}
|
|
225
253
|
}
|
|
226
254
|
const okCount = results.filter(r => r.ok).length;
|
|
255
|
+
const failCount = results.length - okCount;
|
|
227
256
|
if (okCount > 0) {
|
|
228
257
|
const dirHint = platform === "win32" ? "桌面 (Desktop)"
|
|
229
258
|
: platform === "darwin" ? "~/Applications/ (Launchpad / Spotlight)"
|
|
@@ -231,7 +260,16 @@ export function setupDevBrowserShortcut(deps = {}) {
|
|
|
231
260
|
console.log(`\n💡 Double-click the "DevSession" shortcut in ${dirHint} to start a CDP-enabled browser`);
|
|
232
261
|
console.log(` on port ${CDP_PORT} with an isolated profile, then re-run your local-browser-test plan.`);
|
|
233
262
|
}
|
|
234
|
-
|
|
263
|
+
if (failCount > 0) {
|
|
264
|
+
// 醒目告警 + 手动重跑命令(反馈 #18 要求)
|
|
265
|
+
console.warn("");
|
|
266
|
+
console.warn("⚠️ ============================================================");
|
|
267
|
+
console.warn(`⚠️ ${failCount}/${results.length} dev-browser shortcut(s) failed to install.`);
|
|
268
|
+
console.warn("⚠️ Re-run the dedicated CLI to retry with full diagnostics:");
|
|
269
|
+
console.warn("⚠️ npx szcd-mcp-install-shortcuts");
|
|
270
|
+
console.warn("⚠️ ============================================================");
|
|
271
|
+
}
|
|
272
|
+
return { ok: okCount > 0 && failCount === 0, results, platform };
|
|
235
273
|
} catch (e) {
|
|
236
274
|
console.warn(`⚠️ setupDevBrowserShortcut error: ${e.message}`);
|
|
237
275
|
return { ok: false, results, error: e.message };
|
package/scripts/postinstall.js
CHANGED
|
@@ -24,6 +24,7 @@ import { setupClaudeCode } from "./lib/claude-code.js";
|
|
|
24
24
|
import { setupOpenCode } from "./lib/opencode.js";
|
|
25
25
|
import { setupQwenCode } from "./lib/qwen-code.js";
|
|
26
26
|
import { setupQoder } from "./lib/qoder.js";
|
|
27
|
+
import { setupCodex } from "./lib/codex.js";
|
|
27
28
|
import { setupDevBrowserShortcut } from "./lib/dev-browser-shortcut.js";
|
|
28
29
|
|
|
29
30
|
// 递归守卫在 common.js 加载时已执行
|
|
@@ -228,6 +229,10 @@ function main() {
|
|
|
228
229
|
console.log("\n🔧 Setting up Qoder CLI compatibility...\n");
|
|
229
230
|
const qoderResult = setupQoder(deps);
|
|
230
231
|
|
|
232
|
+
// ---- OpenAI Codex CLI ----
|
|
233
|
+
console.log("\n🔧 Setting up Codex CLI compatibility...\n");
|
|
234
|
+
setupCodex(deps);
|
|
235
|
+
|
|
231
236
|
// ---- Dev Browser Shortcut(local-browser-test 一键启动) ----
|
|
232
237
|
console.log("\n🔧 Setting up dev-browser shortcut for local-browser-test...\n");
|
|
233
238
|
setupDevBrowserShortcut(deps);
|