diff --git a/.gitignore b/.gitignore index a3995023..37932fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ __pycache__/ .env.* *.log sync_tokens.env +# QQ 邮箱授权码(勿提交) +**/QQ邮箱拉取/.qq_mail_env # 飞书妙记(用户 token / Cookie,勿提交) **/智能纪要/脚本/feishu_user_token.txt **/智能纪要/脚本/cookie_minutes.txt diff --git a/02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md b/02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md new file mode 100644 index 00000000..421a183f --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md @@ -0,0 +1,121 @@ +--- +name: MCP 搜索与连接 +description: 当卡若AI需要连接 MCP 时,使用本技能搜索、发现并安装 MCP 服务器。触发词:MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现。 +owner: 水桥 +group: 水 +version: "1.0" +updated: "2026-02-23" +--- + +# MCP 搜索与连接 Skill + +> 需要啥 MCP,搜一搜、连一连。 —— 水桥 + +--- + +## 核心能力 + +**当卡若AI遇到需要 MCP 能力的场景时,使用本技能完成:** +1. 搜索 MCP 服务器(5000+ 可发现) +2. 获取安装配置 +3. 写入 Cursor/Claude/Windsurf 等客户端配置 +4. 按需使用相应 MCP 工具 + +--- + +## 推荐工具:MCPfinder + +**MCPfinder** 是「MCP 的 App Store」—— 一次安装,AI 自主发现并安装 MCP 服务器。 + +| 能力 | 说明 | +|:---|:---| +| 搜索 | 5000+ 服务器,多注册表聚合(Official、Glama、Smithery) | +| 安装 | 一键生成 Cursor / Claude Desktop / Windsurf 等配置 | +| 排名 | 按相关性、热度、多注册表覆盖、更新 recency 排序 | + +### 安装(Cursor) + +在 `~/.cursor/mcp.json` 或项目 `.cursor/mcp.json` 中加入: + +```json +{ + "mcpServers": { + "mcpfinder": { + "command": "npx", + "args": ["-y", "@mcpfinder/server@beta"] + } + } +} +``` + +> 注:首次运行会同步注册表(约 1~2 分钟),之后本地缓存自动刷新。 + +### MCPfinder 工具一览 + +| 工具 | 用途 | 触发场景 | +|:---|:---|:---| +| `search_mcp_servers` | 按关键词/用例/技术搜索 | 用户需要你当前没有的能力 | +| `get_server_details` | 获取详情(描述、env、来源等) | 评估是否适合安装 | +| `get_install_command` | 生成可直接粘贴的配置 | 用户要安装某个 MCP | +| `list_categories` | 按分类浏览 | 用户不确定需要啥 | +| `browse_category` | 查看某分类下的热门服务器 | 探索某领域(数据库、AI、云等) | + +--- + +## 执行流程(卡若AI 使用本技能时) + +``` +用户需求(需要某种 MCP 能力) + ↓ +① 判断:当前 MCP 工具是否已有该能力? + ├── 有 → 直接调用对应 MCP 工具 + └── 无 → 进入 ② + ↓ +② 调用 MCPfinder.search_mcp_servers(query) 搜索 + ↓ +③ 若有结果,选 1~3 个候选,调用 get_server_details 评估 + ↓ +④ 调用 get_install_command 生成 Cursor 配置 + ↓ +⑤ 写入 ~/.cursor/mcp.json(或项目 mcp.json) + ↓ +⑥ 提示用户重启 Cursor 或等待自动检测 + ↓ +⑦ 配置生效后,使用新 MCP 完成用户需求 +``` + +--- + +## 其他 MCP 发现资源(备用) + +当 MCPfinder 未安装或搜索无果时,可引导用户到: + +| 资源 | 链接 | 说明 | +|:---|:---|:---| +| GitHub MCP Registry | https://github.com/mcp | 官方精选,VS Code 一键安装 | +| MCP Awesome | https://mcp-awesome.com | 1200+ 经核验服务器 | +| Find My MCP | https://findmymcp.com | 可搜索目录 | +| Cursor Directory | https://cursor.directory/mcp | Cursor 专用目录 | +| awesome-mcp-servers | https://github.com/appcypher/awesome-mcp-servers | GitHub 5k+ star 列表 | + +--- + +## 与 Skill 的配合 + +- **需要功能时**:本技能负责「找 MCP、连 MCP」 +- **功能落地时**:按 `SKILL_REGISTRY.md` 匹配现有 Skill,或结合「技能工厂」为新 MCP 写封装 Skill +- **MCP 安装后**:该 MCP 工具即成为卡若AI 的扩展能力,可直接在对话中调用 + +--- + +## 触发词 + +`MCP`、`找MCP`、`连接MCP`、`MCP搜索`、`发现MCP`、`添加MCP`、`需要MCP`、`MCP安装`、`MCP发现`、`查MCP`、`装MCP` + +--- + +## 引用 + +- MCPfinder 新仓库:https://github.com/lksrz/mcpfinder +- MCPfinder 官网:https://mcpfinder.dev · https://findmcp.dev +- npm 包:`@mcpfinder/server`(beta) diff --git a/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/README.md b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/README.md new file mode 100644 index 00000000..115750e1 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/README.md @@ -0,0 +1,32 @@ +# QQ 邮箱 IMAP 拉取 + +> 水桥 · 平台对接 | 命令行接收 QQ 邮件 + +## 配置说明 + +- **授权码**:已保存在 `.qq_mail_env`(本地,不提交 git) +- **账号**:zhengzhiqun@qq.com +- **用途**:脚本启动时自动读取 `.qq_mail_env`,无需再手动设置环境变量 + +## 命令行用法 + +```bash +# 拉取最近 30 天(默认) +python qq_mail_fetch.py + +# 拉取最近 7 天 +python qq_mail_fetch.py --days 7 + +# 最多拉取 50 封 +python qq_mail_fetch.py --limit 50 + +# 组合:最近 7 天、最多 20 封 +python qq_mail_fetch.py --days 7 --limit 20 +``` + +## 登录失败排查 + +若出现 `Login fail`,请检查: +1. QQ 邮箱 → 设置 → 账户 → POP3/IMAP/SMTP → **已开启 IMAP** +2. 授权码为刚生成的可再试一次(有时需等待数分钟生效) +3. 更换 QQ 密码会使授权码失效,需重新生成 diff --git a/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_analyze.py b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_analyze.py new file mode 100644 index 00000000..cc05dde3 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_analyze.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +""" +QQ 邮箱导出数据分析 · 生成整体总结报告 +""" +import json +import re +from pathlib import Path +from collections import Counter +from datetime import datetime + +def analyze(json_path: str) -> dict: + with open(json_path, "r", encoding="utf-8") as f: + emails = json.load(f) + + if not emails: + return {"total": 0, "error": "无邮件数据"} + + # 基础统计 + total = len(emails) + dates = [] + senders = [] + subject_keywords = [] + + for e in emails: + d = e.get("date", "")[:10] + if d and len(d) >= 10: + dates.append(d) + from_addr = e.get("from", "") + if "<" in from_addr: + m = re.search(r"[\w.-]+@[\w.-]+", from_addr) + sender = m.group(0) if m else from_addr[:50] + else: + sender = from_addr[:50] if from_addr else "unknown" + senders.append(sender) + + subj = e.get("subject", "") + # 提取发件域名/服务 + if "github" in sender.lower(): + if "Run failed" in subj or "Sync" in subj: + subject_keywords.append("GitHub_同步失败") + elif "security" in subj.lower(): + subject_keywords.append("GitHub_安全告警") + else: + subject_keywords.append("GitHub_其他") + elif "synology" in sender.lower(): + subject_keywords.append("Synology_NAS") + elif "vercel" in sender.lower(): + subject_keywords.append("Vercel_部署") + elif "ollama" in sender.lower(): + subject_keywords.append("Ollama_验证") + elif "apple" in sender.lower() or "icloud" in sender.lower(): + subject_keywords.append("Apple_iCloud") + elif "trip.com" in sender.lower(): + subject_keywords.append("Trip_推广") + elif "facebook" in sender.lower(): + subject_keywords.append("Facebook_通知") + elif "adobe" in sender.lower(): + subject_keywords.append("Adobe_推广") + elif "docker" in sender.lower(): + subject_keywords.append("Docker_推广") + elif "airbnb" in sender.lower(): + subject_keywords.append("Airbnb_通知") + elif "cebbank" in sender.lower() or "95595" in sender: + subject_keywords.append("光大银行") + elif "bosszhipin" in sender.lower(): + subject_keywords.append("Boss直聘") + elif "openrouter" in sender.lower(): + subject_keywords.append("OpenRouter_AI") + else: + subject_keywords.append("其他") + + # 按发件人统计 + sender_counts = Counter(senders) + top_senders = sender_counts.most_common(15) + + # 按类型统计 + type_counts = Counter(subject_keywords) + top_types = type_counts.most_common(15) + + # 日期范围 + dates_ok = [d for d in dates if re.match(r"\d{4}-\d{2}-\d{2}", d)] + date_min = min(dates_ok) if dates_ok else "" + date_max = max(dates_ok) if dates_ok else "" + + return { + "total": total, + "date_range": {"min": date_min, "max": date_max}, + "top_senders": top_senders, + "top_types": top_types, + "by_type": dict(type_counts), + } + + +def main(): + import argparse + ap = argparse.ArgumentParser() + ap.add_argument("json", nargs="?", default="/Users/karuo/Documents/卡若Ai的文件夹/报告/qq_mail_full_export.json") + ap.add_argument("-o", "--output", help="输出报告路径") + args = ap.parse_args() + + r = analyze(args.json) + + lines = [ + "# QQ 邮箱整体分析报告", + "", + "## 一、概览", + "", + f"- 邮件总数:**{r['total']}** 封", + f"- 时间范围:{r['date_range']['min']} ~ {r['date_range']['max']}", + "", + "## 二、按发件人统计(Top 15)", + "", + "| 发件人 | 数量 |", + "|:---|:---|", + ] + for s, c in r["top_senders"]: + lines.append(f"| {s[:60]} | {c} |") + + lines.extend([ + "", + "## 三、按内容类型统计", + "", + "| 类型 | 数量 | 占比 |", + "|:---|:---|:---|", + ]) + for t, c in r["top_types"]: + pct = round(c / r["total"] * 100, 1) if r["total"] else 0 + lines.append(f"| {t} | {c} | {pct}% |") + + # 四、核心发现 + lines.extend([ + "", + "## 四、核心发现", + "", + ]) + gh_fail = r["by_type"].get("GitHub_同步失败", 0) + syno = r["by_type"].get("Synology_NAS", 0) + vercel = r["by_type"].get("Vercel_部署", 0) + boss = r["by_type"].get("Boss直聘", 0) + if gh_fail: + lines.append(f"- **GitHub 同步告警占 {round(gh_fail/r['total']*100,1)}%**:cunkebao_doc 的 Coding 同步长期失败,建议修复或停用工作流") + if syno: + lines.append(f"- **Synology NAS 通知 {syno} 封**:容器异常、连接断连频繁,需排查 nas-frpc、mongodb 等") + if vercel: + lines.append(f"- **Vercel 部署失败 {vercel} 封**:部署权限或集成问题待查") + if boss: + lines.append(f"- **Boss直聘 {boss} 封**:招聘/求职相关") + lines.extend([ + "", + "## 五、建议行动", + "", + "1. 优先处理 cunkebao_doc 同步失败,减少告警噪音", + "2. 排查 Synology 容器稳定性与 lkdie 连接", + "3. 检查 Vercel 与 GitHub 集成", + "4. 按需归档或过滤推广类邮件(Trip、Facebook、Adobe 等)", + "", + "## 六、数据说明", + "", + "- 数据来源:IMAP 收件箱(INBOX)全量拉取", + "- 网页版「我的文件夹」等需在 QQ 邮箱设置中勾选「收取我的文件夹」后,方能在 IMAP 中访问", + "- 导出文件:`qq_mail_full_export.json`", + "", + ]) + + text = "\n".join(lines) + + if args.output: + Path(args.output).write_text(text, encoding="utf-8") + print(f"已写入 {args.output}") + else: + print(text) + + +if __name__ == "__main__": + main() diff --git a/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch.py b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch.py new file mode 100644 index 00000000..91e0c957 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +""" +QQ 邮箱 IMAP 拉取脚本 · 水桥(平台对接) +用途:拉取收件箱全部或指定时间范围的邮件,输出为可读格式 + +前置:QQ 邮箱设置 → 账户 → POP3/IMAP/SMTP → 开启 IMAP → 生成授权码 +用法: + python qq_mail_fetch.py # 拉取最近 30 天 + python qq_mail_fetch.py --days 7 # 拉取最近 7 天 + python qq_mail_fetch.py --all # 拉取全部历史邮件(无日期限制) + python qq_mail_fetch.py --all --output out.json # 导出到 JSON 便于分析 +""" +import imaplib +import email +import argparse +import json +import os +import sys +from email.utils import parsedate_to_datetime +from datetime import datetime, timedelta +from pathlib import Path + +# 加载本地 .qq_mail_env(授权码保存于此,调用时直接使用) +def _load_env(): + env_file = Path(__file__).resolve().parent / ".qq_mail_env" + if env_file.exists(): + for line in env_file.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if line and not line.startswith("#") and "=" in line: + k, v = line.split("=", 1) + os.environ.setdefault(k.strip(), v.strip()) + +_load_env() + +# 配置:优先环境变量,次之 .qq_mail_env +IMAP_HOST = "imap.qq.com" +IMAP_PORT = 993 +EMAIL = os.environ.get("QQ_MAIL", "zhengzhiqun@qq.com") +AUTH_CODE = os.environ.get("QQ_MAIL_AUTH_CODE", "") # 授权码,非 QQ 密码 + + +def list_folders() -> list[str]: + """列出所有 IMAP 文件夹""" + if not AUTH_CODE: + return [] + server = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) + server.login(EMAIL, AUTH_CODE) + typ, data = server.list() + server.logout() + if typ != "OK": + return [] + folders = [] + for line in data: + if line: + parts = line.decode().split('"') + if len(parts) >= 2: + folders.append(parts[-2]) + return folders + + +def fetch_emails(days: int = 30, limit: int = 0, all_mail: bool = False, progress: bool = True, folder: str = "INBOX") -> list[dict]: + """拉取收件箱邮件,返回 [{date, from, subject, preview}, ...]""" + if not AUTH_CODE: + print("请设置环境变量 QQ_MAIL_AUTH_CODE(QQ 邮箱授权码)") + print("或在脚本内填写 AUTH_CODE 变量") + return [] + + server = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) + server.login(EMAIL, AUTH_CODE) + # 含空格的文件夹名需加双引号(如 "Sent Messages") + mb = f'"{folder}"' if " " in folder else folder + try: + server.select(mb, readonly=True) + except (imaplib.IMAP4.error, imaplib.IMAP4.readonly): + server.select(mb, readonly=False) + + if all_mail: + typ, data = server.search(None, "ALL") + else: + since = (datetime.now() - timedelta(days=days)).strftime("%d-%b-%Y") + typ, data = server.search(None, f"(SINCE {since})") + + if typ != "OK": + server.close() + server.logout() + return [] + + ids = data[0].split() + ids = list(reversed(ids)) # 新的在前 + total = len(ids) + if limit and total > limit: + ids = ids[:limit] + total_fetch = len(ids) + + if progress: + print(f"共 {total} 封邮件,将拉取 {total_fetch} 封...", file=sys.stderr) + + results = [] + for i, num in enumerate(ids): + typ, msg_data = server.fetch(num, "(RFC822)") + if typ != "OK": + continue + raw = msg_data[0][1] + msg = email.message_from_bytes(raw) + subject = msg.get("Subject", "") + if isinstance(subject, bytes): + from email.header import decode_header + parts = decode_header(subject) + subject = "".join( + p.decode(c or "utf-8", errors="replace") if isinstance(p, bytes) else p + for p, c in parts + ) + from_addr = msg.get("From", "") + date_str = msg.get("Date", "") + try: + dt = parsedate_to_datetime(date_str) + date_display = dt.strftime("%Y-%m-%d %H:%M") + except Exception: + date_display = date_str[:20] if date_str else "" + preview = "" + if msg.is_multipart(): + for part in msg.walk(): + if part.get_content_type() == "text/plain": + try: + preview = part.get_payload(decode=True).decode("utf-8", errors="replace")[:200] + except Exception: + pass + break + else: + try: + preview = msg.get_payload(decode=True).decode("utf-8", errors="replace")[:200] + except Exception: + preview = "(无法解析正文)" + results.append({ + "date": date_display, + "from": from_addr[:80], + "subject": subject[:120], + "preview": preview.replace("\n", " ").strip()[:200], + }) + if progress and (i + 1) % 500 == 0: + print(f" 已拉取 {i + 1}/{total_fetch} ...", file=sys.stderr) + + server.close() + server.logout() + return results + + +def main(): + ap = argparse.ArgumentParser(description="QQ 邮箱 IMAP 拉取") + ap.add_argument("--days", type=int, default=30, help="拉取最近 N 天(与 --all 互斥)") + ap.add_argument("--all", dest="all_mail", action="store_true", help="拉取全部历史邮件") + ap.add_argument("--folder", type=str, default="INBOX", help="指定文件夹,如 INBOX、我的文件夹 等;先 --list-folders 查看") + ap.add_argument("--list-folders", action="store_true", help="列出所有 IMAP 文件夹") + ap.add_argument("--limit", type=int, default=0, help="最多拉取 N 封,0 表示不限制") + ap.add_argument("--output", "-o", type=str, default="", help="导出到 JSON 文件") + ap.add_argument("--quiet", "-q", action="store_true", help="不显示进度") + args = ap.parse_args() + + if args.list_folders: + for f in list_folders(): + print(f) + return + + days = 365 * 20 if args.all_mail else args.days + emails = fetch_emails( + days=days, + limit=args.limit, + all_mail=args.all_mail, + progress=not args.quiet, + folder=args.folder, + ) + + if args.output: + out_path = Path(args.output) + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, "w", encoding="utf-8") as f: + json.dump(emails, f, ensure_ascii=False, indent=None) + print(f"已导出 {len(emails)} 封到 {out_path}", file=sys.stderr) + else: + for i, e in enumerate(emails, 1): + print(f"[{i}] {e['date']} | {e['from']}") + print(f" 主题: {e['subject']}") + print(f" 摘要: {e['preview']}") + print("-" * 60) + + +if __name__ == "__main__": + main() diff --git a/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch_all.py b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch_all.py new file mode 100644 index 00000000..ae4b65ec --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch_all.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +QQ 邮箱多文件夹批量拉取 · 收件箱 + 垃圾箱 + 已发送 + 我的文件夹(若已开启) +""" +import json +import sys +from pathlib import Path +from qq_mail_fetch import fetch_emails, AUTH_CODE + +OUT_DIR = Path("/Users/karuo/Documents/卡若Ai的文件夹/报告") + +FOLDERS = [ + ("INBOX", "收件箱"), + ("Junk", "垃圾箱"), + ("Sent Messages", "已发送"), + ("Drafts", "草稿箱"), + ("Deleted Messages", "已删除"), +] + +# 我的文件夹(需在 QQ 设置中勾选「收取我的文件夹」后才能在 IMAP 中看到子目录) +MY_FOLDERS = ["&UXZO1mWHTvZZOQ-"] # 父级,可扩展子目录 + + +def main(): + if not AUTH_CODE: + print("请配置 .qq_mail_env 中的授权码") + return 1 + + results = {} + for folder, label in FOLDERS: + try: + emails = fetch_emails(days=365 * 20, limit=0, all_mail=True, progress=True, folder=folder) + results[label] = {"folder": folder, "count": len(emails), "emails": emails} + out = OUT_DIR / f"qq_{folder.replace(' ', '_').lower()}_export.json" + with open(out, "w", encoding="utf-8") as f: + json.dump(emails, f, ensure_ascii=False, indent=None) + print(f" -> {out.name}: {len(emails)} 封") + except Exception as e: + print(f"{label} ({folder}): 失败 - {e}") + results[label] = {"folder": folder, "count": 0, "error": str(e)} + + for folder in MY_FOLDERS: + try: + emails = fetch_emails(days=365 * 20, limit=0, all_mail=True, progress=True, folder=folder) + results["我的文件夹"] = {"folder": folder, "count": len(emails), "emails": emails} + out = OUT_DIR / "qq_myfolders_export.json" + with open(out, "w", encoding="utf-8") as f: + json.dump(emails, f, ensure_ascii=False, indent=None) + print(f" 我的文件夹 -> {out.name}: {len(emails)} 封") + except Exception as e: + print(f"我的文件夹: 失败 - {e}") + + # 合并统计 + total = sum(r.get("count", 0) for r in results.values() if isinstance(r, dict)) + print(f"\n合计拉取: {total} 封") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/获取其他邮件_方法说明.md b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/获取其他邮件_方法说明.md new file mode 100644 index 00000000..0d595ba6 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/QQ邮箱拉取/获取其他邮件_方法说明.md @@ -0,0 +1,65 @@ +# 获取 QQ 邮箱其他邮件的办法 + +## 一、IMAP 已支持的文件夹 + +| 网页显示 | IMAP 文件夹名 | 状态 | 当前拉取数 | +|:---|:---|:---|:---| +| 收件箱 | INBOX | ✅ 已支持 | 643 | +| 垃圾箱 | Junk | ✅ 已支持 | 22 | +| 已发送 | Sent Messages | ✅ 已支持 | 40 | +| 草稿箱 | Drafts | ✅ 已支持 | 0 | +| 已删除 | Deleted Messages | ✅ 已支持 | 0 | + +## 二、获取方式 + +### 方式 1:命令行逐文件夹拉取 + +```bash +# 收件箱 +python3 qq_mail_fetch.py --all -o 报告/qq_inbox_export.json + +# 垃圾箱 +python3 qq_mail_fetch.py --all --folder Junk -o 报告/qq_junk_export.json + +# 已发送(含空格需用引号) +python3 qq_mail_fetch.py --all --folder "Sent Messages" -o 报告/qq_sent_export.json + +# 草稿箱 +python3 qq_mail_fetch.py --all --folder Drafts -o 报告/qq_drafts_export.json + +# 已删除 +python3 qq_mail_fetch.py --all --folder "Deleted Messages" -o 报告/qq_deleted_export.json +``` + +### 方式 2:一键批量拉取 + +```bash +python3 qq_mail_fetch_all.py +``` + +会依次拉取上述文件夹并导出到 `报告/` 目录。 + +## 三、我的文件夹(14239 封) + +**现状**:IMAP 中 `&UXZO1mWHTvZZOQ-` 为父级目录,本身无邮件;子文件夹未在 LIST 中列出。 + +**解决办法**: + +1. **QQ 邮箱网页** → 右上角 **设置** → **账户** → **收取选项** +2. 勾选 **「收取我的文件夹」** +3. 保存后重新执行拉取 +4. 执行 `python3 qq_mail_fetch.py --list-folders` 查看是否出现新的子文件夹 + +## 四、群邮件(212 封) + +**现状**:QQ 邮箱 IMAP 标准文件夹列表中**未发现**群邮件对应目录。 + +**可能原因**:群邮件可能以标签/虚拟文件夹形式存在,需在网页端开启「收取群邮件」等选项后,才在 IMAP 中可见。 + +**建议**:在 QQ 邮箱设置中检查「群邮件」「收取选项」相关配置,确认是否支持 IMAP 收取。 + +## 五、已修复的技术点 + +1. **含空格文件夹名**:`Sent Messages`、`Deleted Messages` 需在脚本中加双引号,已在本仓库脚本中处理。 +2. **只读文件夹**:`我的文件夹` 父级为只读,已改为使用 `readonly=True`(EXAMINE)选择。 +3. **批量拉取**:新增 `qq_mail_fetch_all.py` 支持一键拉取多个文件夹。 diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index 0e502735..d3667675 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-46dpgqFzddzUbmSNaiAQN2l5mWU5k1orhUaaZBM00xP7", - "refresh_token": "ur-4v_0_alr52JanOSvj3DFNul5mgq5k1WpOoaaUNQ00BPj", + "access_token": "u-5jTDb7Rkl57WWjJ3pzwPS6l5moW5k1MXV8aaJxQ00ACm", + "refresh_token": "ur-5ve40_WDh2CUZE2rESveY6l5mOU5k1MphEaaUBM00xCm", "name": "飞书用户", "auth_time": "2026-02-23T09:58:30.247057" } \ No newline at end of file diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py index 9101d71d..d99c3f1b 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py @@ -43,6 +43,51 @@ ROWS = { # 场次→按日期列填写时的日期(表头为当月日期 1~31) SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23'} +# 小程序当日运营数据:日期号 → {访问次数, 访客, 交易金额},填表时自动写入对应日期列 +# 数据来源:微信公众平台 → 小程序 → 统计 → 实时访问/概况 +# 历史有数据的都填入,批量写入用 write_miniprogram_batch.py +MINIPROGRAM_EXTRA = { + '20': {'访问次数': 45, '访客': 45, '交易金额': 0}, # 2月20日 + '21': {'访问次数': 52, '访客': 52, '交易金额': 0}, # 2月21日 + '23': {'访问次数': 55, '访客': 55, '交易金额': 0}, # 2月23日 +} + + +def _find_row_for_keyword(vals, keywords): + """在 vals 中找 A 列包含任一 keyword 的行号(1-based)""" + for ri, row in enumerate(vals): + a1 = (row[0] if row and len(row) > 0 else '') + a1 = str(a1 or '').strip() + for kw in keywords: + if kw in a1: + return ri + 1 + return None + + +def _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter): + """若当日有小程序数据,写入 交易金额、访客、小程序访问 到对应行""" + extra = MINIPROGRAM_EXTRA.get(date_col) + if not extra: + return + # 行→extra 中的键 + writes = [ + (_find_row_for_keyword(vals, ['交易金额']), extra.get('交易金额', 0)), + (_find_row_for_keyword(vals, ['访客']), extra.get('访客', extra.get('访问次数'))), + (_find_row_for_keyword(vals, ['小程序访问']), extra.get('访问次数')), + ] + written = 0 + for row_num, val in writes: + if row_num is None or val is None: + continue + rng = f"{sheet_id}!{col_letter}{row_num}" + code, body = update_sheet_range(token, spreadsheet_token, rng, [[_to_cell_value(val)]]) + if code == 401 or body.get('code') in (99991677, 99991663): + return + if code == 200 and body.get('code') in (0, None): + written += 1 + if written > 0: + print(f'✅ 已写入小程序运营数据(2月{date_col}日列):访问次数 {extra.get("访问次数","")}、访客 {extra.get("访客","")}、交易金额 {extra.get("交易金额",0)}') + def load_token(): if not os.path.exists(TOKEN_FILE): @@ -223,7 +268,7 @@ def main(): values = [_to_cell_value(raw[0])] + [_to_cell_value(raw[i]) for i in range(1, EFFECT_COLS)] spreadsheet_token = WIKI_NODE_OR_SPREADSHEET_TOKEN sheet_id = SHEET_ID - range_read = f"{sheet_id}!A1:AG30" + range_read = f"{sheet_id}!A1:AG35" vals, read_code, read_body = read_sheet_range(token, spreadsheet_token, range_read) # 401 时刷新 token 并重试读取,确保能定位到日期列 if (read_code == 401 or read_body.get('code') in (99991677, 99991663)) and not vals: @@ -302,6 +347,7 @@ def main(): ok, msg = _verify_write(spreadsheet_token, sheet_id, col_letter, values, token) if ok: print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter}3:{col_letter}{2+len(values)},共{len(values)}格),校验通过') + _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter) _maybe_send_group(session, raw) return print(f'⚠️ 写入成功但校验未通过:{msg}') @@ -324,6 +370,7 @@ def main(): ok, msg = _verify_write(spreadsheet_token, sheet_id, col_letter, values, token) if ok: print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter} 逐格),校验通过') + _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter) _maybe_send_group(session, raw) return print(f'⚠️ 逐格写入成功但校验未通过:{msg}') diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_batch.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_batch.py new file mode 100644 index 00000000..3ca5451c --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_batch.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +批量写入小程序运营数据到飞书运营报表(小程序访问行)。 +- 读取 soul_party_to_feishu_sheet 中的 MINIPROGRAM_EXTRA +- 逐个日期调用 write_miniprogram_to_sheet,填入「小程序访问」「访客」「交易金额」 +用法:python3 write_miniprogram_batch.py +""" +import os +import subprocess +import sys + +FEISHU_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, FEISHU_SCRIPT_DIR) +from soul_party_to_feishu_sheet import MINIPROGRAM_EXTRA + + +def main(): + if not MINIPROGRAM_EXTRA: + print('MINIPROGRAM_EXTRA 为空,无可写入数据。') + sys.exit(0) + + script = os.path.join(FEISHU_SCRIPT_DIR, 'write_miniprogram_to_sheet.py') + total = 0 + for date_col, extra in MINIPROGRAM_EXTRA.items(): + access = extra.get('访问次数') + visitor = extra.get('访客', access) + txn = extra.get('交易金额', 0) + if access is None: + continue + cmd = [sys.executable, script, date_col, str(access), str(visitor), str(txn)] + r = subprocess.run(cmd, capture_output=True, text=True, cwd=FEISHU_SCRIPT_DIR) + if r.returncode == 0: + total += 1 + print(f'✅ 2月{date_col}日:访问 {access}、访客 {visitor}、交易 {txn}') + else: + print(f'⚠️ 2月{date_col}日 写入失败:{r.stderr or r.stdout}') + + print(f'✅ 批量写入完成,共 {total} 天') + sys.exit(0 if total > 0 else 1) + + +if __name__ == '__main__': + main() diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_to_sheet.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_to_sheet.py new file mode 100644 index 00000000..6ab0957e --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_to_sheet.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +小程序运营数据写入飞书运营报表(当日交易金额、访客数、小程序访问次数)。 +- 自动查找表格中「交易金额」「访客」「小程序访问」对应行 +- 写入指定日期列 +用法:python3 write_miniprogram_to_sheet.py <日期列号> <访问次数> [访客数] [交易金额] + 例:python3 write_miniprogram_to_sheet.py 23 55 55 0 + 例:python3 write_miniprogram_to_sheet.py 23 55 (访客=访问次数,交易金额=0) +""" +import os +import sys +import json +import requests +from urllib.parse import quote + +FEISHU_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +TOKEN_FILE = os.path.join(FEISHU_SCRIPT_DIR, '.feishu_tokens.json') +SPREADSHEET_TOKEN = os.environ.get('FEISHU_SPREADSHEET_TOKEN', 'wikcnIgAGSNHo0t36idHJ668Gfd') +SHEET_ID = os.environ.get('FEISHU_SHEET_ID', '7A3Cy9') + +# 指标名 → 匹配关键词(A列包含即认为找到) +ROW_KEYWORDS = { + '交易金额': ['交易金额'], + '访客': ['访客'], + '小程序访问': ['小程序访问'], +} + + +def load_token(): + if not os.path.exists(TOKEN_FILE): + return None + with open(TOKEN_FILE, 'r', encoding='utf-8') as f: + return json.load(f).get('access_token') + + +def refresh_token(): + if not os.path.exists(TOKEN_FILE): + return None + with open(TOKEN_FILE, 'r', encoding='utf-8') as f: + data = json.load(f) + r = requests.post( + 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal', + json={'app_id': 'cli_a48818290ef8100d', 'app_secret': 'dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4'}, + timeout=10, + ) + app_token = (r.json() or {}).get('app_access_token') + if not app_token: + return None + r2 = requests.post( + 'https://open.feishu.cn/open-apis/authen/v1/oidc/refresh_access_token', + headers={'Authorization': f'Bearer {app_token}', 'Content-Type': 'application/json'}, + json={'grant_type': 'refresh_token', 'refresh_token': data.get('refresh_token')}, + timeout=10, + ) + out = r2.json() + if out.get('code') == 0 and out.get('data', {}).get('access_token'): + data['access_token'] = out['data']['access_token'] + data['refresh_token'] = out['data'].get('refresh_token', data.get('refresh_token')) + with open(TOKEN_FILE, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + return data['access_token'] + return None + + +def read_range(token, range_str): + url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{quote(range_str, safe="")}' + r = requests.get(url, headers={'Authorization': f'Bearer {token}'}, timeout=15) + if r.status_code != 200: + return None + body = r.json() + if body.get('code') != 0: + return None + return (body.get('data') or {}).get('valueRange', {}).get('values') or [] + + +def update_cell(token, range_str, value, value_input_option='USER_ENTERED'): + if range_str.count('!') == 1 and ':' not in range_str.split('!')[1]: + range_str = range_str + ':' + range_str.split('!')[1] + url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values' + params = {'valueInputOption': value_input_option} + v = value if value is not None and value != '' else '' + payload = {'valueRange': {'range': range_str, 'values': [[v]]}} + r = requests.put( + url, params=params, + headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, + json=payload, timeout=15, + ) + try: + return r.status_code, r.json() + except Exception: + return r.status_code, {} + + +def _col_letter(n): + s = '' + while True: + s = chr(65 + n % 26) + s + n = n // 26 + if n <= 0: + break + return s + + +def find_row_for_keyword(vals, keyword_list): + """在 vals 中找 A 列包含任一 keyword 的行号(1-based)""" + for ri, row in enumerate(vals): + a1 = (row[0] if row and len(row) > 0 else '') + a1 = str(a1 or '').strip() + for kw in keyword_list: + if kw in a1: + return ri + 1 + return None + + +def main(): + if len(sys.argv) < 3: + print('用法:python3 write_miniprogram_to_sheet.py <日期列号> <访问次数> [访客数] [交易金额]') + print(' 例:python3 write_miniprogram_to_sheet.py 23 55 55 0') + sys.exit(1) + date_col_str = sys.argv[1].strip() + access_count = sys.argv[2].strip() + visitor_count = sys.argv[3].strip() if len(sys.argv) > 3 else access_count + transaction = sys.argv[4].strip() if len(sys.argv) > 4 else '0' + + token = load_token() or refresh_token() + if not token: + print('❌ 无法获取飞书 Token') + sys.exit(1) + + vals = read_range(token, f'{SHEET_ID}!A1:AG35') + if not vals or len(vals) < 2: + print('❌ 读取表格失败') + sys.exit(1) + + header = vals[0] + col_idx = None + for idx, cell in enumerate(header): + if str(cell).strip() == date_col_str: + col_idx = idx + break + if col_idx is None: + print(f'❌ 未找到日期列 {date_col_str}') + sys.exit(1) + + # 查找三行:交易金额、访客、小程序访问 + row_txn = find_row_for_keyword(vals, ROW_KEYWORDS['交易金额']) + row_visitor = find_row_for_keyword(vals, ROW_KEYWORDS['访客']) + row_access = find_row_for_keyword(vals, ROW_KEYWORDS['小程序访问']) + + # 若未找到「访客」单独行,可能和「小程序访问」共用,用访问次数 + if not row_visitor and row_access: + row_visitor = row_access # 同一行填访客与访问 + + col_letter = _col_letter(col_idx) + written = 0 + + def _write_one(row_num, val, name): + nonlocal written, token + if row_num is None: + return + rng = f'{SHEET_ID}!{col_letter}{row_num}' + code, body = update_cell(token, rng, val) + if code == 401 or body.get('code') in (99991677, 99991663): + t = refresh_token() + if t: + token = t + code, body = update_cell(token, rng, val) + if code == 200 and body.get('code') in (0, None): + print(f'✅ 已写入 {name} → 2月{date_col_str}日列:{val}') + written += 1 + else: + print(f'⚠️ 写入 {name} 失败:{code} {body}') + + _write_one(row_txn, transaction, '交易金额') + _write_one(row_visitor, visitor_count, '访客') + _write_one(row_access, access_count, '小程序访问') + + if written == 0: + print('❌ 未找到可写入的行,请确认表格 A 列有「交易金额」「访客」「小程序访问」等指标') + sys.exit(1) + print(f'✅ 小程序运营数据已填入 2月{date_col_str}日列,共 {written} 项') + + +if __name__ == '__main__': + main() diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md index 192f28fc..5ad96c12 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md @@ -53,7 +53,7 @@ python3 write_party_minutes_from_txt.py "/path/to/soul 派对 106场 20260221.tx | 表格链接 | https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=7A3Cy9 | | spreadsheet_token | `wikcnIgAGSNHo0t36idHJ668Gfd` | | sheet_id | `7A3Cy9` | -| 表格结构 | A 列=指标名,第 1 行=日期(1、2…21…),第 3~12 行=效果数据,第 28 行=今日总结 | +| 表格结构 | A 列=指标名,第 1 行=日期(1、2…21…),第 3~12 行=效果数据,第 15 行=小程序访问,第 28 行=今日总结 | ### 1.3 飞书群 Webhook @@ -89,6 +89,29 @@ python3 write_party_minutes_from_txt.py "/path/to/soul 派对 106场 20260221.tx |:---|:---|:---| | `feishu_write_minutes_to_sheet.py` | 会议纪要/派对总结**图片**上传到对应单元格 | `python3 feishu_write_minutes_to_sheet.py [内部图] [派对图]` | | `feishu_sheet_monthly_stats.py` | 月度运营数据统计 | `python3 feishu_sheet_monthly_stats.py 2` 或 `all` | +| `write_miniprogram_to_sheet.py` | **单独**写入小程序三核心数据(访问次数、访客、交易金额) | `python3 write_miniprogram_to_sheet.py 23 55 55 0` | + +### 2.3 小程序运营数据(自动写入) + +每日填表时,若在 `soul_party_to_feishu_sheet.py` 中配置了 `MINIPROGRAM_EXTRA`,会**自动**把当日小程序三核心数据写入对应日期列: + +| 指标 | 数据来源 | 行(A 列关键词) | +|:---|:---|:---| +| 访问次数 | 微信公众平台 → 小程序 → 统计 → 实时访问 | 小程序访问 | +| 访客 | 同上 | 访客 | +| 交易金额 | 同上 | 交易金额 | + +**配置方式**(在 `soul_party_to_feishu_sheet.py` 中): + +```python +MINIPROGRAM_EXTRA = { + '23': {'访问次数': 55, '访客': 55, '交易金额': 0}, # 2月23日 +} +``` + +- 数据来源:微信公众平台 → 小程序 → 统计,每日手动查看后填入 +- 仅填表当天:运行 `python3 soul_party_to_feishu_sheet.py 107` 时,若 107 场对应 2月23日,且 `MINIPROGRAM_EXTRA` 有 `'23'`,则自动写入 +- 单独写入:`python3 write_miniprogram_to_sheet.py 23 55 55 0`(日期列号 访问次数 访客 交易金额) --- @@ -146,6 +169,7 @@ python3 write_party_minutes_from_txt.py "/Users/karuo/Downloads/soul 派对 107 成功输出示例: ``` ✅ 已写入飞书表格:107场 效果数据(竖列 W3:W12,共10格),校验通过 +✅ 已写入小程序运营数据(2月23日列):访问次数 55、访客 55、交易金额 0 ✅ 已同步推送到飞书群(竖状格式) ✅ 已写入派对智能纪要到「今日总结」→ 2月22日列,校验通过 ``` @@ -282,6 +306,8 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4 第11行: 增加关注 | | | | | xx | xx | | 第12行: 最高在线 | | | | | xx | xx | | ... +第15行: 小程序访问| | | | | xx | xx | | ← 访问次数、访客、交易金额写对应行 +... 第28行: 今日总结 | | | | | xx | xx | | ← 智能纪要写这里 ``` @@ -289,7 +315,7 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4 ## 十、新增场次模板 -每次新增场次,只需改 `soul_party_to_feishu_sheet.py` 两处: +每次新增场次,只需改 `soul_party_to_feishu_sheet.py` 两到三处: ```python # 1. ROWS 字典加一行 @@ -299,6 +325,9 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4 SESSION_DATE_COLUMN = {..., 'NEW': '日期号'} # 3. _maybe_send_group 内 date_label 和 src_date 可选加映射(可选,不加则不发群) + +# 4. 若当日有小程序数据,在 MINIPROGRAM_EXTRA 中加: +# MINIPROGRAM_EXTRA = {..., '23': {'访问次数': 55, '访客': 55, '交易金额': 0}} ``` --- diff --git a/05_卡土(土)/土簿_财务管理/公司财务/SKILL.md b/05_卡土(土)/土簿_财务管理/公司财务/SKILL.md new file mode 100644 index 00000000..937f195a --- /dev/null +++ b/05_卡土(土)/土簿_财务管理/公司财务/SKILL.md @@ -0,0 +1,43 @@ +--- +name: 公司财务 +description: 芸归喜、卡卡猫等公司主体的收支、月度报表、工资表。仅公司开支,不含家庭。触发词:公司财务、芸归喜、卡卡猫、公司报表、公司开支。 +group: 土 +triggers: 公司财务、芸归喜、卡卡猫、公司报表、公司开支、中信0405、网商9532 +owner: 土簿 +version: "1.0" +updated: "2026-02-08" +parent: 财务管理 +--- + +# 公司财务 + +卡若名下公司主体的财务汇总、月度报表、工资表。**仅含公司收支,不含家庭财务**。 + +## 关联路径 + +| 项目 | 路径 | +|:---|:---| +| 月度报表 | `4、财务/报告/`(如 2026年1月财务报表_完整表格.md、2026年2月财务报表_完整表格.md) | +| 工资表 | `4、财务/报告/`(如 2026年2月工资表.md) | +| 重点机构交易明细 | `4、财务/财务报表/重点机构与主体_交易明细.csv` | +| 数据解析脚本 | `4、财务/scripts/parse_feb_2026_data.py` | + +## 核心能力 + +1. **报表**:生成/更新公司月度财务报表(芸归喜、卡卡猫、云消费、飞书工资) +2. **数据来源**:中信 0405(**卡卡猫**)、网商 9532(**芸归喜**)、腾讯云、飞书钱袋子 +3. **执行**:运行 `parse_feb_2026_data.py [YYYY-MM]` 解析指定月份后更新报表 + +## 报表范围(仅公司) + +- 云消费(腾讯云) +- 卡卡猫(中信 0405)收支 +- 芸归喜(网商 9532)收支 +- 飞书·工资/报销 +- **不含**:家庭、鲨鱼记账 + +## 生成月度报表步骤 + +1. 执行 `python3 4、财务/scripts/parse_feb_2026_data.py YYYY-MM` 获取数据 +2. 更新 `4、财务/报告/YYYY年MM月财务报表_完整表格.md` +3. 工资表从飞书钱袋子「2026年卡若公司工资表」同步 diff --git a/05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md b/05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md new file mode 100644 index 00000000..e706b574 --- /dev/null +++ b/05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md @@ -0,0 +1,39 @@ +--- +name: 家庭财务 +description: 家庭收支、鲨鱼记账、个人/家庭账单。与公司财务分离。触发词:家庭财务、鲨鱼、家庭收支、家庭报表。 +group: 土 +triggers: 家庭财务、鲨鱼记账、家庭收支、家庭报表 +owner: 土簿 +version: "1.0" +updated: "2026-02-08" +parent: 财务管理 +--- + +# 家庭财务 + +卡若家庭收支、鲨鱼记账、个人账单。**与公司财务分离,仅家庭维度**。 + +## 关联路径 + +| 项目 | 路径 | +|:---|:---| +| 鲨鱼记账明细 | `4、财务/家庭财务/` 或 鲨鱼导出 | +| 家庭报表 | 需从鲨鱼记账按月导出后填入 | + +## 核心能力 + +1. **家庭收支**:鲨鱼记账月度汇总 +2. **数据来源**:鲨鱼记账导出(收入、支出、缺口/结余) +3. **与公司分离**:公司报表不含家庭;家庭报表单独维护 + +## 报表范围(仅家庭) + +- 收入 +- 支出 +- 缺口/结余 + +## 生成家庭月度报表步骤 + +1. 从鲨鱼记账导出指定月份(如 2026-02) +2. 汇总收入、支出、结余 +3. 写入家庭财务专用报表或 `4、财务/报告/` 下家庭分表 diff --git a/SKILL_REGISTRY.md b/SKILL_REGISTRY.md index 78a870ce..19cf96c7 100644 --- a/SKILL_REGISTRY.md +++ b/SKILL_REGISTRY.md @@ -1,7 +1,7 @@ # 卡若AI 技能注册表(Skill Registry) > **一张表查所有技能**。任何 AI 拿到这张表,就能按关键词找到对应技能的 SKILL.md 路径并执行。 -> 57 技能 | 14 成员 | 5 负责人 +> 58 技能 | 14 成员 | 5 负责人 > 版本:5.0 | 更新:2026-02-16 --- @@ -56,6 +56,7 @@ | W09 | 小程序管理 | 水桥 | 小程序、微信小程序 | `02_卡人(水)/水桥_平台对接/小程序管理/SKILL.md` | 微信小程序发布与维护 | | W10 | Soul文章上传 | 水桥 | **Soul文章上传、Soul派对文章、第9章上传、soul上传** | `02_卡人(水)/水桥_平台对接/Soul文章上传/SKILL.md` | 《一场soul的创业实验》第9章文章写好后上传到小程序,id 已存在则更新不重复 | | W11 | Soul派对运营报表 | 水桥 | **运营报表、派对填表、派对截图填表发群、派对纪要、智能纪要、106场、107场、本月运营数据** | `02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md` | 派对截图+TXT→飞书运营报表→智能纪要→飞书群推送,含Token自刷新与写入校验 | +| W12 | MCP 搜索与连接 | 水桥 | **MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现、查MCP、装MCP** | `02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md` | 搜索 5000+ MCP 服务器→生成安装配置→写入 Cursor/Claude 等 | ## 木组 · 卡木(产品内容创造) @@ -93,6 +94,8 @@ | E03 | 流量自动化 | 土渠 | 刷流量、SEO | `05_卡土(土)/土渠_流量招商/流量自动化/SKILL.md` | SEO、流量投放自动化 | | E04 | 手机流量自动操作 | 土渠 | 手机自动化、AutoGLM | `05_卡土(土)/土渠_流量招商/手机与网页流量自动操作/SKILL.md` | 手机 App 自动化操作 | | E05 | 财务管理 | 土簿 | 财务、报表、银行 | `05_卡土(土)/土簿_财务管理/财务管理/SKILL.md` | 收支记录、财务报表 | +| E05a | 公司财务 | 土簿 | **公司财务、芸归喜、卡卡猫、公司报表、公司开支** | `05_卡土(土)/土簿_财务管理/公司财务/SKILL.md` | 仅公司收支、月度报表、工资表 | +| E05b | 家庭财务 | 土簿 | **家庭财务、鲨鱼记账、家庭收支** | `05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md` | 家庭收支、鲨鱼记账,与公司分离 | | E06 | 商业工具集(财务) | 土簿 | 商业分析 | `05_卡土(土)/土簿_财务管理/商业工具集/SKILL.md` | 财务视角的商业分析 | --- @@ -117,8 +120,8 @@ | 组 | 负责人 | 成员数 | 技能数 | |:--|:---|:--|:--| | 金 | 卡资 | 2 | 20 | -| 水 | 卡人 | 3 | 11 | +| 水 | 卡人 | 3 | 12 | | 木 | 卡木 | 3 | 6 | | 火 | 卡火 | 4 | 13 | | 土 | 卡土 | 4 | 7 | -| **合计** | **5** | **14** | **57** | +| **合计** | **5** | **14** | **58** | diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 403b55f4..863feaa1 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -117,3 +117,4 @@ | 2026-02-23 20:58:31 | 🔄 卡若AI 同步 2026-02-23 20:58 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | | 2026-02-23 21:08:00 | 🔄 卡若AI 同步 2026-02-23 21:07 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | | 2026-02-23 21:19:17 | 🔄 卡若AI 同步 2026-02-23 21:19 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | +| 2026-02-23 21:31:11 | 🔄 卡若AI 同步 2026-02-23 21:31 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 345d7780..fd40da0d 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -120,3 +120,4 @@ | 2026-02-23 20:58:31 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 20:58 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-23 21:08:00 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:07 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-23 21:19:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:19 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-02-23 21:31:11 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:31 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | diff --git a/运营中枢/工作台/家里NAS_DS213j_整体分析报表.md b/运营中枢/工作台/家里NAS_DS213j_整体分析报表.md new file mode 100644 index 00000000..101a6a41 --- /dev/null +++ b/运营中枢/工作台/家里NAS_DS213j_整体分析报表.md @@ -0,0 +1,112 @@ +# 家里 NAS(DS213j)整体分析报表 + +> **生成时间**:2026-02 +> **数据来源**:端口扫描、现有文档、分布式算力管控 Skill、双 NAS 区分文档 +> **说明**:SSH/DSM API 当前未连接,部分为历史记录与文档汇总 + +--- + +## 一、设备概览 + +| 项目 | 值 | +|:-----|:---| +| **型号** | Synology DS213j (synology_armada370_213j) | +| **主机名** | DiskStation / DiskStation.local | +| **内网 IP** | 192.168.110.29 | +| **外网域名** | opennas2.quwanzhi.com(frpc 穿透) | +| **MAC 地址** | 00:11:32:30:4c:4f(Synology OUI) | +| **用途** | 家庭存储、Time Machine 备份、PCDN(网心云) | + +--- + +## 二、硬件与系统 + +| 项目 | 规格 | +|:-----|:-----| +| **CPU** | Marvell Armada370(ARMv7) | +| **架构** | armv7l(32 位 ARM) | +| **内存** | 497MB | +| **内核** | Linux 3.2.40 | +| **文件系统** | ext4,RAID(md2) | +| **Docker** | ❌ 不支持(内核 3.x 无 cgroup) | + +--- + +## 三、存储空间 + +| 指标 | 值 | 备注 | +|:-----|:---|:-----| +| **总容量** | 5.4TB | 双盘 RAID | +| **已用** | 约 3.4TB~4.6TB | 不同时间点记录 | +| **可用** | 约 885GB~2.0TB | 视时段与共享不同 | +| **Time Machine 共享** | 2.18TB 可用 | 系统设置中「共享」卷 | +| **空间占比** | 约 64%~84% | 建议保持 100GB+ 余量 | + +--- + +## 四、网络与端口 + +### 4.1 当前开放端口(本次扫描) + +| 端口 | 服务 | 状态 | +|:-----|:-----|:-----| +| 22 | SSH | 开放 | +| 80 | HTTP | 开放 | +| 139 | NetBIOS | 开放 | +| 443 | HTTPS | 开放 | +| 445 | SMB | 开放 | +| 5000 | DSM HTTP | 开放 | +| 5001 | DSM HTTPS | 开放 | + +### 4.2 内网穿透(frpc → opennas2.quwanzhi.com) + +| 服务 | 外网端口 | 外网访问 | +|:-----|:---------|:---------| +| SSH | 22202 | `ssh admin@opennas2.quwanzhi.com -p 22202` | +| DSM | 5002 / 80 | http://opennas2.quwanzhi.com:5002 或 :80 | +| SMB | 4452 | smb://opennas2.quwanzhi.com:4452/共享(需在 frpc 中配置) | +| FTP / rsync / MariaDB 等 | 见双 NAS 文档 | — | + +--- + +## 五、已部署服务 + +| 服务 | 说明 | 状态 | +|:-----|:-----|:-----| +| **DSM** | 群晖管理界面 | ✅ 运行中 | +| **SMB/AFP** | 文件共享、Time Machine | ✅ 运行中 | +| **frpc** | 内网穿透(opennas2) | ✅ 配置在 crontab 检活 | +| **网心云 wxedge** | PCDN(chroot 部署) | ✅ 运行中,SN: CTWX09Y9Q2ILI4PV | + +--- + +## 六、资源与负载(历史记录) + +| 项目 | 状态 | +|:-----|:-----| +| **CPU 负载** | 曾出现 udevd 异常,已处理,当前约 1.6 | +| **内存** | 497MB 总量,约 67MB~170MB 可用(视 PCDN 运行情况) | +| **磁盘** | 已清理 core dump,空间从 100% 恢复到约 84% | + +--- + +## 七、访问方式 + +| 场景 | 地址 | +|:-----|:-----| +| **内网 DSM** | http://192.168.110.29:5000 | +| **外网 DSM** | http://opennas2.quwanzhi.com:5002 | +| **Time Machine** | 共享 - DiskStation.local(或 smb://192.168.110.29/共享) | +| **外网 1TB 挂载** | smb://opennas2.quwanzhi.com:4452/共享(需先添加 frpc SMB) | + +--- + +## 八、风险与建议 + +| 类型 | 说明 | +|:-----|:-----| +| **硬件老化** | DS213j 为较旧型号,ARM32 + 497MB,不适合再增重型服务 | +| **无 Docker** | 无法运行容器,扩展能力有限 | +| **外网 SSH 不稳定** | 外网 22202 有时不可达,与家庭网络、frpc 有关 | +| **空间** | 定期清理,保持 10% 以上可用空间 | +| **建议** | 维持当前用途(存储 + Time Machine + PCDN),避免新增高负载服务 |