diff --git a/.cursor/rules/karuo-ai.mdc b/.cursor/rules/karuo-ai.mdc index d7b929c9..d9f4fc60 100644 --- a/.cursor/rules/karuo-ai.mdc +++ b/.cursor/rules/karuo-ai.mdc @@ -71,6 +71,9 @@ alwaysApply: true ### 工作台路径 - `/Users/karuo/Documents/个人/卡若AI/` +### MD 预览(全局) +- 所有 .md 的打开/预览一律用 **Markdown Preview Enhanced** 单界面方式;已在 Cursor User 设置中配置 `workbench.editorAssociations: "*.md": "markdown-preview-enhanced"`,科室及任何对话中打开 .md 均按此方式,不改用内置预览或双界面。 + ### 项目与端口注册表(有变更时必更) - 凡**项目、端口、启动命令或部署流程**有更新/变更,须同步更新 **`运营中枢/工作台/项目与端口注册表.md`**(含注册项目列表与版本记录),使该 doc 始终保持最新。 diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md index 218eb4cf..2f6a0e9d 100755 --- a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md @@ -39,6 +39,12 @@ python3 /Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台 - `python3 feishu_token_cli.py set-march-token ` → 将 3 月文档 node token 写入本地(`.feishu_month_wiki_tokens.json`),写日志时自动使用 - `python3 feishu_token_cli.py get-march-token` → 输出当前 3 月 wiki token(环境变量 > 本地文件) +**Token 自动获取(无 token 时)**:写今日日志(如 `write_today_with_summary.py`)时,若未配置当月(如 3 月)文档 token,脚本会**自动通过飞书 API** 用 2 月文档所在知识空间列出节点、匹配标题含「3月」的文档,将 token 写入 `.feishu_month_wiki_tokens.json` 后继续写入,无需手动配置。若自动获取失败(如空间内无 3 月文档),再提示用命令行:`python3 feishu_token_cli.py set-march-token <从飞书地址栏复制的 token>`。 + +**Token 出现问题时的处理**:若出现「获取文档失败」或「token 无效」,一律用命令行处理:先 `get-access-token` 确保登录有效,再 `set-march-token <正确 token>` 写入当月文档 token;写日志脚本会优先使用本地已保存的 token,确保能写入飞书。 + +**今日日志 + 配图**:`write_today_with_summary.py` 写入成功后会自动生成一张「今日进度」图(本月 12%、距目标 88% 等)并上传插入到当日飞书文档;若插入图片 API 报错,图片会保存在 `参考资料/今日进度_XX.png`,可手动拖入飞书文档。 + **自动完成**: 1. ✅ **静默Token刷新** → 优先使用 refresh_token 自动刷新(无需授权);也可命令行 `get-access-token` 2. ✅ **检查服务** → 自动启动后端服务 diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/auto_log.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/auto_log.py index 8235c7f1..64d964ed 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/auto_log.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/auto_log.py @@ -240,6 +240,71 @@ def parse_month_from_date_str(date_str): return None +MONTH_TOKENS_FILE = os.path.join(os.path.dirname(__file__), ".feishu_month_wiki_tokens.json") + + +def _try_auto_fetch_march_token(access_token): + """无 3 月 token 时通过 API 自动获取:用 2 月文档所在 space 列出节点,匹配标题含「3月」的节点并写入本地。返回 token 或 None。""" + feb_token = (CONFIG.get("MONTH_WIKI_TOKENS") or {}).get(2) or CONFIG.get("WIKI_TOKEN") + if not feb_token: + return None + headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"} + try: + r = requests.get( + "https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node", + params={"token": feb_token}, + headers=headers, + timeout=15, + ) + j = r.json() + if j.get("code") != 0: + return None + data = j.get("data", {}) + node = data.get("node", {}) + space_id = node.get("space_id") or data.get("space_id") + if not space_id: + return None + # 列出空间下节点(可能需分页) + page_token = None + for _ in range(5): + params = {"page_size": 50} + if page_token: + params["page_token"] = page_token + r2 = requests.get( + f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes", + params=params, + headers=headers, + timeout=15, + ) + j2 = r2.json() + if j2.get("code") != 0: + break + items = j2.get("data", {}).get("items", []) + for n in items: + title = (n.get("title") or "") + if "3月" in title or "3 月" in title: + tok = n.get("node_token") or n.get("obj_token") or n.get("token") + if tok: + data = {} + if os.path.exists(MONTH_TOKENS_FILE): + try: + with open(MONTH_TOKENS_FILE, encoding="utf-8") as f: + data = json.load(f) + except Exception: + pass + data["3"] = tok + with open(MONTH_TOKENS_FILE, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + print("✅ 已通过 API 自动获取 3 月文档 token 并写入本地") + return tok + page_token = j2.get("data", {}).get("page_token") + if not page_token or not j2.get("data", {}).get("has_more"): + break + except Exception as e: + print(f"⚠️ 自动获取 3 月 token 异常: {e}") + return None + + def _get_month_wiki_token(month): """当月 wiki token:3 月优先 环境变量 > 本地 .feishu_month_wiki_tokens.json > CONFIG""" if month == 3: @@ -247,9 +312,8 @@ def _get_month_wiki_token(month): if v: return v try: - path = os.path.join(os.path.dirname(__file__), ".feishu_month_wiki_tokens.json") - if os.path.exists(path): - with open(path, encoding="utf-8") as f: + if os.path.exists(MONTH_TOKENS_FILE): + with open(MONTH_TOKENS_FILE, encoding="utf-8") as f: v = (json.load(f).get("3") or "").strip() if v: return v @@ -311,16 +375,36 @@ def write_log(token, date_str=None, tasks=None, wiki_token=None, overwrite=False if not date_str or not tasks: date_str, tasks = get_today_tasks() target_wiki_token = resolve_wiki_token_for_date(date_str, wiki_token) + month = parse_month_from_date_str(date_str) if not target_wiki_token: - month = parse_month_from_date_str(date_str) - print(f"❌ 未配置当月文档 token({month or '?'} 月请设置 FEISHU_MARCH_WIKI_TOKEN 或对应环境变量)") - return False + if month == 3: + print("🔄 未配置 3 月 token,尝试通过 API 自动获取...") + target_wiki_token = _try_auto_fetch_march_token(token) + if not target_wiki_token: + print(f"❌ 未配置当月文档 token({month or '?'} 月请用 feishu_token_cli.py set-march-token 或设置环境变量)") + return False - # 获取文档ID - r = requests.get(f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={target_wiki_token}", + # 获取文档ID(若为 3 月且 get_node 失败,可再尝试自动获取后重试一次) + r = requests.get(f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={target_wiki_token}", headers=headers, timeout=30) + if r.json().get('code') != 0 and month == 3: + target_wiki_token = _try_auto_fetch_march_token(token) + if target_wiki_token: + r = requests.get(f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={target_wiki_token}", + headers=headers, timeout=30) if r.json().get('code') != 0: - print(f"❌ 获取文档失败") + # 若本地曾保存过无效 token,清除以便下次可重新自动获取或手动 set + try: + if os.path.exists(MONTH_TOKENS_FILE) and month == 3: + with open(MONTH_TOKENS_FILE, encoding="utf-8") as f: + data = json.load(f) + if (data.get("3") or "").strip() == (target_wiki_token or "").strip(): + data["3"] = "" + with open(MONTH_TOKENS_FILE, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + except Exception: + pass + print(f"❌ 获取文档失败(当月 token 无效或网络异常,可用 feishu_token_cli.py set-march-token 写入正确 token)") return False node = r.json()['data']['node'] doc_id = node['obj_token'] diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_today_with_summary.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_today_with_summary.py index e00c20c6..7cbfbde9 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_today_with_summary.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_today_with_summary.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 """ 今日飞书日志:搜索全库+聊天/纪要后的最近进度总结汇总 + 每天切片20个视频 + 成交1980及全链路 + 目标百分比写清楚。 +无 3 月 token 时会自动通过 API 获取;写入成功后生成一张今日进度图并上传到飞书文档。 """ import sys +import requests from datetime import datetime from pathlib import Path @@ -11,6 +13,76 @@ sys.path.insert(0, str(SCRIPT_DIR)) from auto_log import get_token_silent, write_log, open_result, resolve_wiki_token_for_date +REF_DIR = SCRIPT_DIR.parent / "参考资料" + + +def _generate_progress_image(date_str: str) -> Path | None: + """生成今日进度图(本月 12% 距目标 88%),返回图片路径;无 Pillow 则返回 None。""" + try: + from PIL import Image, ImageDraw, ImageFont + except ImportError: + return None + out = REF_DIR / f"今日进度_{date_str.replace('月', '').replace('日', '')}.png" + REF_DIR.mkdir(parents=True, exist_ok=True) + w, h = 480, 160 + img = Image.new("RGB", (w, h), color=(255, 252, 240)) + draw = ImageDraw.Draw(img) + try: + font = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 28) + font_small = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 20) + except Exception: + font = ImageFont.load_default() + font_small = font + draw.text((20, 30), f"今日进度 · {date_str}", fill=(60, 60, 60), font=font) + draw.text((20, 75), "本月 12% / 距目标 88%", fill=(180, 80, 60), font=font) + draw.text((20, 115), "20 切片 · 1980 全链路", fill=(80, 80, 80), font=font_small) + img.save(out) + return out + + +def _get_doc_id(token: str, wiki_token: str) -> str | None: + """根据 wiki node token 获取文档 obj_token。""" + r = requests.get( + "https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node", + params={"token": wiki_token}, + headers={"Authorization": f"Bearer {token}"}, + timeout=15, + ) + if r.json().get("code") != 0: + return None + return r.json().get("data", {}).get("node", {}).get("obj_token") + + +def _upload_and_insert_image(token: str, doc_id: str, image_path: Path, date_str: str) -> bool: + """上传图片到文档并插入到当日标题后。""" + from write_0301_feishu_log import upload_image_to_feishu, insert_image_block + file_token = upload_image_to_feishu(token, doc_id, image_path) + if not file_token: + return False + # 获取文档 blocks,找到 date_str 所在位置,在其后插入 + r = requests.get( + f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks", + params={"document_revision_id": -1, "page_size": 500}, + headers={"Authorization": f"Bearer {token}"}, + timeout=15, + ) + if r.json().get("code") != 0: + return False + items = r.json().get("data", {}).get("items", []) + root = [b for b in items if b.get("parent_id") == doc_id] + idx = None + for i, b in enumerate(root): + for key in ("heading4", "text"): + if key in b: + for el in b[key].get("elements", []): + if date_str in el.get("text_run", {}).get("content", ""): + idx = i + break + if idx is not None: + break + insert_at = (idx + 2) if idx is not None else 1 + return insert_image_block(token, doc_id, file_token, image_path.name, insert_at) + def build_tasks_today_with_summary(): """今日:最近进度汇总 + 每天20切片 + 1980成交及全链路 + 目标百分比""" @@ -69,13 +141,24 @@ def main(): target_wiki_token = resolve_wiki_token_for_date(date_str) ok = write_log(token, date_str, tasks, target_wiki_token, overwrite=True) if ok: + target_wiki_token = resolve_wiki_token_for_date(date_str) + doc_id = _get_doc_id(token, target_wiki_token) if target_wiki_token else None + if doc_id: + img_path = _generate_progress_image(date_str) + if img_path and img_path.exists(): + if _upload_and_insert_image(token, doc_id, img_path, date_str): + print("✅ 今日进度图已上传并插入飞书文档") + else: + print(f"⚠️ 图片已生成:{img_path},可手动拖入飞书文档") + else: + print("💡 未安装 Pillow 或生成失败,可手动添加配图") open_result(target_wiki_token) print(f"✅ {date_str} 飞书日志已更新(含进度汇总与目标百分比)") sys.exit(0) print("❌ 写入失败") - ref_path = SCRIPT_DIR.parent / "参考资料" / f"{date_str}_飞书日志正文_三件事与未完成.md" + ref_path = SCRIPT_DIR.parent / "参考资料" / f"{date_str}_飞书日志_进度汇总与百分比.md" if ref_path.exists(): - print(f"💡 可复制 {ref_path} 内容到飞书当月文档手动粘贴") + print(f"💡 可复制 {ref_path} 内容到飞书 3 月文档「{date_str}」下粘贴") sys.exit(1) diff --git a/运营中枢/工作台/Cursor文档预览配置说明.md b/运营中枢/工作台/Cursor文档预览配置说明.md index a7986df5..7a4e3403 100644 --- a/运营中枢/工作台/Cursor文档预览配置说明.md +++ b/运营中枢/工作台/Cursor文档预览配置说明.md @@ -1,6 +1,6 @@ # Cursor 文档预览配置说明 -> 更新:2026-03-06。**去掉本机原有 Markdown 预览**,**只用 Markdown Preview Enhanced 插件的预览模式与显示风格**;不搞错、不敷衍。 +> 更新:2026-03-06。**点 .md 只出现一个界面**,且该界面为 **Markdown Preview Enhanced 插件的预览**(增强预览、非代码);不用本机原有预览、不出现两个面板。 --- @@ -8,51 +8,50 @@ 在 **Cursor User settings**(`~/Library/Application Support/Cursor/User/settings.json`)中: -**① 去掉本机内置预览,不把 .md 关联到内置预览:** +**① .md 默认用插件的 Enhanced 预览打开,且只显示一个界面:** ```json "workbench.editorAssociations": { - "*.md": "default" + "*.md": "markdown-preview-enhanced" } ``` -效果:.md 不再用本机「Markdown 原来的那个」预览;点开 .md 先以源码编辑器打开。 +效果:点击 .md 后**直接只打开一个界面**,即 **Markdown Preview Enhanced 插件的预览**(增强页面),不是代码编辑页,也不是左右两个面板。 -**② 只用插件的预览模式,并自动打开插件预览(显示格式=插件风格):** +**② 不自动再开侧边预览,避免出现两个界面:** ```json -"markdown-preview-enhanced.automaticallyShowPreviewOfMarkdownBeingEdited": true, -"markdown-preview-enhanced.openPreviewToTheSide": true +"markdown-preview-enhanced.automaticallyShowPreviewOfMarkdownBeingEdited": false ``` -效果:打开 .md 后,**Markdown Preview Enhanced 插件**会自动在侧边打开预览,**你看到的预览界面和格式就是插件的(Enhanced)风格**,不是本机原有的那个预览。 +效果:不会在已有预览旁再自动打开一个侧边预览,保证**只显示一个**。 --- -## 预览时一律用插件(不用本机原有) +## 需要改源码时 -- **显示的格式**:必须是 **Markdown Preview Enhanced 插件** 的预览样式,不是 Cursor 内置的 Markdown 预览。 -- **如何打开插件预览**: - - **⌘⇧V**:打开插件预览(主预览) - - **⌘K V**:在侧边打开插件预览(编辑+预览同屏) - - **命令面板**(⇧⌘P)→ 输入 `Markdown Preview Enhanced` → 选「Open Preview」或「Open Preview to the Side」。 -- **不要点标签栏的「Preview」**:那个会打开本机原有预览,会搞混;只用上面快捷键或命令面板,确保是插件的预览。 +当前点 .md 默认是**插件的 Enhanced 预览(单界面)**。若要编辑源码: -已在 `keybindings.json` 中解除本机预览的 ⌘⇧V / ⌘K V 绑定,并改为只打开 Markdown Preview Enhanced,保证按快捷键时**一定是插件的预览模式与风格**。 +- **右键该 .md 标签** →「Reopen Editor With...」→ 选 **「Text Editor」**,即可切到源码编辑。 --- ## 相关设置(已有) -- `workbench.editorAssociations`:`"*.md": "default"` → 本机原有预览**已去掉**,不用于 .md。 -- `markdown-preview-enhanced.automaticallyShowPreviewOfMarkdownBeingEdited`: **true** → 打开 .md 后自动用**插件**在侧边出预览,**显示格式=插件风格**。 -- 内置 `markdown.preview.*` 仅影响本机预览,**不影响插件**;当前配置下预览界面一律是插件的。 +- `workbench.editorAssociations`:`"*.md": "markdown-preview-enhanced"` → 点 .md **只出一个界面**,且是**插件的增强预览**。 +- `markdown-preview-enhanced.automaticallyShowPreviewOfMarkdownBeingEdited`: **false** → 不再自动开侧边,**确定只显示一个**。 -**上述配置保证:本机原有增强预览去掉,只用 Markdown Preview Enhanced 插件的预览模式与显示格式;不搞错。** +上述配置保证:**点一下只出现一个界面,是默认的插件 Enhanced 预览,不是代码页,没有两个。** --- -## 若仍出现本机预览或两个预览 +## 最终确认:普通预览已关掉,只用 Enhanced PREVIEW -- 不要点击标签栏上的「Preview」,否则会打开本机原有预览。 -- 关掉多出来的本机预览标签,以后只用 **⌘⇧V** 或 **⌘K V**(或命令面板「Markdown Preview Enhanced: Open Preview」),确保看到的是**插件的**预览界面与风格。 +- **打开 .md**:`workbench.editorAssociations: "*.md": "markdown-preview-enhanced"` → 一定是 **Enhanced** 的 Preview,不是普通/内置预览。 +- **快捷键**:`keybindings.json` 已解除内置 `markdown.showPreview` / `markdown.showPreviewToSide`,⌘⇧V、⌘K V 只触发 **markdown-preview-enhanced**,普通预览不会通过快捷键打开。 +- **不要点标签栏「Preview」**:该按钮会调出内置预览;只用 Enhanced 时通过点 .md(已关联)或命令面板「Markdown Preview Enhanced: Open Preview」即可。 +- **自动侧边**:`automaticallyShowPreviewOfMarkdownBeingEdited: false`,不会多出一个界面。 + +当前设置无误;打开 .md = 只出现 **Enhanced 的 Preview**,普通预览已关掉。 + +**若仍打开为内置预览**:在左侧资源管理器对任意 .md 文件**右键** → **Open With** → 选 **Markdown Preview Enhanced**(可编辑的增强预览);若弹出「Configure default editor for '*.md'」则选它,即可强制去掉内置、以后都用 Enhanced。 diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 37bc168f..4f69ceae 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -244,3 +244,4 @@ | 2026-03-06 12:41:57 | 🔄 卡若AI 同步 2026-03-06 12:41 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-06 12:42:08 | 🔄 卡若AI 同步 2026-03-06 12:42 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-06 13:07:01 | 🔄 卡若AI 同步 2026-03-06 13:06 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-06 13:09:56 | 🔄 卡若AI 同步 2026-03-06 13:09 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 1f24572f..a78c8609 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -247,3 +247,4 @@ | 2026-03-06 12:41:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-06 12:41 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-06 12:42:08 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-06 12:42 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-06 13:07:01 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-06 13:06 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-06 13:09:56 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-06 13:09 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |