Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:
- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
- docx family: kept docx (official, full toolchain) + docx-cn
(GB/T 9704 Chinese official-document constants),
dropped docx_writer (no scripts, name collided with docx)
- humanizer family: kept humanizer-zh (6 zh reference docs),
dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
live credentials
Result: 25 skills, 572 files, ~7.5 MB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
354 lines
11 KiB
Python
354 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
WindPy 环境检测与配置(macOS + Windows)。
|
||
|
||
核心功能: 检查 WindPy 是否可 import,不可时尝试自动修复。
|
||
|
||
macOS:
|
||
WindPy.py 在 Wind API.app 内部,不在 site-packages。
|
||
修复方式: 创建 symlink 到 site-packages + 链接 ~/.Wind 配置。
|
||
|
||
Windows:
|
||
Wind 终端安装时会将 WindPy 注册到 Python 路径。
|
||
如果不可 import,说明安装不完整,提示用户重新配置。
|
||
|
||
用法:
|
||
# 作为模块
|
||
from setup_windpy import ensure_windpy
|
||
ensure_windpy()
|
||
from WindPy import w
|
||
|
||
# 作为脚本
|
||
python setup_windpy.py # 检测
|
||
python setup_windpy.py --fix # 检测 + 修复
|
||
python setup_windpy.py --verify # 检测 + 连接验证
|
||
"""
|
||
|
||
import os
|
||
import platform
|
||
import site
|
||
import sys
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# macOS 常量
|
||
# ---------------------------------------------------------------------------
|
||
WIND_APP_PATH = "/Applications/Wind API.app"
|
||
WIND_WINDPY_SOURCE_MAC = os.path.join(WIND_APP_PATH, "Contents", "python", "WindPy.py")
|
||
WIND_DOT_DIR_CONTAINER = os.path.expanduser(
|
||
"~/Library/Containers/com.wind.mac.api/Data/.Wind"
|
||
)
|
||
WIND_DOT_DIR_HOME = os.path.expanduser("~/.Wind")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 平台判断
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def is_macos() -> bool:
|
||
return platform.system() == "Darwin"
|
||
|
||
|
||
def is_windows() -> bool:
|
||
return platform.system() == "Windows"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 通用检测
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def windpy_importable() -> bool:
|
||
"""WindPy 是否可 import。"""
|
||
try:
|
||
import importlib
|
||
importlib.import_module("WindPy")
|
||
return True
|
||
except ImportError:
|
||
return False
|
||
|
||
|
||
def _find_windpy_location() -> str | None:
|
||
"""查找 WindPy 在 sys.path 中的实际路径。"""
|
||
for p in sys.path:
|
||
for name in ("WindPy.py", "WindPy.pyd", "WindPy.so"):
|
||
candidate = os.path.join(p, name)
|
||
if os.path.exists(candidate):
|
||
return candidate
|
||
return None
|
||
|
||
|
||
def verify_wind_connection() -> tuple[bool, str]:
|
||
"""连接 Wind 并执行测试查询。返回 (success, message)。"""
|
||
try:
|
||
from WindPy import w
|
||
|
||
ret = w.start()
|
||
if not w.isconnected():
|
||
return False, f"w.start() 返回但未连接: {ret}"
|
||
|
||
ret = w.wss("000001.SZ", "sec_name", "")
|
||
if hasattr(ret, "ErrorCode") and ret.ErrorCode != 0:
|
||
w.stop()
|
||
return False, f"测试查询失败 (ErrorCode={ret.ErrorCode}): {ret.Data}"
|
||
|
||
sec_name = ""
|
||
if hasattr(ret, "Data") and ret.Data and ret.Data[0]:
|
||
sec_name = str(ret.Data[0][0])
|
||
|
||
w.stop()
|
||
return True, f"验证通过 — 000001.SZ = {sec_name}"
|
||
|
||
except ImportError:
|
||
return False, "WindPy 导入失败"
|
||
except Exception as e:
|
||
return False, f"验证异常: {e}"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# macOS: symlink 修复
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _force_symlink(src: str, dst: str):
|
||
"""强制创建 symlink,已存在则替换。"""
|
||
if os.path.islink(dst) or os.path.isfile(dst):
|
||
os.remove(dst)
|
||
os.symlink(src, dst)
|
||
|
||
|
||
def _mac_fix_windpy_import() -> list[str]:
|
||
"""创建 WindPy.py symlink 到 site-packages。返回创建的路径列表。"""
|
||
if not os.path.isfile(WIND_WINDPY_SOURCE_MAC):
|
||
return []
|
||
|
||
linked = []
|
||
|
||
# User site-packages
|
||
user_sp = site.getusersitepackages()
|
||
if user_sp:
|
||
os.makedirs(user_sp, exist_ok=True)
|
||
dst = os.path.join(user_sp, "WindPy.py")
|
||
_force_symlink(WIND_WINDPY_SOURCE_MAC, dst)
|
||
linked.append(dst)
|
||
|
||
# Global site-packages
|
||
try:
|
||
global_sps = site.getsitepackages()
|
||
if global_sps:
|
||
dst = os.path.join(global_sps[0], "WindPy.py")
|
||
_force_symlink(WIND_WINDPY_SOURCE_MAC, dst)
|
||
linked.append(dst)
|
||
except (PermissionError, OSError):
|
||
pass
|
||
|
||
return linked
|
||
|
||
|
||
def _mac_fix_dot_wind() -> bool:
|
||
"""创建 ~/.Wind -> 容器目录的 symlink。"""
|
||
if not os.path.isdir(WIND_DOT_DIR_CONTAINER):
|
||
return False
|
||
_force_symlink(WIND_DOT_DIR_CONTAINER, WIND_DOT_DIR_HOME)
|
||
return True
|
||
|
||
|
||
def _mac_dot_wind_ok() -> bool:
|
||
"""~/.Wind 是否正确链接。"""
|
||
if not os.path.exists(WIND_DOT_DIR_HOME):
|
||
return False
|
||
if os.path.islink(WIND_DOT_DIR_HOME):
|
||
return os.readlink(WIND_DOT_DIR_HOME) == WIND_DOT_DIR_CONTAINER
|
||
return os.path.isdir(WIND_DOT_DIR_HOME)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 诊断结果
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class DiagResult:
|
||
def __init__(self):
|
||
self.platform: str = platform.system()
|
||
self.windpy_importable: bool = False
|
||
self.windpy_location: str | None = None
|
||
self.dot_wind_ok: bool = True
|
||
self.verified: bool = False
|
||
self.verify_message: str = ""
|
||
self.errors: list[str] = []
|
||
self.warnings: list[str] = []
|
||
self.fixed: list[str] = []
|
||
|
||
@property
|
||
def ready(self) -> bool:
|
||
return self.windpy_importable
|
||
|
||
def summary(self) -> str:
|
||
plat = "macOS" if self.platform == "Darwin" else self.platform
|
||
lines = [
|
||
"=" * 60,
|
||
f"WindPy 环境诊断 ({plat})",
|
||
"=" * 60,
|
||
f" Python: {sys.version.split()[0]} ({sys.executable})",
|
||
"",
|
||
f" [{'OK' if self.windpy_importable else 'FAIL'}] WindPy 可 import",
|
||
]
|
||
|
||
if self.windpy_location:
|
||
lines.append(f" 位置: {self.windpy_location}")
|
||
|
||
if self.platform == "Darwin":
|
||
lines.append(f" [{'OK' if self.dot_wind_ok else 'WARN'}] ~/.Wind 配置目录")
|
||
|
||
if self.verified:
|
||
lines.append(f" [ OK ] 连接验证: {self.verify_message}")
|
||
elif self.verify_message:
|
||
lines.append(f" [FAIL] 连接验证: {self.verify_message}")
|
||
|
||
if self.fixed:
|
||
lines += ["", "已修复:"]
|
||
for f in self.fixed:
|
||
lines.append(f" [FIXED] {f}")
|
||
|
||
if self.warnings:
|
||
lines += ["", "警告:"]
|
||
for w in self.warnings:
|
||
lines.append(f" [WARN] {w}")
|
||
|
||
if self.errors:
|
||
lines += ["", "错误:"]
|
||
for e in self.errors:
|
||
lines.append(f" [ERROR] {e}")
|
||
|
||
lines += ["", f"状态: {'READY' if self.ready else 'NOT READY'}", "=" * 60]
|
||
return "\n".join(lines)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 诊断主流程
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def diagnose(fix: bool = False, verify: bool = False) -> DiagResult:
|
||
r = DiagResult()
|
||
|
||
# 1. 核心检查: WindPy 是否可 import
|
||
r.windpy_importable = windpy_importable()
|
||
r.windpy_location = _find_windpy_location()
|
||
|
||
# 2. 可 import → 跳到验证
|
||
if r.windpy_importable:
|
||
pass
|
||
|
||
# 3. 不可 import → 平台特定修复
|
||
elif is_macos():
|
||
if not os.path.isdir(WIND_APP_PATH):
|
||
r.errors.append(f"Wind API.app 未安装 ({WIND_APP_PATH})")
|
||
elif not os.path.isfile(WIND_WINDPY_SOURCE_MAC):
|
||
r.errors.append(f"WindPy.py 不存在于 {WIND_WINDPY_SOURCE_MAC},安装可能不完整")
|
||
elif fix:
|
||
linked = _mac_fix_windpy_import()
|
||
for path in linked:
|
||
r.fixed.append(f"symlink: {path} -> {WIND_WINDPY_SOURCE_MAC}")
|
||
import importlib
|
||
importlib.invalidate_caches()
|
||
r.windpy_importable = windpy_importable()
|
||
r.windpy_location = _find_windpy_location()
|
||
if not r.windpy_importable:
|
||
r.errors.append("创建 symlink 后仍无法导入,请检查权限")
|
||
else:
|
||
r.errors.append(
|
||
"WindPy 无法导入。运行 --fix 自动修复,或手动:\n"
|
||
f" ln -sf '{WIND_WINDPY_SOURCE_MAC}' "
|
||
f"'{os.path.join(site.getusersitepackages(), 'WindPy.py')}'"
|
||
)
|
||
|
||
elif is_windows():
|
||
r.errors.append(
|
||
"WindPy 无法导入。请检查:\n"
|
||
" 1. Wind 金融终端是否已安装\n"
|
||
" 2. 安装时是否勾选了「Python API」组件\n"
|
||
" 3. 当前 Python 版本是否与 Wind 支持的版本一致\n"
|
||
" 修复: 打开 Wind 终端 → 帮助 → 修复 Python 接口"
|
||
)
|
||
|
||
else:
|
||
r.errors.append(f"不支持的平台: {r.platform}")
|
||
return r
|
||
|
||
# 4. macOS: ~/.Wind 配置目录
|
||
if is_macos():
|
||
r.dot_wind_ok = _mac_dot_wind_ok()
|
||
if not r.dot_wind_ok and fix:
|
||
if _mac_fix_dot_wind():
|
||
r.fixed.append(f"symlink: {WIND_DOT_DIR_HOME} -> {WIND_DOT_DIR_CONTAINER}")
|
||
r.dot_wind_ok = True
|
||
else:
|
||
r.warnings.append("~/.Wind 配置目录不存在,请先打开 Wind API.app 并登录一次")
|
||
elif not r.dot_wind_ok:
|
||
r.warnings.append("~/.Wind 未正确链接,运行 --fix 修复")
|
||
|
||
# 5. 连接验证
|
||
if r.windpy_importable and verify:
|
||
r.verified, r.verify_message = verify_wind_connection()
|
||
if not r.verified:
|
||
r.warnings.append(f"连接验证失败: {r.verify_message}")
|
||
|
||
return r
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 对外接口
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def ensure_windpy(auto_fix: bool = True, verbose: bool = True):
|
||
"""
|
||
确保 WindPy 可用。不可用时自动修复(macOS: symlink)。
|
||
|
||
Raises:
|
||
RuntimeError: WindPy 无法使用且无法修复。
|
||
"""
|
||
if windpy_importable():
|
||
if verbose:
|
||
loc = _find_windpy_location()
|
||
print(f"[setup_windpy] WindPy 已就绪 ({loc})", flush=True)
|
||
return
|
||
|
||
if verbose:
|
||
print("[setup_windpy] WindPy 不可导入,开始诊断...", flush=True)
|
||
|
||
result = diagnose(fix=auto_fix)
|
||
|
||
if verbose:
|
||
print(result.summary(), flush=True)
|
||
|
||
if not result.ready:
|
||
errors = "; ".join(result.errors) if result.errors else "未知错误"
|
||
raise RuntimeError(f"WindPy 环境配置失败: {errors}")
|
||
|
||
if verbose:
|
||
print("[setup_windpy] WindPy 环境配置完成", flush=True)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# CLI
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def main():
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description="WindPy 环境检测与配置")
|
||
parser.add_argument("--fix", action="store_true", help="自动修复")
|
||
parser.add_argument("--check", action="store_true", help="仅检测不修复")
|
||
parser.add_argument("--verify", action="store_true", help="连接 Wind 并执行测试查询")
|
||
parser.add_argument("--quiet", action="store_true", help="静默模式,仅在失败时输出")
|
||
args = parser.parse_args()
|
||
|
||
do_fix = args.fix and not args.check
|
||
result = diagnose(fix=do_fix, verify=args.verify or args.fix)
|
||
|
||
if not args.quiet or not result.ready:
|
||
print(result.summary())
|
||
|
||
sys.exit(0 if result.ready else 1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|