skills/windpy-sdk/scripts/setup_windpy.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
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>
2026-07-13 14:47:12 +08:00

354 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()