- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
142 lines
4.8 KiB
Python
142 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Soul 运营全链路技能包:将 SKILL + 脚本 + Cursor 入口打成 zip,默认输出到用户「下载」文件夹。
|
||
用法:
|
||
python3 scripts/pack_soul_operation_skills.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import sys
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
STAMP = "20260320"
|
||
BUNDLE_TOP = f"Soul运营全链路技能包_{STAMP}"
|
||
|
||
# 卡若AI 根目录(按你本机实际修改)
|
||
KARUO_AI = Path("/Users/karuo/Documents/个人/卡若AI")
|
||
CURSOR_SKILLS = Path.home() / ".cursor" / "skills"
|
||
DOWNLOADS = Path.home() / "Downloads"
|
||
|
||
# 在永平项目下临时组装(本仓库内,便于工具写入)
|
||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||
STAGING_PARENT = REPO_ROOT / ".tmp_skill_bundle"
|
||
STAGING = STAGING_PARENT / BUNDLE_TOP
|
||
|
||
|
||
def ignore_copy(dirpath: str, names: list[str]) -> list[str]:
|
||
"""排除缓存、浏览器运行时目录(含断链/套接字,会导致 copytree 失败)。"""
|
||
skip_dirs = {"__pycache__", ".browser_state", "chromium_data"}
|
||
skip_files = {".DS_Store"}
|
||
ignored: list[str] = []
|
||
for n in names:
|
||
if n in skip_dirs or n in skip_files or n.endswith(".pyc"):
|
||
ignored.append(n)
|
||
return ignored
|
||
|
||
|
||
def copytree(src: Path, dst: Path) -> None:
|
||
if not src.exists():
|
||
print(f"SKIP 不存在: {src}", file=sys.stderr)
|
||
return
|
||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||
shutil.copytree(src, dst, dirs_exist_ok=True, ignore=ignore_copy)
|
||
|
||
|
||
def main() -> int:
|
||
if not KARUO_AI.is_dir():
|
||
print(f"ERROR: 未找到卡若AI目录: {KARUO_AI}", file=sys.stderr)
|
||
return 1
|
||
|
||
if STAGING.exists():
|
||
shutil.rmtree(STAGING)
|
||
STAGING.mkdir(parents=True)
|
||
|
||
# Cursor 入口
|
||
csk = STAGING / ".cursor" / "skills"
|
||
csk.mkdir(parents=True, exist_ok=True)
|
||
for name in ("soul-operation-report", "soul-party-project"):
|
||
p = CURSOR_SKILLS / name
|
||
if p.is_dir():
|
||
copytree(p, csk / name)
|
||
|
||
kai = STAGING / "卡若AI"
|
||
copytree(
|
||
KARUO_AI / "02_卡人(水)" / "水岸_项目管理",
|
||
kai / "02_卡人(水)" / "水岸_项目管理",
|
||
)
|
||
bridge = KARUO_AI / "02_卡人(水)" / "水桥_平台对接"
|
||
for sub in ("飞书管理", "智能纪要", "Soul创业实验"):
|
||
copytree(bridge / sub, kai / "02_卡人(水)" / "水桥_平台对接" / sub)
|
||
|
||
wood = KARUO_AI / "03_卡木(木)" / "木叶_视频内容"
|
||
wdst = kai / "03_卡木(木)" / "木叶_视频内容"
|
||
for sub in (
|
||
"视频切片",
|
||
"多平台分发",
|
||
"抖音发布",
|
||
"B站发布",
|
||
"视频号发布",
|
||
"小红书发布",
|
||
"快手发布",
|
||
):
|
||
copytree(wood / sub, wdst / sub)
|
||
|
||
idx = KARUO_AI / "运营中枢" / "工作台" / "00_账号与API索引.md"
|
||
if idx.is_file():
|
||
(kai / "运营中枢" / "工作台").mkdir(parents=True, exist_ok=True)
|
||
shutil.copy2(idx, kai / "运营中枢" / "工作台" / idx.name)
|
||
|
||
readme = STAGING / "解压后必读.md"
|
||
readme.write_text(
|
||
"""# Soul 运营全链路技能包
|
||
|
||
## 包含内容
|
||
|
||
- `.cursor/skills/`:`soul-operation-report`、`soul-party-project`
|
||
- `卡若AI/02_卡人(水)/水岸_项目管理/`
|
||
- `卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/`、`智能纪要/`、`Soul创业实验/`
|
||
- `卡若AI/03_卡木(木)/木叶_视频内容/`:视频切片、多平台分发、各平台发布
|
||
- `卡若AI/运营中枢/工作台/00_账号与API索引.md`(若源机存在)
|
||
|
||
## 另一台电脑怎么用
|
||
|
||
1. 解压后,将 `卡若AI/` **合并**到你本机卡若AI根目录(先备份)。
|
||
2. 将 `.cursor/skills/` 下两个目录复制到 `~/.cursor/skills/`。
|
||
3. 安装 Python/FFmpeg/conda 等依赖,按各 SKILL 与 `Soul创业实验/上传/环境与TOKEN配置.md` 配置 Token、Cookie、永平项目 `.env`。
|
||
4. 文档或脚本里的 `/Users/karuo/...` 需改成本机路径。
|
||
|
||
**安全**:包内可能有凭证说明,勿上传公开网盘。
|
||
""",
|
||
encoding="utf-8",
|
||
)
|
||
|
||
for p in list(STAGING.rglob("__pycache__")):
|
||
if p.is_dir():
|
||
shutil.rmtree(p, ignore_errors=True)
|
||
for p in STAGING.rglob("*.pyc"):
|
||
try:
|
||
p.unlink()
|
||
except OSError:
|
||
pass
|
||
|
||
DOWNLOADS.mkdir(parents=True, exist_ok=True)
|
||
zip_path = DOWNLOADS / f"{BUNDLE_TOP}.zip"
|
||
|
||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||
for f in STAGING.rglob("*"):
|
||
if f.is_file():
|
||
arcname = Path(BUNDLE_TOP) / f.relative_to(STAGING)
|
||
zf.write(f, arcname.as_posix())
|
||
|
||
mb = zip_path.stat().st_size / (1024 * 1024)
|
||
print(f"完成: {zip_path} ({mb:.2f} MB)")
|
||
print(f"临时目录(可删): {STAGING}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|