{"id":"6b0387e5-aad5-4954-9708-e3864325c105","entityType":"agent","slug":"clawhub-liuboacean-mubu-integration","name":"mubu-integration","canonicalUrl":"https://www.xpersona.co/agent/clawhub-liuboacean-mubu-integration","canonicalPath":"/agent/clawhub-liuboacean-mubu-integration","generatedAt":"2026-10-09T19:02:58.710Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":null},"description":"幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Skill: mubu-integration Owner: liuboacean Summary: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Tags: agent-skills:1.3.14, ai-agent:1.3.14, api:1.3.15, automation:1.3.16, ci:1.3.14, claude-code:1.3.14, cli:1.3.16, integration:1.3.15, knowledge-management:1.3.14, latest:1.3.16, markdown:1.3.16, mubu:1.3.16, mubu-notes:1.3.14, notes:1.3.14, obsidian:1.3.16,","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.8K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s1779brgcyhkdmz9tgjpwarx3h84vb82:mubu-integration","sourceUrl":"https://clawhub.ai/liuboacean/mubu-integration","homepage":"https://clawhub.ai/liuboacean/skills/mubu-integration","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/liuboacean/mubu-integration","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/liuboacean/skills/mubu-integration","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":40,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Skill: mubu-integration Owner: liuboacean Summary: 幕布"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":null},"stars":null,"forks":null,"downloads":2847,"packageName":null,"latestVersion":"1.3.16","tractionLabel":"2.8K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T11:10:28.767Z","lastCrawledAt":"2026-10-09T11:10:28.767Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T11:10:28.767Z","lastVerifiedAt":null,"highlights":[{"version":"1.3.16","createdAt":"2026-09-02T08:27:17.846Z","changelog":"v1.3.16: CI lint gate hardened; ruff import fixes (I001/F401/F841); known-first-party=mubu added.","fileCount":23,"zipByteSize":107550},{"version":"1.3.15","createdAt":"2026-08-28T04:40:34.291Z","changelog":"v1.3.15: P0 信任修复(create 缺 name 参数坏命令)+测试计数对齐;P1 client 429 限流退避/get_doc 解析保护/search include_content、ruff CI 门禁、Auto-refresh auth 措辞/Troubleshooting 表/cli rename 帮助、分发素材;115 tests green","fileCount":23,"zipByteSize":106689},{"version":"1.3.14","createdAt":"2026-08-25T01:10:01.454Z","changelog":"fix(save): changeset 节点归一化加固（hypothesis 2 闭环）；save_doc 缺失 member_id 时清晰报错（v1.3.13）；升 v1.3.14","fileCount":18,"zipByteSize":98621},{"version":"1.3.12","createdAt":"2026-08-06T08:38:01.216Z","changelog":"README landing page rewrite, AI Agent Skill repositioning, demo GIF + social preview","fileCount":18,"zipByteSize":95014},{"version":"1.3.11","createdAt":"2026-08-05T00:52:58.220Z","changelog":"文档补充：README 新增贡献指引（Issues/PR 流程），欢迎社区提 Bug 与 PR。无代码变更。","fileCount":16,"zipByteSize":91351},{"version":"1.3.10","createdAt":"2026-08-04T07:52:18.906Z","changelog":"文档刷新：移除过时的 save/move/rename 不可用声明，补充 MUBU_MEMBER_ID 配置说明，测试计数 100->112。无代码变更。","fileCount":16,"zipByteSize":90488},{"version":"1.3.9","createdAt":"2026-08-04T07:23:45.862Z","changelog":"v1.3.9: move/save real endpoints rebuilt from captured web traffic. move now uses real /list/custom/drag (was /list/move, code:17) with browser-parity x-headers (x-session-id=uuid:epoch, x-reg-entrance=https://mubu.com/app). save_doc now uses real /v3/api/colla/events (was /doc/save, code:17) via build_update_event root-node update event; per-doc x-reg-entrance=https://mubu.com/app/edit/home/<doc_id>; member_id from MUBU_MEMBER_ID env (~/.workbuddy/.env.mubu) with token-cache fallback. rename_doc now uses separate /list/rename_doc endpoint. Known limit: personal-doc memberId is not API-discoverable, so save_doc requires MUBU_MEMBER_ID set (or cached member_id). Tests: 112 passed (was 109), no regressions.","fileCount":16,"zipByteSize":89795},{"version":"1.3.8","createdAt":"2026-08-04T01:57:02.461Z","changelog":"v1.3.8: 修正 SKILL.md 过期模块引用(scripts/mubu/client.py)，import 路径与 cli.py 一致；无业务代码改动；102 passed","fileCount":16,"zipByteSize":84429}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1779brgcyhkdmz9tgjpwarx3h84vb82:mubu-integration","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T19:02:58.705Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-mubu-integration/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":null},"readme":"Skill: mubu-integration\n\nOwner: liuboacean\n\nSummary: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出\n\nTags: agent-skills:1.3.14, ai-agent:1.3.14, api:1.3.15, automation:1.3.16, ci:1.3.14, claude-code:1.3.14, cli:1.3.16, integration:1.3.15, knowledge-management:1.3.14, latest:1.3.16, markdown:1.3.16, mubu:1.3.16, mubu-notes:1.3.14, notes:1.3.14, obsidian:1.3.16, outline:1.3.14, outliner:1.3.16, productivity:1.3.14, python:1.3.16, skill:1.3.14, sync:1.3.14\n\nVersion history:\n\nv1.3.16 | 2026-09-02T08:27:17.846Z | user\n\nv1.3.16: CI lint gate hardened; ruff import fixes (I001/F401/F841); known-first-party=mubu added.\n\nv1.3.15 | 2026-08-28T04:40:34.291Z | user\n\nv1.3.15: P0 信任修复(create 缺 name 参数坏命令)+测试计数对齐;P1 client 429 限流退避/get_doc 解析保护/search include_content、ruff CI 门禁、Auto-refresh auth 措辞/Troubleshooting 表/cli rename 帮助、分发素材;115 tests green\n\nv1.3.14 | 2026-08-25T01:10:01.454Z | user\n\nfix(save): changeset 节点归一化加固（hypothesis 2 闭环）；save_doc 缺失 member_id 时清晰报错（v1.3.13）；升 v1.3.14\n\nv1.3.12 | 2026-08-06T08:38:01.216Z | user\n\nREADME landing page rewrite, AI Agent Skill repositioning, demo GIF + social preview\n\nv1.3.11 | 2026-08-05T00:52:58.220Z | user\n\n文档补充：README 新增贡献指引（Issues/PR 流程），欢迎社区提 Bug 与 PR。无代码变更。\n\nv1.3.10 | 2026-08-04T07:52:18.906Z | user\n\n文档刷新：移除过时的 save/move/rename 不可用声明，补充 MUBU_MEMBER_ID 配置说明，测试计数 100->112。无代码变更。\n\nv1.3.9 | 2026-08-04T07:23:45.862Z | user\n\nv1.3.9: move/save real endpoints rebuilt from captured web traffic. move now uses real /list/custom/drag (was /list/move, code:17) with browser-parity x-headers (x-session-id=uuid:epoch, x-reg-entrance=https://mubu.com/app). save_doc now uses real /v3/api/colla/events (was /doc/save, code:17) via build_update_event root-node update event; per-doc x-reg-entrance=https://mubu.com/app/edit/home/<doc_id>; member_id from MUBU_MEMBER_ID env (~/.workbuddy/.env.mubu) with token-cache fallback. rename_doc now uses separate /list/rename_doc endpoint. Known limit: personal-doc memberId is not API-discoverable, so save_doc requires MUBU_MEMBER_ID set (or cached member_id). Tests: 112 passed (was 109), no regressions.\n\nv1.3.8 | 2026-08-04T01:57:02.461Z | user\n\nv1.3.8: 修正 SKILL.md 过期模块引用(scripts/mubu/client.py)，import 路径与 cli.py 一致；无业务代码改动；102 passed\n\nv1.3.7 | 2026-08-04T01:25:18.354Z | user\n\nv1.3.7: Roadmap done 同步、README 写回受限标注、触发词收敛、dependabot pip 治理(open-pull-requests-limit:0)、purge_item 安全修复(缺失 trash 不再默认 folder 硬删)、功能可用性清单; 102 passed\n\nv1.3.6 | 2026-07-15T08:53:15.624Z | user\n\n# v1.3.6 — 安全姿态补丁（client.delete 对齐软删）\n\n本版本为 SkillSpector 复审后的安全姿态补丁（GitHub tag `v1.3.6`），无破坏性变更。\n\n## 核心修复\n- **`client.delete()` 对齐软删除语义**：现路由到 `trash_item`（本地软删除，**零网络调用**），与 v1.3.5 CLI 语义及 `client.py` 设计注释一致。此前 `client.delete()` 实际走 `delete_folder`/`delete_doc` 远程硬删，程序化调用方会触发不可逆云端删除。\n- 真实硬删仅保留 `purge_item` / `delete_doc` / `delete_folder`（由 `purge --yes` 调用）。\n\n## 文档修正\n- README `delete` 命令描述由「不可逆」修正为「软删除（本地回收站）」；`purge --yes` 才是唯一不可逆操作。\n- CHANGELOG 新增 M16 / v1.3.6 章节；Roadmap 软删除/回收站 与 依赖锁文件 项标记为已在 M15/v1.3.5 完成（✅）。\n\n## 测试\n- **100 passed（无回归）**。`client.delete()` 不触发任何网络调用，既有 `delete --yes` 守卫 / 回收站写入 / `purge` 行为测试全部保持。\n\n## 已知限制（非阻塞）\n- 依赖锁文件在本机 Python 3.13 解释器上生成（pip-tools 7.5.3 已移除 `--python-version` / `--check` flag）；锁定版本均支持 CI 的 3.9–3.12 矩阵，非阻塞。\n\nv1.3.5 | 2026-07-15T08:08:08.345Z | user\n\n## v1.3.5 — 软删除回收站 + 依赖锁文件\n\n- **软删除回收站**：`delete` 改为本地软删除，仅标记到 `~/.workbuddy/.mubu_trash.json`，**零网络调用**，云端副本保留。\n  - 新增 `restore <id>`：仅移除本地标记，零风险。\n  - 新增 `purge <id> --yes`：**真实服务端删除 + 移除标记，不可逆**。\n  - 新增 `trash`：列出回收站项。\n  - `list` / `search` 默认过滤已软删项，新增 `--include-trash` 可包含。\n- **依赖锁文件（pip-tools）**：新增 `requirements.in` / `requirements-dev.in`，`requirements.txt` / `requirements-dev.txt` 重写为 `pip-compile --generate-hashes` 精确锁定 + 哈希；CI 新增锁文件漂移校验。\n- **文档**：README / SKILL 版本号 → 1.3.5，新增软删除 / 回收站章节，安装说明改锁定文件，`delete` 不再标记为「不可逆」。\n- **测试**：100 passed（软删除零网络 + 回收站写入校验；新增 `TestTrash` 覆盖 restore / purge / list / search 过滤及 `--include-trash` 反例）。\n\n### 已知限制\n- 锁文件在本机 Python 3.13 解释器生成（pip-tools 7.5.3 已移除 `--python-version` / `--check`）；锁定版本 requests 2.34.2 / pytest 9.1.1 / responses 0.26.2 及其传递依赖均支持 CI 3.9–3.12 矩阵，非阻塞。\n\n### 破坏性\n- 无破坏性变更。`delete` 语义由「永久删除」变为「本地软删除」（保留 `--yes` 守卫）；`purge --yes` 为唯一不可逆操作。\n\nv1.3.4 | 2026-07-15T06:46:48.280Z | user\n\nv1.3.3: get_doc 真实端点 POST /document/edit/get + definition 解析 + nodes 形状 + 勾选态渲染。v1.3.4: OPML/FreeMind 支持真实 nodes 形状(不再空大纲); save_doc 客户端契约校验(幕布服务端反爬不可达,已知限制)。\n\nv1.1.6 | 2026-07-10T01:16:02.790Z | user\n\nM7 审计补充：pytest>=8.3.5 修复 CVE-2025-71176（测试依赖 tmpdir 处理漏洞，仅 CI/本地测试使用，运行时不受影响）。pytest 8.4.2 下 48/48 测试全过。\n\nv1.1.5 | 2026-07-10T00:54:49.863Z | user\n\nM6 遗留清理：修复 export_markdown 根节点 note 导出丢失。children 循环后追加 f'> {note}'（与子节点 note 格式一致）。新增 3 测试（存在性/位置/空值/往返）。工程师 IS_PASS YES，QA 独立验证 48/48 全过，路由 NoOne。\n\nv1.1.4 | 2026-07-10T00:41:17.070Z | user\n\nClawHub 安全审计全量整改：requests 升级至 >=2.32.4（修复 CVE-2024-47081/35195）；SKILL.md 新增权限与安全边界声明；触发词收窄为 幕布/mubu/幕布同步/幕布大纲导出；delete 增加不可逆警示与 CLI --yes 硬确认拦截。pytest 45 用例全过，QA 独立复验 7 项审计发现全部 RESOLVED。\n\nv1.1.3 | 2026-07-09T09:44:36.546Z | user\n\nM4 收尾（1.1.3）：① fcntl 跨平台安全化（try/except ImportError，无 fcntl 平台如 Windows 降级为无锁）；② 新增 test_search_global_limit_enforced 真实验证 search() 全局 limit 上限（破坏性验证通过）；③ SKILL.md Token 示例修正为原子写(tempfile+os.replace)+chmod 0o600 并注明 fcntl 锁；④ 45 项 pytest 全过。\n\nv1.1.2 | 2026-07-09T08:52:04.607Z | user\n\nM4 设计保真收尾：锁改模块级、清理双重 import、测试并入既有类（修正并行编辑冲突）\n\nv1.1.1 | 2026-07-09T08:33:09.602Z | user\n\nM4 完善：ENDPOINTS 元组化、search 深度/数量限制、网络兜底优化、token 文件锁、网络健壮性测试、env 解析回归、SKILL.md 清理、CHANGELOG\n\nv1.1.0 | 2026-07-09T07:24:02.235Z | user\n\nv1.1.0：工程化收尾与文档增强。scripts/mubu_api.py 补全全量类型注解（typing 模块，Python 3.8+ 兼容）；新增 requirements.txt（requests+pytest+responses）；新增 .github/workflows/test.yml CI（push/PR 触发，Python 3.9-3.12 矩阵，41 用例全过）。README 全面美化：CI 状态徽章、架构与工作流程 Mermaid 图、30 秒快速体验、FAQ、环境依赖与测试 CI 小节。保留全部 CLI 子命令与 Markdown 大纲双向往返能力。\n\nv1.0.3 | 2026-05-19T08:39:23.016Z | user\n\nAdd ClawScan publisher note\n\nv1.0.2 | 2026-05-15T06:36:33.056Z | auto\n\n- Added a CONTRIBUTING.md file to guide contributions.\n- Updated README.md with new or revised information.\n- No interface or feature changes to core functionality.\n\nv1.0.1 | 2026-05-15T06:16:33.266Z | auto\n\n- Added detailed documentation for integrating with the Mubu (幕布) note-taking service, including authentication, document and folder management, outline export, and token handling.\n- Provided Python code examples covering login, CRUD operations for documents/folders, token caching, and Markdown export.\n- Outlined API endpoints, authentication method, and required environment variables.\n- Included guidance on outline data structure and best practices for token management.\n- Described usage notes and limitations, emphasizing non-official API use and potential stability considerations.\n\nv1.0.0 | 2026-04-14T06:28:17.781Z | auto\n\nInitial release of mubu-integration:\n\n- 支持幕布（mubu.com）账号登录与 Token 管理\n- 提供创建、获取、更新、删除文档和文件夹的 API 接口\n- 支持思维导图内容（大纲结构）获取与 Markdown 导出\n- 涵盖基本的 Token 缓存与自动刷新\n- 提供典型操作的 Python 接口实现与使用示例\n- 适配幕布用户的主要大纲笔记和导出需求\n\nArchive index:\n\nArchive v1.3.16: 23 files, 107550 bytes\n\nFiles: CHANGELOG.md (47215b), CONTRIBUTING.md (785b), docs/hellogithub-pitch.md (1596b), docs/positioning.md (1274b), docs/v2ex-post.md (1020b), docs/xiaoshuopai-tutorial.md (1694b), examples/weekly.md (212b), README.md (11802b), README.zh-CN.md (10660b), requirements-dev.txt (16469b), requirements.txt (8955b), ruff.toml (594b), scripts/gen_assets.py (3055b), scripts/mubu_api.py (3344b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15931b), scripts/mubu/client.py (39841b), scripts/mubu/config.py (8737b), scripts/mubu/convert.py (15143b), skill-card.md (2370b), SKILL.md (20290b), tests/test_mubu_api.py (82356b), _meta.json (136b)\n\nFile v1.3.16:SKILL.md\n\n---\nname: mubu-integration\ndescription: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出\n---\n\n# 幕布集成 Skill\n\n幕布（mubu.com）是一款极简大纲工具，支持将大纲一键转为思维导图。本 Skill 提供 API 集成能力。\n\n## 权限与安全边界\n本 Skill 以你的幕布账号身份操作**远程真实内容**，使用前请知悉其权限边界：\n- **读取**：仅读取环境变量 `MUBU_PHONE` / `MUBU_PASSWORD` / `MUBU_MEMBER_ID`（环境变量未设置时，才由仓库外的 `~/.workbuddy/.env.mubu` 补全；`MUBU_MEMBER_ID` 即幕布 colla 成员 ID，仅 `save` 写回需要。**该值任何 API 都不返回，无法自动获取，必须手动设置 `MUBU_MEMBER_ID` 或提前写入 token 缓存的 `member_id`**）。\n- **写入**：仅在本地写入 Token 缓存文件 `~/.mubu_token`（权限 `0o600` + 跨进程 `fcntl` 锁），不写入其它文件。\n- **网络**：仅访问 `api2.mubu.com`（base URL 可由 `MUBU_BASE_URL` 覆盖，但仅限 `mubu.com` 家族域名，防 MITM），**无第三方服务、无遥测、无数据外发**。\n- **写操作需确认**：真实会改动幕布内容的写操作为 `create`（新建）、`rename_folder`（重命名文件夹）、`save`（保存文档，端点 `/colla/events`，需 `member_id`）、`move`（移动，端点 `/list/custom/drag`）、`rename_doc`（文档重命名，端点 `/list/rename_doc`）、`purge`（彻底删除，唯一真实调用服务端删除，不可逆）；`delete` 现为**软删除**（仅标记进本地回收站，云端副本仍在）。`save` / `move` / `rename_doc` 已在 v1.3.9 经真机验证可用。所有真实写操作均需显式传 `--yes` 才执行，否则中止并提示。\n- **信任边界**：Skill 不读取你的其它本地文件、不执行与幕布无关的 shell 命令；它只做「登录 → 读写你的幕布文档」这一件事。\n\n## 功能概览\n\n| 功能 | 接口 | 说明 |\n|------|------|------|\n| 用户登录 | `POST /user/phone_login` | 手机号密码登录获取 Token |\n| Token 刷新 | 自动处理 | access_token 2小时过期，临近过期自动重新登录（重登仅重试 1 次，杜绝死循环） |\n| 创建文件夹 | `POST /list/create_folder` | 在指定位置创建文件夹 |\n| 创建文档 | `POST /list/create_doc` | 创建新的大纲文档 |\n| 获取列表 | `POST /list/get` | 获取文件夹下的文档列表 |\n| 获取文档 | `POST /document/edit/get` | 获取文档详细内容（真实端点；body 为 docId+password+isFromDocDir，返回 data.definition 为 JSON 字符串需二次解析） |\n| 更新文档 | `POST /colla/events` | 保存/更新文档内容（v1.3.9 起可用；colla 协同端点，`events` 承载 changeset；需 `member_id`，私人文档由 `MUBU_MEMBER_ID` 或 token 缓存提供）|\n| 删除文档 | `POST /list/delete_doc` | 删除文档（按类型区分端点） |\n| 删除文件夹 | `POST /list/delete_folder` | 删除文件夹（原 `/list/delete` 实测非法，已弃用） |\n| 移动文档 | `POST /list/custom/drag` | 移动文档/文件夹到其他位置（v1.3.9 起可用；body `dst/src/folderId`，已真机验证）|\n| 导出 Markdown | 本地转换 | 将大纲结构转换为 Markdown |\n\n## API 基础信息\n\n- **Base URL**: `https://api2.mubu.com/v3/api`\n- **认证方式**: JWT Token，通过请求头 `Jwt-Token` 传递\n- **Content-Type**: `application/json;charset=UTF-8`\n\n## 环境变量配置\n\n在使用前，需要配置以下环境变量：\n\n```bash\nexport MUBU_PHONE=\"your_phone_number\"    # 幕布账号手机号\nexport MUBU_PASSWORD=\"your_password\"      # 幕布账号密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"your_collab_member_id\"\n```\n\n> 切勿在脚本或代码中硬编码明文密码；凭据仅通过环境变量或仓库外的\n> `~/.workbuddy/.env.mubu` 提供。\n\n---\n\n## 使用说明\n\n### 1. 使用 MubuClient\n\n所有操作都通过 `scripts/mubu/client.py` 中的 `MubuClient` 类完成（`scripts/mubu_api.py` 仅为向后兼容的重新导出 shim，不再建议直接使用；**不再有**独立的\n`login()` / `create_folder()` / `create_doc()` / `get_list()` / `get_doc()` / `save_doc()` /\n`delete_item()` 模块级函数）。实例化时自动读取 `MUBU_PHONE` / `MUBU_PASSWORD`\n环境变量（或 `~/.workbuddy/.env.mubu`）并加载本地缓存 Token：\n\n```python\nfrom mubu.client import MubuClient\n\n# 登录：凭据来自环境变量；返回扁平 data（token / id / name）\nclient = MubuClient()\ninfo = client.login()\nprint(info[\"user_id\"], info[\"username\"])   # 注意是扁平 data[\"id\"]，非 data[\"user\"][\"id\"]\n\n# 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感）\nresults = client.search(\"项目\", max_depth=3, limit=50)[\"results\"]\nfor r in results:\n    print(r[\"type\"], r[\"name\"], r[\"path\"])\n```\n\n> 登录返回结构为**扁平** `data`：`data[\"id\"]`=用户 ID，`data[\"name\"]`=用户名，\n> `data[\"token\"]`=令牌。这与旧版嵌套 `result[\"data\"][\"user\"][\"id\"]` 不同。\n\n---\n\n## 大纲内容格式\n\n幕布文档内容使用特定的 JSON 格式表示大纲结构：\n\n```json\n{\n  \"name\": \"文档标题\",\n  \"nodes\": [\n    {\n      \"id\": \"node_1\",\n      \"text\": \"一级标题\",\n      \"children\": [\n        {\n          \"id\": \"node_1_1\",\n          \"text\": \"二级标题\",\n          \"children\": []\n        }\n      ],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    },\n    {\n      \"id\": \"node_2\",\n      \"text\": \"另一个一级标题\",\n      \"children\": [],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    }\n  ]\n}\n```\n\n---\n\n## Token 管理建议\n\n由于幕布的 access_token 仅约 2 小时有效（无 refresh_token 机制，代码也无任何 refresh 逻辑），建议：\n\n1. **本地缓存**: 将 Token 保存到本地文件（如 `~/.mubu_token`）\n2. **自动刷新**: 在 Token 快过期时自动刷新\n3. **错误重试**: 遇到 401 错误时重新登录\n\n```python\nimport os\nimport time\nimport json\nimport tempfile\n\nTOKEN_FILE = os.path.expanduser(\"~/.mubu_token\")\n\ndef save_token(token_data):\n    \"\"\"原子写 + 仅属主可读写：避免中途崩溃留下残缺文件，并防止其它用户读取。\"\"\"\n    token_data = dict(token_data)\n    token_data[\"expires_at\"] = time.time() + 7200  # 2小时后过期\n    # 注：真实 scripts/mubu_api.py 的 _save_token 还会用跨进程 fcntl.flock\n    # advisory 锁包裹整段写（M4 已做成跨平台安全：无 fcntl 平台降级为无锁）；\n    # 此处省略锁，聚焦写盘逻辑。\n    dir_name = os.path.dirname(TOKEN_FILE) or \".\"\n    fd, tmp = tempfile.mkstemp(dir=dir_name, prefix=\".mubu_token.\", suffix=\".tmp\")\n    try:\n        with os.fdopen(fd, \"w\") as f:\n            json.dump(token_data, f)\n        os.chmod(tmp, 0o600)        # 仅属主可读写\n        os.replace(tmp, TOKEN_FILE) # 原子重命名，避免残缺文件\n    except Exception:\n        if os.path.exists(tmp):\n            os.remove(tmp)\n        raise\n\ndef load_token():\n    \"\"\"从本地加载未过期的 Token；已过期或损坏则返回 None。\"\"\"\n    if not os.path.exists(TOKEN_FILE):\n        return None\n    try:\n        with open(TOKEN_FILE) as f:\n            data = json.load(f)\n    except Exception:\n        return None\n    if time.time() >= data.get(\"expires_at\", 0):  # 已过期视为无效\n        return None\n    return data\n```\n\n说明：原示例中朴素的 `is_token_valid` 已移除——其职责（\"是否过期\"）已并入 `load_token`，仅返回未过期的 token。真实实现 `scripts/mubu_api.py` 的 `_save_token` 还包含跨进程 `fcntl.flock` 锁与统一的 `TOKEN_FILE_MODE` 权限管理，此处不再重复。\n\n---\n\n## 导出 / 导入 Markdown\n\nM1 已落地真正的 Markdown 导入/导出（往返保真，不再是占位）。核心纯函数位于 `scripts/mubu_api.py`：\n\n```python\ndef doc_to_markdown(node, level=0):\n    \"\"\"将节点（及子树）渲染为 Markdown 列表片段。\n    '- ' 列表项，缩进 = 2 * level；含 checked → '- [x]'/'- [ ]'；\n    含 note → 子树后追加 '> {note}'。根标题由 export_markdown 负责。\"\"\"\n    ...\n\ndef export_markdown(doc):\n    \"\"\"doc 为 get_doc() 返回的 data 层 {\"node\": {...}}。\n    首行 '# 标题'，其余递归为 '- ' 列表。结构无效时抛 MubuError。\"\"\"\n    ...\n\ndef markdown_to_doc(md):\n    \"\"\"Markdown 文本 → {\"node\": {\"id\": \"root\", \"text\": ..., \"children\": [...]}}。\n    标题为顶层节点；多标题时首个为 root，其余作为 root 的 children；\n    列表项用栈按缩进深度维护层级；'- [ ]'/'- [x]' 设 checked；\n    '> ' 作为对应层级节点的 note。\"\"\"\n    ...\n```\n\n导出示例（幕布 → Markdown）：\n\n```\n# 读书笔记\n- 第一章\n  - [x] 读完\n  - [ ] 写笔记\n> 第一章的备注\n```\n\n> 说明：根节点的 `text` 渲染为 `# 标题`，其直接子节点从缩进 0 的 `- ` 列表开始；\n> note 出现在其所属节点（含子树）之后，并按缩进深度归属到对应节点。\n\n---\n\n## 命令参考（CLI）\n\n| 命令 | 说明 |\n|------|------|\n| `login` | 手机号密码登录，Token 本地缓存 |\n| `list --folder <id> [--include-trash]` | 获取文件夹下的文档/子文件夹列表（`--json` 输出原始 JSON；`--include-trash` 包含已软删除项） |\n| `mkdir <name> --parent <id>` | 创建文件夹 |\n| `create <name> --folder <id> [--content <json>] [--md <file>]` | 创建文档；`--md` 从 Markdown 文件导入 |\n| `get <doc_id> [--export markdown\\|json]` | 获取文档；`--export markdown` 输出真实 Markdown |\n| `save <doc_id> [--file <f>] [--md <file>] [--content <c>]` | 保存文档；`--md` 从 Markdown 文件导入 |\n| `delete <id> [--type doc\\|folder] --yes` | **软删除**：移入本地回收站（云端仍在，`restore` 可恢复，`purge` 可彻底删除）；`--type` 默认 folder，必须显式 `--yes` 才执行 |\n| `restore <id>` | 从本地回收站恢复（仅移除标记，零服务端调用） |\n| `purge <id> --yes` | **彻底删除（不可逆）**：调用服务端真实删除 API 后移除本地标记；必须显式 `--yes` 才执行 |\n| `trash` | 列出本地回收站中已软删除的项 |\n| `move <item_id> --target <folder_id> [--type doc\\|folder]` | 移动文档/文件夹到其他位置（v1.3.9 起可用，端点 `/list/custom/drag`，已真机验证）|\n| `search <关键字> [--max-depth N] [--limit N] [--include-trash]` | 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感；`--include-trash` 包含已软删除项） |\n| `export-tree --folder <id> [--output <dir>] [--max-depth N]` | 递归导出整个文件夹树为嵌套 Markdown 文件 |\n| `rename <id> --name <新名> [--type doc\\|folder]` | 重命名文档（`/list/rename_doc` 端点）或文件夹（已验证端点 `/list/rename_folder`，`folderId` 填自身 id）|\n| `opml <doc_id> [--format opml\\|freeplane]` | 导出为 OPML 2.0 / FreeMind XML（兼容 XMind 等其它大纲工具）|\n\n### 软删除 / 回收站（v1.3.5）\n\n`delete` 现在不再是真正的服务端删除，而是**软删除**：\n\n- `delete <id> [--type doc\\|folder] --yes` —— **移入本地回收站**：仅把项的元数据标记进本地回收站文件 `~/.workbuddy/.mubu_trash.json`，**云端副本保持不变**，不调用任何删除 API。缺省（无 `--yes`）仅打印提示并退出，绝不软删除。\n- `restore <id>` —— 从本地回收站恢复：仅移除本地标记，**零服务端调用**（即使云端项已不存在也安全）。未找到该项时提示「未找到回收站项」。\n- `purge <id> --yes` —— **彻底删除（不可逆）**：唯一真正调用服务端删除 API（`delete_doc` / `delete_folder`）的操作，成功后移除本地标记。必须显式 `--yes`，否则中止。\n- `trash` —— 列出本地回收站中已软删除的项（id / type / name / deleted_at）。\n\n`list --include-trash` 与 `search <关键字> --include-trash` 可在列表中**包含**已软删除项（`get_list` / `search` 默认过滤回收站项）。\n\n> 回收站仅存元数据快照（id / type / name / parent_id / deleted_at），作为「云端仍在、可恢复」的安全网，**不作为重建来源**。\n\nMarkdown 往返示例：\n\n```bash\n# 导出为 Markdown\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# 从 Markdown 创建文档\npython3 scripts/mubu_api.py create \"我的文档\" --folder <folder_id> --md ./outline.md\n\n# 从 Markdown 更新文档\npython3 scripts/mubu_api.py save <doc_id> --md ./outline.md\n\n# 移动文档\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# 按名称本地搜索文档/文件夹（递归遍历所有子文件夹，大小写不敏感）\npython3 scripts/mubu_api.py search \"项目\"\npython3 scripts/mubu_api.py search \"项目\" --json\n\n# 递归导出整个文件夹树为嵌套 Markdown\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# 重命名文档 / 文件夹\npython3 scripts/mubu_api.py rename <doc_id> --name \"新标题\" --type doc\npython3 scripts/mubu_api.py rename <folder_id> --name \"新文件夹名\" --type folder\n\n# 导出为 OPML / FreeMind\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n---\n\n## Token 刷新策略\n\n- access_token 有效期约 2 小时，本地以 `expires_at` 缓存于 `~/.mubu_token`。\n- 每次请求发起前调用 `ensure_valid_token()`：若未持有 token，或距过期不足\n  `300 + 60`（leeway）秒，则使用缓存的 `phone`/`password` **重新登录**获取新 token。\n- **刷新不依赖 refresh_token**（M1 未启用）。\n- **鉴权失败仅重试 1 次**：`_request` 捕获 401 / 登录失效类错误后重新登录并重试最多一次；\n  第二次仍失败则抛出 `MubuError`，**不再重登**，避免密码错误/账号封禁场景下的死循环。\n- 403（权限不足）或其它非 0 业务 code **不触发重登**。\n- Token 写入采用原子写（先写 `.tmp` 再 `os.rename`），写完追加 `os.chmod(TOKEN_FILE, 0o600)`，\n  确保 Token 文件仅属主可读写（M2 的 T5 已实现）。\n\n---\n\n## 配置说明\n\n脚本通过环境变量读取凭据（优先级：环境变量 > `~/.workbuddy/.env.mubu` 文件；\n两者皆无时，`login` 子命令会交互式提示输入，绝不接受明文命令行参数）：\n\n```bash\nexport MUBU_PHONE=\"你的手机号\"\nexport MUBU_PASSWORD=\"你的密码\"\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"你的幕布 colla 成员 ID\"\n```\n\n也可在 `~/.workbuddy/.env.mubu` 中配置（由 Skill 宿主加载为环境变量，且仅属主可读写）：\n\n```\nMUBU_PHONE=你的手机号\nMUBU_PASSWORD=你的密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nMUBU_MEMBER_ID=你的幕布 colla 成员 ID\n```\n\n---\n\n## 已知限制（M1）\n\n- `expand`（幕布大纲的折叠/展开状态）不在本期往返范围内，导入后节点默认展开。\n- 有序列表 `1.` 不被解析，仅支持无序列表 `- `。\n- 图片 / 附件类型节点不在本期 Markdown 往返范围内（会丢失媒体内容）。\n- 多个顶层标题导入时，首个为 root，其余作为 root 的 children；其后的列表项统一挂在\n  root 下（规范未要求按标题再嵌套）。\n- `save`（文档保存）需要幕布 colla 成员 ID：私人文档的 `memberId` **任何 API 都不暴露**，工具无法自动获取，必须由 `MUBU_MEMBER_ID` 环境变量提供（或提前写入 token 缓存的 `member_id`）。**缺失时 `save` 会明确报错提示配置，不影响 `get` / `create` / `move` / `rename` 等其它操作**。v1.3.9 已修复 `save`（`/colla/events`）、`move`（`/list/custom/drag`）、doc `rename`（`/list/rename_doc`）的真机可用性。\n\n---\n\n## 注意事项\n\n1. **非官方 API**: 幕布未提供官方开放平台，此 Skill 基于逆向分析实现\n2. **稳定性**: API 可能随版本更新而变化，如遇问题请反馈\n3. **频率限制**: 请勿频繁调用，避免触发限流\n4. **数据安全**: Token 存储在本地，请勿泄露\n\n---\n\n## Agent 使用指引\n\n当用户提到幕布、mubu 相关操作（如将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记）时，使用本 Skill 的脚本完成操作。\n\n### 前置检查\n\n1. 确认系统已安装 Python 3 和 requests 库：\n   ```bash\n   python3 -c \"import requests; print('OK')\"\n   ```\n   如果缺少 requests：`pip3 install requests`\n\n2. 确认环境变量已配置：\n   - `MUBU_PHONE` — 幕布手机号\n   - `MUBU_PASSWORD` — 幕布密码\n   - 如未配置，需提示用户先设置\n\n### 脚本路径\n\n```\n~/.workbuddy/skills/mubu-integration/scripts/mubu_api.py\n```\n\n### 常用命令速查\n\n| 用户意图 | 执行命令 |\n|---------|---------|\n| 登录幕布 | `python3 scripts/mubu_api.py login` |\n| 查看文档列表 | `python3 scripts/mubu_api.py list` |\n| 查看某文件夹 | `python3 scripts/mubu_api.py list --folder <folder_id>` |\n| 创建文件夹 | `python3 scripts/mubu_api.py mkdir \"文件夹名\"` |\n| 创建文档 | `python3 scripts/mubu_api.py create \"文档名\" --folder <folder_id>` |\n| 从 Markdown 创建文档 | `python3 scripts/mubu_api.py create \"文档名\" --folder <folder_id> --md outline.md` |\n| 获取文档内容 | `python3 scripts/mubu_api.py get <doc_id>` |\n| 导出为 Markdown | `python3 scripts/mubu_api.py get <doc_id> --export markdown` |\n| 从 Markdown 保存文档 | `python3 scripts/mubu_api.py save <doc_id> --md outline.md` |\n| 从文件保存文档 | `python3 scripts/mubu_api.py save <doc_id> --file content.json` |\n| 移动文档 | `python3 scripts/mubu_api.py move <doc_id> --target <folder_id>` |\n| 软删除（移入回收站） | `python3 scripts/mubu_api.py delete <id> --type doc\\|folder --yes`（云端仍在，`restore` 可恢复）|\n| 从回收站恢复 | `python3 scripts/mubu_api.py restore <id>` |\n| 彻底删除（不可逆） | `python3 scripts/mubu_api.py purge <id> --yes`（必须显式 `--yes`，调用服务端真实删除）|\n| 查看回收站 | `python3 scripts/mubu_api.py trash` |\n| 按名称搜索 | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N]` |\n| 按名称搜索（含回收站） | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N] --include-trash` |\n| 按名称搜索（JSON） | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N] --json` |\n\n### 典型工作流\n\n**场景 1：用户说\"把这份大纲同步到幕布\"**\n1. 确认内容来源（文件或对话中直接提供）\n2. 如果是 Markdown，直接用脚本创建文档并导入\n3. 返回新文档 ID 和链接\n\n**场景 2：用户说\"导出我的幕布笔记\"**\n1. 先列出文档列表让用户选择，或按名称搜索\n2. 获取文档内容\n3. 转换为 Markdown 格式返回\n\n**场景 3：用户说\"在幕布建一个项目文件夹\"**\n1. 确认文件夹名称和层级结构\n2. 批量创建文件夹\n3. 返回创建结果\n\n---\n\n## 工作流示例\n\n### 示例 1: 从 Markdown 创建幕布文档\n\n```\n用户: 把这份 Markdown 大纲同步到幕布\n```\n\n执行步骤：\n1. 解析 Markdown 结构\n2. 转换为幕布 JSON 格式\n3. 登录获取 Token\n4. 创建文档并保存内容\n\n### 示例 2: 导出幕布文档为 Markdown\n\n```\n用户: 导出我的\"读书笔记\"文档\n```\n\n执行步骤：\n1. 登录获取 Token\n2. 本地搜索匹配文档：`python3 scripts/mubu_api.py search \"读书笔记\"`\n3. 获取文档内容\n4. 转换为 Markdown 并返回\n\n### 示例 3: 批量创建文件夹结构\n\n```\n用户: 在幕布创建项目文档结构：需求分析、设计文档、开发日志、测试报告\n```\n\n执行步骤：\n1. 登录获取 Token\n2. 创建项目文件夹\n3. 批量创建子文件夹\n4. 返回创建结果\n\nFile v1.3.16:README.md\n\n[English](README.md) | [中文](README.zh-CN.md)\n\n![mubu-integration](assets/social-preview.png)\n\n# mubu-integration\n\n> Turn Mubu (幕布) into a Markdown-native, AI-agent-controllable outliner.\n\n[![GitHub stars](https://img.shields.io/github/stars/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/network/members)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://img.shields.io/github/actions/workflow/status/liuboacean/mubu-integration/test.yml?branch=main)](https://github.com/liuboacean/mubu-integration/actions/workflows/test.yml)\n\nManage your Mubu (幕布) outlines from the command line — **and as an AI Agent Skill** — with lossless Markdown import/export round-trip.\n\n---\n\n## ✨ Try it in 3 commands (magic moment)\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --md examples/weekly.md    # Markdown outline → Mubu\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # Mubu → Markdown\ndiff weekly.md out.md                                              # no output = byte-for-byte identical\n```\n\n![demo](assets/demo.gif)\n\n---\n\n## 🆚 Why mubu-integration?\n\n| Capability | Manual copy | Existing export-plugin scripts | **mubu-integration** |\n| :--- | :---: | :---: | :---: |\n| Mubu → Markdown | ✅ | ⚠️ partial | ✅ |\n| Markdown → Mubu | ❌ | ❌ | ✅ **(only)** |\n| Round-trip fidelity (`diff` = no diff) | ❌ | ❌ | ✅ **(only)** |\n| Whole-tree batch / OPML / FreeMind | ❌ | ⚠️ some | ✅ |\n| Callable by an AI Agent | ❌ | ❌ | ✅ **(only)** |\n| Scriptable from the command line | ❌ | ⚠️ | ✅ |\n\n---\n\n## 💡 Use cases\n\n**① Let your AI Agent read & write Mubu directly** — turn Mubu into your Agent's long-term, structured memory.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > memory.md   # Agent pulls the latest outline\n# ... Agent edits memory.md ...\npython3 scripts/mubu_api.py save <doc-id> --md memory.md                 # writes the updated outline back to Mubu\n```\n\n**② Obsidian ↔ Mubu, two-way outlines** — keep your knowledge base and your outliner in sync as plain Markdown.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > vault/notes/mubu.md   # Mubu → Obsidian\npython3 scripts/mubu_api.py create --md vault/notes/mubu.md --folder <folder-id>   # Obsidian → Mubu\n```\n\n**③ Auto-archive weekly meeting notes** — push `examples/weekly.md` into Mubu in one step.\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --folder <folder-id> --md examples/weekly.md\n```\n\n---\n\n## 🚀 30-second quick start\n\n1. Set your Mubu credentials (phone + password). They are never passed as CLI arguments — use env vars or a local file:\n\n   ```bash\n   export MUBU_PHONE=\"your-phone\"\n   export MUBU_PASSWORD=\"your-password\"\n   ```\n\n   …or write them to `~/.workbuddy/.env.mubu` (env vars take precedence; the file is auto-chmod `0o600`):\n\n   ```ini\n   MUBU_PHONE=your-phone\n   MUBU_PASSWORD=your-password\n   ```\n\n2. Grab the bundled sample outline (`examples/weekly.md`):\n\n   ```markdown\n   # 产品周会\n   - 上周进展\n     - [x] 上线新版本\n     - [ ] 修复登录 bug\n   - 本周计划\n     - 性能优化\n   > 备注：记得同步给设计团队\n   ```\n\n3. Import it, then export it back — headings, `[x]` checkboxes and `> note` remarks all survive intact:\n\n   ```bash\n   python3 scripts/mubu_api.py create \"产品周会\" --folder <folder_id> --md examples/weekly.md\n   python3 scripts/mubu_api.py get <doc_id> --export markdown\n   ```\n\n---\n\n## 📦 Install\n\n```bash\nnpx skills add liuboacean/mubu-integration\n```\n\nThis installs the Skill for your agent. It is a Python package — you also need **Python 3.9+** and the runtime dependency:\n\n```bash\npip install -r requirements.txt\n```\n\nDev/test dependencies live in `requirements-dev.txt` (`pip install -r requirements-dev.txt`).\n\n---\n\n## 🛡️ Reliability\n\nmubu-integration talks to the **same HTTPS endpoints the Mubu web app uses** — no scraping, no browser automation.\n\n- ✅ **Verified against the live service** — last real-device check on **2026-08-05** against mubu.com's production environment. `move`, `save_doc`, `rename`, OPML, FreeMind and `export-tree` are all confirmed working.\n- ✅ **115 tests × 4 Python versions, always green** — the GitHub Actions matrix runs Python 3.9 / 3.10 / 3.11 / 3.12 on every push and PR.\n- ✅ **Auto-refresh auth** — expired tokens re-login automatically using your cached credentials (env vars / `~/.workbuddy/.env.mubu`); no manual re-entry needed after initial setup.\n- ✅ **Pinned supply chain** — `requirements.txt` locks exact versions; Dependabot keeps them current automatically.\n- ✅ **Your data stays yours** — the tool only ever accesses the account you log into, with your own credentials. Credentials are stored locally at `~/.mubu_token` with `0o600` permissions (readable only by you).\n\n<details>\n<summary>Technical notes</summary>\n\nmubu-integration is an **unofficial** integration that uses the same endpoints as the Mubu web client. All requests go to `https://api2.mubu.com/v3/api`; auth is a JWT passed via the `jwt-token` header. The `access_token` expires in ~2 hours and is refreshed automatically (one retry only, to avoid lockout loops); `403` and other errors do not trigger re-login.\n\n**Known limits:** outline collapse state (`expand`), ordered lists (`1.`), and image/attachment nodes are not part of the current Markdown round-trip. Round-trip is lossless but not a live two-way sync (no diff/merge) — re-importing creates a new copy.\n\n</details>\n\n---\n\n## ⚙️ How it works\n\n```mermaid\nflowchart LR\n    U[User / AI Agent] -->|CLI subcommands| CLI[scripts/mubu_api.py]\n    CLI --> Client[MubuClient]\n    Client -->|HTTPS + jwt-token| API[(api2.mubu.com/v3/api)]\n    API --> Client\n    Client -->|Markdown import/export round-trip| MD[(local .md outline)]\n    Client -->|Token cache 0o600| Cache[(local credentials)]\n```\n\nMarkdown outline ⇄ Mubu document (round-trip):\n\n```mermaid\nflowchart LR\n    subgraph MD[Markdown outline]\n        M[\"# heading<br>- bullet<br>- [x] checkbox<br>&gt; note remark\"]\n    end\n    subgraph MU[Mubu document]\n        B[\"node + level + checkbox + note\"]\n    end\n    M -->|import: create --md| B\n    B -->|export: get --export markdown| M\n```\n\n**Project structure** (modular Python package; `scripts/mubu_api.py` is a backward-compatible shim):\n\n```\nscripts/\n├── mubu_api.py        # backward-compatible shim (re-exports the mubu package)\n└── mubu/              # modular package (v1.3.0+)\n    ├── __init__.py    # package identity (__version__)\n    ├── config.py      # constants / config / logging / MubuError / path safety / token lock\n    ├── convert.py     # doc ↔ Markdown / OPML / FreeMind conversion + display formatting\n    ├── client.py      # MubuClient (auth / requests / doc·folder·search·tree export)\n    └── cli.py         # CLI entrypoint main() + logging setup\n```\n\n---\n\n## 📚 CLI reference\n\n<details>\n<summary>Show all 20+ commands</summary>\n\n```bash\n# Login (first use requires credentials configured)\npython3 scripts/mubu_api.py login\n\n# List root directory\npython3 scripts/mubu_api.py list\n\n# List a sub-folder\npython3 scripts/mubu_api.py list --folder <folder_id>\n\n# Create a folder\npython3 scripts/mubu_api.py mkdir \"New Folder\"\n\n# Create a document\npython3 scripts/mubu_api.py create \"New Doc\" --folder <folder_id>\n\n# Create a document from a Markdown file\npython3 scripts/mubu_api.py create \"New Doc\" --folder <folder_id> --md examples/weekly.md\n\n# Get document content (JSON)\npython3 scripts/mubu_api.py get <doc_id>\n\n# Export as Markdown (round-trip, not a placeholder)\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# Save document\npython3 scripts/mubu_api.py save <doc_id> --content \"content\"\npython3 scripts/mubu_api.py save <doc_id> --file content.md\n\n# Update a document from a Markdown file\npython3 scripts/mubu_api.py save <doc_id> --md outline.md\n\n# Move a document to another folder\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# Delete (⚠️ irreversible — confirm the ID; requires explicit --yes; --type defaults to folder)\npython3 scripts/mubu_api.py delete <id> --type folder --yes\npython3 scripts/mubu_api.py delete <doc_id> --type doc --yes\n\n# Local search by name (recursive across all sub-folders, case-insensitive)\npython3 scripts/mubu_api.py search \"project\"\npython3 scripts/mubu_api.py search \"project\" --json\n\n# Recursively export a whole folder tree as nested Markdown (default: cwd; --output sets root)\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# Rename a document (save_doc name; round-trip preserves content)\npython3 scripts/mubu_api.py rename <doc_id> --name \"New Title\" --type doc\n\n# Rename a folder (verified endpoint /list/rename_folder; folderId = its own id)\npython3 scripts/mubu_api.py rename <folder_id> --name \"New Folder Name\" --type folder\n\n# Export as OPML 2.0 / FreeMind (compatible with XMind and other outliners)\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n</details>\n\n---\n\n## 🤖 Agent trigger words\n\n> 幕布、mubu、幕布大纲导入导出\n\nWhen these keywords appear in a conversation, the Skill can be triggered automatically.\n\n---\n\n## 🧪 Tests & CI\n\nRun the full suite locally (**115** pytest cases):\n\n```bash\nPYTHONPATH=scripts python -m pytest -v\n```\n\nContinuous integration: on every push to `main` and on every Pull Request, GitHub Actions runs the **115** cases across the **Python 3.9 / 3.10 / 3.11 / 3.12** matrix — all executed for real, not faked green.\n\n---\n\n## 🔧 Troubleshooting\n\n| 现象 / 错误码 | 可能原因 | 解决 |\n|------|------|------|\n| `save` 返回 `code 17` / `illegal request`，msg 提到 `memberId` | `MUBU_MEMBER_ID` 未设置 | 设置环境变量 `MUBU_MEMBER_ID`（见上方 Credentials 章节）。**该值为服务端限制，任何 API 都不返回，无法自动获取**；缺失时 `save` 会明确报错，不影响 `get`/`create`/`list`/`search`/`export-tree`。 |\n| `code 5` / 参数错误 | 请求参数不正确 | 检查参数，例如 `rename_folder` 的 `folderId` 必须填文件夹**自身** id，不能填根目录魔法值 `\"0\"`。 |\n| `code 403` / 权限不足 | 账号缺少该操作权限 | 确认账号权限；部分写操作需特定权限。 |\n| 登录失败 / `401` | 凭据错误 | 核对 `MUBU_PHONE` / `MUBU_PASSWORD`，或重新设置环境变量 / `.env.mubu`。 |\n\n> 非官方逆向集成：幕布服务端可能调整接口或限流策略，若某端点突然失效，请以抓包结果为准并反馈 Issue。\n\n## ❓ FAQ\n\n**Q: Do I need a Mubu account?**\nA: Yes. Log in with your phone + password (`MUBU_PHONE` / `MUBU_PASSWORD`). This is your official Mubu account; the Skill does not provide one.\n\n**Q: It's an unofficial integration — are my credentials safe?**\nA: Credentials are stored only locally. The login token is written to a local file with `0o600` permissions (you-only read/write) and uses no third-party service. Env vars take precedence over the `.env.mubu` file. See [Reliability](#-reliability).\n\n**Q: Are image / attachment nodes supported?**\nA: Not yet. Outline collapse state (`expand`), ordered lists (`1.`), and image/attachment nodes are outside the current Markdown round-trip. See the [technical notes](#-reliability) for the full list of known limits.\n\n---\n\n## 📄 License\n\n[MIT](https://opensource.org/licenses/MIT)\n\nFile v1.3.16:_meta.json\n\n{\n  \"ownerId\": \"kn73qbrbqs4s8t2nh8pm22wxbd84vm7r\",\n  \"slug\": \"mubu-integration\",\n  \"version\": \"1.3.16\",\n  \"publishedAt\": 1788337637846\n}\n\nFile v1.3.16:CHANGELOG.md\n\n# Changelog\n\n记录 mubu-integration Skill 的里程碑演化。\n\n## v1.3.16（CI 门禁闭环 · ruff 导入合规修复，2026-09-02）\n\n本轮无新功能、无破坏性变更，仅修复 v1.3.15 引入的 `lint` job 在 CI 中实际失败却被 `continue-on-error` 掩盖的问题，使 CI 真实通过。\n\n### CI 修复\n- **ci: `lint` job 改为必过门禁** —— 移除 `.github/workflows/test.yml` 中 `lint` job 的 `continue-on-error: true`。原 v1.3.15 因沙箱无法本地装 ruff 验证而放宽，现确认 `ruff check scripts` 通过，故升级为硬性门禁，拦截未来导入类回归。\n- **fix(ruff): 导入类违规修复（覆盖 6 个文件，零逻辑改动）** —— 分两轮闭环：\n  - **第一轮（18 处，已提交 c1a7500）**：I001 导入排序 ×6、F401 未使用导入 ×9、F841 未使用局部变量 ×2，覆盖 `gen_assets.py`、`cli.py`、`client.py`、`config.py`、`convert.py`、`mubu_api.py`。\n  - **第二轮（follow-up，2 处 I001）**：首轮手工复刻 CI diff 时对 isort 规则理解有误（`from` 导入须排在同段 `import` 之后；首方导入块不可被注释+空行拆分），CI 硬门禁重跑后精准定位并修复：\n    - `client.py`：`from pathlib import Path` 由 `import os` 之后移至 `import uuid` 之后（straight import 段内）。\n    - `mubu_api.py`：删除 `# 包内模块符号` 注释及其前的空行，使 `from mubu.cli/client/config/convert` 首方导入块紧凑连续。\n  - **ruff.toml**：新增 `[lint.isort] known-first-party = [\"mubu\"]`，显式声明本地包，消除首方/第三方归类歧义（与 ruff 默认 `detect-first-party` 行为一致，纯声明性加固）。\n\n### 验证\n- `python -m py_compile` 全部通过；`import mubu_api / mubu.cli / mubu.client / mubu.config / mubu.convert` 无 `NameError`（移除的导入经 grep 确认仅为字符串字面量引用，确为冗余）。\n- 测试矩阵：**115 passed（0 失败，较 v1.3.15 无回归）**。\n- 沙箱本地因环境限制无法运行 ruff 二进制（harness 杀进程），以 GitHub Actions CI 重跑 `ruff check scripts` 作为真实验证（见 commit 后 CI 状态，lint job 须为绿色）。\n\n## v1.3.15（P0+P1 改进，2026-08-28）\n\n本轮落实产品评审诊断（deliverables/gstack/product-review-improve-2026-08-28.md）的 P0（信任修复）+ P1（质量/CI/文档）共 12 项，零新功能、无破坏性变更。\n\n### P0 信任修复（零风险、确定性）\n- **doc(README/zh-CN/CONTRIBUTING): 修正落地页首行坏命令** —— \"Try it in 3 commands\" 的 `create --md weekly.md` 缺必填位置参数 `name`，实测直接报 `error: the following arguments are required: name`；改为 `create \"周会\" --md examples/weekly.md`（cli.py:71 已证实 name 必填）。\n- **doc: 测试数 84 → 113 对齐** —— README(×3)/README.zh-CN(×3)/CONTRIBUTING 全量修正为真实的 113（CI 实测 113 passed）。\n\n### P1 质量 / CI / 文档\n- **fix(client._http_request): 429 限流退避** —— 新增 HTTP 429 分支，按 `Retry-After` 头（封顶 30s）或 NETWORK_BACKOFF 退避重试，重试用尽抛清晰 MubuError；对齐 5xx 重试逻辑（不重登）。\n- **fix(client.get_doc): 解析保护** —— `json.loads(data[\"definition\"])` 包裹为 try/except(KeyError/TypeError/ValueError) → MubuError，不再裸抛。\n- **ci: 新增 ruff 静态门禁** —— `.github/workflows/test.yml` 加 `lint` job（安装 ruff 跑 `ruff check scripts`，规则集 E/F/W/I）；新增 `ruff.toml`。沙箱本地因 SOCKS 代理无法安装 ruff 验证，故该 job 初版 `continue-on-error`（**已于 v1.3.16 修复 18 处违规后改为必过门禁**）。\n- **doc(cli.rename help): 修正矛盾** —— `rename --type doc` 帮助由\"走 save_doc name（⚠️ 真机受限）\"改为\"走已验证端点 /list/rename_doc（内容保真）\"，与实现一致。\n- **doc(README): Auto-refresh auth 措辞** —— \"Zero-touch auth\" 改为 \"Auto-refresh auth\"，明确重登依赖缓存凭据（env/.env.mubu），不声称免密码；zh-CN 同步。\n- **doc(README): 新增 Troubleshooting** —— 错误码表（memberId/code 17·5·403/登录失败），含服务端限制说明。\n- **feat(client.search): include_content** —— 新增可选参数，对名称未命中的文档额外拉取正文递归搜索节点 text/note，命中带 `matched_in: \"content\"`（名称命中为 `\"name\"`）；默认关闭保性能。新增 2 例回归测试。\n- **doc(docs/): 分发与定位素材** —— 新增 `docs/positioning.md`（项目定位纠偏）、`docs/hellogithub-pitch.md`（HelloGitHub 投递文案）、`docs/xiaoshuopai-tutorial.md`（小虱派教程）、`docs/v2ex-post.md`（V2EX 帖），支撑 awesome 榜单之外的多渠道分发（均未发布，待人工投递）。\n\n### 已知限制（重申）\n- `memberId` 仍无法经任何 API 自动获取，`save` 写回必须手动设置 `MUBU_MEMBER_ID`；本次仅补文档与错误码表，不改变该前置条件。\n- test: **115 passed（0 失败，较 v1.3.14 的 113 新增 2 例 search include_content 测试，无回归）**。\n\n## v1.3.14（本期发布版本 · hypothesis 2 归一化加固，2026-08-25）\n\n本期闭环 hypothesis 2：changeset 节点未做网页端 `tr()` 归一化（缺 note/collapsed/\nfinish/priority/color/时间戳等字段），markdown 直转产物若直喂 save 理论上仍可能触发\n`illegal request`。经核查，CLI `save` 命令（`scripts/mubu/cli.py:225-237`）确会把\n`markdown_to_doc` 的根节点直接包成 `{\"nodes\":[...]}` 喂入 `build_update_event` → `save_doc`，\n该路径真实可达，故需加固。\n\n- **fix(save): changeset 节点归一化加固（hypothesis 2 闭环）**：新增 `normalize_node`\n  （`scripts/mubu/convert.py`），**递归**补全缺失的契约字段——`note=\"\"`、`collapsed=False`、\n  `finish=False`、`priority=0`、`color=0`，时间戳 `createTime`/`modifyTime`/`timestamp`\n  用节点已有值或 `int(time.time()*1000)`；已存在的 `id`/`text`/`children`/`checked` 等字段\n  原样保留。在 `build_update_event`（`scripts/mubu/client.py`）构造 children 前对每个节点归一化。\n  `get_doc` 返回的完整 nodes 字段本就齐全、不受此影响，**不破坏标准 save 路径**；仅对\n  `markdown_to_doc` 构造的缺字段节点做补全，避免残缺 payload 触发服务端 `code:17 illegal request`。\n- **root.id 维持 doc_id（保守不改）**：`build_update_event` 的 `root.id` 仍取 `doc_id`\n  （逆向自「文档根节点 id == 文档 id」假设，v1.3.9 真机验证可用）。无法从 `get_doc` 返回\n  （仅 `{\"name\",\"nodes\"}`，无独立根 id 字段）确证真实根 id，故保留现状并加注释，未盲目改动。\n- **测试同步更新（2 例精确形状断言改为关键字段断言）**：`test_save_doc_auto_fetches_version_and_definition`\n  与 `test_build_update_event_shape` 原断言节点精确形状（如 `{\"id\":\"n1\",\"text\":\"A\"}`），归一化后\n  节点含补全字段，改为断言关键字段存在 + 时间戳类型为 int；`markdown_to_doc` 自身单元不受影响。\n- **⚠️ KNOWN LIMITATION（重申）**：`memberId` 仍无法经任何 API 自动获取，`save` 写回必须手动设置\n  `MUBU_MEMBER_ID`（见 v1.3.13）；本次仅补足节点契约字段，不改变该前置条件。另：本次归一化属客户端\n  契约补全，`save` 端到端落库仍受服务端反爬签名前置条件约束（同历史 KNOWN LIMITATION）。\n- test: **113 passed（0 失败，较 v1.3.13 同步更新 2 例 save 路径断言以匹配归一化形状，无新增用例、无回归）**。\n\n## v1.3.13（本期发布版本 · issue #8 修复，2026-08-23）\n\n本期修复 issue #8「幕布成功创建了同名文档，但正文保存被服务端以 illegal request 拒绝」的根因，\n并修正文档中与代码矛盾的「memberId 可自动缓存、无需手动设置」误导表述。\n\n- **fix(save): member_id 缺失时明确报错（替代静默发空串）**：`save_doc`（`scripts/mubu/client.py`）\n  构造 `/colla/events` payload 前校验 `member_id`；缺失时抛 `MubuError` 明确提示配置\n  `MUBU_MEMBER_ID`（附浏览器抓包获取 memberId 的方法），不再静默发空串被服务端以\n  `code:17 / illegal request` 拒绝。这是 issue #8「能建文档、存正文被拒」的 100% 吻合根因\n  （`create_doc` 不需要 memberId，`save_doc` 需要）。\n- **fix(login): 防御性读取 memberId**：`login()` 读取 token/id/name 后，尝试从登录响应读取\n  `memberId`/`member_id` 作兜底；已知限制下登录响应不含该字段，读不到则保持原值（由上方校验兜底），\n  属无害加固。\n- **doc(SKILL.md): 修正「自动缓存、无需手动设置」误导表述**：删除「登录后自动缓存到\n  `~/.mubu_token`，一般无需手动设置」等错误描述，明确说明「`memberId` 任何 API 都不暴露，\n  `save` 写回**必须手动**设置 `MUBU_MEMBER_ID`（或提前写入 token 缓存的 `member_id`）；\n  缺失时 `save` 会明确报错，不影响 `get`/`create`/`move`/`rename`」。与 M19 KNOWN LIMITATION 对齐。\n- **⚠️ KNOWN LIMITATION（重申）**：个人文档的 `memberId` 仍无法经任何 API 自动获取，工具无法绕过；\n  必须手动设置 `MUBU_MEMBER_ID`。v1.3.13 仅让缺失时的报错清晰可操作，并非自动解决（服务端限制）。\n- test: **113 passed（0 失败，较 v1.3.12 的 112 新增 1 例 save_doc 缺失 member_id 回归测试，无回归）**。\n\n## v1.3.12（本期发布版本 · P0 落地页，2026-08-06）\n\n- **README 落地页化**：英文 `README.md` 重写为产品落地页（social-preview 头图、`assets/demo.gif` 动图、能力对比表、3 个 Use Case、Reliability 章节）；新增 `README.zh-CN.md` 全量中文版（英文默认 + 中文全量）。\n- **视觉资产**：新增 `assets/social-preview.png` 与 `assets/demo.gif`；配套 `demo.vhs` 录制脚本与 `scripts/gen_assets.py`。\n- **GitHub 仓库元数据重构**：描述 / homepage / topics 更新为 **AI Agent Skill** 定位，新增 20 个 topics（如 `ai-agent`、`claude-code`、`knowledge-management`、`outliner`）。\n- 版本号 `1.3.11 → 1.3.12`（满足 ClawHub 唯一版本号要求，避免重复版本被拒）。\n\n## M1 (P0) — 基础能力\n- 登录（手机号密码 → JWT Token，请求头 `jwt-token`）\n- 文档/文件夹 CRUD：create_folder / create_doc / get_doc / save_doc / delete / move\n- Markdown 双向：doc_to_markdown / export_markdown / markdown_to_doc（含 note 备注、勾选 [x] 往返）\n- Token 本地缓存 + 临近过期自动重登（仅重试 1 次，杜绝死循环）\n- 发布 GitHub tag 1.0.0\n\n## M2 (P1) — 网络健壮性 + 本地搜索\n- 网络层 5xx 指数退避重试（最多 2 次，与 401 重登分层互不干扰）\n- 非 JSON 响应友好异常 + body 截断\n- .env 凭据加载（仅环境变量未设置时补全）\n- Token 文件权限 0o600（原子写 + chmod）\n- 本地搜索 search()（递归遍历 + 按名称过滤）+ CLI search 子命令\n- 发布 ClawHub v1.1.0\n\n## M3 (P2) — 工程化收尾\n- scripts/mubu_api.py 全量类型注解（typing 模块，Python 3.8+ 兼容，100% 覆盖）\n- 新增 requirements.txt（requests + pytest + responses，.env.mubu 手写解析无 dotenv 依赖）\n- 新增 .github/workflows/test.yml CI（push/PR 触发，Python 3.9–3.12 矩阵，41 用例全过）\n- README 美化：CI 状态徽章、架构/双向转换 Mermaid 图、30 秒快速体验、FAQ\n\n## M4 (P0/P1/P2) — 完善阶段（本期）\n- P1-5：ENDPOINTS 元组化 (method, path)，消除调用点硬编码 \"POST\"\n- P0-2：search() 上限（max_depth / limit / max_requests）经核实仓库原版已具备，CLI 透传 --max-depth / --limit（本期仅核对确认，非新增）\n- P1-3：_http_request 兜底消息避免渲染字面 : None\n- P2-6：_save_token 增加 fcntl.flock 跨进程 advisory 锁（已加固为跨平台安全：try/except ImportError，无 fcntl 平台如 Windows 降级为无锁）\n- P1-4 / P2-7：补充网络层（5xx 混合降级、ConnectionError）与 .env 回归测试\n- P0-1：SKILL.md 清理旧版独立函数，改为指向 MubuClient 的引用与示例\n- T2 收尾：新增 `test_search_global_limit_enforced`，真实验证 `search()` 全局 `limit` 上限被强制执行（破坏性验证：移除上限逻辑则用例失败）\n- SKILL.md「Token 管理建议」示例修正：朴素 `open()+json.dump` 改为原子写（tempfile + os.replace）+ chmod 0o600，并注明真实 `_save_token` 还含跨进程 fcntl 锁；删除误导性的 `is_token_valid`\n\n## M5 (审计整改) — ClawHub Security Audit 全量修复（1 High + 6 Medium）\n- **High · 供应链（依赖未锁定 CVE）**：`requirements.txt` `requests>=2.28,<3` → `requests>=2.32.4,<3`，修复 CVE-2024-47081（.netrc 凭据泄漏）、CVE-2024-35195（Session 复用 `verify` 被覆盖）。CVE-2026-25645（extract_zipped_paths 临时文件复用）经核查本代码路径不可达，仅作升级加固。\n- **Medium · MCP 最小权限（Lp3）**：SKILL.md 新增 `## 权限与安全边界` 段落，明确声明只读/写入/网络/破坏性操作需确认/信任边界 5 条约束。\n- **Medium · 触发词歧义（Vague Triggers ×2）**：frontmatter `description` 与激活指引触发词收窄为 `幕布、mubu、幕布同步、幕布大纲导出`，移除易误触的 `大纲笔记`、`思维导图导出`；全仓 grep 复核 0 残留。\n- **Medium · 缺失破坏性操作警示（Missing User Warnings ×3）**：`delete` 增加 `⚠️ 删除不可逆` 明确提示；CLI 增加 `--yes` 显式确认标志，`main()` 删除分支硬拦截（未传 `--yes` 则打印警示并 `sys.exit(1)`，0 网络请求）。README 删除示例同步更新为 `delete <id> --yes`。\n- **验证**：pytest 45 用例全过；`py_compile` 通过；QA 独立 monkeypatch 复验确认 delete 无 `--yes` 时实际发出 0 次网络请求；7 项审计发现全部 RESOLVED，路由判定 NoOne。\n- 发布 ClawHub v1.1.4（清除 Review 状态）。\n\n## M6 (遗留清理) — 根节点 note 导出修复\n- 修复 `export_markdown()` 遗漏根节点 note 输出的 Bug：children 循环后追加 `f\"> {note}\"`（与子节点 note 格式一致）。\n- 新增 3 个测试：根 note 存在性与位置（在 children 之后）、空 note 省略（不产生孤立 `> ` 行）、含根 note 文档往返一致性（md→doc→md）。\n- 工程师 IS_PASS YES，QA 独立验证 48/48 全过（45 既有 + 3 新增），路由 NoOne。\n- 发布 ClawHub v1.1.5。\n\n## M7 (审计补充) — pytest CVE 版本锁定\n- `requirements.txt` `pytest>=7,<9` → `pytest>=8.3.5,<9`，修复 CVE-2025-71176（pytest tmpdir 处理漏洞；仅测试依赖，运行时不受影响）。\n- 48/48 测试全过（pytest 8.x 兼容）。\n- 发布 ClawHub v1.1.6。\n\n## M8 (安全加固) — 凭据 / 路径 / 文档一致性（本期）\n- **安全官 #1/#2/#12**：`.env.mubu` 加载时强制 `chmod 0o600`；CLI 移除 `--phone`/`--password` 明文参数，凭据改由环境变量 / `.env.mubu` 提供，`login` 子命令缺失时交互式 `getpass` 输入密码；SKILL.md 删除\"直接在脚本中配置\"类表述。\n- **安全官 #3**：新增 `_safe_local_path()`，在 `create --md` / `save --file` 读取本地文件前校验路径——拒绝绝对路径、`..` 越界路径及当前工作目录之外的路径，防任意文件读取外发。\n- **安全官 #8 / 排障手 #8**：实现 `MUBU_BASE_URL` 环境变量覆盖，仅限 `mubu.com` 家族域名白名单（防 MITM 指向恶意服务器）；SKILL.md「网络」边界说明同步更新。\n- **排障手 #7**：收紧 `_is_auth_error` 关键字匹配（移除 \"token\"/\"auth\"/\"expire\"/\"login\"/\"过期\" 等泛化词，仅保留明确登录失效短语），避免正常业务错误误触发重登。\n- **He H3**：`_http_request` 兜底消息避免渲染字面 `None`（非预期 + `last_err or '未知错误'`）。\n- **文档一致性**：CONTRIBUTING 修正开发/运行指令（`git clone` + `pip install -r requirements.txt` + `PYTHONPATH=scripts python -m pytest -v` + 入口 `python3 scripts/mubu_api.py`）；README 版本号统一为 v1.1.6、测试数统一为 69、去除割裂的双版本叙事。\n- **测试补充（排障手 #5/#6、He H4）**：新增真实 API 方法 payload 单测（create_folder/create_doc/get_doc/save_doc/delete/move 的请求体 JSON 与返回 id 提取）、`delete --yes` 守卫回归（无 `--yes` 0 网络调用且 `sys.exit(1)`）、`login` CLI 无明文参数 + getpass 交互测试；全量测试 69 用例通过（48 既有 + 20 新增 + 1 路径校验补强）。\n- **说明**：`search()` 的 `max_depth`/`limit`/`max_requests` 上限经核实为仓库原版已具备（非本期新增），对应方案 He H2 审计结论已修正。\n- （已随 v1.2.0 发布；详情见文末「v1.2.0」段。）\n\n## M9 (P1 第一批) — 产品文案 / 错误指引 / 日志 / 搜索截断 / CI 加固\n\n- **产品官 #10/#11/#15**：README「双向同步」表述改为「Markdown 导入/导出（往返保真）」，明确说明**非**真正双向同步（无 diff/merge，重复导入生成新副本），并加 true-sync「不在本期范围」说明；README 顶部新增显眼「第一步：配置凭据」块（环境变量 / `~/.workbuddy/.env.mubu`，不假设用户已就绪）；新增 `examples/weekly.md` 示例大纲供 `create --md examples/weekly.md` 直接体验。SKILL.md 同步修正「幕布同步」触发词与 `search()` 示例（`[\"results\"]` 解包）。\n- **产品官+排障手 #15 / 错误操作指引**：`_http_request` / `_request` 按 HTTP 状态码给出下一步文案并打到 stderr——401→「登录失效或密码错误，请检查凭据后重试」；403→「权限不足，请确认账号权限」；5xx→「幕布服务暂不可用，请稍后重试」；网络异常→「网络连接失败，请检查网络」。\n- **排障手 #17 / 搜索截断 + 环检测**：`search()` 返回值由 `List` 改为含 `truncated: bool` 的字典结构（`{\"results\", \"truncated\", \"limit\", \"max_depth\"}`），到达 `limit`/`max_depth`/`max_requests` 上限时标记 `truncated=True`（不再静默丢失）；新增已访问 `folder_id` 的 `visited` 集合去重，防止幕布环引用导致无限递归（双保险）。`main()` 与测试同步适配，调用方可知结果不完整。\n- **排障手 #19 / 移除冗余 ensure_login**：`get_list`/`create_folder`/`create_doc`/`get_doc`/`save_doc`/`delete`/`move` 移除冗余 `ensure_login()` 调用，统一走 `auth=True`（`_request` 内 `ensure_valid_token` 已处理），行为不变；`ensure_login` 方法保留作兼容。\n- **排障手+安全官 #16 / 日志规范**：引入 `logging` 取代散落 `print`；`warning`/`error` 分级，CLI 加 `--verbose` 开关控制 `debug`；异常只记 `msg`、不记请求/响应体（`body` 200 字符截断保留），确认明文密码 / token 不进日志。\n- **安全官 #20 / CI hash pinning + Dependabot**：`.github/workflows/test.yml` 将 `actions/checkout@v4` 锁定为完整 commit SHA `11bd71901bbe5b1630ceea73d27597364c9af683`(v4.2.2)、`actions/setup-python@v5` 锁定为 `a26af69be951a213d495a4c3e4e4022e16d87065`(v5)，并加最小 `permissions: contents: read`；新增 `.github/dependabot.yml` 启用 GitHub Actions 与 pip 依赖每周自动更新 PR（SHA 钉死由人工 review 确认）。\n- **测试补充（+11，共 80）**：搜索 `truncated` 标记（limit 命中 / 环检测无无限递归）、错误指引 4 类状态码文案、ensure_login 冗余已移除（2 处不触发 ensure_login 调用）、日志脱敏（明文密码不进日志）+ `--verbose` 启用 debug、`examples/weekly.md` 可解析。全量测试 **80 用例通过**（69 既有 + 11 新增）。\n- 已随 **v1.2.0** 发布（GitHub tag v1.2.0，2026-07-14）。\n\n## v1.2.0（本期发布版本 · 2026-07-14）\n\n本期合并发布 **M8（安全加固）+ M9（P1 第一批：产品文案 / 错误指引 / 日志 / 搜索截断 / CI）+ P2（工程化增强）**，构成自 v1.1.6 以来的完整能力跃升（GitHub tag v1.2.0）。\n\n- **安全（M8）**：凭据文件强制 `0o600`、移除明文 CLI 参数改交互式 `getpass`、本地路径越界防护 `_safe_local_path`、API 域名白名单（`MUBU_BASE_URL` 仅限 mubu.com 家族）防 MITM。\n- **健壮性（M9）**：`search()` 返回含 `truncated: bool` 并加 `visited` 集合防环；标准 `logging` + 敏感信息脱敏 + `--verbose`；401/403/5xx/网络错误打到 stderr 的下一步指引；CI 钉死 `actions/checkout@v4` / `actions/setup-python@v5` 完整 SHA + 新增 Dependabot 周更。\n- **工程化（P2）**：`MubuClient` 复用 `requests.Session` 连接池；依赖拆分为 `requirements.txt`（运行时）+ `requirements-dev.txt`（pytest / responses `<1`）；新增本 Roadmap 段。\n- 测试覆盖：**84 用例全过**（69 P0 基线 + 11 P1 + 4 P2），CI 矩阵 Python 3.9–3.12。\n- 删除操作保留 `--yes` 守卫与「不可逆」stderr 警示（无回收站，软删除待幕布 API 能力确认）。\n\n## M10 (Roadmap 实施) — 整树导出 / 重命名 / OPML·FreeMind（本期）\n\n- **整树导出**：新增 `MubuClient.export_tree(root_folder_id, output_dir, max_depth)` 与 CLI `export-tree` 子命令，递归遍历文件夹树并将每个文档写为 `<name>.md`，子文件夹创建为同级子目录；单点拉取失败不阻断整体遍历（记入 `errors` 统计）。\n- **重命名**：新增 `rename_doc(doc_id, new_name)`（走 `save_doc` 的 `name` 参数，round-trip 保内容）+ CLI `rename --type doc`；新增 `rename_folder(folder_id, new_name)` 走逆向推测端点 `/list/update_folder`（幕布无官方 API 文档，真实环境需验证），对应 CLI `rename --type folder`。\n- **OPML / FreeMind 导出**：新增模块级 `doc_to_opml(doc)` / `doc_to_freeplane(doc)` 与 CLI `opml <doc_id> --format opml|freeplane`，将幕布大纲转为 OPML 2.0 / FreeMind XML，兼容 XMind 等其它大纲工具。\n- **软删除降级说明**：幕布回收站 API 未文档化，本期维持 `delete` 硬删 + `--yes` 守卫 + 「不可逆」stderr 警示，**不实现软删除**（避免引入未经证实的 API 调用）。\n- **测试补充（+9，共 93）**：export_tree 嵌套文件生成 / 单点失败处理、rename_doc 调 save 带 name / rename_folder 走推测端点、doc_to_opml / doc_to_freeplane 合法 XML、_safe_filename 非法字符替换。全量测试 **93 用例通过**。\n- 模块拆分（单文件 → 包）已在 M11 完成。\n\n## M11 (Roadmap · 大重构) — 模块拆分（单文件 → 包，非 breaking）\n\n将单文件 `scripts/mubu_api.py` 按职责拆分为正式 Python 包 `scripts/mubu/`，`mubu_api.py` 降级为向后兼容 shim（重新导出全部公开符号），**对外接口零破坏**。\n\n- **`scripts/mubu/config.py`**：常量 / 配置（`DEFAULT_BASE_URL` / `ENDPOINTS` / 重试与搜索上限）、日志、异常 `MubuError`、路径安全 `_safe_local_path`、Token 文件锁 `_token_file_lock`、域名白名单解析。\n- **`scripts/mubu/convert.py`**：文档结构 ↔ Markdown / OPML / FreeMind 转换（`doc_to_markdown` / `export_markdown` / `markdown_to_doc` / `doc_to_opml` / `doc_to_freeplane`）与展示格式化（`_safe_filename` / `format_list` / `format_search`）。\n- **`scripts/mubu/client.py`**：`MubuClient`（鉴权 / 请求 / 文档·文件夹·搜索·整树导出）。\n- **`scripts/mubu/cli.py`**：命令行入口 `main()` + `_configure_logging()`。\n- **`scripts/mubu/__init__.py`**：包标识（`__version__ = \"1.3.0\"`）。\n- **`scripts/mubu_api.py`（shim）**：`from mubu.* import ...` 重新导出全部公开符号（含 `os` / `sys` / `json` / `Path` 等标准库模块级名称，保持旧调用方与既有测试兼容）；`__main__` 仍调用 `main()`。\n\n**兼容性验证**：\n- `import mubu_api` 及其公开符号（`MubuClient` / `MubuError` / `doc_to_markdown` / ...）全部可用；`python scripts/mubu_api.py <subcommand>` 行为不变。\n- 新增包内导入路径：`from mubu.client import MubuClient`、`from mubu.convert import export_markdown`、`from mubu.config import MubuError` 等均可独立使用。\n- 既有测试适配：因 `os` / `getpass` 为单例模块，`monkeypatch(mubu_api.os / mubu_api.getpass)` 仍生效；函数 / `Path` / `TOKEN_FILE` / `ENV_FILE` 的 patch 目标修正为使用点（`mubu.cli.*`、`mubu.client.TOKEN_FILE`、`mubu.client.ENV_FILE`、`mubu.config.Path`）。全量测试 **93 用例通过**（无用例增减，纯结构重构）。\n\n## M12 (Roadmap · 真机验证 + 端点修正) — rename_folder / delete 实测修正（2026-07-15）\n\n用真实幕布账号逐端点实测，修正此前逆向推测的多处错误写端点：\n\n- **rename_folder 修正（关键）**：原 `/list/update_folder` 实测返回 `code 17 illegal request`。正确端点为 **`POST /list/rename_folder`**，且必须同时携带 `id` 与 `folderId`，**`folderId` 必须填文件夹自身真实 id（不能填根目录魔法值 `\"0\"`，否则 `code 5`）**。已用真实账号验证改名生效（建临时文件夹 → 改名 → 回查确认 → 删除，无残留）。\n- **delete 修正（关键 latent bug）**：原 `delete` 走 `/list/delete` 实测返回 `code 17 illegal request`。正确端点须按类型区分：**`/list/delete_folder`**（文件夹）、**`/list/delete_doc`**（文档），均 `{\"id\": ...}`。已拆为 `delete_folder()` / `delete_doc()`，`delete(item_id, item_type=\"folder\")` 兼容分发；CLI `delete` 新增 `--type doc|folder`（默认 folder）。\n- **move 仍待验证**：`/list/move` 及多种变体（`move_folder` / `move_doc` / 不同字段名 `folderId`/`toFolderId`/`parentId`/`targetId`）实测均返回 `illegal request`，真实端点未知。本期**不改动 move 代码**，标记为「未实测验证待抓包」，待浏览器 DevTools 抓包确认正确端点后再修。\n- **测试适配（+0 用例，仍为 93）**：`test_rename_folder_uses_update_endpoint` 改为断言 `/list/rename_folder` + `folderId`；`test_delete_*` 三个用例 mock URL 由 `/list/delete` 改为 `/list/delete_folder`。全量 **93 用例通过**。\n- 账号清理：验证过程产生的临时文件夹均已通过 `delete_folder` 删除，无残留。\n\n## v1.3.1（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M12（真机验证 + 端点修正）**，构成自 v1.3.0 以来的关键正确性修复（GitHub tag v1.3.1）。\n\n- **rename_folder 真机可用**：端点 `/list/rename_folder`，`folderId` 填自身 id。\n- **delete 真机可用**：拆分 `delete_folder` / `delete_doc`（原 `/list/delete` 为错误端点）；CLI `delete` 加 `--type`。\n- **move 已知未验证**：保留原实现并标注待抓包确认。\n- 测试覆盖：**93 用例全过**，无任何端点相关回归。\n\n## M13 (Roadmap · 真机验证) — get_list 字段名修正 + get_doc 待抓包（2026-07-15）\n\n用真实账号实测 `list` / `search` / `export-tree` 时发现隐藏缺陷并修正：\n\n- **get_list 字段名修正（关键 latent bug）**：代码与测试均假设 `get_list` 返回文档列表字段为 `docs`，但幕布真机返回的是 **`documents`**（带 s）。导致 `list` / `export-tree` / `search` / `format_list` 在真机上**读不到任何文档**（单元测试因 mock 同样用 `docs` 键而假绿，93 用例全过却掩盖了真机失效）。\n  - 修复：`client.export_tree` / `client.search` / `convert.format_list` 统一改为读取 `data.get(\"documents\")`，并保留对旧 `docs` 键的兜底兼容（防御字段名变化）。\n  - 测试：将 `test_documents_key_is_ignored`（错误假设「忽略 documents 键」）改为 `test_documents_key_is_used`（验证 documents 被正确读取 + 兼容 docs），使测试真正反映真机结构。\n  - 真机验证：`search(\"三级等保\")` 命中 1 个文档（修复前 0）、根目录文档总数读到 **80 个**（修复前 0）。\n- **get_doc 端点不可用（待抓包）**：`get_doc`（`POST /doc/get`）在真机对所有文档（含新建文档）均返回 `code 17 illegal request`；额外探测 `/doc/detail` / `/doc/info` / `/doc/open` / `/doc/load` / `/doc/getContent` / `/list/doc` 等 11 个变体**全部 illegal request**。文档正文获取机制与当前端点对不上，需浏览器 DevTools 抓包确认幕布真实的文档加载 API 后才能修复。\n  - **影响**：`export-tree`（导出正文）、`rename_doc`（依赖 get_doc）当前真机不可用；两者均已有 try/except 优雅降级（export_tree 记 errors 继续，不会崩溃）。`list` / `search`（仅列目录）已恢复可用。\n- **move 仍待验证**：同 M12，真实端点未知，保留原实现并标注待抓包。\n- 测试覆盖：**93 用例全过**，docs/documents 修复零回归。\n\n## v1.3.2（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M13（get_list 字段名修正）**，修复 `list` / `search` / `format_list` 在真机上读不到文档的隐藏缺陷（GitHub tag v1.3.2）。\n\n- **list / search 真机可用**：统一读取 `documents` 字段（兼容旧 `docs`）。\n- **export-tree / rename_doc 正文功能暂不可用**：因 `get_doc`（`/doc/get`）真机返回 illegal request，待抓包确认文档加载 API 后修复；列目录能力正常。\n- **move 已知未验证**：保留原实现并标注待抓包确认。\n- 测试覆盖：**93 用例全过**。\n\n## M14 (Roadmap · 真机验证) — get_doc 真实端点与 definition 解析修复（2026-07-15）\n\n用真实幕布账号抓包确认 `get_doc` 文档加载机制并修复（M13 标记为待抓包）：\n\n- get_doc 端点由错误 `/doc/get` 修正为真实 `POST /document/edit/get`。\n- 请求体由 `{\"id\":...}` 修正为 `{\"docId\":..., \"password\":\"\", \"isFromDocDir\":true}`。\n- 响应 `data.definition` 为 JSON 字符串，二次解析为 `{\"nodes\":[...]}`；`get_doc` 现返回 `{\"name\":..., \"nodes\":[...]}`（与旧 `{\"node\":{...}}` 形状不同）。\n- `convert.export_markdown` 支持新 `nodes` 形状（保留旧 `node` 形状向后兼容）；`doc_to_markdown` 支持真实 `finish` 勾选态渲染为 `- [x]` / `- [ ]`。\n- SKILL.md 校正 get_doc 端点（`POST /document/edit/get`）、`Jwt-Token` 请求头、移除不存在的 `refresh_token 30天` 描述。\n- 无破坏性变更；93 用例全过。\n\n## v1.3.3（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M14（get_doc 真实端点与 definition 解析修复）**，修复文档正文获取在真机上失效的问题（GitHub tag v1.3.3）。\n\n- fix: `get_doc` 改用真实端点 `POST /document/edit/get`（原 `/doc/get` 为错误端点，导致取不到文档）\n- fix: 请求体改为 `{\"docId\":..., \"password\":\"\", \"isFromDocDir\":true}`（原 `{\"id\":...}` 错误）\n- fix: 解析响应 `data.definition`（JSON 字符串）为 `{\"nodes\":[...]}`，`get_doc` 现返回 `{\"name\":..., \"nodes\":[...]}`\n- fix: `convert.export_markdown` 支持新 `nodes` 形状（保留旧 `node` 形状向后兼容）；`doc_to_markdown` 支持真实 `finish` 勾选态渲染为 `- [x]`/`- [ ]`\n- docs: SKILL.md 校正 get_doc 端点、`Jwt-Token` 请求头、移除不存在的 `refresh_token 30天` 描述\n- 依赖/破坏性：无破坏性变更\n\n## v1.3.4（本期发布版本 · 2026-07-15）\n\n本期为 OPML/FreeMind 导出兼容真实 nodes 形状修复 + save_doc 客户端契约校验（GitHub tag v1.3.4）。\n\n- fix: `doc_to_opml` / `doc_to_freeplane` 支持真实 `nodes` 形状（双形状兼容旧 `node`）；OPML/FreeMind 导出不再产出空大纲\n- fix: `cli` 的 `create` / `save` 回写 content 使用 `{\"nodes\":[node]}` definition 字符串，与 `get_doc` 同构\n- verify: `save_doc` 真机 round-trip 被幕布服务端签名/反爬校验拒绝（`illegal request`, code 17），与请求体形状无关；客户端契约（content = definition JSON 字符串 `json.dumps({\"nodes\":[...]}, ensure_ascii=False)`）正确，**无需改代码**，docstring 已标注此已知限制\n- test: 96 passed（修正 1 个过时 rename 夹具 + 新增 OPML/FreeMind nodes 形状测试）\n- 已知限制: `save_doc` / `rename_doc` 真机写回需服务端对该客户端放行（反爬签名），目前端到端落库不可达\n- 依赖/破坏性：无破坏性变更\n\n## M15 (Roadmap · 软删除/锁文件) — 本地回收站软删除 + pip-tools 依赖锁文件（2026-07-15）\n\n- feat: `delete` 改为本地软删除——仅将文档标记写入 `~/.workbuddy/.mubu_trash.json`，**零网络调用**，云端副本保留；新增 `restore <id>`（仅移除本地标记、零风险）、`purge <id> --yes`（真实服务端删除 + 移除标记，不可逆）、`trash`（列出回收站项）；`list` / `search` 默认过滤已软删项，新增 `--include-trash` 可包含。\n- feat: 引入 pip-tools 锁文件——新增 `requirements.in`（requests）/ `requirements-dev.in`（pytest, responses），`requirements.txt` / `requirements-dev.txt` 重写为 `pip-compile --generate-hashes` 精确锁定 + 哈希；CI 新增锁文件漂移校验步骤。\n- docs: README / SKILL 版本号 → 1.3.5、新增软删除 / 回收站章节、安装说明改锁定文件、`delete` 不再标记为「不可逆」（仅 `purge --yes` 为不可逆）。\n- test: 100 passed（迁移 `test_delete_with_yes_calls_api` → `test_delete_with_yes_marks_trash`（0 网络 + 校验回收站写入），新增 `TestTrash`：restore / purge --yes 调 API / list 过滤 / search 过滤 + `--include-trash` 反例）。\n- 已知限制: 锁文件在本机 Python 3.13 解释器上生成（pip-tools 7.5.3 已移除 `--python-version` / `--check` flag）；锁定版本 requests 2.34.2 / pytest 9.1.1 / responses 0.26.2 及其传递依赖均支持 CI 的 3.9–3.12 矩阵，非阻塞。\n- 依赖/破坏性：无破坏性变更（注意 `delete` 语义由「永久删除」变为「本地软删除」，属行为变更但保留 `--yes` 守卫）。\n\n## v1.3.5（本期发布版本 · 2026-07-15）\n\n本期为本地回收站软删除（restore / purge / trash）+ pip-tools 依赖锁文件（GitHub tag v1.3.5）。详见上方 M15。\n\n- 依赖/破坏性：无破坏性变更（`delete` 语义由「永久删除」变为「本地软删除」，保留 `--yes` 守卫；`purge --yes` 为唯一不可逆操作）\n\n## M16 (安全姿态补丁) — client.delete 对齐软删除 + 文档修正（2026-07-15）\n\nClawHub SkillSpector 复审（GO；1 项 High 降为 Medium）3 项真实发现的修复：\n\n- fix: `client.delete()` 现路由到 `trash_item`（本地软删除，零网络），与 v1.3.5 CLI 语义及 `client.py` 设计注释（\"delete = 软删除（仅本地标记，云端仍在）\"）一致；真实硬删仅保留 `purge_item` / `delete_doc` / `delete_folder`（由 `purge --yes` 调用）。此前 `client.delete()` 实际走 `delete_folder`/`delete_doc` 远程硬删，与注释及 CLI 语义矛盾，程序化调用方会触发不可逆云端删除。\n- docs: README `delete` 命令描述由「不可逆」修正为「软删除（本地回收站）」；`purge --yes` 才是不可逆。同步修正 P2 里程碑表对 `delete` 的「不可逆」表述。\n- test: 100 passed（无回归）。`client.delete()` 现不触发任何网络调用，既有 `delete --yes` 守卫 / 回收站写入 / `purge` 行为测试全部保持。\n- 依赖/破坏性：无破坏性变更（`delete()` 由「远程硬删」收敛为「本地软删」，与文档语义一致）。\n\n## v1.3.6（本期发布版本 · 2026-07-15）\n\n本期为 SkillSpector 复审安全姿态补丁（GitHub tag v1.3.6）。详见上方 M16。\n\n- fix: `client.delete()` 对齐 v1.3.5 CLI 软删除语义（本地标记、零网络）；真实硬删仅 `purge --yes`。\n- docs: README L261 `delete` 由「不可逆」改为「软删除（本地回收站）」；Roadmap 软删除 / 依赖锁文件项标记为已在 M15/v1.3.5 完成。\n- test: 100 passed（无回归）。\n- 依赖/破坏性：无破坏性变更。\n\n## Roadmap（向前展望，尚未实现）\n\n以下为已识别、尚未排入实施的能力增强与重构方向，供后续迭代参考：\n\n- **模块拆分（排障手 #18）**：✅ 已在 M11 完成——`scripts/mubu_api.py` 拆分为 `scripts/mubu/`（config / convert / client / cli），`mubu_api.py` 保留为向后兼容 shim，93 用例通过。\n- **文件夹重命名 / 移动增强（产品官 #14）**：✅ 重命名已在 M10 完成（`rename_doc`/`rename_folder`，M12/M14 真机验证）；⚠️ 移动（`move`）端点仍待真机抓包（真机 `illegal request`），保持未实现。\n- **整树递归导出（产品官 #14）**：✅ 已在 M10 完成——`export_tree` 递归导出整个文件夹树为嵌套 Markdown（`export-tree` 子命令）。\n- **软删除 / 回收站（产品官 #14 P2）**：✅ 已在 M15/v1.3.5 完成——`delete` 改为本地软删除（仅标记进 `~/.workbuddy/.mubu_trash.json`，零网络），`restore` 恢复、`purge <id> --yes` 真实硬删、`trash` 列出；`list`/`search` 默认过滤已软删项。\n- **互操作导出（产品官 #25）**：✅ 已在 M10 完成——OPML 2.0 / FreeMind（`doc_to_opml`/`doc_to_freeplane` + `opml` 子命令），兼容 XMind 等其它大纲工具。\n- **依赖锁文件（排障手 #21）**：✅ 已在 M15/v1.3.5 完成——引入 `pip-tools`，`requirements.in`/`requirements-dev.in` 由 `pip-compile --generate-hashes` 生成精确版本 + 哈希锁文件，CI 校验漂移。\n\n## M17 (v1.3.7) — 文档/配置诚实度 + 触发词收敛 + dependabot 治理 + purge 安全加固（2026-08-04，仅本地提交）\n\n本期聚焦\"降低偶发误激活 + 文档/代码诚实度 + 一处本地安全加固\"，**无功能性回归**（purge 为本地安全加固，其余为文档/配置；未 push/tag/release）。\n\n- **Roadmap ✅ 同步**：原 Roadmap 区块的\"文件夹重命名/移动增强\"、\"整树递归导出\"、\"互操作导出（OPML/FreeMind）\"三项实为 M10 已完成能力，补充 ✅ 标记，消除与 CHANGELOG 历史段的矛盾（移动子项仍标 ⚠️ 待抓包）。\n- **README 诚实度**：命令参考 `save` / `rename` / `move` 行与「已知限制」处加注——`save_doc`/`rename_doc` 在真机被服务端反爬签名拒绝（`code:17 / illegal request`，round-trip 写回不可达），当前 skill 聚焦读取/导入/导出；`move` 真机实测 `illegal request`，当前不可用。特性亮点同步收敛写回类操作的措辞。\n- **触发词收敛**：SKILL.md frontmatter `description` 与 README「Agent 触发词」将宽泛的\"幕布大纲导入导出\"收窄为动作意图（\"将幕布大纲导入 Obsidian\"、\"把 Markdown 同步到幕布\"、\"幕布笔记导出\"），并**统一两处表述**，保留核心触发词（幕布/mubu），降低 ClawHub 扫描偶发激活风险。\n- **dependabot 治理**：`.github/dependabot.yml` 的 pip 生态设 `open-pull-requests-limit: 0`，**阻止 dependabot 直接改 `requirements.txt`/`requirements-dev.txt` 锁文件**（会丢失哈希、与 pip-tools 范式冲突，2026-07-28 曾开 PR 改锁文件）；保留 `github-actions` 生态不变。\n- **purge_item 安全修复（必做）**：原实现在回收站记录缺失时**默认按 folder 硬删**，可能把 doc 当 folder 误删（`/list/delete_folder` 端点与文档 id 不匹配）。改为：优先从回收站记录读 `item_type`；缺失时回退到调用方显式 `--type`，**二者皆无则抛出明确错误要求显式指定**，杜绝误删。CLI `purge` 新增可选 `--type doc|folder`（仅回收站记录缺失时必填）。零网络风险本地加固。\n- **move / save_doc / rename_doc 注释**：代码注释补\"真机不可用/真机受限\"（`illegal request` / 反爬签名），与 CHANGELOG 历史段一致，修正此前偏乐观的\"可用\"表述。\n\n## v1.3.7（本期发布版本 · 2026-08-04，仅本地提交，未发版）\n\n本期为文档/配置诚实度 + 触发词收敛 + dependabot 治理 + purge 安全加固（GitHub tag **待发版**，本次未 push/tag/release）。详见上方 M17。\n\n- fix: `purge_item` 回收站记录缺失时不再默认 folder，缺失且未显式 `--type` 则报错，杜绝 doc 误删（CLI `purge` 新增可选 `--type`）\n- docs: Roadmap ✅ 同步、README 写回类操作真机限制标注、触发词收敛（SKILL.md + README 统一）、dependabot 治理\n- test: 新增 `test_purge_without_trash_requires_type` / `test_purge_explicit_type_doc`（锁定 purge 安全行为）\n- 依赖/破坏性：无破坏性变更（purge CLI 新增可选 `--type`，向后兼容；其余均为文档/配置）\n\n## M18 (v1.3.8) — SKILL.md 过期模块引用修正（2026-08-04，发布）\n\n本期为纯文档一致性修正，无业务代码改动（GitHub tag v1.3.8）。\n\n- **docs: SKILL.md 过期引用修正**：`scripts/mubu_api.py` 早在 M11 已重构为正式包 `scripts/mubu/`，`mubu_api.py` 降级为仅重新导出的向后兼容 shim。SKILL.md 第 58/64 行仍将 `MubuClient` 描述为位于 `scripts/mubu_api.py` 并以 `from scripts.mubu_api import MubuClient` 引入，与真实代码及 `cli.py` 不一致。修正为：类位于 `scripts/mubu/client.py`，import 路径 `from mubu.client import MubuClient`；并注明 `mubu_api.py` 仅为重新导出 shim，不再建议直接使用。\n- **一致性核对**：`cli.py` 与 `client.py` 的 import 路径本就为 `mubu.client`，本次仅让文档与其对齐，无导入逻辑变化。\n- 依赖/破坏性：无业务代码改动，纯文档一致性；测试 **102 passed（0 失败，无回归）**。\n\n## v1.3.8（本期发布版本 · 2026-08-04）\n\n本期为 SKILL.md 过期模块引用修正（GitHub tag v1.3.8）。详见上方 M18。\n\n- docs: SKILL.md 将 `MubuClient` 位置由 `scripts/mubu_api.py` 修正为 `scripts/mubu/client.py`，import 语句由 `from scripts.mubu_api import MubuClient` 修正为 `from mubu.client import MubuClient`，与 `cli.py` 一致；标注 `mubu_api.py` 仅为向后兼容 shim\n- test: 102 passed（0 失败，无用例增减，纯文档修正）\n- 依赖/破坏性：无业务代码改动，纯文档一致性\n\n## M19 (v1.3.9) — move/save 真实端点重构（2026-08-04，发布）\n\n本期为两个真机 `code:17 / illegal request` 写回类端点的真实端点重构，使 `move` 与 `save_doc`/`rename_doc` 在真机恢复可用（此前被服务端反爬签名拒绝）。\n\n- **fix(move): 真实移动端点**：`move` 由错误的 `/list/move` 改为真实抓包的 `/list/custom/drag`（旧端点真机返回 `code:17`）；对齐浏览器级请求头 `x-session-id = uuid:epoch`、`x-reg-entrance = https://mubu.com/app`，与幕布 Web 客户端一致，消除反爬签名拒绝。\n- **fix(save): 真实保存端点**：`save_doc` 由错误的 `/doc/save` 改为真实抓包的 `/v3/api/colla/events`（旧端点真机返回 `code:17`）；新增 `build_update_event`，构造根节点 update 事件 `{name:\"update\", updated:[{updated:root, original:root}]}`，其中 `root = {id:doc_id, children:nodes, modified:ts}`。\n- **fix(save): 每文档 `x-reg-entrance`**：保存请求头 `x-reg-entrance` 改为每文档 `https://mubu.com/app/edit/home/<doc_id>`（取代统一的 app 首页入口）。\n- **fix(save): `member_id` 来源**：`member_id` 改由环境变量 `MUBU_MEMBER_ID`（位于 `~/.workbuddy/.env.mubu`）读取，token 缓存中缺失时回退到缓存值；不再依赖不可达的发现接口。\n- **fix(rename): 独立重命名端点**：`rename_doc` 改用独立的 `/list/rename_doc` 端点（此前与保存共用错误端点）。\n- **⚠️ KNOWN LIMITATION（save_doc 前置条件）**：个人文档的 `memberId` 无法通过任何 API 发现，因此 `save_doc` 要求 `~/.workbuddy/.env.mubu` 中设置 `MUBU_MEMBER_ID`（或 token 缓存中已有 `member_id`）；二者皆缺时保存会失败。\n- test: **112 passed（0 失败，较 v1.3.8 的 109 新增 3 例，无回归）**。\n\n## v1.3.9（本期发布版本 · 2026-08-04）\n\n本期为 move/save 真实端点重构（GitHub tag v1.3.9）。详见上方 M19。\n\n- fix(move): `move` 改用真实端点 `/list/custom/drag`（原为 `/list/move`，真机 `code:17`）；对齐浏览器级 `x-session-id`(uuid:epoch) / `x-reg-entrance`(https://mubu.com/app) 请求头\n- fix(save): `save_doc` 改用真实端点 `/v3/api/colla/events`（原为 `/doc/save`，`code:17`）；新增 `build_update_event`（根节点 update 事件，root=`{id, children, modified}`）\n- fix(save): 每文档 `x-reg-entrance = https://mubu.com/app/edit/home/<doc_id>`；`member_id` 由 `MUBU_MEMBER_ID` 环境变量（`~/.workbuddy/.env.mubu`）+ token 缓存回退读取\n- fix(rename): `rename_doc` 改用独立端点 `/list/rename_doc`\n- ⚠️ 已知限制：`memberId` 无法经 API 发现，个人文档 `save_doc` 需设置 `MUBU_MEMBER_ID`（或缓存 `member_id`），否则保存失败\n- test: 112 passed（0 失败，较 109 新增 3 例，无回归）\n\n## M20 (v1.3.10) — 文档刷新（2026-08-04，发布）\n\n纯文档同步，无代码/行为变更。修正 v1.3.9 之前遗留、与已发布 CHANGELOG M19 自相矛盾的「写回类操作不可用」错误声明，并补充 `MUBU_MEMBER_ID` 配置说明。\n\n- **doc(SKILL.md / README.md): 移除过时「不可用」声明**：更新 `save`（`/colla/events`）、`move`（`/list/custom/drag`）、doc `rename`（`/list/rename_doc`）均为 v1.3.9 已真机验证可用；删除旧的 `code:17 / illegal request 当前不可用` 警示。\n- **doc: 补充 `MUBU_MEMBER_ID` 配置**：在两文档的凭据配置章节新增可选环境变量 `MUBU_MEMBER_ID`（幕布 colla 成员 ID，仅 `save` 写回需要，登录后自动缓存到 `~/.mubu_token`，一般无需手动设置）。\n- **doc: 测试计数更新**：README 测试用例数由 100 更正为 112（与 v1.3.9 实测一致）。\n\n## v1.3.10（本期发布版本 · 2026-08-04）\n\n本期为文档刷新（GitHub tag v1.3.10）。详见上方 M20。\n\n- doc: 移除 SKILL.md / README.md 中遗留的「save/move/rename 真机不可用」错误声明（与已发布 M19 矛盾）\n- doc: 补充 `MUBU_MEMBER_ID` 配置说明（`save` 写回前置条件）\n- doc: README 测试计数 100 → 112\n\n## M21 (v1.3.11) — 文档补充贡献指引（2026-08-05，发布）\n\n纯文档同步，无代码/行为变更。在 README 新增「🤝 贡献」章节，欢迎社区提 Bug、建议与 PR。\n\n- **doc(README.md): 新增 `## 🤝 贡献` 章节**：引导用户通过 GitHub Issues 提 Bug / 建议（含 enhancement 标签说明），并给出 Fork → 跑通 112 passed → 向 `main` 提 PR 的流程；点明本 Skill 为非官方逆向项目，欢迎同步新端点 / 新返回码。同步在文档目录加入 `#贡献` 锚点。\n\n## v1.3.11（本期发布版本 · 2026-08-05）\n\n本期为文档补充（GitHub tag v1.3.11）。详见上方 M21。\n\n- doc: README 新增贡献指引（Issues / PR 流程）\n- 无代码/行为变更\n\nFile v1.3.16:CONTRIBUTING.md\n\n# Contributing\n\n欢迎贡献！\n\n## 如何贡献\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/amazing-feature`)\n3. 提交更改 (`git commit -m 'Add amazing feature'`)\n4. 推送到分支 (`git push origin feature/amazing-feature`)\n5. 提交 Pull Request\n\n## 开发环境\n\n```bash\n# 克隆仓库\ngit clone https://github.com/liuboacean/mubu-integration.git\ncd mubu-integration\n\n# 安装依赖（运行时 + 测试；开发依赖在 requirements-dev.txt）\npip install -r requirements.txt -r requirements-dev.txt\n\n# 运行全部测试（共 115 个 pytest 用例）\nPYTHONPATH=scripts python -m pytest -v\n\n# 脚本运行入口\npython3 scripts/mubu_api.py --help\n```\n\n## 行为准则\n\n- 尊重所有贡献者\n- 建设性讨论\n- 专注于对项目最有利的事情\n\nFile v1.3.16:docs/hellogithub-pitch.md\n\n# mubu-integration —— 把幕布变成可被命令行和 AI 操控的 Markdown 大纲\n\n## 一句话\nmubu-integration 是一个非官方但**真机验证可用**的幕布（Mubu）集成：用一条命令把幕布大纲导出为 Markdown、再导回去，还能让 AI Agent 直接读 / 写你的幕布知识库。\n\n## 亮点\n- **零配置读取 / 备份 / 导出**：只需手机号 + 密码，无需任何逆向技巧，`get` / `create` / `export-tree` / `list` / `search` 开箱即用。\n- **Markdown 无损往返**：`create --md` 导入、`get --export markdown` 导出，`diff` 无差异（标题、`[x]` 勾选、`> 备注` 全部保真）。\n- **可被 AI Agent 调用**：作为 Agent Skill，让 AI 把幕布当长期结构化记忆来读写。\n- **整树导出 / OPML / FreeMind**：批量备份整个文件夹树，兼容 XMind 等大纲工具。\n- **CI 113 测试 × 4 Python 版本常绿**，在非官方逆向项目里少见的健康度。\n\n## 安装\n```bash\nnpx skills add liuboacean/mubu-integration\npip install -r requirements.txt\n```\n\n## 3 条命令体验\n```bash\nexport MUBU_PHONE=你的手机号\nexport MUBU_PASSWORD=你的密码\npython3 scripts/mubu_api.py create \"周会\" --md examples/weekly.md   # Markdown → 幕布\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # 幕布 → Markdown\ndiff examples/weekly.md out.md                                      # 无输出 = 字节级一致\n```\n\n## 链接\n- GitHub：https://github.com/liuboacean/mubu-integration\n- 非官方集成，遵循幕布服务端限制；欢迎提 Issue / PR 同步新端点。\n\nFile v1.3.16:docs/positioning.md\n\n# 定位纠偏建议（给主理人 / QA）\n\n## 核心判断\n当前 README 把 `save` 写回放在 Use Case ① 首位，但 `save` 需 `MUBU_MEMBER_ID`（服务端限制、采用率最低），与\"幕布用户最想要的零配置备份 / 读取\"错位。应把**零配置读取 / 备份 / 导出**作为首要叙事。\n\n## 建议的 Use Case 新排序\n1. **零配置备份与读取**：`export-tree` 整树导出、`get` 导出 Markdown、`list` / `search` 浏览——只需手机号 + 密码，无需 memberId。\n2. **进 Obsidian / 给 AI 当长期记忆**：Markdown 往返保真，知识库可进 Obsidian，AI 可直接读。\n3. **写回（power-user 选装）**：`save` 写回——明确前置 `MUBU_MEMBER_ID` 说明，放在靠后位置并加 ⚠️。\n\n## 一句话 hero 标语\n\"零配置把幕布变成可被命令行与 AI 操控的 Markdown 大纲——备份、检索、写回，一条命令搞定。\"\n\n## 落地建议\n- README Use Cases 三段按上述顺序重排；首段前置\"只需手机号密码，无需 memberId\"。\n- Reliability 章节已含 Auto-refresh auth，可在首屏 hero 下方补一句\"读取 / 备份 / 导出零配置\"。\n- `save` 写回相关示例保留，但统一加 memberId 前置说明（已在 Troubleshooting 表覆盖）。\n\nFile v1.3.16:docs/v2ex-post.md\n\n# 分享：我写了个把幕布变成 Markdown 的小工具 mubu-integration\n\n幕布的大纲很好用，但一直缺好用的命令行导出 / 备份，也没法让 AI Agent 直接读写。我逆向了幕布 Web 端用的一套接口，做了个非官方集成 mubu-integration：\n\n- 一条命令把整本幕布导出成 Markdown（`export-tree`），标题 / `[x]` 勾选 / `> 备注` 往返保真；\n- 反向 `create --md` 把 Markdown 导回幕布；\n- 还能当 AI Agent 的 Skill，让 AI 直接读 / 写你的幕布。\n\n诚实说：非官方逆向，读 / 导出零配置可用；但 `save` 写回受幕布 colla 成员 ID 限制（服务端不返回，需手动配 `MUBU_MEMBER_ID`）。CI 113 测试常绿。\n\n```bash\nnpx skills add liuboacean/mubu-integration\npython3 scripts/mubu_api.py export-tree --folder <id> --output ./backup\n```\n\nGitHub：https://github.com/liuboacean/mubu-integration\n\n想问下大家：你们备份 / 导出幕布都用啥方案？有没有踩过接口变动的坑？欢迎交流。\n\nFile v1.3.16:docs/xiaoshuopai-tutorial.md\n\n# 教程：用 mubu-integration 把幕布变成你的本地 Markdown 知识库\n\n## ① 为什么要把幕布变成 Markdown\n幕布适合梳理大纲，但搜索弱、难备份、不易进 Obsidian 等知识管理工具，更没法让 AI 直接读。mubu-integration 解决这三件事：一键全量备份、进 Obsidian、给 AI 当长期记忆。\n\n## ② 5 分钟配置凭据\n密码不进命令行。用环境变量或仓库外文件：\n```bash\nexport MUBU_PHONE=你的手机号\nexport MUBU_PASSWORD=你的密码\n# 可选：仅 save 写回需要\n# export MUBU_MEMBER_ID=你的幕布 colla 成员 ID\n```\n或写入 `~/.workbuddy/.env.mubu`（脚本自动加载，权限 0o600）。\n\n## ③ 一键把整本幕布导出为本地备份\n```bash\npython3 scripts/mubu_api.py export-tree --folder <根文件夹id> --output ./backup\n```\n每个文档写成 `<名称>.md`，子文件夹生成同级目录，递归整树。单点失败不中断，记入统计。\n\n## ④ 在 Obsidian 里管理这些大纲\n把 `./backup` 作为 Obsidian 仓库的笔记目录，大纲即普通 Markdown，双向链接、全文搜索、图谱视图都能用。\n\n## ⑤ 让 AI Agent 直接读 / 写你的幕布\n作为 Agent Skill 接入后，让 AI「把这份大纲导出」「读我的周会文档」即可。注意：`save` 写回需要 `MUBU_MEMBER_ID`（幕布服务端限制，任何 API 都不返回，需手动设置），缺失时 `save` 会明确报错，不影响读取 / 导出。\n\n## ⑥ 常见问题\n- 非官方逆向，服务端可能调整接口；某端点失效以抓包为准并反馈 Issue。\n- `save` 报 code 17 / memberId → 设置 `MUBU_MEMBER_ID`。\n- 更多见项目 README 的 Troubleshooting。\n\nFile v1.3.16:examples/weekly.md\n\n# 产品周会\n- 上周进展\n  - [x] 上线新版本\n  - [ ] 修复登录 bug\n  - [ ] 补齐集成测试\n- 本周计划\n  - 性能优化\n  - 文档与示例整理\n> 备注：记得把纪要同步给设计团队\n\nFile v1.3.16:README.zh-CN.md\n\n[English](README.md) | [中文](README.zh-CN.md)\n\n![mubu-integration](assets/social-preview.png)\n\n# mubu-integration\n\n> 把幕布变成 Markdown 原生、可被 AI Agent 操控的大纲工具。\n\n[![GitHub stars](https://img.shields.io/github/stars/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/network/members)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://img.shields.io/github/actions/workflow/status/liuboacean/mubu-integration/test.yml?branch=main)](https://github.com/liuboacean/mubu-integration/actions/workflows/test.yml)\n\n通过命令行管理你的幕布（Mubu）大纲 —— **同时作为一个 AI Agent Skill** —— 支持 Markdown 导入/导出的无损往返保真。\n\n---\n\n## ✨ 三条命令上手（magic moment）\n\n```bash\npython3 scripts/mubu_api.py create --md weekly.md                    # Markdown 大纲 → 幕布\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # 幕布 → Markdown\ndiff weekly.md out.md                                              # 无输出 = 一字不差\n```\n\n![demo](assets/demo.gif)\n\n---\n\n## 🆚 为什么选 mubu-integration？\n\n| 能力 | 手动复制 | 现有导出插件脚本 | **mubu-integration** |\n| :--- | :---: | :---: | :---: |\n| 幕布 → Markdown | ✅ | ⚠️ 部分 | ✅ |\n| Markdown → 幕布 | ❌ | ❌ | ✅ **（唯一）** |\n| 往返保真（diff 无差异） | ❌ | ❌ | ✅ **（唯一）** |\n| 整树批量 / OPML / FreeMind | ❌ | ⚠️ 部分 | ✅ |\n| 可被 AI Agent 调用 | ❌ | ❌ | ✅ **（唯一）** |\n| 命令行可脚本化 | ❌ | ⚠️ | ✅ |\n\n---\n\n## 💡 使用场景\n\n**① 让 AI Agent 直接读写你的幕布** —— 把幕布变成 Agent 的长期结构化记忆。\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > memory.md   # Agent 拉取最新大纲\n# ... Agent 编辑 memory.md ...\npython3 scripts/mubu_api.py save <doc-id> --md memory.md                 # 把更新后的大纲写回幕布\n```\n\n**② Obsidian ↔ 幕布 双向大纲** —— 以纯 Markdown 让你的知识库和大纲工具保持同步。\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > vault/notes/mubu.md   # 幕布 → Obsidian\npython3 scripts/mubu_api.py create --md vault/notes/mubu.md --folder <folder-id>   # Obsidian → 幕布\n```\n\n**③ 周会纪要自动归档** —— 一步把 `examples/weekly.md` 推入幕布。\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --folder <folder-id> --md examples/weekly.md\n```\n\n---\n\n## 🚀 30 秒快速体验\n\n1. 配置幕布凭据（手机号 + 密码）。凭据**不会**作为命令行参数传递 —— 用环境变量或本地文件：\n\n   ```bash\n   export MUBU_PHONE=\"你的手机号\"\n   export MUBU_PASSWORD=\"你的密码\"\n   ```\n\n   …或写入仓库外的 `~/.workbuddy/.env.mubu`（环境变量优先；文件权限自动 `0o600`）：\n\n   ```ini\n   MUBU_PHONE=你的手机号\n   MUBU_PASSWORD=你的密码\n   ```\n\n2. 使用自带的示例大纲（`examples/weekly.md`）：\n\n   ```markdown\n   # 产品周会\n   - 上周进展\n     - [x] 上线新版本\n     - [ ] 修复登录 bug\n   - 本周计划\n     - 性能优化\n   > 备注：记得同步给设计团队\n   ```\n\n3. 导入后再导出来 —— 标题层级、`[x]` 勾选、`> note` 备注都会原样还原：\n\n   ```bash\n   python3 scripts/mubu_api.py create \"产品周会\" --folder <folder_id> --md examples/weekly.md\n   python3 scripts/mubu_api.py get <doc_id> --export markdown\n   ```\n\n---\n\n## 📦 安装\n\n```bash\nnpx skills add liuboacean/mubu-integration\n```\n\n这会为你的 Agent 安装该 Skill。它是一个 Python 包 —— 你还需要 **Python 3.9+** 及运行时依赖：\n\n```bash\npip install -r requirements.txt\n```\n\n开发与测试依赖在 `requirements-dev.txt`（`pip install -r requirements-dev.txt`）。\n\n---\n\n## 🛡️ 可靠性\n\nmubu-integration 调用的是**与幕布 Web 端相同的 HTTPS 接口** —— 不爬取、不操控浏览器。\n\n- ✅ **已对线上环境真机验证** —— 最近一次真机校验为 **2026-08-05**，针对 mubu.com 生产环境。`move`、`save_doc`、`rename`、OPML、FreeMind、`export-tree` 均已确认可用。\n- ✅ **115 用例 × 4 个 Python 版本，每次全绿** —— GitHub Actions 矩阵在每次 push 和 PR 时于 Python 3.9 / 3.10 / 3.11 / 3.12 上运行。\n- ✅ **自动刷新鉴权** —— Token 过期后用缓存凭据（环境变量 / `~/.workbuddy/.env.mubu`）自动重新登录，初始配置后无需再次手动输入。\n- ✅ **依赖全量锁定** —— `requirements.txt` 锁定精确版本；Dependabot 自动跟进更新。\n- ✅ **数据边界清晰** —— 工具仅以你的凭据访问你自己账号下的数据。凭据仅本地存储于 `~/.mubu_token`，权限 `0o600`（仅本人可读写）。\n\n<details>\n<summary>技术细节</summary>\n\nmubu-integration 是一个**非官方**集成，使用的接口与幕布 Web 端一致。所有请求发往 `https://api2.mubu.com/v3/api`；鉴权 JWT 通过请求头 `jwt-token` 传递。`access_token` 约 2 小时过期并自动刷新（仅重试 1 次，避免锁定死循环）；`403` 及其它错误不触发重新登录。\n\n**已知限制：** 大纲折叠状态 `expand`、有序列表 `1.`、图片 / 附件节点不在当前 Markdown 往返保真范围。往返保真但非实时双向同步（无 diff/merge），重复导入会生成新副本。\n\n</details>\n\n---\n\n## ⚙️ 工作原理\n\n```mermaid\nflowchart LR\n    U[用户 / AI Agent] -->|CLI 子命令| CLI[scripts/mubu_api.py]\n    CLI --> Client[MubuClient]\n    Client -->|HTTPS + jwt-token| API[(api2.mubu.com/v3/api)]\n    API --> Client\n    Client -->|Markdown 导入/导出（往返保真）| MD[(本地 .md 大纲)]\n    Client -->|Token 缓存 0o600| Cache[(本地凭据)]\n```\n\nMarkdown 大纲 ⇄ 幕布文档（往返保真）示意：\n\n```mermaid\nflowchart LR\n    subgraph MD[Markdown 大纲]\n        M[\"# 标题<br>- 要点<br>- [x] 勾选<br>&gt; note 备注\"]\n    end\n    subgraph MU[幕布文档]\n        B[\"节点 + 层级 + 勾选 + 备注\"]\n    end\n    M -->|import: create --md| B\n    B -->|export: get --export markdown| M\n```\n\n**项目结构**（模块化 Python 包；`scripts/mubu_api.py` 为向后兼容 shim）：\n\n```\nscripts/\n├── mubu_api.py        # 向后兼容 shim（重新导出 mubu 包）\n└── mubu/              # 模块化包（v1.3.0+）\n    ├── __init__.py    # 包标识（__version__）\n    ├── config.py      # 常量 / 配置 / 日志 / MubuError / 路径安全 / Token 锁\n    ├── convert.py     # 文档 ↔ Markdown / OPML / FreeMind 转换 + 展示格式化\n    ├── client.py      # MubuClient（鉴权 / 请求 / 文档·文件夹·搜索·整树导出）\n    └── cli.py         # 命令行入口 main() + 日志配置\n```\n\n---\n\n## 📚 命令行参考\n\n<details>\n<summary>展开全部 20+ 命令</summary>\n\n```bash\n# 登录（首次使用需先配置凭据）\npython3 scripts/mubu_api.py login\n\n# 获取根目录列表\npython3 scripts/mubu_api.py list\n\n# 获取子文件夹内容\npython3 scripts/mubu_api.py list --folder <folder_id>\n\n# 创建文件夹\npython3 scripts/mubu_api.py mkdir \"新文件夹\"\n\n# 创建文档\npython3 scripts/mubu_api.py create \"新文档\" --folder <folder_id>\n\n# 从 Markdown 文件导入创建文档\npython3 scripts/mubu_api.py create \"新文档\" --folder <folder_id> --md examples/weekly.md\n\n# 获取文档内容（JSON）\npython3 scripts/mubu_api.py get <doc_id>\n\n# 导出为 Markdown（往返保真，非占位）\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# 保存文档\npython3 scripts/mubu_api.py save <doc_id> --content \"内容\"\npython3 scripts/mubu_api.py save <doc_id> --file content.md\n\n# 从 Markdown 文件导入更新文档\npython3 scripts/mubu_api.py save <doc_id> --md outline.md\n\n# 移动文档到其他文件夹\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# 删除（⚠️ 不可逆，执行前务必确认目标 ID；必须显式 --yes；--type 默认 folder）\npython3 scripts/mubu_api.py delete <id> --type folder --yes\npython3 scripts/mubu_api.py delete <doc_id> --type doc --yes\n\n# 按名称本地搜索文档/文件夹（递归遍历所有子文件夹，大小写不敏感）\npython3 scripts/mubu_api.py search \"项目\"\npython3 scripts/mubu_api.py search \"项目\" --json\n\n# 递归导出整个文件夹树为嵌套 Markdown 文件（默认当前目录，--output 指定输出根）\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# 重命名文档（走 save_doc 的 name 参数，round-trip 保内容）\npython3 scripts/mubu_api.py rename <doc_id> --name \"新标题\" --type doc\n\n# 重命名文件夹（已真机验证端点 /list/rename_folder，folderId 填自身 id）\npython3 scripts/mubu_api.py rename <folder_id> --name \"新文件夹名\" --type folder\n\n# 导出为 OPML 2.0 / FreeMind（兼容 XMind 等其它大纲工具）\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n</details>\n\n---\n\n## 🤖 Agent 触发词\n\n> 幕布、mubu、幕布大纲导入导出\n\n当对话中出现以上关键词时，Skill 可被自动触发。\n\n---\n\n## 🧪 测试与 CI\n\n本地运行全部测试（共 **115** 个 pytest 用例）：\n\n```bash\nPYTHONPATH=scripts python -m pytest -v\n```\n\n持续集成：在 push 到 `main` 分支或提交 Pull Request 时，GitHub Actions 会于 **Python 3.9 / 3.10 / 3.11 / 3.12** 矩阵中自动运行测试。115 个用例在四个 Python 版本上均真实执行（非假成功）。\n\n---\n\n## ❓ 常见问题 FAQ\n\n**Q：需要有幕布账号吗？**\nA：需要。使用你的手机号 + 密码登录（`MUBU_PHONE` / `MUBU_PASSWORD`）。这是幕布官方账号，本 Skill 不提供账号。\n\n**Q：这是非官方集成，我的凭据安全吗？**\nA：凭据仅本地存储——登录 Token 写入本地文件且权限为 `0o600`（仅本人可读写），不依赖任何第三方服务。环境变量优先于 `.env.mubu` 文件加载。详见[可靠性](#-可靠性)。\n\n**Q：支持图片 / 附件节点吗？**\nA：当前不支持。大纲折叠状态 `expand`、有序列表 `1.`、图片 / 附件节点不在当前 Markdown 往返保真范围。完整已知限制见[技术细节](#-可靠性)。\n\n---\n\n## 📄 License\n\n[MIT](https://opensource.org/licenses/MIT)\n\nFile v1.3.16:skill-card.md\n\n## Description:\n\nmubu-integration helps agents import Mubu outlines into Obsidian, sync Markdown back to Mubu, and query or export Mubu notes.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[liuboacean](https://clawhub.ai/user/liuboacean)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to let an agent read, export, create, and update Mubu outlines as Markdown for Obsidian or local knowledge workflows. It is intended for users who are comfortable granting an agent access to their own Mubu account.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can use saved account credentials to read and modify real Mubu notes.\n\nMitigation: Install only when agent access to the account is acceptable, protect MUBU_PHONE, MUBU_PASSWORD, MUBU_MEMBER_ID, and ~/.mubu_token, and require human confirmation before create, save, move, rename, export-tree, or purge actions.\n\nRisk: A misconfigured MUBU_BASE_URL could send authenticated requests to an unintended endpoint.\n\nMitigation: Do not set MUBU_BASE_URL unless it is an HTTPS Mubu URL.\n\nRisk: The security scan requires review because safety claims are broader than the code enforces.\n\nMitigation: Review the skill before deployment and enforce approval around commands that alter or export account content.\n\n## Reference(s):\n\n- [ClawHub skill page](https://clawhub.ai/liuboacean/skills/mubu-integration)\n- [Mubu API base endpoint](https://api2.mubu.com/v3/api)\n- [README](artifact/README.md)\n- [Skill usage guide](artifact/SKILL.md)\n- [Changelog](artifact/CHANGELOG.md)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, code, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown, JSON, OPML or FreeMind XML, and CLI text depending on the command]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May perform authenticated Mubu API calls and write local Markdown exports when explicitly commanded.]\n\n## Skill Version(s):\n\n1.3.16 (source: server release evidence and CHANGELOG, released 2026-09-02)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.3.15: 23 files, 106689 bytes\n\nFiles: CHANGELOG.md (45055b), CONTRIBUTING.md (785b), docs/hellogithub-pitch.md (1596b), docs/positioning.md (1274b), docs/v2ex-post.md (1020b), docs/xiaoshuopai-tutorial.md (1694b), examples/weekly.md (212b), README.md (11802b), README.zh-CN.md (10660b), requirements-dev.txt (16469b), requirements.txt (8955b), ruff.toml (372b), scripts/gen_assets.py (3054b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15996b), scripts/mubu/client.py (39866b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (15143b), skill-card.md (2640b), SKILL.md (20290b), tests/test_mubu_api.py (82356b), _meta.json (136b)\n\nFile v1.3.15:SKILL.md\n\n---\nname: mubu-integration\ndescription: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出\n---\n\n# 幕布集成 Skill\n\n幕布（mubu.com）是一款极简大纲工具，支持将大纲一键转为思维导图。本 Skill 提供 API 集成能力。\n\n## 权限与安全边界\n本 Skill 以你的幕布账号身份操作**远程真实内容**，使用前请知悉其权限边界：\n- **读取**：仅读取环境变量 `MUBU_PHONE` / `MUBU_PASSWORD` / `MUBU_MEMBER_ID`（环境变量未设置时，才由仓库外的 `~/.workbuddy/.env.mubu` 补全；`MUBU_MEMBER_ID` 即幕布 colla 成员 ID，仅 `save` 写回需要。**该值任何 API 都不返回，无法自动获取，必须手动设置 `MUBU_MEMBER_ID` 或提前写入 token 缓存的 `member_id`**）。\n- **写入**：仅在本地写入 Token 缓存文件 `~/.mubu_token`（权限 `0o600` + 跨进程 `fcntl` 锁），不写入其它文件。\n- **网络**：仅访问 `api2.mubu.com`（base URL 可由 `MUBU_BASE_URL` 覆盖，但仅限 `mubu.com` 家族域名，防 MITM），**无第三方服务、无遥测、无数据外发**。\n- **写操作需确认**：真实会改动幕布内容的写操作为 `create`（新建）、`rename_folder`（重命名文件夹）、`save`（保存文档，端点 `/colla/events`，需 `member_id`）、`move`（移动，端点 `/list/custom/drag`）、`rename_doc`（文档重命名，端点 `/list/rename_doc`）、`purge`（彻底删除，唯一真实调用服务端删除，不可逆）；`delete` 现为**软删除**（仅标记进本地回收站，云端副本仍在）。`save` / `move` / `rename_doc` 已在 v1.3.9 经真机验证可用。所有真实写操作均需显式传 `--yes` 才执行，否则中止并提示。\n- **信任边界**：Skill 不读取你的其它本地文件、不执行与幕布无关的 shell 命令；它只做「登录 → 读写你的幕布文档」这一件事。\n\n## 功能概览\n\n| 功能 | 接口 | 说明 |\n|------|------|------|\n| 用户登录 | `POST /user/phone_login` | 手机号密码登录获取 Token |\n| Token 刷新 | 自动处理 | access_token 2小时过期，临近过期自动重新登录（重登仅重试 1 次，杜绝死循环） |\n| 创建文件夹 | `POST /list/create_folder` | 在指定位置创建文件夹 |\n| 创建文档 | `POST /list/create_doc` | 创建新的大纲文档 |\n| 获取列表 | `POST /list/get` | 获取文件夹下的文档列表 |\n| 获取文档 | `POST /document/edit/get` | 获取文档详细内容（真实端点；body 为 docId+password+isFromDocDir，返回 data.definition 为 JSON 字符串需二次解析） |\n| 更新文档 | `POST /colla/events` | 保存/更新文档内容（v1.3.9 起可用；colla 协同端点，`events` 承载 changeset；需 `member_id`，私人文档由 `MUBU_MEMBER_ID` 或 token 缓存提供）|\n| 删除文档 | `POST /list/delete_doc` | 删除文档（按类型区分端点） |\n| 删除文件夹 | `POST /list/delete_folder` | 删除文件夹（原 `/list/delete` 实测非法，已弃用） |\n| 移动文档 | `POST /list/custom/drag` | 移动文档/文件夹到其他位置（v1.3.9 起可用；body `dst/src/folderId`，已真机验证）|\n| 导出 Markdown | 本地转换 | 将大纲结构转换为 Markdown |\n\n## API 基础信息\n\n- **Base URL**: `https://api2.mubu.com/v3/api`\n- **认证方式**: JWT Token，通过请求头 `Jwt-Token` 传递\n- **Content-Type**: `application/json;charset=UTF-8`\n\n## 环境变量配置\n\n在使用前，需要配置以下环境变量：\n\n```bash\nexport MUBU_PHONE=\"your_phone_number\"    # 幕布账号手机号\nexport MUBU_PASSWORD=\"your_password\"      # 幕布账号密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"your_collab_member_id\"\n```\n\n> 切勿在脚本或代码中硬编码明文密码；凭据仅通过环境变量或仓库外的\n> `~/.workbuddy/.env.mubu` 提供。\n\n---\n\n## 使用说明\n\n### 1. 使用 MubuClient\n\n所有操作都通过 `scripts/mubu/client.py` 中的 `MubuClient` 类完成（`scripts/mubu_api.py` 仅为向后兼容的重新导出 shim，不再建议直接使用；**不再有**独立的\n`login()` / `create_folder()` / `create_doc()` / `get_list()` / `get_doc()` / `save_doc()` /\n`delete_item()` 模块级函数）。实例化时自动读取 `MUBU_PHONE` / `MUBU_PASSWORD`\n环境变量（或 `~/.workbuddy/.env.mubu`）并加载本地缓存 Token：\n\n```python\nfrom mubu.client import MubuClient\n\n# 登录：凭据来自环境变量；返回扁平 data（token / id / name）\nclient = MubuClient()\ninfo = client.login()\nprint(info[\"user_id\"], info[\"username\"])   # 注意是扁平 data[\"id\"]，非 data[\"user\"][\"id\"]\n\n# 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感）\nresults = client.search(\"项目\", max_depth=3, limit=50)[\"results\"]\nfor r in results:\n    print(r[\"type\"], r[\"name\"], r[\"path\"])\n```\n\n> 登录返回结构为**扁平** `data`：`data[\"id\"]`=用户 ID，`data[\"name\"]`=用户名，\n> `data[\"token\"]`=令牌。这与旧版嵌套 `result[\"data\"][\"user\"][\"id\"]` 不同。\n\n---\n\n## 大纲内容格式\n\n幕布文档内容使用特定的 JSON 格式表示大纲结构：\n\n```json\n{\n  \"name\": \"文档标题\",\n  \"nodes\": [\n    {\n      \"id\": \"node_1\",\n      \"text\": \"一级标题\",\n      \"children\": [\n        {\n          \"id\": \"node_1_1\",\n          \"text\": \"二级标题\",\n          \"children\": []\n        }\n      ],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    },\n    {\n      \"id\": \"node_2\",\n      \"text\": \"另一个一级标题\",\n      \"children\": [],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    }\n  ]\n}\n```\n\n---\n\n## Token 管理建议\n\n由于幕布的 access_token 仅约 2 小时有效（无 refresh_token 机制，代码也无任何 refresh 逻辑），建议：\n\n1. **本地缓存**: 将 Token 保存到本地文件（如 `~/.mubu_token`）\n2. **自动刷新**: 在 Token 快过期时自动刷新\n3. **错误重试**: 遇到 401 错误时重新登录\n\n```python\nimport os\nimport time\nimport json\nimport tempfile\n\nTOKEN_FILE = os.path.expanduser(\"~/.mubu_token\")\n\ndef save_token(token_data):\n    \"\"\"原子写 + 仅属主可读写：避免中途崩溃留下残缺文件，并防止其它用户读取。\"\"\"\n    token_data = dict(token_data)\n    token_data[\"expires_at\"] = time.time() + 7200  # 2小时后过期\n    # 注：真实 scripts/mubu_api.py 的 _save_token 还会用跨进程 fcntl.flock\n    # advisory 锁包裹整段写（M4 已做成跨平台安全：无 fcntl 平台降级为无锁）；\n    # 此处省略锁，聚焦写盘逻辑。\n    dir_name = os.path.dirname(TOKEN_FILE) or \".\"\n    fd, tmp = tempfile.mkstemp(dir=dir_name, prefix=\".mubu_token.\", suffix=\".tmp\")\n    try:\n        with os.fdopen(fd, \"w\") as f:\n            json.dump(token_data, f)\n        os.chmod(tmp, 0o600)        # 仅属主可读写\n        os.replace(tmp, TOKEN_FILE) # 原子重命名，避免残缺文件\n    except Exception:\n        if os.path.exists(tmp):\n            os.remove(tmp)\n        raise\n\ndef load_token():\n    \"\"\"从本地加载未过期的 Token；已过期或损坏则返回 None。\"\"\"\n    if not os.path.exists(TOKEN_FILE):\n        return None\n    try:\n        with open(TOKEN_FILE) as f:\n            data = json.load(f)\n    except Exception:\n        return None\n    if time.time() >= data.get(\"expires_at\", 0):  # 已过期视为无效\n        return None\n    return data\n```\n\n说明：原示例中朴素的 `is_token_valid` 已移除——其职责（\"是否过期\"）已并入 `load_token`，仅返回未过期的 token。真实实现 `scripts/mubu_api.py` 的 `_save_token` 还包含跨进程 `fcntl.flock` 锁与统一的 `TOKEN_FILE_MODE` 权限管理，此处不再重复。\n\n---\n\n## 导出 / 导入 Markdown\n\nM1 已落地真正的 Markdown 导入/导出（往返保真，不再是占位）。核心纯函数位于 `scripts/mubu_api.py`：\n\n```python\ndef doc_to_markdown(node, level=0):\n    \"\"\"将节点（及子树）渲染为 Markdown 列表片段。\n    '- ' 列表项，缩进 = 2 * level；含 checked → '- [x]'/'- [ ]'；\n    含 note → 子树后追加 '> {note}'。根标题由 export_markdown 负责。\"\"\"\n    ...\n\ndef export_markdown(doc):\n    \"\"\"doc 为 get_doc() 返回的 data 层 {\"node\": {...}}。\n    首行 '# 标题'，其余递归为 '- ' 列表。结构无效时抛 MubuError。\"\"\"\n    ...\n\ndef markdown_to_doc(md):\n    \"\"\"Markdown 文本 → {\"node\": {\"id\": \"root\", \"text\": ..., \"children\": [...]}}。\n    标题为顶层节点；多标题时首个为 root，其余作为 root 的 children；\n    列表项用栈按缩进深度维护层级；'- [ ]'/'- [x]' 设 checked；\n    '> ' 作为对应层级节点的 note。\"\"\"\n    ...\n```\n\n导出示例（幕布 → Markdown）：\n\n```\n# 读书笔记\n- 第一章\n  - [x] 读完\n  - [ ] 写笔记\n> 第一章的备注\n```\n\n> 说明：根节点的 `text` 渲染为 `# 标题`，其直接子节点从缩进 0 的 `- ` 列表开始；\n> note 出现在其所属节点（含子树）之后，并按缩进深度归属到对应节点。\n\n---\n\n## 命令参考（CLI）\n\n| 命令 | 说明 |\n|------|------|\n| `login` | 手机号密码登录，Token 本地缓存 |\n| `list --folder <id> [--include-trash]` | 获取文件夹下的文档/子文件夹列表（`--json` 输出原始 JSON；`--include-trash` 包含已软删除项） |\n| `mkdir <name> --parent <id>` | 创建文件夹 |\n| `create <name> --folder <id> [--content <json>] [--md <file>]` | 创建文档；`--md` 从 Markdown 文件导入 |\n| `get <doc_id> [--export markdown\\|json]` | 获取文档；`--export markdown` 输出真实 Markdown |\n| `save <doc_id> [--file <f>] [--md <file>] [--content <c>]` | 保存文档；`--md` 从 Markdown 文件导入 |\n| `delete <id> [--type doc\\|folder] --yes` | **软删除**：移入本地回收站（云端仍在，`restore` 可恢复，`purge` 可彻底删除）；`--type` 默认 folder，必须显式 `--yes` 才执行 |\n| `restore <id>` | 从本地回收站恢复（仅移除标记，零服务端调用） |\n| `purge <id> --yes` | **彻底删除（不可逆）**：调用服务端真实删除 API 后移除本地标记；必须显式 `--yes` 才执行 |\n| `trash` | 列出本地回收站中已软删除的项 |\n| `move <item_id> --target <folder_id> [--type doc\\|folder]` | 移动文档/文件夹到其他位置（v1.3.9 起可用，端点 `/list/custom/drag`，已真机验证）|\n| `search <关键字> [--max-depth N] [--limit N] [--include-trash]` | 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感；`--include-trash` 包含已软删除项） |\n| `export-tree --folder <id> [--output <dir>] [--max-depth N]` | 递归导出整个文件夹树为嵌套 Markdown 文件 |\n| `rename <id> --name <新名> [--type doc\\|folder]` | 重命名文档（`/list/rename_doc` 端点）或文件夹（已验证端点 `/list/rename_folder`，`folderId` 填自身 id）|\n| `opml <doc_id> [--format opml\\|freeplane]` | 导出为 OPML 2.0 / FreeMind XML（兼容 XMind 等其它大纲工具）|\n\n### 软删除 / 回收站（v1.3.5）\n\n`delete` 现在不再是真正的服务端删除，而是**软删除**：\n\n- `delete <id> [--type doc\\|folder] --yes` —— **移入本地回收站**：仅把项的元数据标记进本地回收站文件 `~/.workbuddy/.mubu_trash.json`，**云端副本保持不变**，不调用任何删除 API。缺省（无 `--yes`）仅打印提示并退出，绝不软删除。\n- `restore <id>` —— 从本地回收站恢复：仅移除本地标记，**零服务端调用**（即使云端项已不存在也安全）。未找到该项时提示「未找到回收站项」。\n- `purge <id> --yes` —— **彻底删除（不可逆）**：唯一真正调用服务端删除 API（`delete_doc` / `delete_folder`）的操作，成功后移除本地标记。必须显式 `--yes`，否则中止。\n- `trash` —— 列出本地回收站中已软删除的项（id / type / name / deleted_at）。\n\n`list --include-trash` 与 `search <关键字> --include-trash` 可在列表中**包含**已软删除项（`get_list` / `search` 默认过滤回收站项）。\n\n> 回收站仅存元数据快照（id / type / name / parent_id / deleted_at），作为「云端仍在、可恢复」的安全网，**不作为重建来源**。\n\nMarkdown 往返示例：\n\n```bash\n# 导出为 Markdown\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# 从 Markdown 创建文档\npython3 scripts/mubu_api.py create \"我的文档\" --folder <folder_id> --md ./outline.md\n\n# 从 Markdown 更新文档\npython3 scripts/mubu_api.py save <doc_id> --md ./outline.md\n\n# 移动文档\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# 按名称本地搜索文档/文件夹（递归遍历所有子文件夹，大小写不敏感）\npython3 scripts/mubu_api.py search \"项目\"\npython3 scripts/mubu_api.py search \"项目\" --json\n\n# 递归导出整个文件夹树为嵌套 Markdown\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# 重命名文档 / 文件夹\npython3 scripts/mubu_api.py rename <doc_id> --name \"新标题\" --type doc\npython3 scripts/mubu_api.py rename <folder_id> --name \"新文件夹名\" --type folder\n\n# 导出为 OPML / FreeMind\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n---\n\n## Token 刷新策略\n\n- access_token 有效期约 2 小时，本地以 `expires_at` 缓存于 `~/.mubu_token`。\n- 每次请求发起前调用 `ensure_valid_token()`：若未持有 token，或距过期不足\n  `300 + 60`（leeway）秒，则使用缓存的 `phone`/`password` **重新登录**获取新 token。\n- **刷新不依赖 refresh_token**（M1 未启用）。\n- **鉴权失败仅重试 1 次**：`_request` 捕获 401 / 登录失效类错误后重新登录并重试最多一次；\n  第二次仍失败则抛出 `MubuError`，**不再重登**，避免密码错误/账号封禁场景下的死循环。\n- 403（权限不足）或其它非 0 业务 code **不触发重登**。\n- Token 写入采用原子写（先写 `.tmp` 再 `os.rename`），写完追加 `os.chmod(TOKEN_FILE, 0o600)`，\n  确保 Token 文件仅属主可读写（M2 的 T5 已实现）。\n\n---\n\n## 配置说明\n\n脚本通过环境变量读取凭据（优先级：环境变量 > `~/.workbuddy/.env.mubu` 文件；\n两者皆无时，`login` 子命令会交互式提示输入，绝不接受明文命令行参数）：\n\n```bash\nexport MUBU_PHONE=\"你的手机号\"\nexport MUBU_PASSWORD=\"你的密码\"\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"你的幕布 colla 成员 ID\"\n```\n\n也可在 `~/.workbuddy/.env.mubu` 中配置（由 Skill 宿主加载为环境变量，且仅属主可读写）：\n\n```\nMUBU_PHONE=你的手机号\nMUBU_PASSWORD=你的密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nMUBU_MEMBER_ID=你的幕布 colla 成员 ID\n```\n\n---\n\n## 已知限制（M1）\n\n- `expand`（幕布大纲的折叠/展开状态）不在本期往返范围内，导入后节点默认展开。\n- 有序列表 `1.` 不被解析，仅支持无序列表 `- `。\n- 图片 / 附件类型节点不在本期 Markdown 往返范围内（会丢失媒体内容）。\n- 多个顶层标题导入时，首个为 root，其余作为 root 的 children；其后的列表项统一挂在\n  root 下（规范未要求按标题再嵌套）。\n- `save`（文档保存）需要幕布 colla 成员 ID：私人文档的 `memberId` **任何 API 都不暴露**，工具无法自动获取，必须由 `MUBU_MEMBER_ID` 环境变量提供（或提前写入 token 缓存的 `member_id`）。**缺失时 `save` 会明确报错提示配置，不影响 `get` / `create` / `move` / `rename` 等其它操作**。v1.3.9 已修复 `save`（`/colla/events`）、`move`（`/list/custom/drag`）、doc `rename`（`/list/rename_doc`）的真机可用性。\n\n---\n\n## 注意事项\n\n1. **非官方 API**: 幕布未提供官方开放平台，此 Skill 基于逆向分析实现\n2. **稳定性**: API 可能随版本更新而变化，如遇问题请反馈\n3. **频率限制**: 请勿频繁调用，避免触发限流\n4. **数据安全**: Token 存储在本地，请勿泄露\n\n---\n\n## Agent 使用指引\n\n当用户提到幕布、mubu 相关操作（如将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记）时，使用本 Skill 的脚本完成操作。\n\n### 前置检查\n\n1. 确认系统已安装 Python 3 和 requests 库：\n   ```bash\n   python3 -c \"import requests; print('OK')\"\n   ```\n   如果缺少 requests：`pip3 install requests`\n\n2. 确认环境变量已配置：\n   - `MUBU_PHONE` — 幕布手机号\n   - `MUBU_PASSWORD` — 幕布密码\n   - 如未配置，需提示用户先设置\n\n### 脚本路径\n\n```\n~/.workbuddy/skills/mubu-integration/scripts/mubu_api.py\n```\n\n### 常用命令速查\n\n| 用户意图 | 执行命令 |\n|---------|---------|\n| 登录幕布 | `python3 scripts/mubu_api.py login` |\n| 查看文档列表 | `python3 scripts/mubu_api.py list` |\n| 查看某文件夹 | `python3 scripts/mubu_api.py list --folder <folder_id>` |\n| 创建文件夹 | `python3 scripts/mubu_api.py mkdir \"文件夹名\"` |\n| 创建文档 | `python3 scripts/mubu_api.py create \"文档名\" --folder <folder_id>` |\n| 从 Markdown 创建文档 | `python3 scripts/mubu_api.py create \"文档名\" --folder <folder_id> --md outline.md` |\n| 获取文档内容 | `python3 scripts/mubu_api.py get <doc_id>` |\n| 导出为 Markdown | `python3 scripts/mubu_api.py get <doc_id> --export markdown` |\n| 从 Markdown 保存文档 | `python3 scripts/mubu_api.py save <doc_id> --md outline.md` |\n| 从文件保存文档 | `python3 scripts/mubu_api.py save <doc_id> --file content.json` |\n| 移动文档 | `python3 scripts/mubu_api.py move <doc_id> --target <folder_id>` |\n| 软删除（移入回收站） | `python3 scripts/mubu_api.py delete <id> --type doc\\|folder --yes`（云端仍在，`restore` 可恢复）|\n| 从回收站恢复 | `python3 scripts/mubu_api.py restore <id>` |\n| 彻底删除（不可逆） | `python3 scripts/mubu_api.py purge <id> --yes`（必须显式 `--yes`，调用服务端真实删除）|\n| 查看回收站 | `python3 scripts/mubu_api.py trash` |\n| 按名称搜索 | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N]` |\n| 按名称搜索（含回收站） | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N] --include-trash` |\n| 按名称搜索（JSON） | `python3 scripts/mubu_api.py search <关键字> [--max-depth N] [--limit N] --json` |\n\n### 典型工作流\n\n**场景 1：用户说\"把这份大纲同步到幕布\"**\n1. 确认内容来源（文件或对话中直接提供）\n2. 如果是 Markdown，直接用脚本创建文档并导入\n3. 返回新文档 ID 和链接\n\n**场景 2：用户说\"导出我的幕布笔记\"**\n1. 先列出文档列表让用户选择，或按名称搜索\n2. 获取文档内容\n3. 转换为 Markdown 格式返回\n\n**场景 3：用户说\"在幕布建一个项目文件夹\"**\n1. 确认文件夹名称和层级结构\n2. 批量创建文件夹\n3. 返回创建结果\n\n---\n\n## 工作流示例\n\n### 示例 1: 从 Markdown 创建幕布文档\n\n```\n用户: 把这份 Markdown 大纲同步到幕布\n```\n\n执行步骤：\n1. 解析 Markdown 结构\n2. 转换为幕布 JSON 格式\n3. 登录获取 Token\n4. 创建文档并保存内容\n\n### 示例 2: 导出幕布文档为 Markdown\n\n```\n用户: 导出我的\"读书笔记\"文档\n```\n\n执行步骤：\n1. 登录获取 Token\n2. 本地搜索匹配文档：`python3 scripts/mubu_api.py search \"读书笔记\"`\n3. 获取文档内容\n4. 转换为 Markdown 并返回\n\n### 示例 3: 批量创建文件夹结构\n\n```\n用户: 在幕布创建项目文档结构：需求分析、设计文档、开发日志、测试报告\n```\n\n执行步骤：\n1. 登录获取 Token\n2. 创建项目文件夹\n3. 批量创建子文件夹\n4. 返回创建结果\n\nFile v1.3.15:README.md\n\n[English](README.md) | [中文](README.zh-CN.md)\n\n![mubu-integration](assets/social-preview.png)\n\n# mubu-integration\n\n> Turn Mubu (幕布) into a Markdown-native, AI-agent-controllable outliner.\n\n[![GitHub stars](https://img.shields.io/github/stars/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/network/members)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://img.shields.io/github/actions/workflow/status/liuboacean/mubu-integration/test.yml?branch=main)](https://github.com/liuboacean/mubu-integration/actions/workflows/test.yml)\n\nManage your Mubu (幕布) outlines from the command line — **and as an AI Agent Skill** — with lossless Markdown import/export round-trip.\n\n---\n\n## ✨ Try it in 3 commands (magic moment)\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --md examples/weekly.md    # Markdown outline → Mubu\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # Mubu → Markdown\ndiff weekly.md out.md                                              # no output = byte-for-byte identical\n```\n\n![demo](assets/demo.gif)\n\n---\n\n## 🆚 Why mubu-integration?\n\n| Capability | Manual copy | Existing export-plugin scripts | **mubu-integration** |\n| :--- | :---: | :---: | :---: |\n| Mubu → Markdown | ✅ | ⚠️ partial | ✅ |\n| Markdown → Mubu | ❌ | ❌ | ✅ **(only)** |\n| Round-trip fidelity (`diff` = no diff) | ❌ | ❌ | ✅ **(only)** |\n| Whole-tree batch / OPML / FreeMind | ❌ | ⚠️ some | ✅ |\n| Callable by an AI Agent | ❌ | ❌ | ✅ **(only)** |\n| Scriptable from the command line | ❌ | ⚠️ | ✅ |\n\n---\n\n## 💡 Use cases\n\n**① Let your AI Agent read & write Mubu directly** — turn Mubu into your Agent's long-term, structured memory.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > memory.md   # Agent pulls the latest outline\n# ... Agent edits memory.md ...\npython3 scripts/mubu_api.py save <doc-id> --md memory.md                 # writes the updated outline back to Mubu\n```\n\n**② Obsidian ↔ Mubu, two-way outlines** — keep your knowledge base and your outliner in sync as plain Markdown.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > vault/notes/mubu.md   # Mubu → Obsidian\npython3 scripts/mubu_api.py create --md vault/notes/mubu.md --folder <folder-id>   # Obsidian → Mubu\n```\n\n**③ Auto-archive weekly meeting notes** — push `examples/weekly.md` into Mubu in one step.\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --folder <folder-id> --md examples/weekly.md\n```\n\n---\n\n## 🚀 30-second quick start\n\n1. Set your Mubu credentials (phone + password). They are never passed as CLI arguments — use env vars or a local file:\n\n   ```bash\n   export MUBU_PHONE=\"your-phone\"\n   export MUBU_PASSWORD=\"your-password\"\n   ```\n\n   …or write them to `~/.workbuddy/.env.mubu` (env vars take precedence; the file is auto-chmod `0o600`):\n\n   ```ini\n   MUBU_PHONE=your-phone\n   MUBU_PASSWORD=your-password\n   ```\n\n2. Grab the bundled sample outline (`examples/weekly.md`):\n\n   ```markdown\n   # 产品周会\n   - 上周进展\n     - [x] 上线新版本\n     - [ ] 修复登录 bug\n   - 本周计划\n     - 性能优化\n   > 备注：记得同步给设计团队\n   ```\n\n3. Import it, then export it back — headings, `[x]` checkboxes and `> note` remarks all survive intact:\n\n   ```bash\n   python3 scripts/mubu_api.py create \"产品周会\" --folder <folder_id> --md examples/weekly.md\n   python3 scripts/mubu_api.py get <doc_id> --export markdown\n   ```\n\n---\n\n## 📦 Install\n\n```bash\nnpx skills add liuboacean/mubu-integration\n```\n\nThis installs the Skill for your agent. It is a Python package — you also need **Python 3.9+** and the runtime dependency:\n\n```bash\npip install -r requirements.txt\n```\n\nDev/test dependencies live in `requirements-dev.txt` (`pip install -r requirements-dev.txt`).\n\n---\n\n## 🛡️ Reliability\n\nmubu-integration talks to the **same HTTPS endpoints the Mubu web app uses** — no scraping, no browser automation.\n\n- ✅ **Verified against the live service** — last real-device check on **2026-08-05** against mubu.com's production environment. `move`, `save_doc`, `rename`, OPML, FreeMind and `export-tree` are all confirmed working.\n- ✅ **115 tests × 4 Python versions, always green** — the GitHub Actions matrix runs Python 3.9 / 3.10 / 3.11 / 3.12 on every push and PR.\n- ✅ **Auto-refresh auth** — expired tokens re-login automatically using your cached credentials (env vars / `~/.workbuddy/.env.mubu`); no manual re-entry needed after initial setup.\n- ✅ **Pinned supply chain** — `requirements.txt` locks exact versions; Dependabot keeps them current automatically.\n- ✅ **Your data stays yours** — the tool only ever accesses the account you log into, with your own credentials. Credentials are stored locally at `~/.mubu_token` with `0o600` permissions (readable only by you).\n\n<details>\n<summary>Technical notes</summary>\n\nmubu-integration is an **unofficial** integration that uses the same endpoints as the Mubu web client. All requests go to `https://api2.mubu.com/v3/api`; auth is a JWT passed via the `jwt-token` header. The `access_token` expires in ~2 hours and is refreshed automatically (one retry only, to avoid lockout loops); `403` and other errors do not trigger re-login.\n\n**Known limits:** outline collapse state (`expand`), ordered lists (`1.`), and image/attachment nodes are not part of the current Markdown round-trip. Round-trip is lossless but not a live two-way sync (no diff/merge) — re-importing creates a new copy.\n\n</details>\n\n---\n\n## ⚙️ How it works\n\n```mermaid\nflowchart LR\n    U[User / AI Agent] -->|CLI subcommands| CLI[scripts/mubu_api.py]\n    CLI --> Client[MubuClient]\n    Client -->|HTTPS + jwt-token| API[(api2.mubu.com/v3/api)]\n    API --> Client\n    Client -->|Markdown import/export round-trip| MD[(local .md outline)]\n    Client -->|Token cache 0o600| Cache[(local credentials)]\n```\n\nMarkdown outline ⇄ Mubu document (round-trip):\n\n```mermaid\nflowchart LR\n    subgraph MD[Markdown outline]\n        M[\"# heading<br>- bullet<br>- [x] checkbox<br>&gt; note remark\"]\n    end\n    subgraph MU[Mubu document]\n        B[\"node + level + checkbox + note\"]\n    end\n    M -->|import: create --md| B\n    B -->|export: get --export markdown| M\n```\n\n**Project structure** (modular Python package; `scripts/mubu_api.py` is a backward-compatible shim):\n\n```\nscripts/\n├── mubu_api.py        # backward-compatible shim (re-exports the mubu package)\n└── mubu/              # modular package (v1.3.0+)\n    ├── __init__.py    # package identity (__version__)\n    ├── config.py      # constants / config / logging / MubuError / path safety / token lock\n    ├── convert.py     # doc ↔ Markdown / OPML / FreeMind conversion + display formatting\n    ├── client.py      # MubuClient (auth / requests / doc·folder·search·tree export)\n    └── cli.py         # CLI entrypoint main() + logging setup\n```\n\n---\n\n## 📚 CLI reference\n\n<details>\n<summary>Show all 20+ commands</summary>\n\n```bash\n# Login (first use requires credentials configured)\npython3 scripts/mubu_api.py login\n\n# List root directory\npython3 scripts/mubu_api.py list\n\n# List a sub-folder\npython3 scripts/mubu_api.py list --folder <folder_id>\n\n# Create a folder\npython3 scripts/mubu_api.py mkdir \"New Folder\"\n\n# Create a document\npython3 scripts/mubu_api.py create \"New Doc\" --folder <folder_id>\n\n# Create a document from a Markdown file\npython3 scripts/mubu_api.py create \"New Doc\" --folder <folder_id> --md examples/weekly.md\n\n# Get document content (JSON)\npython3 scripts/mubu_api.py get <doc_id>\n\n# Export as Markdown (round-trip, not a placeholder)\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# Save document\npython3 scripts/mubu_api.py save <doc_id> --content \"content\"\npython3 scripts/mubu_api.py save <doc_id> --file content.md\n\n# Update a document from a Markdown file\npython3 scripts/mubu_api.py save <doc_id> --md outline.md\n\n# Move a document to another folder\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# Delete (⚠️ irreversible — confirm the ID; requires explicit --yes; --type defaults to folder)\npython3 scripts/mubu_api.py delete <id> --type folder --yes\npython3 scripts/mubu_api.py delete <doc_id> --type doc --yes\n\n# Local search by name (recursive across all sub-folders, case-insensitive)\npython3 scripts/mubu_api.py search \"project\"\npython3 scripts/mubu_api.py search \"project\" --json\n\n# Recursively export a whole folder tree as nested Markdown (default: cwd; --output sets root)\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# Rename a document (save_doc name; round-trip preserves content)\npython3 scripts/mubu_api.py rename <doc_id> --name \"New Title\" --type doc\n\n# Rename a folder (verified endpoint /list/rename_folder; folderId = its own id)\npython3 scripts/mubu_api.py rename <folder_id> --name \"New Folder Name\" --type folder\n\n# Export as OPML 2.0 / FreeMind (compatible with XMind and other outliners)\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n</details>\n\n---\n\n## 🤖 Agent trigger words\n\n> 幕布、mubu、幕布大纲导入导出\n\nWhen these keywords appear in a conversation, the Skill can be triggered automatically.\n\n---\n\n## 🧪 Tests & CI\n\nRun the full suite locally (**115** pytest cases):\n\n```bash\nPYTHONPATH=scripts python -m pytest -v\n```\n\nContinuous integration: on every push to `main` and on every Pull Request, GitHub Actions runs the **115** cases across the **Python 3.9 / 3.10 / 3.11 / 3.12** matrix — all executed for real, not faked green.\n\n---\n\n## 🔧 Troubleshooting\n\n| 现象 / 错误码 | 可能原因 | 解决 |\n|------|------|------|\n| `save` 返回 `code 17` / `illegal request`，msg 提到 `memberId` | `MUBU_MEMBER_ID` 未设置 | 设置环境变量 `MUBU_MEMBER_ID`（见上方 Credentials 章节）。**该值为服务端限制，任何 API 都不返回，无法自动获取**；缺失时 `save` 会明确报错，不影响 `get`/`create`/`list`/`search`/`export-tree`。 |\n| `code 5` / 参数错误 | 请求参数不正确 | 检查参数，例如 `rename_folder` 的 `folderId` 必须填文件夹**自身** id，不能填根目录魔法值 `\"0\"`。 |\n| `code 403` / 权限不足 | 账号缺少该操作权限 | 确认账号权限；部分写操作需特定权限。 |\n| 登录失败 / `401` | 凭据错误 | 核对 `MUBU_PHONE` / `MUBU_PASSWORD`，或重新设置环境变量 / `.env.mubu`。 |\n\n> 非官方逆向集成：幕布服务端可能调整接口或限流策略，若某端点突然失效，请以抓包结果为准并反馈 Issue。\n\n## ❓ FAQ\n\n**Q: Do I need a Mubu account?**\nA: Yes. Log in with your phone + password (`MUBU_PHONE` / `MUBU_PASSWORD`). This is your official Mubu account; the Skill does not provide one.\n\n**Q: It's an unofficial integration — are my credentials safe?**\nA: Credentials are stored only locally. The login token is written to a local file with `0o600` permissions (you-only read/write) and uses no third-party service. Env vars take precedence over the `.env.mubu` file. See [Reliability](#-reliability).\n\n**Q: Are image / attachment nodes supported?**\nA: Not yet. Outline collapse state (`expand`), ordered lists (`1.`), and image/attachment nodes are outside the current Markdown round-trip. See the [technical notes](#-reliability) for the full list of known limits.\n\n---\n\n## 📄 License\n\n[MIT](https://opensource.org/licenses/MIT)\n\nFile v1.3.15:_meta.json\n\n{\n  \"ownerId\": \"kn73qbrbqs4s8t2nh8pm22wxbd84vm7r\",\n  \"slug\": \"mubu-integration\",\n  \"version\": \"1.3.15\",\n  \"publishedAt\": 1787892034291\n}\n\nFile v1.3.15:CHANGELOG.md\n\n# Changelog\n\n记录 mubu-integration Skill 的里程碑演化。\n\n## v1.3.15（P0+P1 改进，2026-08-28）\n\n本轮落实产品评审诊断（deliverables/gstack/product-review-improve-2026-08-28.md）的 P0（信任修复）+ P1（质量/CI/文档）共 12 项，零新功能、无破坏性变更。\n\n### P0 信任修复（零风险、确定性）\n- **doc(README/zh-CN/CONTRIBUTING): 修正落地页首行坏命令** —— \"Try it in 3 commands\" 的 `create --md weekly.md` 缺必填位置参数 `name`，实测直接报 `error: the following arguments are required: name`；改为 `create \"周会\" --md examples/weekly.md`（cli.py:71 已证实 name 必填）。\n- **doc: 测试数 84 → 113 对齐** —— README(×3)/README.zh-CN(×3)/CONTRIBUTING 全量修正为真实的 113（CI 实测 113 passed）。\n\n### P1 质量 / CI / 文档\n- **fix(client._http_request): 429 限流退避** —— 新增 HTTP 429 分支，按 `Retry-After` 头（封顶 30s）或 NETWORK_BACKOFF 退避重试，重试用尽抛清晰 MubuError；对齐 5xx 重试逻辑（不重登）。\n- **fix(client.get_doc): 解析保护** —— `json.loads(data[\"definition\"])` 包裹为 try/except(KeyError/TypeError/ValueError) → MubuError，不再裸抛。\n- **ci: 新增 ruff 静态门禁** —— `.github/workflows/test.yml` 加 `lint` job（安装 ruff 跑 `ruff check scripts`，规则集 E/F/W/I）；新增 `ruff.toml`。沙箱本地因 SOCKS 代理无法安装 ruff 验证，故该 job 当前 `continue-on-error`，待真实环境跑通后改为必过。\n- **doc(cli.rename help): 修正矛盾** —— `rename --type doc` 帮助由\"走 save_doc name（⚠️ 真机受限）\"改为\"走已验证端点 /list/rename_doc（内容保真）\"，与实现一致。\n- **doc(README): Auto-refresh auth 措辞** —— \"Zero-touch auth\" 改为 \"Auto-refresh auth\"，明确重登依赖缓存凭据（env/.env.mubu），不声称免密码；zh-CN 同步。\n- **doc(README): 新增 Troubleshooting** —— 错误码表（memberId/code 17·5·403/登录失败），含服务端限制说明。\n- **feat(client.search): include_content** —— 新增可选参数，对名称未命中的文档额外拉取正文递归搜索节点 text/note，命中带 `matched_in: \"content\"`（名称命中为 `\"name\"`）；默认关闭保性能。新增 2 例回归测试。\n- **doc(docs/): 分发与定位素材** —— 新增 `docs/positioning.md`（项目定位纠偏）、`docs/hellogithub-pitch.md`（HelloGitHub 投递文案）、`docs/xiaoshuopai-tutorial.md`（小虱派教程）、`docs/v2ex-post.md`（V2EX 帖），支撑 awesome 榜单之外的多渠道分发（均未发布，待人工投递）。\n\n### 已知限制（重申）\n- `memberId` 仍无法经任何 API 自动获取，`save` 写回必须手动设置 `MUBU_MEMBER_ID`；本次仅补文档与错误码表，不改变该前置条件。\n- test: **115 passed（0 失败，较 v1.3.14 的 113 新增 2 例 search include_content 测试，无回归）**。\n\n## v1.3.14（本期发布版本 · hypothesis 2 归一化加固，2026-08-25）\n\n本期闭环 hypothesis 2：changeset 节点未做网页端 `tr()` 归一化（缺 note/collapsed/\nfinish/priority/color/时间戳等字段），markdown 直转产物若直喂 save 理论上仍可能触发\n`illegal request`。经核查，CLI `save` 命令（`scripts/mubu/cli.py:225-237`）确会把\n`markdown_to_doc` 的根节点直接包成 `{\"nodes\":[...]}` 喂入 `build_update_event` → `save_doc`，\n该路径真实可达，故需加固。\n\n- **fix(save): changeset 节点归一化加固（hypothesis 2 闭环）**：新增 `normalize_node`\n  （`scripts/mubu/convert.py`），**递归**补全缺失的契约字段——`note=\"\"`、`collapsed=False`、\n  `finish=False`、`priority=0`、`color=0`，时间戳 `createTime`/`modifyTime`/`timestamp`\n  用节点已有值或 `int(time.time()*1000)`；已存在的 `id`/`text`/`children`/`checked` 等字段\n  原样保留。在 `build_update_event`（`scripts/mubu/client.py`）构造 children 前对每个节点归一化。\n  `get_doc` 返回的完整 nodes 字段本就齐全、不受此影响，**不破坏标准 save 路径**；仅对\n  `markdown_to_doc` 构造的缺字段节点做补全，避免残缺 payload 触发服务端 `code:17 illegal request`。\n- **root.id 维持 doc_id（保守不改）**：`build_update_event` 的 `root.id` 仍取 `doc_id`\n  （逆向自「文档根节点 id == 文档 id」假设，v1.3.9 真机验证可用）。无法从 `get_doc` 返回\n  （仅 `{\"name\",\"nodes\"}`，无独立根 id 字段）确证真实根 id，故保留现状并加注释，未盲目改动。\n- **测试同步更新（2 例精确形状断言改为关键字段断言）**：`test_save_doc_auto_fetches_version_and_definition`\n  与 `test_build_update_event_shape` 原断言节点精确形状（如 `{\"id\":\"n1\",\"text\":\"A\"}`），归一化后\n  节点含补全字段，改为断言关键字段存在 + 时间戳类型为 int；`markdown_to_doc` 自身单元不受影响。\n- **⚠️ KNOWN LIMITATION（重申）**：`memberId` 仍无法经任何 API 自动获取，`save` 写回必须手动设置\n  `MUBU_MEMBER_ID`（见 v1.3.13）；本次仅补足节点契约字段，不改变该前置条件。另：本次归一化属客户端\n  契约补全，`save` 端到端落库仍受服务端反爬签名前置条件约束（同历史 KNOWN LIMITATION）。\n- test: **113 passed（0 失败，较 v1.3.13 同步更新 2 例 save 路径断言以匹配归一化形状，无新增用例、无回归）**。\n\n## v1.3.13（本期发布版本 · issue #8 修复，2026-08-23）\n\n本期修复 issue #8「幕布成功创建了同名文档，但正文保存被服务端以 illegal request 拒绝」的根因，\n并修正文档中与代码矛盾的「memberId 可自动缓存、无需手动设置」误导表述。\n\n- **fix(save): member_id 缺失时明确报错（替代静默发空串）**：`save_doc`（`scripts/mubu/client.py`）\n  构造 `/colla/events` payload 前校验 `member_id`；缺失时抛 `MubuError` 明确提示配置\n  `MUBU_MEMBER_ID`（附浏览器抓包获取 memberId 的方法），不再静默发空串被服务端以\n  `code:17 / illegal request` 拒绝。这是 issue #8「能建文档、存正文被拒」的 100% 吻合根因\n  （`create_doc` 不需要 memberId，`save_doc` 需要）。\n- **fix(login): 防御性读取 memberId**：`login()` 读取 token/id/name 后，尝试从登录响应读取\n  `memberId`/`member_id` 作兜底；已知限制下登录响应不含该字段，读不到则保持原值（由上方校验兜底），\n  属无害加固。\n- **doc(SKILL.md): 修正「自动缓存、无需手动设置」误导表述**：删除「登录后自动缓存到\n  `~/.mubu_token`，一般无需手动设置」等错误描述，明确说明「`memberId` 任何 API 都不暴露，\n  `save` 写回**必须手动**设置 `MUBU_MEMBER_ID`（或提前写入 token 缓存的 `member_id`）；\n  缺失时 `save` 会明确报错，不影响 `get`/`create`/`move`/`rename`」。与 M19 KNOWN LIMITATION 对齐。\n- **⚠️ KNOWN LIMITATION（重申）**：个人文档的 `memberId` 仍无法经任何 API 自动获取，工具无法绕过；\n  必须手动设置 `MUBU_MEMBER_ID`。v1.3.13 仅让缺失时的报错清晰可操作，并非自动解决（服务端限制）。\n- test: **113 passed（0 失败，较 v1.3.12 的 112 新增 1 例 save_doc 缺失 member_id 回归测试，无回归）**。\n\n## v1.3.12（本期发布版本 · P0 落地页，2026-08-06）\n\n- **README 落地页化**：英文 `README.md` 重写为产品落地页（social-preview 头图、`assets/demo.gif` 动图、能力对比表、3 个 Use Case、Reliability 章节）；新增 `README.zh-CN.md` 全量中文版（英文默认 + 中文全量）。\n- **视觉资产**：新增 `assets/social-preview.png` 与 `assets/demo.gif`；配套 `demo.vhs` 录制脚本与 `scripts/gen_assets.py`。\n- **GitHub 仓库元数据重构**：描述 / homepage / topics 更新为 **AI Agent Skill** 定位，新增 20 个 topics（如 `ai-agent`、`claude-code`、`knowledge-management`、`outliner`）。\n- 版本号 `1.3.11 → 1.3.12`（满足 ClawHub 唯一版本号要求，避免重复版本被拒）。\n\n## M1 (P0) — 基础能力\n- 登录（手机号密码 → JWT Token，请求头 `jwt-token`）\n- 文档/文件夹 CRUD：create_folder / create_doc / get_doc / save_doc / delete / move\n- Markdown 双向：doc_to_markdown / export_markdown / markdown_to_doc（含 note 备注、勾选 [x] 往返）\n- Token 本地缓存 + 临近过期自动重登（仅重试 1 次，杜绝死循环）\n- 发布 GitHub tag 1.0.0\n\n## M2 (P1) — 网络健壮性 + 本地搜索\n- 网络层 5xx 指数退避重试（最多 2 次，与 401 重登分层互不干扰）\n- 非 JSON 响应友好异常 + body 截断\n- .env 凭据加载（仅环境变量未设置时补全）\n- Token 文件权限 0o600（原子写 + chmod）\n- 本地搜索 search()（递归遍历 + 按名称过滤）+ CLI search 子命令\n- 发布 ClawHub v1.1.0\n\n## M3 (P2) — 工程化收尾\n- scripts/mubu_api.py 全量类型注解（typing 模块，Python 3.8+ 兼容，100% 覆盖）\n- 新增 requirements.txt（requests + pytest + responses，.env.mubu 手写解析无 dotenv 依赖）\n- 新增 .github/workflows/test.yml CI（push/PR 触发，Python 3.9–3.12 矩阵，41 用例全过）\n- README 美化：CI 状态徽章、架构/双向转换 Mermaid 图、30 秒快速体验、FAQ\n\n## M4 (P0/P1/P2) — 完善阶段（本期）\n- P1-5：ENDPOINTS 元组化 (method, path)，消除调用点硬编码 \"POST\"\n- P0-2：search() 上限（max_depth / limit / max_requests）经核实仓库原版已具备，CLI 透传 --max-depth / --limit（本期仅核对确认，非新增）\n- P1-3：_http_request 兜底消息避免渲染字面 : None\n- P2-6：_save_token 增加 fcntl.flock 跨进程 advisory 锁（已加固为跨平台安全：try/except ImportError，无 fcntl 平台如 Windows 降级为无锁）\n- P1-4 / P2-7：补充网络层（5xx 混合降级、ConnectionError）与 .env 回归测试\n- P0-1：SKILL.md 清理旧版独立函数，改为指向 MubuClient 的引用与示例\n- T2 收尾：新增 `test_search_global_limit_enforced`，真实验证 `search()` 全局 `limit` 上限被强制执行（破坏性验证：移除上限逻辑则用例失败）\n- SKILL.md「Token 管理建议」示例修正：朴素 `open()+json.dump` 改为原子写（tempfile + os.replace）+ chmod 0o600，并注明真实 `_save_token` 还含跨进程 fcntl 锁；删除误导性的 `is_token_valid`\n\n## M5 (审计整改) — ClawHub Security Audit 全量修复（1 High + 6 Medium）\n- **High · 供应链（依赖未锁定 CVE）**：`requirements.txt` `requests>=2.28,<3` → `requests>=2.32.4,<3`，修复 CVE-2024-47081（.netrc 凭据泄漏）、CVE-2024-35195（Session 复用 `verify` 被覆盖）。CVE-2026-25645（extract_zipped_paths 临时文件复用）经核查本代码路径不可达，仅作升级加固。\n- **Medium · MCP 最小权限（Lp3）**：SKILL.md 新增 `## 权限与安全边界` 段落，明确声明只读/写入/网络/破坏性操作需确认/信任边界 5 条约束。\n- **Medium · 触发词歧义（Vague Triggers ×2）**：frontmatter `description` 与激活指引触发词收窄为 `幕布、mubu、幕布同步、幕布大纲导出`，移除易误触的 `大纲笔记`、`思维导图导出`；全仓 grep 复核 0 残留。\n- **Medium · 缺失破坏性操作警示（Missing User Warnings ×3）**：`delete` 增加 `⚠️ 删除不可逆` 明确提示；CLI 增加 `--yes` 显式确认标志，`main()` 删除分支硬拦截（未传 `--yes` 则打印警示并 `sys.exit(1)`，0 网络请求）。README 删除示例同步更新为 `delete <id> --yes`。\n- **验证**：pytest 45 用例全过；`py_compile` 通过；QA 独立 monkeypatch 复验确认 delete 无 `--yes` 时实际发出 0 次网络请求；7 项审计发现全部 RESOLVED，路由判定 NoOne。\n- 发布 ClawHub v1.1.4（清除 Review 状态）。\n\n## M6 (遗留清理) — 根节点 note 导出修复\n- 修复 `export_markdown()` 遗漏根节点 note 输出的 Bug：children 循环后追加 `f\"> {note}\"`（与子节点 note 格式一致）。\n- 新增 3 个测试：根 note 存在性与位置（在 children 之后）、空 note 省略（不产生孤立 `> ` 行）、含根 note 文档往返一致性（md→doc→md）。\n- 工程师 IS_PASS YES，QA 独立验证 48/48 全过（45 既有 + 3 新增），路由 NoOne。\n- 发布 ClawHub v1.1.5。\n\n## M7 (审计补充) — pytest CVE 版本锁定\n- `requirements.txt` `pytest>=7,<9` → `pytest>=8.3.5,<9`，修复 CVE-2025-71176（pytest tmpdir 处理漏洞；仅测试依赖，运行时不受影响）。\n- 48/48 测试全过（pytest 8.x 兼容）。\n- 发布 ClawHub v1.1.6。\n\n## M8 (安全加固) — 凭据 / 路径 / 文档一致性（本期）\n- **安全官 #1/#2/#12**：`.env.mubu` 加载时强制 `chmod 0o600`；CLI 移除 `--phone`/`--password` 明文参数，凭据改由环境变量 / `.env.mubu` 提供，`login` 子命令缺失时交互式 `getpass` 输入密码；SKILL.md 删除\"直接在脚本中配置\"类表述。\n- **安全官 #3**：新增 `_safe_local_path()`，在 `create --md` / `save --file` 读取本地文件前校验路径——拒绝绝对路径、`..` 越界路径及当前工作目录之外的路径，防任意文件读取外发。\n- **安全官 #8 / 排障手 #8**：实现 `MUBU_BASE_URL` 环境变量覆盖，仅限 `mubu.com` 家族域名白名单（防 MITM 指向恶意服务器）；SKILL.md「网络」边界说明同步更新。\n- **排障手 #7**：收紧 `_is_auth_error` 关键字匹配（移除 \"token\"/\"auth\"/\"expire\"/\"login\"/\"过期\" 等泛化词，仅保留明确登录失效短语），避免正常业务错误误触发重登。\n- **He H3**：`_http_request` 兜底消息避免渲染字面 `None`（非预期 + `last_err or '未知错误'`）。\n- **文档一致性**：CONTRIBUTING 修正开发/运行指令（`git clone` + `pip install -r requirements.txt` + `PYTHONPATH=scripts python -m pytest -v` + 入口 `python3 scripts/mubu_api.py`）；README 版本号统一为 v1.1.6、测试数统一为 69、去除割裂的双版本叙事。\n- **测试补充（排障手 #5/#6、He H4）**：新增真实 API 方法 payload 单测（create_folder/create_doc/get_doc/save_doc/delete/move 的请求体 JSON 与返回 id 提取）、`delete --yes` 守卫回归（无 `--yes` 0 网络调用且 `sys.exit(1)`）、`login` CLI 无明文参数 + getpass 交互测试；全量测试 69 用例通过（48 既有 + 20 新增 + 1 路径校验补强）。\n- **说明**：`search()` 的 `max_depth`/`limit`/`max_requests` 上限经核实为仓库原版已具备（非本期新增），对应方案 He H2 审计结论已修正。\n- （已随 v1.2.0 发布；详情见文末「v1.2.0」段。）\n\n## M9 (P1 第一批) — 产品文案 / 错误指引 / 日志 / 搜索截断 / CI 加固\n\n- **产品官 #10/#11/#15**：README「双向同步」表述改为「Markdown 导入/导出（往返保真）」，明确说明**非**真正双向同步（无 diff/merge，重复导入生成新副本），并加 true-sync「不在本期范围」说明；README 顶部新增显眼「第一步：配置凭据」块（环境变量 / `~/.workbuddy/.env.mubu`，不假设用户已就绪）；新增 `examples/weekly.md` 示例大纲供 `create --md examples/weekly.md` 直接体验。SKILL.md 同步修正「幕布同步」触发词与 `search()` 示例（`[\"results\"]` 解包）。\n- **产品官+排障手 #15 / 错误操作指引**：`_http_request` / `_request` 按 HTTP 状态码给出下一步文案并打到 stderr——401→「登录失效或密码错误，请检查凭据后重试」；403→「权限不足，请确认账号权限」；5xx→「幕布服务暂不可用，请稍后重试」；网络异常→「网络连接失败，请检查网络」。\n- **排障手 #17 / 搜索截断 + 环检测**：`search()` 返回值由 `List` 改为含 `truncated: bool` 的字典结构（`{\"results\", \"truncated\", \"limit\", \"max_depth\"}`），到达 `limit`/`max_depth`/`max_requests` 上限时标记 `truncated=True`（不再静默丢失）；新增已访问 `folder_id` 的 `visited` 集合去重，防止幕布环引用导致无限递归（双保险）。`main()` 与测试同步适配，调用方可知结果不完整。\n- **排障手 #19 / 移除冗余 ensure_login**：`get_list`/`create_folder`/`create_doc`/`get_doc`/`save_doc`/`delete`/`move` 移除冗余 `ensure_login()` 调用，统一走 `auth=True`（`_request` 内 `ensure_valid_token` 已处理），行为不变；`ensure_login` 方法保留作兼容。\n- **排障手+安全官 #16 / 日志规范**：引入 `logging` 取代散落 `print`；`warning`/`error` 分级，CLI 加 `--verbose` 开关控制 `debug`；异常只记 `msg`、不记请求/响应体（`body` 200 字符截断保留），确认明文密码 / token 不进日志。\n- **安全官 #20 / CI hash pinning + Dependabot**：`.github/workflows/test.yml` 将 `actions/checkout@v4` 锁定为完整 commit SHA `11bd71901bbe5b1630ceea73d27597364c9af683`(v4.2.2)、`actions/setup-python@v5` 锁定为 `a26af69be951a213d495a4c3e4e4022e16d87065`(v5)，并加最小 `permissions: contents: read`；新增 `.github/dependabot.yml` 启用 GitHub Actions 与 pip 依赖每周自动更新 PR（SHA 钉死由人工 review 确认）。\n- **测试补充（+11，共 80）**：搜索 `truncated` 标记（limit 命中 / 环检测无无限递归）、错误指引 4 类状态码文案、ensure_login 冗余已移除（2 处不触发 ensure_login 调用）、日志脱敏（明文密码不进日志）+ `--verbose` 启用 debug、`examples/weekly.md` 可解析。全量测试 **80 用例通过**（69 既有 + 11 新增）。\n- 已随 **v1.2.0** 发布（GitHub tag v1.2.0，2026-07-14）。\n\n## v1.2.0（本期发布版本 · 2026-07-14）\n\n本期合并发布 **M8（安全加固）+ M9（P1 第一批：产品文案 / 错误指引 / 日志 / 搜索截断 / CI）+ P2（工程化增强）**，构成自 v1.1.6 以来的完整能力跃升（GitHub tag v1.2.0）。\n\n- **安全（M8）**：凭据文件强制 `0o600`、移除明文 CLI 参数改交互式 `getpass`、本地路径越界防护 `_safe_local_path`、API 域名白名单（`MUBU_BASE_URL` 仅限 mubu.com 家族）防 MITM。\n- **健壮性（M9）**：`search()` 返回含 `truncated: bool` 并加 `visited` 集合防环；标准 `logging` + 敏感信息脱敏 + `--verbose`；401/403/5xx/网络错误打到 stderr 的下一步指引；CI 钉死 `actions/checkout@v4` / `actions/setup-python@v5` 完整 SHA + 新增 Dependabot 周更。\n- **工程化（P2）**：`MubuClient` 复用 `requests.Session` 连接池；依赖拆分为 `requirements.txt`（运行时）+ `requirements-dev.txt`（pytest / responses `<1`）；新增本 Roadmap 段。\n- 测试覆盖：**84 用例全过**（69 P0 基线 + 11 P1 + 4 P2），CI 矩阵 Python 3.9–3.12。\n- 删除操作保留 `--yes` 守卫与「不可逆」stderr 警示（无回收站，软删除待幕布 API 能力确认）。\n\n## M10 (Roadmap 实施) — 整树导出 / 重命名 / OPML·FreeMind（本期）\n\n- **整树导出**：新增 `MubuClient.export_tree(root_folder_id, output_dir, max_depth)` 与 CLI `export-tree` 子命令，递归遍历文件夹树并将每个文档写为 `<name>.md`，子文件夹创建为同级子目录；单点拉取失败不阻断整体遍历（记入 `errors` 统计）。\n- **重命名**：新增 `rename_doc(doc_id, new_name)`（走 `save_doc` 的 `name` 参数，round-trip 保内容）+ CLI `rename --type doc`；新增 `rename_folder(folder_id, new_name)` 走逆向推测端点 `/list/update_folder`（幕布无官方 API 文档，真实环境需验证），对应 CLI `rename --type folder`。\n- **OPML / FreeMind 导出**：新增模块级 `doc_to_opml(doc)` / `doc_to_freeplane(doc)` 与 CLI `opml <doc_id> --format opml|freeplane`，将幕布大纲转为 OPML 2.0 / FreeMind XML，兼容 XMind 等其它大纲工具。\n- **软删除降级说明**：幕布回收站 API 未文档化，本期维持 `delete` 硬删 + `--yes` 守卫 + 「不可逆」stderr 警示，**不实现软删除**（避免引入未经证实的 API 调用）。\n- **测试补充（+9，共 93）**：export_tree 嵌套文件生成 / 单点失败处理、rename_doc 调 save 带 name / rename_folder 走推测端点、doc_to_opml / doc_to_freeplane 合法 XML、_safe_filename 非法字符替换。全量测试 **93 用例通过**。\n- 模块拆分（单文件 → 包）已在 M11 完成。\n\n## M11 (Roadmap · 大重构) — 模块拆分（单文件 → 包，非 breaking）\n\n将单文件 `scripts/mubu_api.py` 按职责拆分为正式 Python 包 `scripts/mubu/`，`mubu_api.py` 降级为向后兼容 shim（重新导出全部公开符号），**对外接口零破坏**。\n\n- **`scripts/mubu/config.py`**：常量 / 配置（`DEFAULT_BASE_URL` / `ENDPOINTS` / 重试与搜索上限）、日志、异常 `MubuError`、路径安全 `_safe_local_path`、Token 文件锁 `_token_file_lock`、域名白名单解析。\n- **`scripts/mubu/convert.py`**：文档结构 ↔ Markdown / OPML / FreeMind 转换（`doc_to_markdown` / `export_markdown` / `markdown_to_doc` / `doc_to_opml` / `doc_to_freeplane`）与展示格式化（`_safe_filename` / `format_list` / `format_search`）。\n- **`scripts/mubu/client.py`**：`MubuClient`（鉴权 / 请求 / 文档·文件夹·搜索·整树导出）。\n- **`scripts/mubu/cli.py`**：命令行入口 `main()` + `_configure_logging()`。\n- **`scripts/mubu/__init__.py`**：包标识（`__version__ = \"1.3.0\"`）。\n- **`scripts/mubu_api.py`（shim）**：`from mubu.* import ...` 重新导出全部公开符号（含 `os` / `sys` / `json` / `Path` 等标准库模块级名称，保持旧调用方与既有测试兼容）；`__main__` 仍调用 `main()`。\n\n**兼容性验证**：\n- `import mubu_api` 及其公开符号（`MubuClient` / `MubuError` / `doc_to_markdown` / ...）全部可用；`python scripts/mubu_api.py <subcommand>` 行为不变。\n- 新增包内导入路径：`from mubu.client import MubuClient`、`from mubu.convert import export_markdown`、`from mubu.config import MubuError` 等均可独立使用。\n- 既有测试适配：因 `os` / `getpass` 为单例模块，`monkeypatch(mubu_api.os / mubu_api.getpass)` 仍生效；函数 / `Path` / `TOKEN_FILE` / `ENV_FILE` 的 patch 目标修正为使用点（`mubu.cli.*`、`mubu.client.TOKEN_FILE`、`mubu.client.ENV_FILE`、`mubu.config.Path`）。全量测试 **93 用例通过**（无用例增减，纯结构重构）。\n\n## M12 (Roadmap · 真机验证 + 端点修正) — rename_folder / delete 实测修正（2026-07-15）\n\n用真实幕布账号逐端点实测，修正此前逆向推测的多处错误写端点：\n\n- **rename_folder 修正（关键）**：原 `/list/update_folder` 实测返回 `code 17 illegal request`。正确端点为 **`POST /list/rename_folder`**，且必须同时携带 `id` 与 `folderId`，**`folderId` 必须填文件夹自身真实 id（不能填根目录魔法值 `\"0\"`，否则 `code 5`）**。已用真实账号验证改名生效（建临时文件夹 → 改名 → 回查确认 → 删除，无残留）。\n- **delete 修正（关键 latent bug）**：原 `delete` 走 `/list/delete` 实测返回 `code 17 illegal request`。正确端点须按类型区分：**`/list/delete_folder`**（文件夹）、**`/list/delete_doc`**（文档），均 `{\"id\": ...}`。已拆为 `delete_folder()` / `delete_doc()`，`delete(item_id, item_type=\"folder\")` 兼容分发；CLI `delete` 新增 `--type doc|folder`（默认 folder）。\n- **move 仍待验证**：`/list/move` 及多种变体（`move_folder` / `move_doc` / 不同字段名 `folderId`/`toFolderId`/`parentId`/`targetId`）实测均返回 `illegal request`，真实端点未知。本期**不改动 move 代码**，标记为「未实测验证待抓包」，待浏览器 DevTools 抓包确认正确端点后再修。\n- **测试适配（+0 用例，仍为 93）**：`test_rename_folder_uses_update_endpoint` 改为断言 `/list/rename_folder` + `folderId`；`test_delete_*` 三个用例 mock URL 由 `/list/delete` 改为 `/list/delete_folder`。全量 **93 用例通过**。\n- 账号清理：验证过程产生的临时文件夹均已通过 `delete_folder` 删除，无残留。\n\n## v1.3.1（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M12（真机验证 + 端点修正）**，构成自 v1.3.0 以来的关键正确性修复（GitHub tag v1.3.1）。\n\n- **rename_folder 真机可用**：端点 `/list/rename_folder`，`folderId` 填自身 id。\n- **delete 真机可用**：拆分 `delete_folder` / `delete_doc`（原 `/list/delete` 为错误端点）；CLI `delete` 加 `--type`。\n- **move 已知未验证**：保留原实现并标注待抓包确认。\n- 测试覆盖：**93 用例全过**，无任何端点相关回归。\n\n## M13 (Roadmap · 真机验证) — get_list 字段名修正 + get_doc 待抓包（2026-07-15）\n\n用真实账号实测 `list` / `search` / `export-tree` 时发现隐藏缺陷并修正：\n\n- **get_list 字段名修正（关键 latent bug）**：代码与测试均假设 `get_list` 返回文档列表字段为 `docs`，但幕布真机返回的是 **`documents`**（带 s）。导致 `list` / `export-tree` / `search` / `format_list` 在真机上**读不到任何文档**（单元测试因 mock 同样用 `docs` 键而假绿，93 用例全过却掩盖了真机失效）。\n  - 修复：`client.export_tree` / `client.search` / `convert.format_list` 统一改为读取 `data.get(\"documents\")`，并保留对旧 `docs` 键的兜底兼容（防御字段名变化）。\n  - 测试：将 `test_documents_key_is_ignored`（错误假设「忽略 documents 键」）改为 `test_documents_key_is_used`（验证 documents 被正确读取 + 兼容 docs），使测试真正反映真机结构。\n  - 真机验证：`search(\"三级等保\")` 命中 1 个文档（修复前 0）、根目录文档总数读到 **80 个**（修复前 0）。\n- **get_doc 端点不可用（待抓包）**：`get_doc`（`POST /doc/get`）在真机对所有文档（含新建文档）均返回 `code 17 illegal request`；额外探测 `/doc/detail` / `/doc/info` / `/doc/open` / `/doc/load` / `/doc/getContent` / `/list/doc` 等 11 个变体**全部 illegal request**。文档正文获取机制与当前端点对不上，需浏览器 DevTools 抓包确认幕布真实的文档加载 API 后才能修复。\n  - **影响**：`export-tree`（导出正文）、`rename_doc`（依赖 get_doc）当前真机不可用；两者均已有 try/except 优雅降级（export_tree 记 errors 继续，不会崩溃）。`list` / `search`（仅列目录）已恢复可用。\n- **move 仍待验证**：同 M12，真实端点未知，保留原实现并标注待抓包。\n- 测试覆盖：**93 用例全过**，docs/documents 修复零回归。\n\n## v1.3.2（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M13（get_list 字段名修正）**，修复 `list` / `search` / `format_list` 在真机上读不到文档的隐藏缺陷（GitHub tag v1.3.2）。\n\n- **list / search 真机可用**：统一读取 `documents` 字段（兼容旧 `docs`）。\n- **export-tree / rename_doc 正文功能暂不可用**：因 `get_doc`（`/doc/get`）真机返回 illegal request，待抓包确认文档加载 API 后修复；列目录能力正常。\n- **move 已知未验证**：保留原实现并标注待抓包确认。\n- 测试覆盖：**93 用例全过**。\n\n## M14 (Roadmap · 真机验证) — get_doc 真实端点与 definition 解析修复（2026-07-15）\n\n用真实幕布账号抓包确认 `get_doc` 文档加载机制并修复（M13 标记为待抓包）：\n\n- get_doc 端点由错误 `/doc/get` 修正为真实 `POST /document/edit/get`。\n- 请求体由 `{\"id\":...}` 修正为 `{\"docId\":..., \"password\":\"\", \"isFromDocDir\":true}`。\n- 响应 `data.definition` 为 JSON 字符串，二次解析为 `{\"nodes\":[...]}`；`get_doc` 现返回 `{\"name\":..., \"nodes\":[...]}`（与旧 `{\"node\":{...}}` 形状不同）。\n- `convert.export_markdown` 支持新 `nodes` 形状（保留旧 `node` 形状向后兼容）；`doc_to_markdown` 支持真实 `finish` 勾选态渲染为 `- [x]` / `- [ ]`。\n- SKILL.md 校正 get_doc 端点（`POST /document/edit/get`）、`Jwt-Token` 请求头、移除不存在的 `refresh_token 30天` 描述。\n- 无破坏性变更；93 用例全过。\n\n## v1.3.3（本期发布版本 · 2026-07-15）\n\n本期合并发布 **M14（get_doc 真实端点与 definition 解析修复）**，修复文档正文获取在真机上失效的问题（GitHub tag v1.3.3）。\n\n- fix: `get_doc` 改用真实端点 `POST /document/edit/get`（原 `/doc/get` 为错误端点，导致取不到文档）\n- fix: 请求体改为 `{\"docId\":..., \"password\":\"\", \"isFromDocDir\":true}`（原 `{\"id\":...}` 错误）\n- fix: 解析响应 `data.definition`（JSON 字符串）为 `{\"nodes\":[...]}`，`get_doc` 现返回 `{\"name\":..., \"nodes\":[...]}`\n- fix: `convert.export_markdown` 支持新 `nodes` 形状（保留旧 `node` 形状向后兼容）；`doc_to_markdown` 支持真实 `finish` 勾选态渲染为 `- [x]`/`- [ ]`\n- docs: SKILL.md 校正 get_doc 端点、`Jwt-Token` 请求头、移除不存在的 `refresh_token 30天` 描述\n- 依赖/破坏性：无破坏性变更\n\n## v1.3.4（本期发布版本 · 2026-07-15）\n\n本期为 OPML/FreeMind 导出兼容真实 nodes 形状修复 + save_doc 客户端契约校验（GitHub tag v1.3.4）。\n\n- fix: `doc_to_opml` / `doc_to_freeplane` 支持真实 `nodes` 形状（双形状兼容旧 `node`）；OPML/FreeMind 导出不再产出空大纲\n- fix: `cli` 的 `create` / `save` 回写 content 使用 `{\"nodes\":[node]}` definition 字符串，与 `get_doc` 同构\n- verify: `save_doc` 真机 round-trip 被幕布服务端签名/反爬校验拒绝（`illegal request`, code 17），与请求体形状无关；客户端契约（content = definition JSON 字符串 `json.dumps({\"nodes\":[...]}, ensure_ascii=False)`）正确，**无需改代码**，docstring 已标注此已知限制\n- test: 96 passed（修正 1 个过时 rename 夹具 + 新增 OPML/FreeMind nodes 形状测试）\n- 已知限制: `save_doc` / `rename_doc` 真机写回需服务端对该客户端放行（反爬签名），目前端到端落库不可达\n- 依赖/破坏性：无破坏性变更\n\n## M15 (Roadmap · 软删除/锁文件) — 本地回收站软删除 + pip-tools 依赖锁文件（2026-07-15）\n\n- feat: `delete` 改为本地软删除——仅将文档标记写入 `~/.workbuddy/.mubu_trash.json`，**零网络调用**，云端副本保留；新增 `restore <id>`（仅移除本地标记、零风险）、`purge <id> --yes`（真实服务端删除 + 移除标记，不可逆）、`trash`（列出回收站项）；`list` / `search` 默认过滤已软删项，新增 `--include-trash` 可包含。\n- feat: 引入 pip-tools 锁文件——新增 `requirements.in`（requests）/ `requirements-dev.in`（pytest, responses），`requirements.txt` / `requirements-dev.txt` 重写为 `pip-compile --generate-hashes` 精确锁定 + 哈希；CI 新增锁文件漂移校验步骤。\n- docs: README / SKILL 版本号 → 1.3.5、新增软删除 / 回收站章节、安装说明改锁定文件、`delete` 不再标记为「不可逆」（仅 `purge --yes` 为不可逆）。\n- test: 100 passed（迁移 `test_delete_with_yes_calls_api` → `test_delete_with_yes_marks_trash`（0 网络 + 校验回收站写入），新增 `TestTrash`：restore / purge --yes 调 API / list 过滤 / search 过滤 + `--include-trash` 反例）。\n- 已知限制: 锁文件在本机 Python 3.13 解释器上生成（pip-tools 7.5.3 已移除 `--python-version` / `--check` flag）；锁定版本 requests 2.34.2 / pytest 9.1.1 / responses 0.26.2 及其传递依赖均支持 CI 的 3.9–3.12 矩阵，非阻塞。\n- 依赖/破坏性：无破坏性变更（注意 `delete` 语义由「永久删除」变为「本地软删除」，属行为变更但保留 `--yes` 守卫）。\n\n## v1.3.5（本期发布版本 · 2026-07-15）\n\n本期为本地回收站软删除（restore / purge / trash）+ pip-tools 依赖锁文件（GitHub tag v1.3.5）。详见上方 M15。\n\n- 依赖/破坏性：无破坏性变更（`delete` 语义由「永久删除」变为「本地软删除」，保留 `--yes` 守卫；`purge --yes` 为唯一不可逆操作）\n\n## M16 (安全姿态补丁) — client.delete 对齐软删除 + 文档修正（2026-07-15）\n\nClawHub SkillSpector 复审（GO；1 项 High 降为 Medium）3 项真实发现的修复：\n\n- fix: `client.delete()` 现路由到 `trash_item`（本地软删除，零网络），与 v1.3.5 CLI 语义及 `client.py` 设计注释（\"delete = 软删除（仅本地标记，云端仍在）\"）一致；真实硬删仅保留 `purge_item` / `delete_doc` / `delete_folder`（由 `purge --yes` 调用）。此前 `client.delete()` 实际走 `delete_folder`/`delete_doc` 远程硬删，与注释及 CLI 语义矛盾，程序化调用方会触发不可逆云端删除。\n- docs: README `delete` 命令描述由「不可逆」修正为「软删除（本地回收站）」；`purge --yes` 才是不可逆。同步修正 P2 里程碑表对 `delete` 的「不可逆」表述。\n- test: 100 passed（无回归）。`client.delete()` 现不触发任何网络调用，既有 `delete --yes` 守卫 / 回收站写入 / `purge` 行为测试全部保持。\n- 依赖/破坏性：无破坏性变更（`delete()` 由「远程硬删」收敛为「本地软删」，与文档语义一致）。\n\n## v1.3.6（本期发布版本 · 2026-07-15）\n\n本期为 SkillSpector 复审安全姿态补丁（GitHub tag v1.3.6）。详见上方 M16。\n\n- fix: `client.delete()` 对齐 v1.3.5 CLI 软删除语义（本地标记、零网络）；真实硬删仅 `purge --yes`。\n- docs: README L261 `delete` 由「不可逆」改为「软删除（本地回收站）」；Roadmap 软删除 / 依赖锁文件项标记为已在 M15/v1.3.5 完成。\n- test: 100 passed（无回归）。\n- 依赖/破坏性：无破坏性变更。\n\n## Roadmap（向前展望，尚未实现）\n\n以下为已识别、尚未排入实施的能力增强与重构方向，供后续迭代参考：\n\n- **模块拆分（排障手 #18）**：✅ 已在 M11 完成——`scripts/mubu_api.py` 拆分为 `scripts/mubu/`（config / convert / client / cli），`mubu_api.py` 保留为向后兼容 shim，93 用例通过。\n- **文件夹重命名 / 移动增强（产品官 #14）**：✅ 重命名已在 M10 完成（`rename_doc`/`rename_folder`，M12/M14 真机验证）；⚠️ 移动（`move`）端点仍待真机抓包（真机 `illegal request`），保持未实现。\n- **整树递归导出（产品官 #14）**：✅ 已在 M10 完成——`export_tree` 递归导出整个文件夹树为嵌套 Markdown（`export-tree` 子命令）。\n- **软删除 / 回收站（产品官 #14 P2）**：✅ 已在 M15/v1.3.5 完成——`delete` 改为本地软删除（仅标记进 `~/.workbuddy/.mubu_trash.json`，零网络），`restore` 恢复、`purge <id> --yes` 真实硬删、`trash` 列出；`list`/`search` 默认过滤已软删项。\n- **互操作导出（产品官 #25）**：✅ 已在 M10 完成——OPML 2.0 / FreeMind（`doc_to_opml`/`doc_to_freeplane` + `opml` 子命令），兼容 XMind 等其它大纲工具。\n- **依赖锁文件（排障手 #21）**：✅ 已在 M15/v1.3.5 完成——引入 `pip-tools`，`requirements.in`/`requirements-dev.in` 由 `pip-compile --generate-hashes` 生成精确版本 + 哈希锁文件，CI 校验漂移。\n\n## M17 (v1.3.7) — 文档/配置诚实度 + 触发词收敛 + dependabot 治理 + purge 安全加固（2026-08-04，仅本地提交）\n\n本期聚焦\"降低偶发误激活 + 文档/代码诚实度 + 一处本地安全加固\"，**无功能性回归**（purge 为本地安全加固，其余为文档/配置；未 push/tag/release）。\n\n- **Roadmap ✅ 同步**：原 Roadmap 区块的\"文件夹重命名/移动增强\"、\"整树递归导出\"、\"互操作导出（OPML/FreeMind）\"三项实为 M10 已完成能力，补充 ✅ 标记，消除与 CHANGELOG 历史段的矛盾（移动子项仍标 ⚠️ 待抓包）。\n- **README 诚实度**：命令参考 `save` / `rename` / `move` 行与「已知限制」处加注——`save_doc`/`rename_doc` 在真机被服务端反爬签名拒绝（`code:17 / illegal request`，round-trip 写回不可达），当前 skill 聚焦读取/导入/导出；`move` 真机实测 `illegal request`，当前不可用。特性亮点同步收敛写回类操作的措辞。\n- **触发词收敛**：SKILL.md frontmatter `description` 与 README「Agent 触发词」将宽泛的\"幕布大纲导入导出\"收窄为动作意图（\"将幕布大纲导入 Obsidian\"、\"把 Markdown 同步到幕布\"、\"幕布笔记导出\"），并**统一两处表述**，保留核心触发词（幕布/mubu），降低 ClawHub 扫描偶发激活风险。\n- **dependabot 治理**：`.github/dependabot.yml` 的 pip 生态设 `open-pull-requests-limit: 0`，**阻止 dependabot 直接改 `requirements.txt`/`requirements-dev.txt` 锁文件**（会丢失哈希、与 pip-tools 范式冲突，2026-07-28 曾开 PR 改锁文件）；保留 `github-actions` 生态不变。\n- **purge_item 安全修复（必做）**：原实现在回收站记录缺失时**默认按 folder 硬删**，可能把 doc 当 folder 误删（`/list/delete_folder` 端点与文档 id 不匹配）。改为：优先从回收站记录读 `item_type`；缺失时回退到调用方显式 `--type`，**二者皆无则抛出明确错误要求显式指定**，杜绝误删。CLI `purge` 新增可选 `--type doc|folder`（仅回收站记录缺失时必填）。零网络风险本地加固。\n- **move / save_doc / rename_doc 注释**：代码注释补\"真机不可用/真机受限\"（`illegal request` / 反爬签名），与 CHANGELOG 历史段一致，修正此前偏乐观的\"可用\"表述。\n\n## v1.3.7（本期发布版本 · 2026-08-04，仅本地提交，未发版）\n\n本期为文档/配置诚实度 + 触发词收敛 + dependabot 治理 + purge 安全加固（GitHub tag **待发版**，本次未 push/tag/release）。详见上方 M17。\n\n- fix: `purge_item` 回收站记录缺失时不再默认 folder，缺失且未显式 `--type` 则报错，杜绝 doc 误删（CLI `purge` 新增可选 `--type`）\n- docs: Roadmap ✅ 同步、README 写回类操作真机限制标注、触发词收敛（SKILL.md + README 统一）、dependabot 治理\n- test: 新增 `test_purge_without_trash_requires_type` / `test_purge_explicit_type_doc`（锁定 purge 安全行为）\n- 依赖/破坏性：无破坏性变更（purge CLI 新增可选 `--type`，向后兼容；其余均为文档/配置）\n\n## M18 (v1.3.8) — SKILL.md 过期模块引用修正（2026-08-04，发布）\n\n本期为纯文档一致性修正，无业务代码改动（GitHub tag v1.3.8）。\n\n- **docs: SKILL.md 过期引用修正**：`scripts/mubu_api.py` 早在 M11 已重构为正式包 `scripts/mubu/`，`mubu_api.py` 降级为仅重新导出的向后兼容 shim。SKILL.md 第 58/64 行仍将 `MubuClient` 描述为位于 `scripts/mubu_api.py` 并以 `from scripts.mubu_api import MubuClient` 引入，与真实代码及 `cli.py` 不一致。修正为：类位于 `scripts/mubu/client.py`，import 路径 `from mubu.client import MubuClient`；并注明 `mubu_api.py` 仅为重新导出 shim，不再建议直接使用。\n- **一致性核对**：`cli.py` 与 `client.py` 的 import 路径本就为 `mubu.client`，本次仅让文档与其对齐，无导入逻辑变化。\n- 依赖/破坏性：无业务代码改动，纯文档一致性；测试 **102 passed（0 失败，无回归）**。\n\n## v1.3.8（本期发布版本 · 2026-08-04）\n\n本期为 SKILL.md 过期模块引用修正（GitHub tag v1.3.8）。详见上方 M18。\n\n- docs: SKILL.md 将 `MubuClient` 位置由 `scripts/mubu_api.py` 修正为 `scripts/mubu/client.py`，import 语句由 `from scripts.mubu_api import MubuClient` 修正为 `from mubu.client import MubuClient`，与 `cli.py` 一致；标注 `mubu_api.py` 仅为向后兼容 shim\n- test: 102 passed（0 失败，无用例增减，纯文档修正）\n- 依赖/破坏性：无业务代码改动，纯文档一致性\n\n## M19 (v1.3.9) — move/save 真实端点重构（2026-08-04，发布）\n\n本期为两个真机 `code:17 / illegal request` 写回类端点的真实端点重构，使 `move` 与 `save_doc`/`rename_doc` 在真机恢复可用（此前被服务端反爬签名拒绝）。\n\n- **fix(move): 真实移动端点**：`move` 由错误的 `/list/move` 改为真实抓包的 `/list/custom/drag`（旧端点真机返回 `code:17`）；对齐浏览器级请求头 `x-session-id = uuid:epoch`、`x-reg-entrance = https://mubu.com/app`，与幕布 Web 客户端一致，消除反爬签名拒绝。\n- **fix(save): 真实保存端点**：`save_doc` 由错误的 `/doc/save` 改为真实抓包的 `/v3/api/colla/events`（旧端点真机返回 `code:17`）；新增 `build_update_event`，构造根节点 update 事件 `{name:\"update\", updated:[{updated:root, original:root}]}`，其中 `root = {id:doc_id, children:nodes, modified:ts}`。\n- **fix(save): 每文档 `x-reg-entrance`**：保存请求头 `x-reg-entrance` 改为每文档 `https://mubu.com/app/edit/home/<doc_id>`（取代统一的 app 首页入口）。\n- **fix(save): `member_id` 来源**：`member_id` 改由环境变量 `MUBU_MEMBER_ID`（位于 `~/.workbuddy/.env.mubu`）读取，token 缓存中缺失时回退到缓存值；不再依赖不可达的发现接口。\n- **fix(rename): 独立重命名端点**：`rename_doc` 改用独立的 `/list/rename_doc` 端点（此前与保存共用错误端点）。\n- **⚠️ KNOWN LIMITATION（save_doc 前置条件）**：个人文档的 `memberId` 无法通过任何 API 发现，因此 `save_doc` 要求 `~/.workbuddy/.env.mubu` 中设置 `MUBU_MEMBER_ID`（或 token 缓存中已有 `member_id`）；二者皆缺时保存会失败。\n- test: **112 passed（0 失败，较 v1.3.8 的 109 新增 3 例，无回归）**。\n\n## v1.3.9（本期发布版本 · 2026-08-04）\n\n本期为 move/save 真实端点重构（GitHub tag v1.3.9）。详见上方 M19。\n\n- fix(move): `move` 改用真实端点 `/list/custom/drag`（原为 `/list/move`，真机 `code:17`）；对齐浏览器级 `x-session-id`(uuid:epoch) / `x-reg-entrance`(https://mubu.com/app) 请求头\n- fix(save): `save_doc` 改用真实端点 `/v3/api/colla/events`（原为 `/doc/save`，`code:17`）；新增 `build_update_event`（根节点 update 事件，root=`{id, children, modified}`）\n- fix(save): 每文档 `x-reg-entrance = https://mubu.com/app/edit/home/<doc_id>`；`member_id` 由 `MUBU_MEMBER_ID` 环境变量（`~/.workbuddy/.env.mubu`）+ token 缓存回退读取\n- fix(rename): `rename_doc` 改用独立端点 `/list/rename_doc`\n- ⚠️ 已知限制：`memberId` 无法经 API 发现，个人文档 `save_doc` 需设置 `MUBU_MEMBER_ID`（或缓存 `member_id`），否则保存失败\n- test: 112 passed（0 失败，较 109 新增 3 例，无回归）\n\n## M20 (v1.3.10) — 文档刷新（2026-08-04，发布）\n\n纯文档同步，无代码/行为变更。修正 v1.3.9 之前遗留、与已发布 CHANGELOG M19 自相矛盾的「写回类操作不可用」错误声明，并补充 `MUBU_MEMBER_ID` 配置说明。\n\n- **doc(SKILL.md / README.md): 移除过时「不可用」声明**：更新 `save`（`/colla/events`）、`move`（`/list/custom/drag`）、doc `rename`（`/list/rename_doc`）均为 v1.3.9 已真机验证可用；删除旧的 `code:17 / illegal request 当前不可用` 警示。\n- **doc: 补充 `MUBU_MEMBER_ID` 配置**：在两文档的凭据配置章节新增可选环境变量 `MUBU_MEMBER_ID`（幕布 colla 成员 ID，仅 `save` 写回需要，登录后自动缓存到 `~/.mubu_token`，一般无需手动设置）。\n- **doc: 测试计数更新**：README 测试用例数由 100 更正为 112（与 v1.3.9 实测一致）。\n\n## v1.3.10（本期发布版本 · 2026-08-04）\n\n本期为文档刷新（GitHub tag v1.3.10）。详见上方 M20。\n\n- doc: 移除 SKILL.md / README.md 中遗留的「save/move/rename 真机不可用」错误声明（与已发布 M19 矛盾）\n- doc: 补充 `MUBU_MEMBER_ID` 配置说明（`save` 写回前置条件）\n- doc: README 测试计数 100 → 112\n\n## M21 (v1.3.11) — 文档补充贡献指引（2026-08-05，发布）\n\n纯文档同步，无代码/行为变更。在 README 新增「🤝 贡献」章节，欢迎社区提 Bug、建议与 PR。\n\n- **doc(README.md): 新增 `## 🤝 贡献` 章节**：引导用户通过 GitHub Issues 提 Bug / 建议（含 enhancement 标签说明），并给出 Fork → 跑通 112 passed → 向 `main` 提 PR 的流程；点明本 Skill 为非官方逆向项目，欢迎同步新端点 / 新返回码。同步在文档目录加入 `#贡献` 锚点。\n\n## v1.3.11（本期发布版本 · 2026-08-05）\n\n本期为文档补充（GitHub tag v1.3.11）。详见上方 M21。\n\n- doc: README 新增贡献指引（Issues / PR 流程）\n- 无代码/行为变更\n\nFile v1.3.15:CONTRIBUTING.md\n\n# Contributing\n\n欢迎贡献！\n\n## 如何贡献\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/amazing-feature`)\n3. 提交更改 (`git commit -m 'Add amazing feature'`)\n4. 推送到分支 (`git push origin feature/amazing-feature`)\n5. 提交 Pull Request\n\n## 开发环境\n\n```bash\n# 克隆仓库\ngit clone https://github.com/liuboacean/mubu-integration.git\ncd mubu-integration\n\n# 安装依赖（运行时 + 测试；开发依赖在 requirements-dev.txt）\npip install -r requirements.txt -r requirements-dev.txt\n\n# 运行全部测试（共 115 个 pytest 用例）\nPYTHONPATH=scripts python -m pytest -v\n\n# 脚本运行入口\npython3 scripts/mubu_api.py --help\n```\n\n## 行为准则\n\n- 尊重所有贡献者\n- 建设性讨论\n- 专注于对项目最有利的事情\n\nFile v1.3.15:docs/hellogithub-pitch.md\n\n# mubu-integration —— 把幕布变成可被命令行和 AI 操控的 Markdown 大纲\n\n## 一句话\nmubu-integration 是一个非官方但**真机验证可用**的幕布（Mubu）集成：用一条命令把幕布大纲导出为 Markdown、再导回去，还能让 AI Agent 直接读 / 写你的幕布知识库。\n\n## 亮点\n- **零配置读取 / 备份 / 导出**：只需手机号 + 密码，无需任何逆向技巧，`get` / `create` / `export-tree` / `list` / `search` 开箱即用。\n- **Markdown 无损往返**：`create --md` 导入、`get --export markdown` 导出，`diff` 无差异（标题、`[x]` 勾选、`> 备注` 全部保真）。\n- **可被 AI Agent 调用**：作为 Agent Skill，让 AI 把幕布当长期结构化记忆来读写。\n- **整树导出 / OPML / FreeMind**：批量备份整个文件夹树，兼容 XMind 等大纲工具。\n- **CI 113 测试 × 4 Python 版本常绿**，在非官方逆向项目里少见的健康度。\n\n## 安装\n```bash\nnpx skills add liuboacean/mubu-integration\npip install -r requirements.txt\n```\n\n## 3 条命令体验\n```bash\nexport MUBU_PHONE=你的手机号\nexport MUBU_PASSWORD=你的密码\npython3 scripts/mubu_api.py create \"周会\" --md examples/weekly.md   # Markdown → 幕布\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # 幕布 → Markdown\ndiff examples/weekly.md out.md                                      # 无输出 = 字节级一致\n```\n\n## 链接\n- GitHub：https://github.com/liuboacean/mubu-integration\n- 非官方集成，遵循幕布服务端限制；欢迎提 Issue / PR 同步新端点。\n\nFile v1.3.15:docs/positioning.md\n\n# 定位纠偏建议（给主理人 / QA）\n\n## 核心判断\n当前 README 把 `save` 写回放在 Use Case ① 首位，但 `save` 需 `MUBU_MEMBER_ID`（服务端限制、采用率最低），与\"幕布用户最想要的零配置备份 / 读取\"错位。应把**零配置读取 / 备份 / 导出**作为首要叙事。\n\n## 建议的 Use Case 新排序\n1. **零配置备份与读取**：`export-tree` 整树导出、`get` 导出 Markdown、`list` / `search` 浏览——只需手机号 + 密码，无需 memberId。\n2. **进 Obsidian / 给 AI 当长期记忆**：Markdown 往返保真，知识库可进 Obsidian，AI 可直接读。\n3. **写回（power-user 选装）**：`save` 写回——明确前置 `MUBU_MEMBER_ID` 说明，放在靠后位置并加 ⚠️。\n\n## 一句话 hero 标语\n\"零配置把幕布变成可被命令行与 AI 操控的 Markdown 大纲——备份、检索、写回，一条命令搞定。\"\n\n## 落地建议\n- README Use Cases 三段按上述顺序重排；首段前置\"只需手机号密码，无需 memberId\"。\n- Reliability 章节已含 Auto-refresh auth，可在首屏 hero 下方补一句\"读取 / 备份 / 导出零配置\"。\n- `save` 写回相关示例保留，但统一加 memberId 前置说明（已在 Troubleshooting 表覆盖）。\n\nFile v1.3.15:docs/v2ex-post.md\n\n# 分享：我写了个把幕布变成 Markdown 的小工具 mubu-integration\n\n幕布的大纲很好用，但一直缺好用的命令行导出 / 备份，也没法让 AI Agent 直接读写。我逆向了幕布 Web 端用的一套接口，做了个非官方集成 mubu-integration：\n\n- 一条命令把整本幕布导出成 Markdown（`export-tree`），标题 / `[x]` 勾选 / `> 备注` 往返保真；\n- 反向 `create --md` 把 Markdown 导回幕布；\n- 还能当 AI Agent 的 Skill，让 AI 直接读 / 写你的幕布。\n\n诚实说：非官方逆向，读 / 导出零配置可用；但 `save` 写回受幕布 colla 成员 ID 限制（服务端不返回，需手动配 `MUBU_MEMBER_ID`）。CI 113 测试常绿。\n\n```bash\nnpx skills add liuboacean/mubu-integration\npython3 scripts/mubu_api.py export-tree --folder <id> --output ./backup\n```\n\nGitHub：https://github.com/liuboacean/mubu-integration\n\n想问下大家：你们备份 / 导出幕布都用啥方案？有没有踩过接口变动的坑？欢迎交流。\n\nFile v1.3.15:docs/xiaoshuopai-tutorial.md\n\n# 教程：用 mubu-integration 把幕布变成你的本地 Markdown 知识库\n\n## ① 为什么要把幕布变成 Markdown\n幕布适合梳理大纲，但搜索弱、难备份、不易进 Obsidian 等知识管理工具，更没法让 AI 直接读。mubu-integration 解决这三件事：一键全量备份、进 Obsidian、给 AI 当长期记忆。\n\n## ② 5 分钟配置凭据\n密码不进命令行。用环境变量或仓库外文件：\n```bash\nexport MUBU_PHONE=你的手机号\nexport MUBU_PASSWORD=你的密码\n# 可选：仅 save 写回需要\n# export MUBU_MEMBER_ID=你的幕布 colla 成员 ID\n```\n或写入 `~/.workbuddy/.env.mubu`（脚本自动加载，权限 0o600）。\n\n## ③ 一键把整本幕布导出为本地备份\n```bash\npython3 scripts/mubu_api.py export-tree --folder <根文件夹id> --output ./backup\n```\n每个文档写成 `<名称>.md`，子文件夹生成同级目录，递归整树。单点失败不中断，记入统计。\n\n## ④ 在 Obsidian 里管理这些大纲\n把 `./backup` 作为 Obsidian 仓库的笔记目录，大纲即普通 Markdown，双向链接、全文搜索、图谱视图都能用。\n\n## ⑤ 让 AI Agent 直接读 / 写你的幕布\n作为 Agent Skill 接入后，让 AI「把这份大纲导出」「读我的周会文档」即可。注意：`save` 写回需要 `MUBU_MEMBER_ID`（幕布服务端限制，任何 API 都不返回，需手动设置），缺失时 `save` 会明确报错，不影响读取 / 导出。\n\n## ⑥ 常见问题\n- 非官方逆向，服务端可能调整接口；某端点失效以抓包为准并反馈 Issue。\n- `save` 报 code 17 / memberId → 设置 `MUBU_MEMBER_ID`。\n- 更多见项目 README 的 Troubleshooting。\n\nFile v1.3.15:examples/weekly.md\n\n# 产品周会\n- 上周进展\n  - [x] 上线新版本\n  - [ ] 修复登录 bug\n  - [ ] 补齐集成测试\n- 本周计划\n  - 性能优化\n  - 文档与示例整理\n> 备注：记得把纪要同步给设计团队\n\nFile v1.3.15:README.zh-CN.md\n\n[English](README.md) | [中文](README.zh-CN.md)\n\n![mubu-integration](assets/social-preview.png)\n\n# mubu-integration\n\n> 把幕布变成 Markdown 原生、可被 AI Agent 操控的大纲工具。\n\n[![GitHub stars](https://img.shields.io/github/stars/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/network/members)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://img.shields.io/github/actions/workflow/status/liuboacean/mubu-integration/test.yml?branch=main)](https://github.com/liuboacean/mubu-integration/actions/workflows/test.yml)\n\n通过命令行管理你的幕布（Mubu）大纲 —— **同时作为一个 AI Agent Skill** —— 支持 Markdown 导入/导出的无损往返保真。\n\n---\n\n## ✨ 三条命令上手（magic moment）\n\n```bash\npython3 scripts/mubu_api.py create --md weekly.md                    # Markdown 大纲 → 幕布\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # 幕布 → Markdown\ndiff weekly.md out.md                                              # 无输出 = 一字不差\n```\n\n![demo](assets/demo.gif)\n\n---\n\n## 🆚 为什么选 mubu-integration？\n\n| 能力 | 手动复制 | 现有导出插件脚本 | **mubu-integration** |\n| :--- | :---: | :---: | :---: |\n| 幕布 → Markdown | ✅ | ⚠️ 部分 | ✅ |\n| Markdown → 幕布 | ❌ | ❌ | ✅ **（唯一）** |\n| 往返保真（diff 无差异） | ❌ | ❌ | ✅ **（唯一）** |\n| 整树批量 / OPML / FreeMind | ❌ | ⚠️ 部分 | ✅ |\n| 可被 AI Agent 调用 | ❌ | ❌ | ✅ **（唯一）** |\n| 命令行可脚本化 | ❌ | ⚠️ | ✅ |\n\n---\n\n## 💡 使用场景\n\n**① 让 AI Agent 直接读写你的幕布** —— 把幕布变成 Agent 的长期结构化记忆。\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > memory.md   # Agent 拉取最新大纲\n# ... Agent 编辑 memory.md ...\npython3 scripts/mubu_api.py save <doc-id> --md memory.md                 # 把更新后的大纲写回幕布\n```\n\n**② Obsidian ↔ 幕布 双向大纲** —— 以纯 Markdown 让你的知识库和大纲工具保持同步。\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > vault/notes/mubu.md   # 幕布 → Obsidian\npython3 scripts/mubu_api.py create --md vault/notes/mubu.md --folder <folder-id>   # Obsidian → 幕布\n```\n\n**③ 周会纪要自动归档** —— 一步把 `examples/weekly.md` 推入幕布。\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --folder <folder-id> --md examples/weekly.md\n```\n\n---\n\n## 🚀 30 秒快速体验\n\n1. 配置幕布凭据（手机号 + 密码）。凭据**不会**作为命令行参数传递 —— 用环境变量或本地文件：\n\n   ```bash\n   export MUBU_PHONE=\"你的手机号\"\n   export MUBU_PASSWORD=\"你的密码\"\n   ```\n\n   …或写入仓库外的 `~/.workbuddy/.env.mubu`（环境变量优先；文件权限自动 `0o600`）：\n\n   ```ini\n   MUBU_PHONE=你的手机号\n   MUBU_PASSWORD=你的密码\n   ```\n\n2. 使用自带的示例大纲（`examples/weekly.md`）：\n\n   ```markdown\n   # 产品周会\n   - 上周进展\n     - [x] 上线新版本\n     - [ ] 修复登录 bug\n   - 本周计划\n     - 性能优化\n   > 备注：记得同步给设计团队\n   ```\n\n3. 导入后再导出来 —— 标题层级、`[x]` 勾选、`> note` 备注都会原样还原：\n\n   ```bash\n   python3 scripts/mubu_api.py create \"产品周会\" --folder <folder_id> --md examples/weekly.md\n   python3 scripts/mubu_api.py get <doc_id> --export markdown\n   ```\n\n---\n\n## 📦 安装\n\n```bash\nnpx skills add liuboacean/mubu-integration\n```\n\n这会为你的 Agent 安装该 Skill。它是一个 Python 包 —— 你还需要 **Python 3.9+** 及运行时依赖：\n\n```bash\npip install -r requirements.txt\n```\n\n开发与测试依赖在 `requirements-dev.txt`（`pip install -r requirements-dev.txt`）。\n\n---\n\n## 🛡️ 可靠性\n\nmubu-integration 调用的是**与幕布 Web 端相同的 HTTPS 接口** —— 不爬取、不操控浏览器。\n\n- ✅ **已对线上环境真机验证** —— 最近一次真机校验为 **2026-08-05**，针对 mubu.com 生产环境。`move`、`save_doc`、`rename`、OPML、FreeMind、`export-tree` 均已确认可用。\n- ✅ **115 用例 × 4 个 Python 版本，每次全绿** —— GitHub Actions 矩阵在每次 push 和 PR 时于 Python 3.9 / 3.10 / 3.11 / 3.12 上运行。\n- ✅ **自动刷新鉴权** —— Token 过期后用缓存凭据（环境变量 / `~/.workbuddy/.env.mubu`）自动重新登录，初始配置后无需再次手动输入。\n- ✅ **依赖全量锁定** —— `requirements.txt` 锁定精确版本；Dependabot 自动跟进更新。\n- ✅ **数据边界清晰** —— 工具仅以你的凭据访问你自己账号下的数据。凭据仅本地存储于 `~/.mubu_token`，权限 `0o600`（仅本人可读写）。\n\n<details>\n<summary>技术细节</summary>\n\nmubu-integration 是一个**非官方**集成，使用的接口与幕布 Web 端一致。所有请求发往 `https://api2.mubu.com/v3/api`；鉴权 JWT 通过请求头 `jwt-token` 传递。`access_token` 约 2 小时过期并自动刷新（仅重试 1 次，避免锁定死循环）；`403` 及其它错误不触发重新登录。\n\n**已知限制：** 大纲折叠状态 `expand`、有序列表 `1.`、图片 / 附件节点不在当前 Markdown 往返保真范围。往返保真但非实时双向同步（无 diff/merge），重复导入会生成新副本。\n\n</details>\n\n---\n\n## ⚙️ 工作原理\n\n```mermaid\nflowchart LR\n    U[用户 / AI Agent] -->|CLI 子命令| CLI[scripts/mubu_api.py]\n    CLI --> Client[MubuClient]\n    Client -->|HTTPS + jwt-token| API[(api2.mubu.com/v3/api)]\n    API --> Client\n    Client -->|Markdown 导入/导出（往返保真）| MD[(本地 .md 大纲)]\n    Client -->|Token 缓存 0o600| Cache[(本地凭据)]\n```\n\nMarkdown 大纲 ⇄ 幕布文档（往返保真）示意：\n\n```mermaid\nflowchart LR\n    subgraph MD[Markdown 大纲]\n        M[\"# 标题<br>- 要点<br>- [x] 勾选<br>&gt; note 备注\"]\n    end\n    subgraph MU[幕布文档]\n        B[\"节点 + 层级 + 勾选 + 备注\"]\n    end\n    M -->|import: create --md| B\n    B -->|export: get --export markdown| M\n```\n\n**项目结构**（模块化 Python 包；`scripts/mubu_api.py` 为向后兼容 shim）：\n\n```\nscripts/\n├── mubu_api.py        # 向后兼容 shim（重新导出 mubu 包）\n└── mubu/              # 模块化包（v1.3.0+）\n    ├── __init__.py    # 包标识（__version__）\n    ├── config.py      # 常量 / 配置 / 日志 / MubuError / 路径安全 / Token 锁\n    ├── convert.py     # 文档 ↔ Markdown / OPML / FreeMind 转换 + 展示格式化\n    ├── client.py      # MubuClient（鉴权 / 请求 / 文档·文件夹·搜索·整树导出）\n    └── cli.py         # 命令行入口 main() + 日志配置\n```\n\n---\n\n## 📚 命令行参考\n\n<details>\n<summary>展开全部 20+ 命令</summary>\n\n```bash\n# 登录（首次使用需先配置凭据）\npython3 scripts/mubu_api.py login\n\n# 获取根目录列表\npython3 scripts/mubu_api.py list\n\n# 获取子文件夹内容\npython3 scripts/mubu_api.py list --folder <folder_id>\n\n# 创建文件夹\npython3 scripts/mubu_api.py mkdir \"新文件夹\"\n\n# 创建文档\npython3 scripts/mubu_api.py create \"新文档\" --folder <folder_id>\n\n# 从 Markdown 文件导入创建文档\npython3 scripts/mubu_api.py create \"新文档\" --folder <folder_id> --md examples/weekly.md\n\n# 获取文档内容（JSON）\npython3 scripts/mubu_api.py get <doc_id>\n\n# 导出为 Markdown（往返保真，非占位）\npython3 scripts/mubu_api.py get <doc_id> --export markdown\n\n# 保存文档\npython3 scripts/mubu_api.py save <doc_id> --content \"内容\"\npython3 scripts/mubu_api.py save <doc_id> --file content.md\n\n# 从 Markdown 文件导入更新文档\npython3 scripts/mubu_api.py save <doc_id> --md outline.md\n\n# 移动文档到其他文件夹\npython3 scripts/mubu_api.py move <doc_id> --target <folder_id>\n\n# 删除（⚠️ 不可逆，执行前务必确认目标 ID；必须显式 --yes；--type 默认 folder）\npython3 scripts/mubu_api.py delete <id> --type folder --yes\npython3 scripts/mubu_api.py delete <doc_id> --type doc --yes\n\n# 按名称本地搜索文档/文件夹（递归遍历所有子文件夹，大小写不敏感）\npython3 scripts/mubu_api.py search \"项目\"\npython3 scripts/mubu_api.py search \"项目\" --json\n\n# 递归导出整个文件夹树为嵌套 Markdown 文件（默认当前目录，--output 指定输出根）\npython3 scripts/mubu_api.py export-tree --folder <root_folder_id> --output ./backup\n\n# 重命名文档（走 save_doc 的 name 参数，round-trip 保内容）\npython3 scripts/mubu_api.py rename <doc_id> --name \"新标题\" --type doc\n\n# 重命名文件夹（已真机验证端点 /list/rename_folder，folderId 填自身 id）\npython3 scripts/mubu_api.py rename <folder_id> --name \"新文件夹名\" --type folder\n\n# 导出为 OPML 2.0 / FreeMind（兼容 XMind 等其它大纲工具）\npython3 scripts/mubu_api.py opml <doc_id> --format opml\npython3 scripts/mubu_api.py opml <doc_id> --format freeplane\n```\n\n</details>\n\n---\n\n## 🤖 Agent 触发词\n\n> 幕布、mubu、幕布大纲导入导出\n\n当对话中出现以上关键词时，Skill 可被自动触发。\n\n---\n\n## 🧪 测试与 CI\n\n本地运行全部测试（共 **115** 个 pytest 用例）：\n\n```bash\nPYTHONPATH=scripts python -m pytest -v\n```\n\n持续集成：在 push 到 `main` 分支或提交 Pull Request 时，GitHub Actions 会于 **Python 3.9 / 3.10 / 3.11 / 3.12** 矩阵中自动运行测试。115 个用例在四个 Python 版本上均真实执行（非假成功）。\n\n---\n\n## ❓ 常见问题 FAQ\n\n**Q：需要有幕布账号吗？**\nA：需要。使用你的手机号 + 密码登录（`MUBU_PHONE` / `MUBU_PASSWORD`）。这是幕布官方账号，本 Skill 不提供账号。\n\n**Q：这是非官方集成，我的凭据安全吗？**\nA：凭据仅本地存储——登录 Token 写入本地文件且权限为 `0o600`（仅本人可读写），不依赖任何第三方服务。环境变量优先于 `.env.mubu` 文件加载。详见[可靠性](#-可靠性)。\n\n**Q：支持图片 / 附件节点吗？**\nA：当前不支持。大纲折叠状态 `expand`、有序列表 `1.`、图片 / 附件节点不在当前 Markdown 往返保真范围。完整已知限制见[技术细节](#-可靠性)。\n\n---\n\n## 📄 License\n\n[MIT](https://opensource.org/licenses/MIT)\n\nFile v1.3.15:skill-card.md\n\n## Description:\n\nIntegrates Mubu with Obsidian and agent workflows by importing Mubu outlines into Obsidian, syncing Markdown to Mubu, and querying or exporting Mubu notes.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[liuboacean](https://clawhub.ai/user/liuboacean)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, knowledge workers, and AI-agent users use this skill to read, export, import, and update Mubu outlines as Markdown for Obsidian workflows, backups, and account-backed note management.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can access private Mubu outlines using the configured account.\n\nMitigation: Install only for trusted agent workflows, keep token and environment files private, and avoid exposing account credentials in prompts, scripts, or logs.\n\nRisk: Create, save, move, rename, and purge actions can modify account content, and purge is irreversible.\n\nMitigation: Confirm target document or folder IDs before write actions; use purge only when the item and type have been verified and explicit confirmation is intended.\n\nRisk: Automatic trigger wording is broad for a skill that can read and modify private notes.\n\nMitigation: Invoke the skill deliberately for Mubu-specific tasks after credentials, target IDs, and intended read or write scope are clear.\n\nRisk: The integration relies on unofficial Mubu web endpoints that may change or rate-limit requests.\n\nMitigation: Review failures before retrying at scale, keep request volume reasonable, and verify endpoint behavior before relying on write-heavy workflows.\n\n## Reference(s):\n\n- [ClawHub skill page](https://clawhub.ai/liuboacean/skills/mubu-integration)\n- [README](artifact/README.md)\n- [Changelog](artifact/CHANGELOG.md)\n- [Mubu API base endpoint](https://api2.mubu.com/v3/api)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Code, Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands; CLI outputs may include Markdown, JSON, OPML, or FreeMind XML.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires Mubu account credentials; read, export, and write actions operate on account content.]\n\n## Skill Version(s):\n\n1.3.15 (source: server release metadata, package __version__, CHANGELOG released 2026-08-28)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.3.14: 18 files, 98621 bytes\n\nFiles: CHANGELOG.md (42137b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (10722b), README.zh-CN.md (10594b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/gen_assets.py (3054b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15985b), scripts/mubu/client.py (36942b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (15143b), skill-card.md (2503b), SKILL.md (20290b), tests/test_mubu_api.py (80147b), _meta.json (136b)\n\nFile v1.3.14:SKILL.md\n\n---\nname: mubu-integration\ndescription: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出\n---\n\n# 幕布集成 Skill\n\n幕布（mubu.com）是一款极简大纲工具，支持将大纲一键转为思维导图。本 Skill 提供 API 集成能力。\n\n## 权限与安全边界\n本 Skill 以你的幕布账号身份操作**远程真实内容**，使用前请知悉其权限边界：\n- **读取**：仅读取环境变量 `MUBU_PHONE` / `MUBU_PASSWORD` / `MUBU_MEMBER_ID`（环境变量未设置时，才由仓库外的 `~/.workbuddy/.env.mubu` 补全；`MUBU_MEMBER_ID` 即幕布 colla 成员 ID，仅 `save` 写回需要。**该值任何 API 都不返回，无法自动获取，必须手动设置 `MUBU_MEMBER_ID` 或提前写入 token 缓存的 `member_id`**）。\n- **写入**：仅在本地写入 Token 缓存文件 `~/.mubu_token`（权限 `0o600` + 跨进程 `fcntl` 锁），不写入其它文件。\n- **网络**：仅访问 `api2.mubu.com`（base URL 可由 `MUBU_BASE_URL` 覆盖，但仅限 `mubu.com` 家族域名，防 MITM），**无第三方服务、无遥测、无数据外发**。\n- **写操作需确认**：真实会改动幕布内容的写操作为 `create`（新建）、`rename_folder`（重命名文件夹）、`save`（保存文档，端点 `/colla/events`，需 `member_id`）、`move`（移动，端点 `/list/custom/drag`）、`rename_doc`（文档重命名，端点 `/list/rename_doc`）、`purge`（彻底删除，唯一真实调用服务端删除，不可逆）；`delete` 现为**软删除**（仅标记进本地回收站，云端副本仍在）。`save` / `move` / `rename_doc` 已在 v1.3.9 经真机验证可用。所有真实写操作均需显式传 `--yes` 才执行，否则中止并提示。\n- **信任边界**：Skill 不读取你的其它本地文件、不执行与幕布无关的 shell 命令；它只做「登录 → 读写你的幕布文档」这一件事。\n\n## 功能概览\n\n| 功能 | 接口 | 说明 |\n|------|------|------|\n| 用户登录 | `POST /user/phone_login` | 手机号密码登录获取 Token |\n| Token 刷新 | 自动处理 | access_token 2小时过期，临近过期自动重新登录（重登仅重试 1 次，杜绝死循环） |\n| 创建文件夹 | `POST /list/create_folder` | 在指定位置创建文件夹 |\n| 创建文档 | `POST /list/create_doc` | 创建新的大纲文档 |\n| 获取列表 | `POST /list/get` | 获取文件夹下的文档列表 |\n| 获取文档 | `POST /document/edit/get` | 获取文档详细内容（真实端点；body 为 docId+password+isFromDocDir，返回 data.definition 为 JSON 字符串需二次解析） |\n| 更新文档 | `POST /colla/events` | 保存/更新文档内容（v1.3.9 起可用；colla 协同端点，`events` 承载 changeset；需 `member_id`，私人文档由 `MUBU_MEMBER_ID` 或 token 缓存提供）|\n| 删除文档 | `POST /list/delete_doc` | 删除文档（按类型区分端点） |\n| 删除文件夹 | `POST /list/delete_folder` | 删除文件夹（原 `/list/delete` 实测非法，已弃用） |\n| 移动文档 | `POST /list/custom/drag` | 移动文档/文件夹到其他位置（v1.3.9 起可用；body `dst/src/folderId`，已真机验证）|\n| 导出 Markdown | 本地转换 | 将大纲结构转换为 Markdown |\n\n## API 基础信息\n\n- **Base URL**: `https://api2.mubu.com/v3/api`\n- **认证方式**: JWT Token，通过请求头 `Jwt-Token` 传递\n- **Content-Type**: `application/json;charset=UTF-8`\n\n## 环境变量配置\n\n在使用前，需要配置以下环境变量：\n\n```bash\nexport MUBU_PHONE=\"your_phone_number\"    # 幕布账号手机号\nexport MUBU_PASSWORD=\"your_password\"      # 幕布账号密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"your_collab_member_id\"\n```\n\n> 切勿在脚本或代码中硬编码明文密码；凭据仅通过环境变量或仓库外的\n> `~/.workbuddy/.env.mubu` 提供。\n\n---\n\n## 使用说明\n\n### 1. 使用 MubuClient\n\n所有操作都通过 `scripts/mubu/client.py` 中的 `MubuClient` 类完成（`scripts/mubu_api.py` 仅为向后兼容的重新导出 shim，不再建议直接使用；**不再有**独立的\n`login()` / `create_folder()` / `create_doc()` / `get_list()` / `get_doc()` / `save_doc()` /\n`delete_item()` 模块级函数）。实例化时自动读取 `MUBU_PHONE` / `MUBU_PASSWORD`\n环境变量（或 `~/.workbuddy/.env.mubu`）并加载本地缓存 Token：\n\n```python\nfrom mubu.client import MubuClient\n\n# 登录：凭据来自环境变量；返回扁平 data（token / id / name）\nclient = MubuClient()\ninfo = client.login()\nprint(info[\"user_id\"], info[\"username\"])   # 注意是扁平 data[\"id\"]，非 data[\"user\"][\"id\"]\n\n# 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感）\nresults = client.search(\"项目\", max_depth=3, limit=50)[\"results\"]\nfor r in results:\n    print(r[\"type\"], r[\"name\"], r[\"path\"])\n```\n\n> 登录返回结构为**扁平** `data`：`data[\"id\"]`=用户 ID，`data[\"name\"]`=用户名，\n> `data[\"token\"]`=令牌。这与旧版嵌套 `result[\"data\"][\"user\"][\"id\"]` 不同。\n\n---\n\n## 大纲内容格式\n\n幕布文档内容使用特定的 JSON 格式表示大纲结构：\n\n```json\n{\n  \"name\": \"文档标题\",\n  \"nodes\": [\n    {\n      \"id\": \"node_1\",\n      \"text\": \"一级标题\",\n      \"children\": [\n        {\n          \"id\": \"node_1_1\",\n          \"text\": \"二级标题\",\n          \"children\": []\n        }\n      ],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    },\n    {\n      \"id\": \"node_2\",\n      \"text\": \"另一个一级标题\",\n      \"children\": [],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    }\n  ]\n}\n```\n\n---\n\n## Token 管理建议\n\n由于幕布的 access_token 仅约 2 小时有效（无 refresh_token 机制，代码也无任何 refresh 逻辑），建议：\n\n1. **本地缓存**: 将 Token 保存到本地文件（如 `~/.mubu_token`）\n2. **自动刷新**: 在 Token 快过期时自动刷新\n3. **错误重试**: 遇到 401 错误时重新登录\n\n```python\nimport os\nimport time\nimport json\nimport tempfile\n\nTOKEN_FILE = os.path.expanduser(\"~/.mubu_token\")\n\ndef save_token(token_data):\n    \"\"\"原子写 + 仅属主可读写：避免中途崩溃留下残缺文件，并防止其它用户读取。\"\"\"\n    token_data = dict(token_data)\n    token_data[\"expires_at\"] = time.time() + 7200  # 2小时后过期\n    # 注：真实 scripts/mubu_api.py 的 _save_token 还会用跨进程 fcntl.flock\n    # advisory 锁包裹整段写（M4 已做成跨平台安全：无 fcntl 平台降级为无锁）；\n    # 此处省略锁，聚焦写盘逻辑。\n    dir_name = os.path.dirname(TOKEN_FILE) or \".\"\n    fd, tmp = tempfile.mkstemp(dir=dir_name, prefix=\".mubu_token.\", suffix=\".tmp\")\n    try:\n        with os.fdopen(fd, \"w\") as f:\n            json.dump(token_data, f)\n        os.chmod(tmp, 0o600)        # 仅属主可读写\n        os.replace(tmp, TOKEN_FILE) # 原子重命名，避免残缺文件\n    except Exception:\n        if os.path.exists(tmp):\n            os.remove(tmp)\n        raise\n\ndef load_token():\n    \"\"\"从本地加载未过期的 Token；已过期或损坏则返回 None。\"\"\"\n    if not os.path.exists(TOKEN_FILE):\n        return None\n    try:\n        with open(TOKEN_FILE) as f:\n            data = json.load(f)\n    except Exception:\n        return None\n    if time.time() >= data.get(\"expires_at\", 0):  # 已过期视为无效\n        return None\n    return data\n```\n\n说明：原示例中朴素的 `is_token_valid` 已移除——其职责（\"是否过期\"）已并入 `load_token`，仅返回未过期的 token。真实实现 `scripts/mubu_api.py` 的 `_save_token` 还包含跨进程 `fcntl.flock` 锁与统一的 `TOKEN_FILE_MODE` 权限管理，此处不再重复。\n\n---\n\n## 导出 / 导入 Markdown\n\nM1 已落地真正的 Markdown 导入/导出（往返保真，不再是占位）。核心纯函数位于 `scripts/mubu_api.py`：\n\n```python\ndef doc_to_markdown(node, level=0):\n    \"\"\"将节点（及子树）渲染为 Markdown 列表片段。\n    '- ' 列表项，缩进 = 2 * level；含 checked → '- [x]'/'- [ ]'；\n    含 note → 子树后追加 '> {note}'。根标题由 export_markdown 负责。\"\"\"\n    ...\n\ndef export_markdown(doc):\n    \"\"\"doc 为 get_doc() 返回的 data 层 {\"node\": {...}}。\n    首行 '# 标题'，其余递归为 '- ' 列表。结构无效时抛 MubuError。\"\"\"\n    ...\n\ndef markdown_to_doc(md):\n    \"\"\"Markdown 文本 → {\"node\": {\"id\": \"root\", \"text\": ..., \"children\": [...]}}。\n    标题为顶层节点；多标题时首个为 root，其余作为 root 的 children；\n    列表项用栈按缩进深度维护层级；'- [ ]'/'- [x]' 设 checked；\n    '> ' 作为对应层级节点的 note。\"\"\"\n    ...\n```\n\n导出示例（幕布 → Markdown）：\n\n```\n# 读书笔记\n- 第一章\n  - [x] 读完\n  - [ ] 写笔记\n> 第一章的备注\n```\n\n\nArchive v1.3.12: 18 files, 95014 bytes\n\nFiles: CHANGELOG.md (37677b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (10722b), README.zh-CN.md (10594b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/gen_assets.py (3054b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15985b), scripts/mubu/client.py (34893b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (13348b), skill-card.md (2768b), SKILL.md (20087b), tests/test_mubu_api.py (78146b), _meta.json (136b)\n\nArchive v1.3.11: 16 files, 91351 bytes\n\nFiles: CHANGELOG.md (36905b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (18439b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15985b), scripts/mubu/client.py (34893b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (13348b), skill-card.md (2373b), SKILL.md (20087b), tests/test_mubu_api.py (78146b), _meta.json (136b)\n\nArchive v1.3.10: 16 files, 90488 bytes\n\nFiles: CHANGELOG.md (36188b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (17331b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (497b), scripts/mubu/cli.py (15985b), scripts/mubu/client.py (34893b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (13348b), skill-card.md (2335b), SKILL.md (20087b), tests/test_mubu_api.py (78146b), _meta.json (136b)\n\nArchive v1.3.9: 16 files, 89795 bytes\n\nFiles: CHANGELOG.md (34903b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (16787b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (496b), scripts/mubu/cli.py (15985b), scripts/mubu/client.py (34893b), scripts/mubu/config.py (8780b), scripts/mubu/convert.py (13348b), skill-card.md (2562b), SKILL.md (18870b), tests/test_mubu_api.py (78146b), _meta.json (135b)\n\nArchive v1.3.8: 16 files, 84429 bytes\n\nFiles: CHANGELOG.md (32077b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (16787b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (496b), scripts/mubu/cli.py (15632b), scripts/mubu/client.py (30755b), scripts/mubu/config.py (8062b), scripts/mubu/convert.py (13348b), skill-card.md (2468b), SKILL.md (18870b), tests/test_mubu_api.py (69474b), _meta.json (135b)\n\nArchive v1.3.7: 16 files, 84192 bytes\n\nFiles: CHANGELOG.md (30559b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (16787b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (496b), scripts/mubu/cli.py (15632b), scripts/mubu/client.py (30755b), scripts/mubu/config.py (8062b), scripts/mubu/convert.py (13348b), skill-card.md (3026b), SKILL.md (18782b), tests/test_mubu_api.py (69474b), _meta.json (135b)\n\nArchive v1.3.6: 16 files, 81339 bytes\n\nFiles: CHANGELOG.md (27179b), CONTRIBUTING.md (784b), examples/weekly.md (212b), README.md (15721b), requirements-dev.txt (16469b), requirements.txt (8955b), scripts/mubu_api.py (3364b), scripts/mubu/__init__.py (496b), scripts/mubu/cli.py (15258b), scripts/mubu/client.py (29169b), scripts/mubu/config.py (8062b), scripts/mubu/convert.py (13348b), skill-card.md (2261b), SKILL.md (18314b), tests/test_mubu_api.py (68258b), _meta.json (135b)","readmeExcerpt":"Skill: mubu-integration Owner: liuboacean Summary: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Tags: agent-skills:1.3.14, ai-agent:1.3.14, api:1.3.15, automation:1.3.16, ci:1.3.14, claude-code:1.3.14, cli:1.3.16, integration:1.3.15, knowledge-management:1.3.14, latest:1.3.16, markdown:1.3.16, mubu:1.3.16, mubu-notes:1.3.14, notes:1.3.14, obsidian:1.3.16, ","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"export MUBU_PHONE=\"your_phone_number\"    # 幕布账号手机号\nexport MUBU_PASSWORD=\"your_password\"      # 幕布账号密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"your_collab_member_id\""},{"language":"python","snippet":"from mubu.client import MubuClient\n\n# 登录：凭据来自环境变量；返回扁平 data（token / id / name）\nclient = MubuClient()\ninfo = client.login()\nprint(info[\"user_id\"], info[\"username\"])   # 注意是扁平 data[\"id\"]，非 data[\"user\"][\"id\"]\n\n# 按名称本地搜索文档/文件夹（递归遍历，大小写不敏感）\nresults = client.search(\"项目\", max_depth=3, limit=50)[\"results\"]\nfor r in results:\n    print(r[\"type\"], r[\"name\"], r[\"path\"])"},{"language":"json","snippet":"{\n  \"name\": \"文档标题\",\n  \"nodes\": [\n    {\n      \"id\": \"node_1\",\n      \"text\": \"一级标题\",\n      \"children\": [\n        {\n          \"id\": \"node_1_1\",\n          \"text\": \"二级标题\",\n          \"children\": []\n        }\n      ],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    },\n    {\n      \"id\": \"node_2\",\n      \"text\": \"另一个一级标题\",\n      \"children\": [],\n      \"collapsed\": false,\n      \"finish\": false,\n      \"modified\": 0\n    }\n  ]\n}"},{"language":"python","snippet":"import os\nimport time\nimport json\nimport tempfile\n\nTOKEN_FILE = os.path.expanduser(\"~/.mubu_token\")\n\ndef save_token(token_data):\n    \"\"\"原子写 + 仅属主可读写：避免中途崩溃留下残缺文件，并防止其它用户读取。\"\"\"\n    token_data = dict(token_data)\n    token_data[\"expires_at\"] = time.time() + 7200  # 2小时后过期\n    # 注：真实 scripts/mubu_api.py 的 _save_token 还会用跨进程 fcntl.flock\n    # advisory 锁包裹整段写（M4 已做成跨平台安全：无 fcntl 平台降级为无锁）；\n    # 此处省略锁，聚焦写盘逻辑。\n    dir_name = os.path.dirname(TOKEN_FILE) or \".\"\n    fd, tmp = tempfile.mkstemp(dir=dir_name, prefix=\".mubu_token.\", suffix=\".tmp\")\n    try:\n        with os.fdopen(fd, \"w\") as f:\n            json.dump(token_data, f)\n        os.chmod(tmp, 0o600)        # 仅属主可读写\n        os.replace(tmp, TOKEN_FILE) # 原子重命名，避免残缺文件\n    except Exception:\n        if os.path.exists(tmp):\n            os.remove(tmp)\n        raise\n\ndef load_token():\n    \"\"\"从本地加载未过期的 Token；已过期或损坏则返回 None。\"\"\"\n    if not os.path.exists(TOKEN_FILE):\n        return None\n    try:\n        with open(TOKEN_FILE) as f:\n            data = json.load(f)\n    except Exception:\n        return None\n    if time.time() >= data.get(\"expires_at\", 0):  # 已过期视为无效\n        return None\n    return data"},{"language":"python","snippet":"def doc_to_markdown(node, level=0):\n    \"\"\"将节点（及子树）渲染为 Markdown 列表片段。\n    '- ' 列表项，缩进 = 2 * level；含 checked → '- [x]'/'- [ ]'；\n    含 note → 子树后追加 '> {note}'。根标题由 export_markdown 负责。\"\"\"\n    ...\n\ndef export_markdown(doc):\n    \"\"\"doc 为 get_doc() 返回的 data 层 {\"node\": {...}}。\n    首行 '# 标题'，其余递归为 '- ' 列表。结构无效时抛 MubuError。\"\"\"\n    ...\n\ndef markdown_to_doc(md):\n    \"\"\"Markdown 文本 → {\"node\": {\"id\": \"root\", \"text\": ..., \"children\": [...]}}。\n    标题为顶层节点；多标题时首个为 root，其余作为 root 的 children；\n    列表项用栈按缩进深度维护层级；'- [ ]'/'- [x]' 设 checked；\n    '> ' 作为对应层级节点的 note。\"\"\"\n    ..."},{"language":"text","snippet":"# 读书笔记\n- 第一章\n  - [x] 读完\n  - [ ] 写笔记\n> 第一章的备注"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: mubu-integration\ndescription: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出\n---\n\n# 幕布集成 Skill\n\n幕布（mubu.com）是一款极简大纲工具，支持将大纲一键转为思维导图。本 Skill 提供 API 集成能力。\n\n## 权限与安全边界\n本 Skill 以你的幕布账号身份操作**远程真实内容**，使用前请知悉其权限边界：\n- **读取**：仅读取环境变量 `MUBU_PHONE` / `MUBU_PASSWORD` / `MUBU_MEMBER_ID`（环境变量未设置时，才由仓库外的 `~/.workbuddy/.env.mubu` 补全；`MUBU_MEMBER_ID` 即幕布 colla 成员 ID，仅 `save` 写回需要。**该值任何 API 都不返回，无法自动获取，必须手动设置 `MUBU_MEMBER_ID` 或提前写入 token 缓存的 `member_id`**）。\n- **写入**：仅在本地写入 Token 缓存文件 `~/.mubu_token`（权限 `0o600` + 跨进程 `fcntl` 锁），不写入其它文件。\n- **网络**：仅访问 `api2.mubu.com`（base URL 可由 `MUBU_BASE_URL` 覆盖，但仅限 `mubu.com` 家族域名，防 MITM），**无第三方服务、无遥测、无数据外发**。\n- **写操作需确认**：真实会改动幕布内容的写操作为 `create`（新建）、`rename_folder`（重命名文件夹）、`save`（保存文档，端点 `/colla/events`，需 `member_id`）、`move`（移动，端点 `/list/custom/drag`）、`rename_doc`（文档重命名，端点 `/list/rename_doc`）、`purge`（彻底删除，唯一真实调用服务端删除，不可逆）；`delete` 现为**软删除**（仅标记进本地回收站，云端副本仍在）。`save` / `move` / `rename_doc` 已在 v1.3.9 经真机验证可用。所有真实写操作均需显式传 `--yes` 才执行，否则中止并提示。\n- **信任边界**：Skill 不读取你的其它本地文件、不执行与幕布无关的 shell 命令；它只做「登录 → 读写你的幕布文档」这一件事。\n\n## 功能概览\n\n| 功能 | 接口 | 说明 |\n|------|------|------|\n| 用户登录 | `POST /user/phone_login` | 手机号密码登录获取 Token |\n| Token 刷新 | 自动处理 | access_token 2小时过期，临近过期自动重新登录（重登仅重试 1 次，杜绝死循环） |\n| 创建文件夹 | `POST /list/create_folder` | 在指定位置创建文件夹 |\n| 创建文档 | `POST /list/create_doc` | 创建新的大纲文档 |\n| 获取列表 | `POST /list/get` | 获取文件夹下的文档列表 |\n| 获取文档 | `POST /document/edit/get` | 获取文档详细内容（真实端点；body 为 docId+password+isFromDocDir，返回 data.definition 为 JSON 字符串需二次解析） |\n| 更新文档 | `POST /colla/events` | 保存/更新文档内容（v1.3.9 起可用；colla 协同端点，`events` 承载 changeset；需 `member_id`，私人文档由 `MUBU_MEMBER_ID` 或 token 缓存提供）|\n| 删除文档 | `POST /list/delete_doc` | 删除文档（按类型区分端点） |\n| 删除文件夹 | `POST /list/delete_folder` | 删除文件夹（原 `/list/delete` 实测非法，已弃用） |\n| 移动文档 | `POST /list/custom/drag` | 移动文档/文件夹到其他位置（v1.3.9 起可用；body `dst/src/folderId`，已真机验证）|\n| 导出 Markdown | 本地转换 | 将大纲结构转换为 Markdown |\n\n## API 基础信息\n\n- **Base URL**: `https://api2.mubu.com/v3/api`\n- **认证方式**: JWT Token，通过请求头 `Jwt-Token` 传递\n- **Content-Type**: `application/json;charset=UTF-8`\n\n## 环境变量配置\n\n在使用前，需要配置以下环境变量：\n\n```bash\nexport MUBU_PHONE=\"your_phone_number\"    # 幕布账号手机号\nexport MUBU_PASSWORD=\"your_password\"      # 幕布账号密码\n# 可选：幕布 colla 成员 ID（仅 save 写回需要；任何 API 都不暴露，必须手动设置，缺失时 save 会明确报错）\nexport MUBU_MEMBER_ID=\"your_collab_member_id\"\n```\n\n> 切勿在脚本或代码中硬编码明文密码；凭据仅通过环境变量或仓库外的\n> `~/.workbuddy/.env.mubu` 提供。\n\n---\n\n## 使用说明\n\n### 1. 使用 MubuClient\n\n所有操作都通过 `scripts/mubu/client.py` 中的 `MubuClient` 类完成（`scripts/mubu_api.py` 仅为向后兼容的重新导出 shim，不再建议直接使用；**不再有**独立的\n`login()` / `create_folder()` / `create_doc()` / `get_list()` / `get_doc()` / `save_doc()` /\n`delete_item()` 模块级函数）。实例化时自动读取 `MUBU_PHONE` / `MUBU_PASSWORD`\n环境变量（或 `~/.workbuddy/.env.mubu`）并加载本地缓存 Token：\n\n```python\nfrom mubu.client import MubuClient\n\n# 登录：凭据来自环境变量；返回扁平 data（token / id / name）\nclient = MubuClient()\ninfo = client.login()\nprint(info[\"user_id\"], info[\"username\"])   # 注意是扁平 data[\"id\"]，非 data[\"user\"][\"id\"]\n\n"},{"path":"README.md","content":"[English](README.md) | [中文](README.zh-CN.md)\n\n![mubu-integration](assets/social-preview.png)\n\n# mubu-integration\n\n> Turn Mubu (幕布) into a Markdown-native, AI-agent-controllable outliner.\n\n[![GitHub stars](https://img.shields.io/github/stars/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/liuboacean/mubu-integration?style=social)](https://github.com/liuboacean/mubu-integration/network/members)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://img.shields.io/github/actions/workflow/status/liuboacean/mubu-integration/test.yml?branch=main)](https://github.com/liuboacean/mubu-integration/actions/workflows/test.yml)\n\nManage your Mubu (幕布) outlines from the command line — **and as an AI Agent Skill** — with lossless Markdown import/export round-trip.\n\n---\n\n## ✨ Try it in 3 commands (magic moment)\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --md examples/weekly.md    # Markdown outline → Mubu\npython3 scripts/mubu_api.py get <doc-id> --export markdown > out.md  # Mubu → Markdown\ndiff weekly.md out.md                                              # no output = byte-for-byte identical\n```\n\n![demo](assets/demo.gif)\n\n---\n\n## 🆚 Why mubu-integration?\n\n| Capability | Manual copy | Existing export-plugin scripts | **mubu-integration** |\n| :--- | :---: | :---: | :---: |\n| Mubu → Markdown | ✅ | ⚠️ partial | ✅ |\n| Markdown → Mubu | ❌ | ❌ | ✅ **(only)** |\n| Round-trip fidelity (`diff` = no diff) | ❌ | ❌ | ✅ **(only)** |\n| Whole-tree batch / OPML / FreeMind | ❌ | ⚠️ some | ✅ |\n| Callable by an AI Agent | ❌ | ❌ | ✅ **(only)** |\n| Scriptable from the command line | ❌ | ⚠️ | ✅ |\n\n---\n\n## 💡 Use cases\n\n**① Let your AI Agent read & write Mubu directly** — turn Mubu into your Agent's long-term, structured memory.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > memory.md   # Agent pulls the latest outline\n# ... Agent edits memory.md ...\npython3 scripts/mubu_api.py save <doc-id> --md memory.md                 # writes the updated outline back to Mubu\n```\n\n**② Obsidian ↔ Mubu, two-way outlines** — keep your knowledge base and your outliner in sync as plain Markdown.\n\n```bash\npython3 scripts/mubu_api.py get <doc-id> --export markdown > vault/notes/mubu.md   # Mubu → Obsidian\npython3 scripts/mubu_api.py create --md vault/notes/mubu.md --folder <folder-id>   # Obsidian → Mubu\n```\n\n**③ Auto-archive weekly meeting notes** — push `examples/weekly.md` into Mubu in one step.\n\n```bash\npython3 scripts/mubu_api.py create \"周会\" --folder <folder-id> --md examples/weekly.md\n```\n\n---\n\n## 🚀 30-second quick start\n\n1. Set your Mubu credentials (phone + password). They are never passed as CLI arguments — use env vars or a local file:\n\n   ```bash\n   export MUBU_PHONE=\"your-phone\"\n   export MUBU_PASSWORD=\"your-password\"\n   ```\n\n   …or write them to `~/.workbuddy/.env.mubu` (env vars take precede"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn73qbrbqs4s8t2nh8pm22wxbd84vm7r\",\n  \"slug\": \"mubu-integration\",\n  \"version\": \"1.3.16\",\n  \"publishedAt\": 1788337637846\n}"},{"path":"CHANGELOG.md","content":"# Changelog\n\n记录 mubu-integration Skill 的里程碑演化。\n\n## v1.3.16（CI 门禁闭环 · ruff 导入合规修复，2026-09-02）\n\n本轮无新功能、无破坏性变更，仅修复 v1.3.15 引入的 `lint` job 在 CI 中实际失败却被 `continue-on-error` 掩盖的问题，使 CI 真实通过。\n\n### CI 修复\n- **ci: `lint` job 改为必过门禁** —— 移除 `.github/workflows/test.yml` 中 `lint` job 的 `continue-on-error: true`。原 v1.3.15 因沙箱无法本地装 ruff 验证而放宽，现确认 `ruff check scripts` 通过，故升级为硬性门禁，拦截未来导入类回归。\n- **fix(ruff): 导入类违规修复（覆盖 6 个文件，零逻辑改动）** —— 分两轮闭环：\n  - **第一轮（18 处，已提交 c1a7500）**：I001 导入排序 ×6、F401 未使用导入 ×9、F841 未使用局部变量 ×2，覆盖 `gen_assets.py`、`cli.py`、`client.py`、`config.py`、`convert.py`、`mubu_api.py`。\n  - **第二轮（follow-up，2 处 I001）**：首轮手工复刻 CI diff 时对 isort 规则理解有误（`from` 导入须排在同段 `import` 之后；首方导入块不可被注释+空行拆分），CI 硬门禁重跑后精准定位并修复：\n    - `client.py`：`from pathlib import Path` 由 `import os` 之后移至 `import uuid` 之后（straight import 段内）。\n    - `mubu_api.py`：删除 `# 包内模块符号` 注释及其前的空行，使 `from mubu.cli/client/config/convert` 首方导入块紧凑连续。\n  - **ruff.toml**：新增 `[lint.isort] known-first-party = [\"mubu\"]`，显式声明本地包，消除首方/第三方归类歧义（与 ruff 默认 `detect-first-party` 行为一致，纯声明性加固）。\n\n### 验证\n- `python -m py_compile` 全部通过；`import mubu_api / mubu.cli / mubu.client / mubu.config / mubu.convert` 无 `NameError`（移除的导入经 grep 确认仅为字符串字面量引用，确为冗余）。\n- 测试矩阵：**115 passed（0 失败，较 v1.3.15 无回归）**。\n- 沙箱本地因环境限制无法运行 ruff 二进制（harness 杀进程），以 GitHub Actions CI 重跑 `ruff check scripts` 作为真实验证（见 commit 后 CI 状态，lint job 须为绿色）。\n\n## v1.3.15（P0+P1 改进，2026-08-28）\n\n本轮落实产品评审诊断（deliverables/gstack/product-review-improve-2026-08-28.md）的 P0（信任修复）+ P1（质量/CI/文档）共 12 项，零新功能、无破坏性变更。\n\n### P0 信任修复（零风险、确定性）\n- **doc(README/zh-CN/CONTRIBUTING): 修正落地页首行坏命令** —— \"Try it in 3 commands\" 的 `create --md weekly.md` 缺必填位置参数 `name`，实测直接报 `error: the following arguments are required: name`；改为 `create \"周会\" --md examples/weekly.md`（cli.py:71 已证实 name 必填）。\n- **doc: 测试数 84 → 113 对齐** —— README(×3)/README.zh-CN(×3)/CONTRIBUTING 全量修正为真实的 113（CI 实测 113 passed）。\n\n### P1 质量 / CI / 文档\n- **fix(client._http_request): 429 限流退避** —— 新增 HTTP 429 分支，按 `Retry-After` 头（封顶 30s）或 NETWORK_BACKOFF 退避重试，重试用尽抛清晰 MubuError；对齐 5xx 重试逻辑（不重登）。\n- **fix(client.get_doc): 解析保护** —— `json.loads(data[\"definition\"])` 包裹为 try/except(KeyError/TypeError/ValueError) → MubuError，不再裸抛。\n- **ci: 新增 ruff 静态门禁** —— `.github/workflows/test.yml` 加 `lint` job（安装 ruff 跑 `ruff check scripts`，规则集 E/F/W/I）；新增 `ruff.toml`。沙箱本地因 SOCKS 代理无法安装 ruff 验证，故该 job 初版 `continue-on-error`（**已于 v1.3.16 修复 18 处违规后改为必过门禁**）。\n- **doc(cli.rename help): 修正矛盾** —— `rename --type doc` 帮助由\"走 save_doc name（⚠️ 真机受限）\"改为\"走已验证端点 /list/rename_doc（内容保真）\"，与实现一致。\n- **doc(README): Auto-refresh auth 措辞** —— \"Zero-touch auth\" 改为 \"Auto-refresh auth\"，明确重登依赖缓存凭据（env/.env.mubu），不声称免密码；zh-CN 同步。\n- **doc(README): 新增 Troubleshooting** —— 错误码表（memberId/code 17·5·403/登录失败），含服务端限制说明。\n- **feat(client.search): include_content** —— 新增可选参数，对名称未命中的文档额外拉取正文递归搜索节点 text/note，命中带 `matched_in: \"content\"`（名称命中为 `\"name\"`）；默认关闭保性能。新增 2 例回归测试。\n- **doc(docs/): 分发与定位素材** —— 新增 `docs/positioning.md`（项目定位纠偏）、`docs/hellogithub-pitch.md`（HelloGitHub 投递文案）、`docs/xiaoshuopai-tutoria"},{"path":"CONTRIBUTING.md","content":"# Contributing\n\n欢迎贡献！\n\n## 如何贡献\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/amazing-feature`)\n3. 提交更改 (`git commit -m 'Add amazing feature'`)\n4. 推送到分支 (`git push origin feature/amazing-feature`)\n5. 提交 Pull Request\n\n## 开发环境\n\n```bash\n# 克隆仓库\ngit clone https://github.com/liuboacean/mubu-integration.git\ncd mubu-integration\n\n# 安装依赖（运行时 + 测试；开发依赖在 requirements-dev.txt）\npip install -r requirements.txt -r requirements-dev.txt\n\n# 运行全部测试（共 115 个 pytest 用例）\nPYTHONPATH=scripts python -m pytest -v\n\n# 脚本运行入口\npython3 scripts/mubu_api.py --help\n```\n\n## 行为准则\n\n- 尊重所有贡献者\n- 建设性讨论\n- 专注于对项目最有利的事情"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Skill: mubu-integration Owner: liuboacean Summary: 幕布（mubu）与 Obsidian 集成：将幕布大纲导入 Obsidian、把 Markdown 同步到幕布、查询/导出幕布笔记。触发词：幕布、mubu、幕布导入 Obsidian、mubu 同步、幕布笔记导出 Tags: agent-skills:1.3.14, ai-agent:1.3.14, api:1.3.15, automation:1.3.16, ci:1.3.14, claude-code:1.3.14, cli:1.3.16, integration:1.3.15, knowledge-management:1.3.14, latest:1.3.16, markdown:1.3.16, mubu:1.3.16, mubu-notes:1.3.14, notes:1.3.14, obsidian:1.3.16,","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":958,"uniquenessScore":47,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T11:10:28.767Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T19:02:58.710Z","emptyReason":null},"items":[{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-10T18:48:31.762Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}