1use serde_json::json;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Platform {
31 ClaudeCode,
32 Codex,
33 Kimi,
34 Trae,
35 WorkBuddy,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum InitError {
41 UnknownPlatform(String),
42}
43
44impl InitError {
45 pub fn message(&self) -> String {
47 match self {
48 InitError::UnknownPlatform(input) => format!(
49 "未知平台 '{input}'。--platform 支持矩阵:\n \
50 claude-code → 项目根 .mcp.json(${{CLAUDE_PROJECT_DIR:-.}} 变量;W-19 实测)\n \
51 codex → config.toml [mcp_servers.wanning] 片段(无路径变量,占位符+注释;W-35)\n \
52 kimi → .kimi-code/mcp.json(无 type 无变量,占位符;W-40 实测)\n \
53 trae → .trae/mcp.json(${{workspaceFolder}} 变量;W-17)\n \
54 workbuddy → .workbuddy/mcp.json(无 type 无变量,占位符;W-37 直核)\n\
55 未知值 fail-closed,绝不猜。"
56 ),
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct Artifact {
64 pub notes: Vec<String>,
65 pub content: String,
66}
67
68pub fn parse_platform(input: &str) -> Result<Platform, InitError> {
70 match input {
71 "claude-code" => Ok(Platform::ClaudeCode),
72 "codex" => Ok(Platform::Codex),
73 "kimi" => Ok(Platform::Kimi),
74 "trae" => Ok(Platform::Trae),
75 "workbuddy" => Ok(Platform::WorkBuddy),
76 other => Err(InitError::UnknownPlatform(other.to_string())),
77 }
78}
79
80pub fn generate(platform: Platform) -> Artifact {
83 match platform {
84 Platform::ClaudeCode => claude_code(),
85 Platform::Trae => trae(),
86 Platform::Codex => codex(),
87 Platform::Kimi => kimi(),
88 Platform::WorkBuddy => workbuddy(),
89 }
90}
91
92fn single_writer_note() -> &'static str {
93 "多平台同挂一份 WAL 时,第二个写进程 fail-closed 拒启(W-18 单写者锁)是特性不是缺陷"
94}
95
96fn claude_code() -> Artifact {
97 let value = json!({
99 "mcpServers": {
100 "wanning": {
101 "type": "stdio",
102 "command": "cargo",
103 "args": [
104 "run", "--quiet", "-p", "wanning-mcp", "--",
105 "--wal", "${CLAUDE_PROJECT_DIR:-.}/target/mcp-demo.wal"
106 ]
107 }
108 }
109 });
110 let mut content = serde_json::to_string_pretty(&value).expect("静态 JSON 序列化");
111 content.push('\n');
112 Artifact {
113 notes: vec![
114 "Wanning 支付闸 — Claude Code MCP 配置(W-36 生成)".into(),
115 "写入位置:项目根 .mcp.json(与仓内现物逐字段契约测试锁定)".into(),
116 "WAL 落 ${CLAUDE_PROJECT_DIR:-.}/target/mcp-demo.wal —— 每个 Claude Code 项目目录一本账".into(),
117 single_writer_note().into(),
118 "严格 JSON 不支持注释 → 文件内无注释行;实测与语义见 docs/research/mcp-consumption.md".into(),
119 ],
120 content,
121 }
122}
123
124fn trae() -> Artifact {
125 let value = json!({
126 "mcpServers": {
127 "wanning": {
128 "command": "cargo",
129 "args": [
130 "run", "--quiet", "-p", "wanning-mcp", "--",
131 "--wal", "${workspaceFolder}/target/mcp-demo.wal"
132 ]
133 }
134 }
135 });
136 let mut content = serde_json::to_string_pretty(&value).expect("静态 JSON 序列化");
137 content.push('\n');
138 Artifact {
139 notes: vec![
140 "Wanning 支付闸 — Trae MCP 配置(W-36 生成)".into(),
141 "写入位置:项目根 .trae/mcp.json(与仓内现物逐字段契约测试锁定)".into(),
142 "WAL 落 ${workspaceFolder}/target/mcp-demo.wal —— 每个 Trae 工作区一本账".into(),
143 single_writer_note().into(),
144 "严格 JSON 不支持注释 → 文件内无注释行;路径变量依据 W-17 直核".into(),
145 ],
146 content,
147 }
148}
149
150fn codex() -> Artifact {
151 Artifact {
152 notes: vec![
153 "Wanning 支付闸 — Codex CLI MCP 配置片段(W-36 生成)".into(),
154 "codex 配置没有路径变量(W-35 直核)→ 占位符 {{WANNING_BIN}} / {{WAL_PATH}} 必须手改成绝对路径".into(),
155 single_writer_note().into(),
156 "会话级使用需 OpenAI 登录(doctor ✗ auth);配置面免登录已实测(W-35)".into(),
157 ],
158 content: concat!(
159 "# Wanning 支付闸 — Codex CLI MCP 配置片段(W-36 生成;字段依据 W-35 调研 docs/research/codex-mcp.md)\n",
160 "# 用法:追加到 ~/.codex/config.toml(全局)或 <repo>/.codex/config.toml(project-scoped,trust 机制待实测)\n",
161 "# W-35 直核:codex 配置没有路径变量 → 下面两个占位符必须手改成绝对路径:\n",
162 "# {{WANNING_BIN}} = wanning-mcp 可执行文件绝对路径(先在 Wanning 仓 cargo build -p wanning-mcp)\n",
163 "# {{WAL_PATH}} = 审计 WAL 绝对路径(如 D:\\path\\to\\Wanning\\target\\mcp-demo.wal)\n",
164 "# 并发语义:多平台同挂一份 WAL 时,第二个写进程 fail-closed 拒启(W-18 单写者锁)是特性\n",
165 "[mcp_servers.wanning]\n",
166 "command = '{{WANNING_BIN}}'\n",
167 "args = [\"--wal\", '{{WAL_PATH}}']\n",
168 "# 可选加固(文档字段,待 OpenAI 登录后实测):required = true —— server 起不来就 fail 启动,与闸 fail-closed 同构\n",
169 "# cargo run 备选形态与 startup_timeout_sec 说明见 docs/plugins/codex.md\n",
170 )
171 .to_string(),
172 }
173}
174
175fn kimi() -> Artifact {
176 let value = json!({
185 "mcpServers": {
186 "wanning": {
187 "command": "{{WANNING_BIN}}",
188 "args": ["--wal", "{{WAL_PATH}}"]
189 }
190 }
191 });
192 let mut content = serde_json::to_string_pretty(&value).expect("静态 JSON 序列化");
193 content.push('\n');
194 Artifact {
195 notes: vec![
196 "Wanning 支付闸 — Kimi Code CLI MCP 配置(W-36 生成,W-40 按本机实测修订)".into(),
197 "写入位置:用户级 ~/.kimi-code/mcp.json(或 $KIMI_CODE_HOME/mcp.json,所有项目生效)或 <repo>/.kimi-code/mcp.json(单项目)".into(),
198 "kimi-code 无 ${...} 路径变量(W-40 官方文档直核)→ 两个占位符必须手改成绝对路径:".into(),
199 " {{WANNING_BIN}} = wanning-mcp 可执行文件绝对路径(先在 Wanning 仓 cargo build -p wanning-mcp)".into(),
200 " {{WAL_PATH}} = 审计 WAL 绝对路径".into(),
201 "项目级 .kimi-code/mcp.json 在未信任目录会弹 workspace trust 提示(默认拒绝信任)——核对其中列出的命令后再确认;用户级挂法不经 trust 提示".into(),
202 "TUI 内交互管理:/mcp-config(增删改)、/mcp(看连接状态)".into(),
203 single_writer_note().into(),
204 "W-40 已实测:真 kimi 0.39.1 二进制拉起 wanning-mcp,工具注入 + 放行/重放拒/超额拒三判定落 WAL(模型侧为本地 mock,真实模型会话待 kimi 账号登录)".into(),
205 ],
206 content,
207 }
208}
209
210fn workbuddy() -> Artifact {
211 let value = json!({
215 "mcpServers": {
216 "wanning": {
217 "command": "cargo",
218 "args": [
219 "run", "--quiet", "-p", "wanning-mcp", "--",
220 "--wal", "{{WAL_PATH}}"
221 ]
222 }
223 }
224 });
225 let mut content = serde_json::to_string_pretty(&value).expect("静态 JSON 序列化");
226 content.push('\n');
227 Artifact {
228 notes: vec![
229 "Wanning 支付闸 — WorkBuddy MCP 配置(W-36 生成,字段依据 W-37 直核官方 MCP-Guide)".into(),
230 "写入位置:用户级 ~/.workbuddy/mcp.json(所有项目生效)或 <项目目录>/.workbuddy/mcp.json(单项目)"
231 .into(),
232 "WorkBuddy 文档未提及路径变量 → {{WAL_PATH}} 必须手改成审计 WAL 绝对路径".into(),
233 "官方示例字段面无 type(与 claude-code 现物带 type:stdio 是刻意差异);也可走 UI:侧边栏 插件 → MCP 服务器 → 配置 MCP".into(),
234 "传输形态按官方命令启动式示例推断 stdio,真插实测待老板桌面端(待实测项,boss-checklist)".into(),
235 single_writer_note().into(),
236 ],
237 content,
238 }
239}