@hupan56/wlkj 2.6.0 → 2.7.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/bin/cli.js +261 -11
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -0
- package/templates/qoder/agents/insight-research.md +61 -0
- package/templates/qoder/agents/prd-reference.md +8 -2
- package/templates/qoder/commands/optional/wl-insight.md +275 -0
- package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
- package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
- package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
- package/templates/qoder/commands/wl-code.md +10 -2
- package/templates/qoder/commands/wl-commit.md +1 -1
- package/templates/qoder/commands/wl-design-draw.md +78 -0
- package/templates/qoder/commands/wl-design-scan.md +108 -0
- package/templates/qoder/commands/wl-design-spec.md +154 -0
- package/templates/qoder/commands/wl-design.md +32 -0
- package/templates/qoder/commands/wl-init.md +24 -3
- package/templates/qoder/commands/wl-prd-full.md +226 -0
- package/templates/qoder/commands/wl-prd-quick.md +134 -0
- package/templates/qoder/commands/wl-prd-review.md +104 -0
- package/templates/qoder/commands/wl-prd.md +17 -311
- package/templates/qoder/commands/wl-search.md +66 -30
- package/templates/qoder/commands/wl-task.md +290 -59
- package/templates/qoder/commands/wl-test.md +92 -24
- package/templates/qoder/config.yaml +51 -15
- package/templates/qoder/hooks/inject-workflow-state.py +2 -2
- package/templates/qoder/hooks/session-start.py +82 -51
- package/templates/qoder/rules/wl-pipeline.md +216 -105
- package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/archive_prd.py +377 -0
- package/templates/qoder/scripts/autotest.py +1715 -0
- package/templates/qoder/scripts/autotest_batch.py +224 -0
- package/templates/qoder/scripts/autotest_run.py +297 -0
- package/templates/qoder/scripts/benchmark.py +210 -209
- package/templates/qoder/scripts/build_style_index.py +444 -4
- package/templates/qoder/scripts/check_carriers.py +238 -0
- package/templates/qoder/scripts/check_mcp.py +298 -0
- package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
- package/templates/qoder/scripts/common/events.py +46 -0
- package/templates/qoder/scripts/common/extract.py +419 -0
- package/templates/qoder/scripts/common/graph_traverse.py +533 -0
- package/templates/qoder/scripts/common/paths.py +89 -0
- package/templates/qoder/scripts/common/pip_install.py +144 -0
- package/templates/qoder/scripts/common/platform_guard.py +61 -0
- package/templates/qoder/scripts/common/search_engine.py +205 -205
- package/templates/qoder/scripts/common/terms.py +57 -0
- package/templates/qoder/scripts/common/ts_extract.py +536 -0
- package/templates/qoder/scripts/context_pack.py +73 -13
- package/templates/qoder/scripts/enrich_prompt.py +226 -0
- package/templates/qoder/scripts/eval_prd.py +318 -231
- package/templates/qoder/scripts/export.py +487 -487
- package/templates/qoder/scripts/extract_api_params.py +246 -0
- package/templates/qoder/scripts/extract_routes.py +54 -0
- package/templates/qoder/scripts/extract_routes_tree.py +78 -0
- package/templates/qoder/scripts/fill_prototype.py +707 -0
- package/templates/qoder/scripts/gen_design_doc.py +394 -0
- package/templates/qoder/scripts/git_sync.py +27 -15
- package/templates/qoder/scripts/init_doctor.py +292 -40
- package/templates/qoder/scripts/install_qoderwork.py +366 -9
- package/templates/qoder/scripts/kg.py +708 -0
- package/templates/qoder/scripts/kg_auto_login.py +196 -0
- package/templates/qoder/scripts/kg_build.py +612 -0
- package/templates/qoder/scripts/kg_build_db.py +327 -0
- package/templates/qoder/scripts/kg_duckdb.py +549 -0
- package/templates/qoder/scripts/kg_incremental.py +393 -0
- package/templates/qoder/scripts/kg_link_db.py +224 -0
- package/templates/qoder/scripts/kg_mcp_server.py +801 -0
- package/templates/qoder/scripts/kg_semantic.py +150 -0
- package/templates/qoder/scripts/kg_test_runner.py +241 -0
- package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
- package/templates/qoder/scripts/learn.py +118 -39
- package/templates/qoder/scripts/learn_aggregate.py +201 -0
- package/templates/qoder/scripts/mcp_launcher.py +359 -0
- package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
- package/templates/qoder/scripts/repo_root.py +106 -0
- package/templates/qoder/scripts/role.py +12 -0
- package/templates/qoder/scripts/run_weekly_update.bat +5 -0
- package/templates/qoder/scripts/run_weekly_update.sh +5 -0
- package/templates/qoder/scripts/search_index.py +307 -60
- package/templates/qoder/scripts/secure-ls.js +5640 -0
- package/templates/qoder/scripts/setup.py +706 -641
- package/templates/qoder/scripts/setup_lanhu.py +963 -0
- package/templates/qoder/scripts/status.py +250 -11
- package/templates/qoder/scripts/sync_carriers.py +259 -0
- package/templates/qoder/scripts/syncgate.py +2 -2
- package/templates/qoder/scripts/task.py +75 -0
- package/templates/qoder/scripts/team_sync.py +60 -4
- package/templates/qoder/scripts/workspace_init.py +1 -1
- package/templates/qoder/skills/design-import/SKILL.md +226 -0
- package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
- package/templates/qoder/skills/design-review/SKILL.md +82 -25
- package/templates/qoder/skills/prd-generator/SKILL.md +184 -60
- package/templates/qoder/skills/prd-review/SKILL.md +18 -1
- package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
- package/templates/qoder/skills/test-generator/SKILL.md +15 -2
- package/templates/qoder/skills/wl-code/SKILL.md +55 -36
- package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
- package/templates/qoder/skills/wl-design/SKILL.md +55 -0
- package/templates/qoder/skills/wl-init/SKILL.md +76 -67
- package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
- package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
- package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
- package/templates/qoder/skills/wl-report/SKILL.md +131 -107
- package/templates/qoder/skills/wl-search/SKILL.md +141 -75
- package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
- package/templates/qoder/skills/wl-status/SKILL.md +83 -61
- package/templates/qoder/skills/wl-task/SKILL.md +132 -58
- package/templates/qoder/skills/wl-test/SKILL.md +406 -40
- package/templates/qoder/templates/prototype-app.html +13 -8
- package/templates/qoder/templates/prototype-web.html +376 -93
- package/templates/root/AGENTS.md +89 -34
- package/templates/root/requirements.txt +21 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
- package/templates/qoder/agents/prd-planning.md +0 -57
- package/templates/qoder/agents/prd-research.md +0 -34
- package/templates/qoder/commands/wl-insight.md +0 -51
- package/templates/qoder/scripts/__pycache__/init_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/setup.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/common/__pycache__/developer.cpython-39.pyc +0 -0
- package/templates/qoder/skills/wl-prd/SKILL.md +0 -92
|
@@ -23,6 +23,7 @@ QoderWork 从 %USERPROFILE%\\.qoderwork\\skills\\ 加载技能(每个子目录
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import argparse
|
|
26
|
+
import json
|
|
26
27
|
import os
|
|
27
28
|
import shutil
|
|
28
29
|
import subprocess
|
|
@@ -52,6 +53,50 @@ else:
|
|
|
52
53
|
_HOME = Path.home()
|
|
53
54
|
QODERWORK_SKILLS_DIR = _HOME / ".qoderwork" / "skills"
|
|
54
55
|
QODERWORK_COMMANDS_DIR = _HOME / ".qoderwork" / "commands"
|
|
56
|
+
QODERWORK_MCP_FILE = _HOME / ".qoderwork" / "mcp.json"
|
|
57
|
+
|
|
58
|
+
# Playwright MCP 配置 (让 QoderWork 的 AI 能用 Playwright 工具驱动浏览器, 快且稳)
|
|
59
|
+
# 可选增强: 不装 playwright 也不影响 QoderWork 自带连接器
|
|
60
|
+
# ⚠️ 实测: npx @playwright/mcp@latest 冷启动 11.7s, QoderWork 会超时杀掉 → 工具注册失败
|
|
61
|
+
# → AI 瞎猜工具名(BrowserNavigate) → 全失败。
|
|
62
|
+
# 解法: 优先用"全局已装的 cli.js + node 直启"(0.57s), 探测不到才回退 npx。
|
|
63
|
+
def _find_playwright_mcp_cli():
|
|
64
|
+
"""探测全局已装的 @playwright/mcp/cli.js 路径, 返回 (node, cli.js) 或 None。
|
|
65
|
+
Windows 下 npm 是 .cmd, 必须 shell=True 才能调到。"""
|
|
66
|
+
import shutil, subprocess
|
|
67
|
+
node = shutil.which("node")
|
|
68
|
+
if not node:
|
|
69
|
+
return None
|
|
70
|
+
# npm root -g 拿全局 node_modules 路径 (最准, 跨自定义prefix)
|
|
71
|
+
try:
|
|
72
|
+
out = subprocess.run("npm root -g", capture_output=True, text=True,
|
|
73
|
+
shell=True, timeout=10)
|
|
74
|
+
root = out.stdout.strip()
|
|
75
|
+
if root:
|
|
76
|
+
cli = os.path.join(root, "@playwright", "mcp", "cli.js")
|
|
77
|
+
if os.path.isfile(cli):
|
|
78
|
+
return node, cli
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
# 回退: 试常见全局路径
|
|
82
|
+
for cand in [os.path.join(os.path.expanduser("~"), "AppData", "Roaming", "npm",
|
|
83
|
+
"node_modules", "@playwright", "mcp", "cli.js")]:
|
|
84
|
+
if os.path.isfile(cand):
|
|
85
|
+
return node, cand
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _playwright_mcp_entry():
|
|
90
|
+
"""生成 playwright MCP 配置: 优先 node+cli.js(快), 回退 npx(慢但通用)。"""
|
|
91
|
+
found = _find_playwright_mcp_cli()
|
|
92
|
+
if found:
|
|
93
|
+
node, cli = found
|
|
94
|
+
return {"playwright": {"command": node, "args": [cli]}}
|
|
95
|
+
# 回退: npx (冷启动慢, 可能被 QoderWork 超时杀, 但通用)
|
|
96
|
+
return {"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
PLAYWRIGHT_MCP_ENTRY = _playwright_mcp_entry()
|
|
55
100
|
|
|
56
101
|
# 旧的错误路径 (迁移清理用)
|
|
57
102
|
LEGACY_WRONG_DIR = _HOME / ".qoderworkcn" / "skills"
|
|
@@ -197,6 +242,165 @@ def uninstall_one(name: str) -> str:
|
|
|
197
242
|
return f"error: {e}"
|
|
198
243
|
|
|
199
244
|
|
|
245
|
+
def setup_playwright_mcp(dry=False):
|
|
246
|
+
"""把 Playwright MCP 写进 ~/.qoderwork/mcp.json (可选增强, 不影响自带连接器)。
|
|
247
|
+
|
|
248
|
+
让 QoderWork 的 AI 能用 Playwright 工具(browser_navigate/click/type/screenshot)
|
|
249
|
+
驱动浏览器 —— 快(10s级 vs 自带连接器 5-8分钟)、稳(不折腾 tab)、不依赖扩展。
|
|
250
|
+
已有 playwright 配置则跳过(幂等); 没有则追加。
|
|
251
|
+
"""
|
|
252
|
+
import json
|
|
253
|
+
mcp_path = QODERWORK_MCP_FILE
|
|
254
|
+
# 读现有配置
|
|
255
|
+
existing = {}
|
|
256
|
+
if mcp_path.is_file():
|
|
257
|
+
try:
|
|
258
|
+
existing = json.loads(mcp_path.read_text(encoding="utf-8"))
|
|
259
|
+
if not isinstance(existing, dict):
|
|
260
|
+
existing = {}
|
|
261
|
+
except (json.JSONDecodeError, OSError):
|
|
262
|
+
existing = {}
|
|
263
|
+
servers = existing.get("mcpServers", {})
|
|
264
|
+
if not isinstance(servers, dict):
|
|
265
|
+
servers = {}
|
|
266
|
+
if "playwright" in servers:
|
|
267
|
+
# 已有配置: 若是旧的 npx@latest(慢, 易超时失败), 升级成 node+cli.js(快)
|
|
268
|
+
cur = servers["playwright"]
|
|
269
|
+
if cur.get("command") == "npx":
|
|
270
|
+
new_entry = PLAYWRIGHT_MCP_ENTRY["playwright"]
|
|
271
|
+
if new_entry.get("command") != "npx": # 探测到了 cli.js
|
|
272
|
+
if dry:
|
|
273
|
+
return "would-upgrade"
|
|
274
|
+
servers["playwright"] = new_entry
|
|
275
|
+
existing["mcpServers"] = servers
|
|
276
|
+
mcp_path.write_text(json.dumps(existing, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
277
|
+
return "upgraded"
|
|
278
|
+
return "ok-existing"
|
|
279
|
+
if dry:
|
|
280
|
+
return "would-add"
|
|
281
|
+
# 追加 playwright
|
|
282
|
+
servers["playwright"] = PLAYWRIGHT_MCP_ENTRY["playwright"]
|
|
283
|
+
existing["mcpServers"] = servers
|
|
284
|
+
mcp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
285
|
+
mcp_path.write_text(
|
|
286
|
+
json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
|
|
287
|
+
encoding="utf-8",
|
|
288
|
+
)
|
|
289
|
+
return "added"
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def setup_lanhu_mcp(dry=False):
|
|
293
|
+
"""注册蓝湖 MCP 到 ~/.qoderwork/mcp.json (可选增强, 设计稿直读)。
|
|
294
|
+
|
|
295
|
+
蓝湖 MCP (dsphper/lanhu-mcp) 是第三方 HTTP server, 让设计师发蓝湖链接,
|
|
296
|
+
AI 直读设计稿(颜色/尺寸/字体/切图)。注册成 url 模式 (Streamable HTTP),
|
|
297
|
+
跟 playwright/kg/mysql 这些 stdio server 并列。
|
|
298
|
+
|
|
299
|
+
注意: 蓝湖是 HTTP server, 需要用户手动启动
|
|
300
|
+
python .qoder/scripts/setup_lanhu.py start
|
|
301
|
+
(启动脚本自动读当前开发者的 cookie 注入, 不用手动配 .env)
|
|
302
|
+
本函数只注册 url (幂等), 不负责 clone/装依赖/启动 —— 那些由 setup_lanhu.py
|
|
303
|
+
在 setup.py Step 4.5 完成。这里只做 mcp.json 条目同步, 保证团队所有人
|
|
304
|
+
跑 install_qoderwork 后都能看到蓝湖 server。
|
|
305
|
+
|
|
306
|
+
返回: 'ok-existing' / 'added' / 'would-add'
|
|
307
|
+
"""
|
|
308
|
+
# 复用 setup_lanhu.py 的注册逻辑 (单一事实源)
|
|
309
|
+
try:
|
|
310
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
311
|
+
from setup_lanhu import register_lanhu_mcp
|
|
312
|
+
except ImportError:
|
|
313
|
+
return 'would-add'
|
|
314
|
+
return register_lanhu_mcp(developer_name='Developer', dry=dry)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def detect_python_command():
|
|
318
|
+
"""探测本机可用的 python 命令名 (跨平台)。
|
|
319
|
+
Windows: py > python (无 python3); mac/linux: python3 > python。
|
|
320
|
+
返回命令名字符串 (在 PATH 里的), 失败返回 None。"""
|
|
321
|
+
for cand in (('py', '-3'), ('python3', None), ('python', None)):
|
|
322
|
+
name, extra = cand
|
|
323
|
+
if not shutil.which(name):
|
|
324
|
+
continue
|
|
325
|
+
cmd = [name] + ([extra] if extra else []) + ['--version']
|
|
326
|
+
try:
|
|
327
|
+
r = subprocess.run(cmd, capture_output=True, text=True,
|
|
328
|
+
encoding='utf-8', errors='replace')
|
|
329
|
+
if r.returncode == 0:
|
|
330
|
+
# 返回完整调用前缀 (py -3 / python3 / python)
|
|
331
|
+
return ' '.join(cmd[:-1])
|
|
332
|
+
except (OSError, subprocess.SubprocessError):
|
|
333
|
+
continue
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def rewrite_mcp_json_for_launcher():
|
|
338
|
+
"""把 mcp.json 里所有 python MCP (kg/mysql/lanhu) 改成 launcher 模式。
|
|
339
|
+
|
|
340
|
+
旧 (写死路径): {"command": "E:\\...\\python.exe", "args": ["E:\\...\\server.py"]}
|
|
341
|
+
新 (launcher): {"command": "<py cmd>", "args": ["<home>/.qoderwork/mcp_launcher.py", "<name>"]}
|
|
342
|
+
|
|
343
|
+
幂等: 已是 launcher 模式 (args 含 mcp_launcher.py) 则跳过。
|
|
344
|
+
全新机器: mcp.json 不存在时创建空骨架 + 写入 kg/mysql/lanhu 条目。
|
|
345
|
+
mysql env 抽到 ~/.qoderwork/.mcp-mysql-env.json (launcher 读)。
|
|
346
|
+
"""
|
|
347
|
+
# 全新机器: mcp.json 不存在 → 创建空骨架 (playwright 后续会追加)
|
|
348
|
+
if not QODERWORK_MCP_FILE.is_file():
|
|
349
|
+
QODERWORK_MCP_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
350
|
+
QODERWORK_MCP_FILE.write_text(
|
|
351
|
+
'{"mcpServers": {}}\n', encoding="utf-8")
|
|
352
|
+
try:
|
|
353
|
+
cfg = json.loads(QODERWORK_MCP_FILE.read_text(encoding="utf-8"))
|
|
354
|
+
except (json.JSONDecodeError, OSError):
|
|
355
|
+
return
|
|
356
|
+
servers = cfg.get("mcpServers", {})
|
|
357
|
+
if not isinstance(servers, dict):
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
py_cmd = detect_python_command() or "python"
|
|
361
|
+
launcher = str(_HOME / ".qoderwork" / "mcp_launcher.py")
|
|
362
|
+
# MCP 名 → launcher 子命令
|
|
363
|
+
name_map = {
|
|
364
|
+
"qoder-knowledge-graph": "kg",
|
|
365
|
+
"qoder-mysql": "mysql",
|
|
366
|
+
"lanhu": "lanhu",
|
|
367
|
+
}
|
|
368
|
+
changed = False
|
|
369
|
+
for srv, sub in name_map.items():
|
|
370
|
+
entry = servers.get(srv)
|
|
371
|
+
# 全新机器: kg/mysql/lanhu 条目可能不存在 → 创建它 (launcher 模式)
|
|
372
|
+
if not entry:
|
|
373
|
+
servers[srv] = {"command": py_cmd, "args": [launcher, sub]}
|
|
374
|
+
changed = True
|
|
375
|
+
continue
|
|
376
|
+
# 已是 launcher 模式?
|
|
377
|
+
args = entry.get("args", []) if isinstance(entry, dict) else []
|
|
378
|
+
if args and any("mcp_launcher.py" in str(a) for a in args):
|
|
379
|
+
continue
|
|
380
|
+
# mysql env 抽离到独立文件
|
|
381
|
+
if srv == "qoder-mysql" and isinstance(entry, dict) and entry.get("env"):
|
|
382
|
+
env_file = _HOME / ".qoderwork" / ".mcp-mysql-env.json"
|
|
383
|
+
try:
|
|
384
|
+
env_file.write_text(json.dumps(entry["env"], indent=2, ensure_ascii=False) + "\n",
|
|
385
|
+
encoding="utf-8")
|
|
386
|
+
except OSError:
|
|
387
|
+
pass
|
|
388
|
+
# 改成 launcher 模式
|
|
389
|
+
servers[srv] = {"command": py_cmd, "args": [launcher, sub]}
|
|
390
|
+
changed = True
|
|
391
|
+
|
|
392
|
+
if changed:
|
|
393
|
+
cfg["mcpServers"] = servers
|
|
394
|
+
try:
|
|
395
|
+
QODERWORK_MCP_FILE.write_text(
|
|
396
|
+
json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
397
|
+
print(f"\n--- mcp.json 重写为 launcher 模式 ---")
|
|
398
|
+
print(f" [UPD] kg/mysql/lanhu → command={py_cmd}, args=[mcp_launcher.py, <名>]")
|
|
399
|
+
print(f" 不再写死仓库路径/venv路径, 跨机器通用")
|
|
400
|
+
except OSError as e:
|
|
401
|
+
print(f" [ERR] mcp.json 重写失败: {e}")
|
|
402
|
+
|
|
403
|
+
|
|
200
404
|
def main():
|
|
201
405
|
parser = argparse.ArgumentParser(
|
|
202
406
|
description="把 .qoder/skills/ 安装到 QoderWork 桌面端目录"
|
|
@@ -209,8 +413,49 @@ def main():
|
|
|
209
413
|
help="用拷贝代替 junction(非 Windows 或不想软链时用)")
|
|
210
414
|
parser.add_argument("--force-commands", action="store_true",
|
|
211
415
|
help="强制覆盖已存在的 command 文件(升级时用;默认已存在则跳过)")
|
|
416
|
+
parser.add_argument("--mcp-only", action="store_true",
|
|
417
|
+
help="只刷新 launcher + mcp.json,不装 skill(update 时调)")
|
|
212
418
|
args = parser.parse_args()
|
|
213
419
|
|
|
420
|
+
# --mcp-only: 轻量模式, 只刷 launcher + mcp.json, 不装 skill
|
|
421
|
+
if args.mcp_only:
|
|
422
|
+
print("=" * 56)
|
|
423
|
+
print("MCP 配置刷新 (--mcp-only)")
|
|
424
|
+
print("=" * 56)
|
|
425
|
+
# 1. 复制 launcher
|
|
426
|
+
launcher_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "mcp_launcher.py"
|
|
427
|
+
launcher_dst = _HOME / ".qoderwork" / "mcp_launcher.py"
|
|
428
|
+
try:
|
|
429
|
+
launcher_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
430
|
+
if launcher_src.is_file():
|
|
431
|
+
import shutil as _sh
|
|
432
|
+
_sh.copy2(str(launcher_src), str(launcher_dst))
|
|
433
|
+
print(f" [OK] launcher 已刷新: {launcher_dst}")
|
|
434
|
+
else:
|
|
435
|
+
print(f" [ERR] 源不存在: {launcher_src}")
|
|
436
|
+
except OSError as e:
|
|
437
|
+
print(f" [ERR] 复制失败: {e}")
|
|
438
|
+
# 2. 重写 mcp.json 为 launcher 模式 (创建 kg/mysql/lanhu)
|
|
439
|
+
rewrite_mcp_json_for_launcher()
|
|
440
|
+
# 3. 补 playwright (rewrite 不含它)
|
|
441
|
+
try:
|
|
442
|
+
setup_playwright_mcp(dry=False)
|
|
443
|
+
except Exception:
|
|
444
|
+
pass
|
|
445
|
+
# 4. 写 .repo-root 锚点 (launcher 定位仓库根用)
|
|
446
|
+
# 用 PROJECT_ROOT (脚本自身定位的仓库根) 而非 cwd —— 修"安装目录不一致"bug:
|
|
447
|
+
# 用户可能从任意目录跑 install_qoderwork (home/其他项目), cwd ≠ 仓库根,
|
|
448
|
+
# 写 cwd 会让 .repo-root 指向错误目录 → MCP 全找不到仓库。
|
|
449
|
+
# PROJECT_ROOT 由本文件位置推出 (scripts/ -> .qoder/ -> repo), 与 cwd 无关。
|
|
450
|
+
anchor = _HOME / ".qoderwork" / ".repo-root"
|
|
451
|
+
try:
|
|
452
|
+
anchor.parent.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
anchor.write_text(str(PROJECT_ROOT), encoding="utf-8")
|
|
454
|
+
print(f" [OK] .repo-root 锚点: {anchor} -> {PROJECT_ROOT}")
|
|
455
|
+
except OSError:
|
|
456
|
+
pass
|
|
457
|
+
return
|
|
458
|
+
|
|
214
459
|
print("=" * 56)
|
|
215
460
|
print("QoderWork 技能安装器")
|
|
216
461
|
print(f" 源: {SOURCE_SKILLS_DIR}")
|
|
@@ -308,8 +553,8 @@ def main():
|
|
|
308
553
|
if action in ("install", "check") and SOURCE_COMMANDS_DIR.is_dir():
|
|
309
554
|
print("\n--- Commands (/wl-* 斜杠命令) ---")
|
|
310
555
|
cmd_count = {"ok": 0, "new": 0, "upd": 0}
|
|
311
|
-
for cmd_file in sorted(SOURCE_COMMANDS_DIR.
|
|
312
|
-
name = cmd_file.name # e.g. wl-prd.md
|
|
556
|
+
for cmd_file in sorted(SOURCE_COMMANDS_DIR.rglob("*.md")):
|
|
557
|
+
name = cmd_file.name # e.g. wl-prd.md (子目录归类的也平铺到目标)
|
|
313
558
|
target = QODERWORK_COMMANDS_DIR / name
|
|
314
559
|
if action == "check":
|
|
315
560
|
if target.exists():
|
|
@@ -318,19 +563,21 @@ def main():
|
|
|
318
563
|
print(f" [MISS] {name}")
|
|
319
564
|
else: # install
|
|
320
565
|
need_copy = False
|
|
566
|
+
is_update = False
|
|
321
567
|
if not target.exists():
|
|
322
568
|
need_copy = True
|
|
323
|
-
|
|
324
|
-
#
|
|
325
|
-
|
|
569
|
+
else:
|
|
570
|
+
# 始终按内容同步(幂等): 不同就刷新。
|
|
571
|
+
# command 是纯文本小文件, 内容不同说明源已升级, 必须覆盖,
|
|
572
|
+
# 否则 QoderWork 用旧 command → 走错流程(如旧 wl-test 搜代码)。
|
|
326
573
|
if not file_equal(cmd_file, target):
|
|
327
574
|
need_copy = True
|
|
575
|
+
is_update = True
|
|
328
576
|
if need_copy:
|
|
329
577
|
try:
|
|
330
578
|
QODERWORK_COMMANDS_DIR.mkdir(parents=True, exist_ok=True)
|
|
331
|
-
import shutil
|
|
332
579
|
shutil.copy2(str(cmd_file), str(target))
|
|
333
|
-
if
|
|
580
|
+
if is_update:
|
|
334
581
|
cmd_count["upd"] += 1
|
|
335
582
|
else:
|
|
336
583
|
cmd_count["new"] += 1
|
|
@@ -343,11 +590,121 @@ def main():
|
|
|
343
590
|
msg += f" / {cmd_count['upd']} 刷新"
|
|
344
591
|
print(msg)
|
|
345
592
|
|
|
593
|
+
# === 2.5. repo_root.py (QoderWork 桌面端仓库根定位器) ===
|
|
594
|
+
# QoderWork 桌面端工作目录是临时会话目录, 不是仓库根, 相对路径失效。
|
|
595
|
+
# repo_root.py 从 mcp.json 反推仓库根, 命令文件靠它定位脚本/模板。
|
|
596
|
+
if action in ("install", "check"):
|
|
597
|
+
repo_root_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "repo_root.py"
|
|
598
|
+
repo_root_dst = _HOME / ".qoderwork" / "repo_root.py"
|
|
599
|
+
if repo_root_src.is_file():
|
|
600
|
+
if action == "check":
|
|
601
|
+
if repo_root_dst.exists():
|
|
602
|
+
print(f"\n--- repo_root.py (仓库根定位器) ---")
|
|
603
|
+
print(f" [OK] {repo_root_dst}")
|
|
604
|
+
else:
|
|
605
|
+
print(f"\n--- repo_root.py (仓库根定位器) ---")
|
|
606
|
+
print(f" [MISS] 未安装到 {repo_root_dst}")
|
|
607
|
+
else:
|
|
608
|
+
try:
|
|
609
|
+
repo_root_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
610
|
+
shutil.copy2(str(repo_root_src), str(repo_root_dst))
|
|
611
|
+
print(f"\n--- repo_root.py (仓库根定位器) ---")
|
|
612
|
+
print(f" [OK] 已安装: {repo_root_dst}")
|
|
613
|
+
except OSError as e:
|
|
614
|
+
print(f"\n--- repo_root.py (仓库根定位器) ---")
|
|
615
|
+
print(f" [ERR] {e}")
|
|
616
|
+
|
|
617
|
+
# === 2.6. mcp_launcher.py + .repo-root (统一 MCP 启动器, 消除写死路径) ===
|
|
618
|
+
# mcp_launcher.py 让 mcp.json 不再写死仓库路径/venv路径, 跨 Win/Mac 通用。
|
|
619
|
+
# .repo-root 锚点文件让 launcher 能定位仓库根 (不依赖 mcp.json 里的路径)。
|
|
620
|
+
if action in ("install", "check"):
|
|
621
|
+
print(f"\n--- mcp_launcher.py (统一 MCP 启动器) ---")
|
|
622
|
+
launcher_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "mcp_launcher.py"
|
|
623
|
+
launcher_dst = _HOME / ".qoderwork" / "mcp_launcher.py"
|
|
624
|
+
anchor_dst = _HOME / ".qoderwork" / ".repo-root"
|
|
625
|
+
if action == "check":
|
|
626
|
+
if launcher_dst.exists():
|
|
627
|
+
print(f" [OK] launcher 已装: {launcher_dst}")
|
|
628
|
+
else:
|
|
629
|
+
print(f" [MISS] launcher 未装")
|
|
630
|
+
if anchor_dst.exists():
|
|
631
|
+
print(f" [OK] .repo-root 锚点在: {anchor_dst}")
|
|
632
|
+
else:
|
|
633
|
+
print(f" [MISS] .repo-root 锚点缺失 (launcher 会回退向上找)")
|
|
634
|
+
else:
|
|
635
|
+
try:
|
|
636
|
+
launcher_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
637
|
+
if launcher_src.is_file():
|
|
638
|
+
shutil.copy2(str(launcher_src), str(launcher_dst))
|
|
639
|
+
print(f" [OK] launcher 已装: {launcher_dst}")
|
|
640
|
+
else:
|
|
641
|
+
print(f" [ERR] 源不存在: {launcher_src}")
|
|
642
|
+
# 写 .repo-root 锚点 (记录当前仓库根, launcher 读它定位)
|
|
643
|
+
anchor_dst.write_text(str(PROJECT_ROOT) + "\n", encoding="utf-8")
|
|
644
|
+
print(f" [OK] .repo-root 锚点已写: {PROJECT_ROOT}")
|
|
645
|
+
except OSError as e:
|
|
646
|
+
print(f" [ERR] {e}")
|
|
647
|
+
|
|
648
|
+
# === 2.7. 重写 mcp.json: 所有 python MCP 改 launcher 模式 (消除写死路径) ===
|
|
649
|
+
if action == "install":
|
|
650
|
+
rewrite_mcp_json_for_launcher()
|
|
651
|
+
|
|
652
|
+
# === 3. Playwright MCP (可选增强: 让 QoderWork 能用 Playwright 驱动浏览器) ===
|
|
653
|
+
if action in ("install", "check"):
|
|
654
|
+
print("\n--- Playwright MCP (浏览器自动化增强, 可选) ---")
|
|
655
|
+
pw_status = setup_playwright_mcp(dry=(action == "check"))
|
|
656
|
+
if pw_status == "ok-existing":
|
|
657
|
+
print(f" [OK] playwright MCP 已配置: {QODERWORK_MCP_FILE}")
|
|
658
|
+
elif pw_status == "added":
|
|
659
|
+
print(f" [NEW] 已写入 playwright MCP: {QODERWORK_MCP_FILE}")
|
|
660
|
+
print(" QoderWork 重启后, AI 可用 Playwright 工具驱动浏览器(快、稳)")
|
|
661
|
+
elif pw_status == "upgraded":
|
|
662
|
+
print(f" [FIX] playwright MCP 已升级: npx(慢,易超时) → node+cli.js(0.5s启动)")
|
|
663
|
+
print(" ⚠️ 必须 重启 QoderWork 生效。旧 npx 配置会被 QoderWork 超时杀掉导致工具注册失败")
|
|
664
|
+
elif pw_status == "would-upgrade":
|
|
665
|
+
print(f" [FIX] 检测到旧 npx 配置(慢), 安装时会升级为 node+cli.js")
|
|
666
|
+
elif pw_status == "would-add":
|
|
667
|
+
print(f" [MISS] 未配置 playwright MCP, 安装时会写入 {QODERWORK_MCP_FILE}")
|
|
668
|
+
# 检查 playwright mcp 是否已装(优先看 cli.js, 决定能否走快路径)
|
|
669
|
+
pw_cli = _find_playwright_mcp_cli()
|
|
670
|
+
if pw_cli:
|
|
671
|
+
print(f" 启动方式: node + cli.js (0.5s, 快) ← {pw_cli[1]}")
|
|
672
|
+
else:
|
|
673
|
+
npx_ok = shutil.which("npx") is not None
|
|
674
|
+
print(f" 启动方式: {'npx (11s冷启动, 可能被QoderWork超时杀)' if npx_ok else '✗ 未装 Node.js'}")
|
|
675
|
+
if npx_ok:
|
|
676
|
+
print(" 提示: 装 @playwright/mcp 全局后重跑本脚本, 会自动切换到快的 node 启动: npm i -g @playwright/mcp")
|
|
677
|
+
|
|
678
|
+
# === 4. 蓝湖 MCP (可选增强: 设计师发蓝湖链接, AI 直读设计稿) ===
|
|
679
|
+
if action in ("install", "check"):
|
|
680
|
+
print("\n--- 蓝湖 MCP (设计稿直读增强, 可选) ---")
|
|
681
|
+
lh_status = setup_lanhu_mcp(dry=(action == "check"))
|
|
682
|
+
if lh_status == "ok-existing":
|
|
683
|
+
print(f" [OK] 蓝湖 MCP 已注册: {QODERWORK_MCP_FILE}")
|
|
684
|
+
elif lh_status == "added":
|
|
685
|
+
print(f" [NEW] 已写入蓝湖 MCP (STDIO): {QODERWORK_MCP_FILE}")
|
|
686
|
+
print(" QoderWork 开自动起、关自动停; 命令内授权调 mcp__lanhu__*")
|
|
687
|
+
elif lh_status == "upgraded":
|
|
688
|
+
print(f" [UPD] 蓝湖 MCP 升级 HTTP→STDIO: {QODERWORK_MCP_FILE}")
|
|
689
|
+
print(" 重启 QoderWork 后自动开关, 无需手动 start")
|
|
690
|
+
elif lh_status == "would-add":
|
|
691
|
+
print(f" [MISS] 未注册蓝湖 MCP, 安装时会写入 {QODERWORK_MCP_FILE}")
|
|
692
|
+
# STDIO 模式由 QoderWork 自动管, 不查端口; 只确认 wrapper + 源码就绪
|
|
693
|
+
wrapper = PROJECT_ROOT / ".qoder" / "scripts" / "lanhu_stdio_wrapper.py"
|
|
694
|
+
lanhu_dir = PROJECT_ROOT / "external" / "lanhu-mcp"
|
|
695
|
+
if wrapper.is_file():
|
|
696
|
+
print(f" wrapper 就绪: {wrapper.name} (按角色读 cookie)")
|
|
697
|
+
else:
|
|
698
|
+
print(f" [MISS] wrapper 缺失: {wrapper}")
|
|
699
|
+
if not lanhu_dir.is_dir():
|
|
700
|
+
print(" 蓝湖源码: 未 clone (跑 setup.py 或 setup_lanhu.py 安装)")
|
|
701
|
+
|
|
346
702
|
if action == "install" and counts["err"] == 0:
|
|
347
703
|
print("\n✓ 安装完成。重启 QoderWork(或新建对话)后技能生效。")
|
|
348
704
|
print(" 在 QoderWork 里直接用自然语言即可,例如:")
|
|
349
|
-
print(' "写个保险异常筛选的 PRD" -> prd-
|
|
350
|
-
print(' "
|
|
705
|
+
print(' "写个保险异常筛选的 PRD" -> /wl-prd-full(正经需求)')
|
|
706
|
+
print(' "列表加个导出按钮" -> /wl-prd-quick(小改动)')
|
|
707
|
+
print(' "查一下考勤代码在哪" -> /wl-search')
|
|
351
708
|
elif action == "install" and counts["err"] > 0:
|
|
352
709
|
print(f"\n⚠ 有 {counts['err']} 个安装失败,见上方 [ERR] 行。")
|
|
353
710
|
print(" 常见原因: 目标路径被占用为非软链。手动删除该目录后重跑本脚本。")
|