技能备份 - 2026-04-15 (40个技能)

This commit is contained in:
root
2026-04-15 18:53:15 +08:00
parent f62f14814f
commit c65fce24e4
791 changed files with 190773 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.1] - 2026-02-23
### Fixed
- **Skill.json tool count**: Fixed missing tools display on Clawhub
- Verified all 7 tools are properly declared: write_smart, configure, append_smart, transfer_ownership, get_config_status, search_docs, list_docs
## [1.4.0] - 2026-02-23
### Added
- **Document Search Functionality** (`search_docs`)
- Search local index by keywords
- Search in document name, summary, and tags
- Return matching documents with links
- **Document List Functionality** (`list_docs`)
- List all created Feishu documents
- Filter by tags (AI Technology, E-commerce, Health & Sports, etc.)
- Filter by status
- **Automatic Index Management** (`index_manager.py`)
- Auto-update local index at `memory/feishu-docs-index.md` after document creation
- Auto-generate document summary (first 100 characters)
- Intelligent auto-categorization tags:
- AI Technology (AI, artificial intelligence, model, GPT, LLM)
- OpenClaw (OpenClaw, skill, agent)
- Feishu Docs (Feishu, document, docx)
- E-commerce (e-commerce, TikTok, Alibaba)
- Health & Sports (Garmin, Strava, cycling, health)
- Daily Archive (conversation, archive, chat history)
- **Tool Declarations**
- Officially declare `search_docs` and `list_docs` tools in `package.json` and `skill.json`
### Changed
- Updated `SKILL.md` documentation with detailed descriptions of new features
- Improved usage examples and configuration instructions
### Technical Details (Ownership Transfer Fix)
- **Before**: Used `ctx.invoke_tool("exec", ...)` which failed when ctx was None or unavailable
- **After**: Direct HTTP API calls with `aiohttp`, fully independent of OpenClaw context
- **API Endpoint**: `POST /drive/v1/permissions/{token}/members/transfer_owner?type=docx`
### Optimized Ownership Transfer Workflow
- Automatically transfer ownership to user after document creation
- Automatically obtain tenant_access_token without manual configuration
- Fixed "permission denied" errors caused by ctx passing issues
## [1.3.0] - 2026-02-22
### Fixed
- **Fixed Ownership Transfer Functionality** (`transfer_ownership`)
- **BREAKING CHANGE**: Replaced ctx-dependent implementation with independent API calls
- Added `_get_tenant_access_token()` method for independent token retrieval using aiohttp
- **Removed dependency**: No longer relies on `ctx.invoke_tool("exec", ...)` to call curl commands
- **More stable**: Directly use `aiohttp` to call Feishu API, eliminating context passing issues
- **Better error handling**: Returns detailed error messages on failure
- Reads Feishu app credentials from OpenClaw config independently
### Technical Details
- **Before**: Used `ctx.invoke_tool("exec", ...)` which failed when ctx was None or unavailable
- **After**: Direct HTTP API calls with `aiohttp`, fully independent of OpenClaw context
- **API Endpoint**: `POST /drive/v1/permissions/{token}/members/transfer_owner?type=docx`
### Changed
- **Optimized Ownership Transfer Workflow**
- Automatically transfer ownership to user after document creation
- Automatically obtain tenant_access_token without manual configuration
- Fixed "permission denied" errors caused by ctx passing issues
## [1.2.0] - 2026-02-21
### Added
- **Intelligent Chunk Writing Functionality**
- Automatically split long content into chunks (default 2000 characters)
- Avoid blank document issues caused by Feishu API character limits
- Smart paragraph segmentation while preserving heading structure
- Support automatic table-to-text conversion
- **First-Time User Guide**
- Auto-detect first-time usage
- Guide users to obtain OpenID
- Auto-save configuration to `user_config.json`
- **Basic Document Operations**
- `write_smart` - Smart document creation
- `append_smart` - Append content to existing documents
- `transfer_ownership` - Transfer document ownership
- `configure` - Configure OpenID
- `get_config_status` - Check configuration status
## [1.1.0] - 2026-02-20
### Added
- Initial release
- Basic Feishu document creation and writing functionality
- Support Markdown format (headings, lists, code blocks, etc.)
- Support automatic image upload
---
## Version Summary
- **v1.4.0** - Added search, list, and automatic index management
- **v1.3.0** - Fixed ownership transfer using independent API calls (removed ctx dependency)
- **v1.2.0** - Added intelligent chunk writing and first-time user guide
- **v1.1.0** - Initial release with basic document operations
## Key Technical Updates
### v1.4.0 Core Technology
- Local index management system
- Intelligent content categorization algorithm
- Automatic document metadata extraction
### v1.3.0 Core Technology (Critical Fix)
- **Independent tenant_access_token retrieval** using aiohttp
- **Direct Feishu API calls** without OpenClaw context dependency
- **Removed**: `ctx.invoke_tool("exec", ...)` approach
- **Added**: `_get_tenant_access_token()` for standalone token acquisition
- Reads app credentials from `~/.openclaw/openclaw.json` independently
### v1.2.0 Core Technology
- ContentChunker content segmentation
- Intelligent paragraph segmentation algorithm
- User configuration persistence
+143
View File
@@ -0,0 +1,143 @@
# Feishu Smart Doc Writer
> **English**: Feishu/Lark Smart Document Writer. Solves API content limits by auto-chunking long documents and auto-transferring ownership. Guides OpenID config on first use.
>
> **中文**: 飞书智能文档写入器,解决长文档API限制导致的空白问题,自动转移所有权。首次使用自动引导配置。
飞书智能文档写入器 - 解决长文档写入时的 API 限制问题,支持自动转移所有权。
## 认证说明
本 Skill 使用 OpenClaw 内置的飞书工具集,**无需手动获取 token**。
- `tenant_access_token` 由 OpenClaw 自动管理(通过配置好的 `appId``appSecret` 自动换取)
- 你**不需要**去飞书后台点击"获取 token"
- 本 Skill 唯一需要配置的是 **用户 OpenID**(用于文档所有权转移,见下方配置步骤)
## 核心功能
### 1. 智能分块写入
飞书 API 对单次写入有限制(创建~4000字符,追加~2000字符),超过会导致文档空白。本 Skill 自动将长内容分块写入,确保完整无丢失。
### 2. 自动转移所有权
应用创建的文档默认所有权属于应用,用户无法编辑。本 Skill 在创建文档后自动转移所有权给用户,用户拥有完全控制权。
### 3. 首次使用自动引导
首次使用时自动引导用户配置 OpenID,无需查阅文档。
## 使用流程
### 第1步:首次使用(自动引导)
当你第一次使用 `write_smart` 时,Skill 会自动提示配置指南。
### 第2步:获取你的 OpenID
**详细步骤(精确路径):**
1. **登录飞书开放平台**
- 网址:https://open.feishu.cn
2. **进入权限管理并前往调试台**
- 进入你的**相关应用**
- 点击 **"权限管理"**
- 搜索权限:`im:message`
- 鼠标移动到 **"相关API事件"**
- 选择:**【API】发送消息**
- 点击右下角:**"前往API调试台"**
3. **找到 "快速复制 open_id"**
- 在页面中找到 **蓝色文字** "快速复制 open_id"
- 点击这个链接
4. **选择用户并复制**
- 在弹出的选择框中,**选择你的账号**
- 点击 **"复制"** 按钮
- 得到格式如:`ou_xxxxxxxx`
### 第3步:开通并发布权限
⚠️ **重要:需要开通权限并发布应用新版本**
1. **进入权限管理**
- 登录 https://open.feishu.cn
- 进入你的应用 → **权限管理**
2. **搜索并开通权限**
- 搜索:`docs:permission.member:transfer`
- 点击 **"开通"**
3. **发布新版本(关键!)**
- 点击页面右上角的 **"发布"** 按钮
- 等待发布完成
- ⚠️ **不发布的话,权限不会生效!**
### 第4步:配置 Skill
根据引导,使用 `configure` 工具配置:
```python
await ctx.invoke_tool("feishu_smart_doc_writer.configure", {
"openid": "ou_5b921cba0fd6e7c885276a02d730ec19",
"permission_checked": true
})
```
### 第5步:创建文档(自动转移所有权)
配置完成后,直接创建文档:
```python
result = await ctx.invoke_tool("feishu_smart_doc_writer.write_smart", {
"title": "项目报告",
"content": "# 项目报告\n\n很长很长的内容...(支持10000+字)"
})
# 返回:
# {
# "doc_url": "https://feishu.cn/docx/xxx",
# "chunks_count": 5,
# "owner_transferred": true,
# "message": "✅ 文档创建成功,共分 5 块写入,所有权已转移"
# }
```
**配置一次,永久生效!** 之后创建文档会自动转移所有权。
## 工具列表
| 工具名 | 功能 |
|--------|------|
| `write_smart` | 创建文档,自动分块写入,自动转移所有权(首次使用引导配置) |
| `configure` | 配置 OpenID 和确认权限 |
| `append_smart` | 追加内容到已有文档(自动分块) |
| `transfer_ownership` | 转移已有文档的所有权 |
| `get_config_status` | 查看当前配置状态 |
## 为什么需要这个 Skill
### 原生 feishu_doc 的问题
```python
# ❌ 原生方式 - 长内容会失败
feishu_doc.create(
title="项目报告",
content="# 很长很长的内容..." * 1000 # 超过4000字符
)
# 结果:文档创建,但内容空白或报错
```
### 使用本 Skill
```python
# ✅ 自动处理 - 内容分块写入,自动转移所有权
write_smart(
title="项目报告",
content="# 很长很长的内容..." * 1000, # 10000字符
)
# 结果:自动分为5块写入,所有权转移给用户,文档完整
```
## License
MIT
+300
View File
@@ -0,0 +1,300 @@
---
name: feishu-smart-doc-writer
description: |
Feishu/Lark Smart Document Writer - 飞书智能文档写入器.
Core Features / 核心功能:
1. Smart Chunk Writing / 智能分块写入 - Solve API limit blank doc issues / 解决长文档API限制导致的空白问题
2. Auto Ownership Transfer / 自动转移所有权 - Transfer to user after creation / 创建文档后自动转移给用户
3. Auto Index Management / 自动索引管理 - Update local index with search support / 自动更新本地文档索引,支持搜索
4. First-time Guide / 首次使用引导 - Auto guide for OpenID config / 自动引导配置OpenID
---
# Feishu Smart Doc Writer v1.4.1
## 🚀 Core Features / 核心功能
### 1. Smart Document Creation / 智能文档创建
- **Auto-chunk Writing / 自动分块**: Split long content into chunks to avoid API limit blank docs / 长内容自动分割成小块,避免API限制导致的空白文档
- **Auto Ownership Transfer / 自动转移所有权**: Transfer to user using OpenID after creation / 创建后自动使用 OpenID 转移给用户
- **Auto Index Update / 自动索引更新**: Add doc info to local index `memory/feishu-docs-index.md` / 文档信息自动添加到本地索引
- **Smart Categorization / 智能分类**: Auto-tag based on content (AI Tech, E-commerce, Health, etc.) / 根据内容自动打标签(AI技术、电商、健康运动等)
### 2. Document Management / 文档管理
- **Search Documents / 搜索文档**: Search local index by keywords / 按关键词搜索本地索引
- **List Documents / 列出文档**: Filter by tags and status / 按标签、状态筛选文档列表
- **Append Content / 追加内容**: Append to existing docs (auto-chunk) / 向现有文档追加内容(自动分块)
---
## 📋 Tools / 工具列表
### write_smart - Smart Document Creation / 智能创建文档
Create document with auto-chunk writing, ownership transfer, and index update.
创建文档,自动完成分块写入、所有权转移、索引更新。
```json
{
"title": "Document Title / 文档标题",
"content": "Content (long content supported) / 文档内容(支持长内容)",
"folder_token": "Optional folder token / 可选的文件夹token"
}
```
**Returns / 返回:**
```json
{
"doc_url": "https://feishu.cn/docx/xxx",
"doc_token": "xxx",
"chunks_count": 3,
"owner_transferred": true,
"index_updated": true
}
```
### append_smart - Append Content / 追加内容
Append content to existing document with auto-chunk.
向现有文档追加内容(自动分块)。
```json
{
"doc_url": "https://feishu.cn/docx/xxx",
"content": "Content to append / 要追加的内容"
}
```
### search_docs - Search Documents / 搜索文档
Search documents in local index.
搜索本地索引中的文档。
```json
{
"keyword": "Search keyword / 搜索关键词"
}
```
**Returns / 返回:**
```json
{
"results": [
{
"name": "Document Name / 文档名",
"link": "https://...",
"summary": "Summary / 摘要",
"tags": "AI Tech, OpenClaw / AI技术, OpenClaw"
}
],
"count": 1
}
```
### list_docs - List Documents / 列出文档
List all documents with optional filters.
列出所有文档,支持筛选。
```json
{
"tag": "AI Tech / AI技术",
"status": "Completed / 已完成"
}
```
### transfer_ownership - Transfer Ownership / 转移所有权
Manually transfer document ownership (usually auto-handled by write_smart).
手动转移文档所有权(通常不需要,write_smart 已自动处理)。
```json
{
"doc_url": "https://feishu.cn/docx/xxx",
"owner_openid": "ou_xxxxxxxx"
}
```
**Note / 注意:** Only provide OpenID, tenant_access_token is auto-obtained by Skill.
只需要提供 OpenIDtenant_access_token 由 Skill 自动获取。
### configure - Configure OpenID / 配置 OpenID
Configure OpenID for first-time use.
首次使用时配置 OpenID。
```json
{
"openid": "ou_xxxxxxxx",
"permission_checked": true
}
```
### get_config_status - Get Config Status / 查看配置状态
View current configuration status.
查看当前配置状态。
---
## 🚀 Quick Start / 快速开始
### First-time Setup (3 Steps) / 首次使用(3步配置)
**Step 1 / 第1步: Call write_smart / 调用 write_smart**
```
/feishu-smart-doc-writer write_smart
title: Test Document / 测试文档
content: This is a test document. / 这是一个测试文档内容
```
**Step 2 / 第2步: Get OpenID / 获取 OpenID**
If not configured, follow the guide:
如果未配置,会显示引导:
1. Login / 登录 https://open.feishu.cn
2. Go to / 进入 Application → Permission Management / 应用 → 权限管理 → Search / 搜索 `im:message`
3. Click / 点击【API】Send Message / 发送消息 → Go to API Debug Console / 前往API调试台
4. Click / 点击"Quick Copy open_id" / 快速复制 open_id", select your account / 选择你的账号, copy / 复制
**Step 3 / 第3步: Configure and Enable Permissions / 配置并开通权限**
```
/feishu-smart-doc-writer configure
openid: ou_your_openid / ou_你的OpenID
permission_checked: true
```
Then go to Permission Management / 然后到权限管理:
1. Search / 搜索 `docs:permission.member:transfer`
2. Click / 点击"Enable / 开通"
3. **Important / 重要**: Click / 点击"Publish / 发布" button to publish new version / 按钮发布新版本
After setup, future document creation will automatically:
配置完成后,以后创建文档会自动:
- ✅ Chunk write content / 分块写入内容
- ✅ Transfer ownership to you / 转移所有权给你
- ✅ Update local index / 更新本地索引
---
## 📊 Index Management / 索引管理
### Auto Index Workflow / 自动索引流程
```
write_smart creates document / 创建文档
Write content (auto-chunk) / 写入内容(自动分块)
Transfer ownership / 转移所有权
Auto update index → memory/feishu-docs-index.md / 自动更新索引
Done! / 完成!
```
### Auto-categorization Tags / 自动分类标签
Auto-identified based on content / 根据内容自动识别:
- **AI Tech / AI技术** - AI, artificial intelligence / 人工智能, model / 模型, GPT, LLM
- **OpenClaw** - OpenClaw, skill, agent
- **Feishu Docs / 飞书文档** - Feishu / 飞书, document / 文档, feishu
- **E-commerce / 电商** - e-commerce / 电商, TikTok, Alibaba / 阿里巴巴
- **Health & Sports / 健康运动** - Garmin, Strava, cycling / 骑行, health / 健康
- **Daily Archive / 每日归档** - conversation / 对话, archive / 归档, chat history / 聊天记录
### Index File Location / 索引文件位置
`memory/feishu-docs-index.md`
Format / 格式: Markdown table with / Markdown 表格,包含 index / 序号, name / 名称, type / 类型, link / 链接, summary / 摘要, status / 状态, tags / 标签, owner / 所有者
---
## 🔍 Usage Examples / 使用示例
### Example 1 / 示例1: Create Tech Document / 创建技术文档
```
/feishu-smart-doc-writer write_smart
title: AI Tech Research Report / AI技术调研报告
content: # AI Overview / AI技术概述\n\nAI is... / 人工智能(AI)是...
```
Result / 结果:
- Document created successfully / 文档创建成功
- Auto-tagged "AI Tech / AI技术" / 自动打上"AI技术"标签
- Index updated / 索引已更新
### Example 2 / 示例2: Search Documents / 搜索文档
```
/feishu-smart-doc-writer search_docs
keyword: AI Tech / AI技术
```
### Example 3 / 示例3: List All Tech Documents / 列出所有技术文档
```
/feishu-smart-doc-writer list_docs
tag: AI Tech / AI技术
```
---
## ⚙️ Configuration / 配置说明
### User Config File / 用户配置文件
Location / 位置: `skills/feishu-smart-doc-writer/user_config.json`
```json
{
"owner_openid": "ou_5b921cba0fd6e7c885276a02d730ec19",
"permission_noted": true,
"first_time": false
}
```
### Required Permissions / 必需权限
- `docx:document:create` - Create document / 创建文档
- `docx:document:write` - Write content / 写入内容
- `docs:permission.member:transfer` - Transfer ownership ⚠️ Critical / 转移所有权 ⚠️ 关键权限
---
## 📝 Version History / 版本历史
### v1.4.1 (2026-02-23)
- ✅ Fixed description inconsistency between skill.json and package.json / 修复 skill.json 和 package.json 描述不一致问题
- ✅ Unified all file versions to v1.4.1 / 统一所有文件版本号为 v1.4.1
- ✅ Verified all 7 tools properly declared / 确认所有 7 个工具正确声明
### v1.4.0 (2026-02-23)
- ✅ Added auto index management (index_manager.py) / 新增自动索引管理(index_manager.py
- ✅ Added search_docs tool (search local index) / 新增 search_docs 工具(搜索本地索引)
- ✅ Added list_docs tool (list documents) / 新增 list_docs 工具(列出文档)
- ✅ Smart auto-categorization tags / 智能自动分类标签
- ✅ Fixed ownership transfer (independent API calls, no ctx dependency) / 修复所有权转移(独立 API 调用,不依赖 ctx)
### v1.2.0
- ✅ Auto-chunk writing / 自动分块写入
- ✅ Auto ownership transfer / 自动转移所有权
- ✅ First-time user guide / 首次使用引导
### v1.1.0
- ✅ Basic document creation and append / 基础文档创建和追加
---
## 🔧 Troubleshooting / 故障排除
### "open_id is not exist" Error / 错误
**Cause / 原因**: Used user_id instead of openid / 使用了 user_id 而不是 openid
**Solution / 解决**: Use openid starting with `ou_` / 使用以 `ou_` 开头的 openid
### "Permission Denied" Error / "权限不足" 错误
**Cause / 原因**: `docs:permission.member:transfer` not enabled or app not published / 未开通权限,或未发布应用
**Solution / 解决**
1. Permission Management → Search `docs:permission.member:transfer` → Enable / 权限管理 → 搜索 → 开通
2. Click "Publish" button to publish new version (Critical!) / 点击"发布"按钮(关键!)
### Index Not Updated / 索引未更新
**Check / 检查**
1. Check if `memory/feishu-docs-index.md` exists / 查看文件是否存在
2. Check `index_updated` field in write_smart return / 检查返回字段
3. Check error logs / 查看错误日志
---
## 📞 Support / 支持
If issues, please check / 如有问题,请检查:
1. OpenID format correct (starts with ou_) / 格式是否正确(ou_ 开头)
2. Permissions enabled and published / 权限是否已开通并发布
3. Index file path correct / 索引文件路径是否正确
+486
View File
@@ -0,0 +1,486 @@
#!/usr/bin/env python3
"""
Feishu Smart Doc Writer
飞书智能文档写入器 - 自动分段、分批写入
支持首次使用自动引导配置
核心功能:
1. 智能分块写入 - 解决飞书API字数限制导致的空白文档
2. 自动转移所有权 - 创建文档后自动转移给用户
3. 首次使用引导 - 自动询问OpenID并配置
"""
import json
import os
from typing import Dict, Optional
from dataclasses import dataclass, asdict
# 配置文件路径
CONFIG_PATH = os.path.expanduser("~/.openclaw/workspace/skills/feishu-smart-doc-writer/user_config.json")
@dataclass
class UserConfig:
"""用户配置"""
owner_openid: str = ""
permission_noted: bool = False # 用户是否已确认权限
first_time: bool = True # 是否首次使用
def save(self):
"""保存配置"""
try:
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
json.dump(asdict(self), f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存配置失败: {e}")
return False
@classmethod
def load(cls) -> 'UserConfig':
"""加载配置"""
try:
if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
data = json.load(f)
return cls(**data)
except Exception as e:
print(f"加载配置失败: {e}")
return cls()
# 引导消息模板
FIRST_TIME_GUIDE = """👋 **欢迎使用 Feishu Smart Doc Writer**
本 Skill 可以帮助你:
✅ **智能分块写入** - 解决长文档写入时因API限制导致的空白问题
✅ **自动转移所有权** - 创建文档后自动转移给你,拥有完全控制权
---
## 🔧 首次使用配置
### 第1步:获取你的 OpenID
**详细步骤(精确路径):**
1. **登录飞书开放平台**
- 网址:https://open.feishu.cn
2. **进入权限管理并前往调试台**
- 进入你的**相关应用**
- 点击 **"权限管理"**
- 搜索权限:`im:message`
- 鼠标移动到 **"相关API事件"**
- 选择:**【API】发送消息**
- 点击右下角:**"前往API调试台"**
3. **找到 "快速复制 open_id"**
- 在页面中找到 **蓝色文字** "快速复制 open_id"
- 点击这个链接
4. **选择用户并复制**
- 在弹出的选择框中,**选择你的账号**
- 点击 **"复制"** 按钮
- 得到格式如:`ou_5b921cba0fd6e7c885276a02d730ec19`
💡 **提示**OpenID 是以 `ou_` 开头的一串字符,不是数字ID
---
### 第2步:开通并发布权限
⚠️ **重要:需要开通权限并发布应用新版本**
**开通权限步骤:**
1. **进入权限管理**
- 登录 https://open.feishu.cn
- 进入你的应用
- 点击左侧菜单 **"权限管理"**
2. **搜索并开通权限**
- 在搜索框输入:`docs:permission.member:transfer`
- 找到权限 **"转移云文档的所有权"**
- 点击 **"开通"** 按钮
3. **发布新版本(关键!)**
- 开通后,点击页面右上角的 **"发布"** 按钮
- 等待发布完成(显示"已发布"状态)
- ⚠️ **不发布的话,权限不会生效!**
---
## 💬 请回复配置信息
请按以下格式回复:
```
配置OpenIDou_你的OpenID
权限已开通并发布:是
```
例如:
```
配置OpenIDou_5b921cba0fd6e7c885276a02d730ec19
权限已开通并发布:是
```
配置完成后,本 Skill 将自动保存配置,之后创建文档会自动转移所有权给你!
"""
async def write_smart(ctx, args: dict) -> dict:
"""
智能创建飞书文档(自动分块 + 自动转移所有权)
首次使用时会自动引导配置。
Args:
ctx: OpenClaw 上下文
args: 包含 title, content, folder_token, chunk_size, show_progress
Returns:
{"doc_url": "...", "doc_token": "...", "chunks_count": N, "owner_transferred": True/False}
"""
# 加载用户配置
config = UserConfig.load()
# 首次使用或未完成配置,显示引导
if config.first_time or not config.owner_openid:
return {
"doc_url": None,
"doc_token": None,
"chunks_count": 0,
"owner_transferred": False,
"need_config": True,
"message": FIRST_TIME_GUIDE
}
# 已配置,正常执行
title = args.get("title")
content = args.get("content", "")
folder_token = args.get("folder_token")
chunk_size = args.get("chunk_size", 2000)
show_progress = args.get("show_progress", True)
if not title:
raise ValueError("必须提供 title 参数")
from .feishu_smart_doc_writer import FeishuDocWriter, ChunkConfig
chunk_config = ChunkConfig(
chunk_size=chunk_size,
show_progress=show_progress
)
writer = FeishuDocWriter(ctx, chunk_config)
try:
# 使用配置的 owner_openid 自动转移
result = await writer.write_document_with_transfer(
title=title,
content=content,
folder_token=folder_token,
owner_openid=config.owner_openid
)
transfer_msg = ",所有权已转移" if result.get("owner_transferred") else ""
return {
"doc_url": result["doc_url"],
"doc_token": result["doc_token"],
"chunks_count": result["chunks_count"],
"owner_transferred": result["owner_transferred"],
"need_config": False,
"message": f"✅ 文档创建成功,共分 {result['chunks_count']} 块写入{transfer_msg}"
}
except Exception as e:
return {
"doc_url": None,
"doc_token": None,
"chunks_count": 0,
"owner_transferred": False,
"need_config": False,
"message": f"❌ 创建失败: {e}"
}
async def configure(ctx, args: dict) -> dict:
"""
配置 Skill
用户首次使用时,通过此工具配置 OpenID。
Args:
ctx: OpenClaw 上下文
args: 包含 openid, permission_checked
Returns:
{"success": True/False, "message": "..."}
"""
openid = args.get("openid", "").strip()
permission_checked = args.get("permission_checked", False)
# 验证 OpenID 格式
if not openid:
return {
"success": False,
"message": "❌ 请提供 OpenID"
}
if not openid.startswith("ou_"):
return {
"success": False,
"message": "❌ OpenID 格式错误,应以 'ou_' 开头,请检查"
}
# 保存配置
config = UserConfig()
config.owner_openid = openid
config.permission_noted = permission_checked
config.first_time = False
if config.save():
return {
"success": True,
"openid": openid,
"message": f"✅ 配置成功!\n\n你的 OpenID{openid}\n\n配置已保存,现在可以使用 write_smart 创建文档,所有权会自动转移给你。"
}
else:
return {
"success": False,
"message": "❌ 配置保存失败"
}
async def append_smart(ctx, args: dict) -> dict:
"""
智能追加内容到飞书文档(自动分块)
Args:
ctx: OpenClaw 上下文
args: 包含 doc_url, content, chunk_size, show_progress
Returns:
{"success": True/False, "chunks_count": N}
"""
doc_url = args.get("doc_url")
content = args.get("content", "")
chunk_size = args.get("chunk_size", 2000)
show_progress = args.get("show_progress", True)
if not doc_url:
raise ValueError("必须提供 doc_url 参数")
from .feishu_smart_doc_writer import FeishuDocWriter, ChunkConfig, ContentChunker
config = ChunkConfig(
chunk_size=chunk_size,
show_progress=show_progress
)
writer = FeishuDocWriter(ctx, config)
try:
success = await writer.append_to_document(doc_url, content)
# 计算分块数
chunks = ContentChunker(config).chunk_content(content)
return {
"success": success,
"chunks_count": len(chunks),
"message": f"{'' if success else ''} 追加 {'成功' if success else '失败'},共分 {len(chunks)}"
}
except Exception as e:
return {
"success": False,
"chunks_count": 0,
"message": f"❌ 追加失败: {e}"
}
async def transfer_ownership(ctx, args: dict) -> dict:
"""
转移文档所有权
Args:
ctx: OpenClaw 上下文
args: 包含 doc_url, owner_openid
Returns:
{"success": True/False, "message": "..."}
"""
doc_url = args.get("doc_url")
owner_openid = args.get("owner_openid")
if not doc_url or not owner_openid:
raise ValueError("必须提供 doc_url 和 owner_openid 参数")
from .feishu_smart_doc_writer import FeishuDocWriter, ChunkConfig
config = ChunkConfig(show_progress=False)
writer = FeishuDocWriter(ctx, config)
try:
success = await writer.transfer_ownership(doc_url, owner_openid)
return {
"success": success,
"message": f"{'' if success else ''} 所有权转移{'成功' if success else '失败'}"
}
except Exception as e:
return {
"success": False,
"message": f"❌ 转移失败: {e}"
}
async def get_config_status(ctx, args: dict) -> dict:
"""
获取当前配置状态
Returns:
{"configured": True/False, "openid": "...", "message": "..."}
"""
config = UserConfig.load()
if config.owner_openid:
return {
"configured": True,
"openid": config.owner_openid,
"first_time": config.first_time,
"message": f"✅ 已配置\nOpenID: {config.owner_openid}"
}
else:
return {
"configured": False,
"openid": None,
"first_time": config.first_time,
"message": "⚠️ 未配置\n请使用 configure 工具进行配置"
}
async def search_docs(ctx, args: dict) -> dict:
"""
搜索本地索引中的文档
Args:
ctx: OpenClaw 上下文
args: 包含 keyword, search_in(可选)
Returns:
{"results": [...], "count": N, "message": "..."}
"""
keyword = args.get("keyword", "").strip()
search_in = args.get("search_in", ["name", "summary", "tags"])
if not keyword:
return {
"results": [],
"count": 0,
"message": "❌ 请提供搜索关键词"
}
try:
from .index_manager import IndexManager
manager = IndexManager()
results = manager.search_docs(keyword, search_in)
# 格式化结果
formatted_results = []
for doc in results:
formatted_results.append({
"name": doc.get("name", ""),
"type": doc.get("type", ""),
"link": doc.get("link", ""),
"summary": doc.get("summary", ""),
"status": doc.get("status", ""),
"tags": doc.get("tags", ""),
"updated": doc.get("updated", "")
})
return {
"results": formatted_results,
"count": len(formatted_results),
"message": f"✅ 找到 {len(formatted_results)} 个结果" if formatted_results else f"⚠️ 未找到包含 '{keyword}' 的文档"
}
except Exception as e:
return {
"results": [],
"count": 0,
"message": f"❌ 搜索失败: {e}"
}
async def list_docs(ctx, args: dict) -> dict:
"""
列出所有文档(支持筛选)
Args:
ctx: OpenClaw 上下文
args: 包含 tag(可选), status(可选), limit(可选)
Returns:
{"results": [...], "count": N, "message": "..."}
"""
tag = args.get("tag")
status = args.get("status")
limit = args.get("limit", 50)
try:
from .index_manager import IndexManager
manager = IndexManager()
results = manager.list_docs(tag=tag, status=status, limit=limit)
# 格式化结果
formatted_results = []
for doc in results:
formatted_results.append({
"name": doc.get("name", ""),
"type": doc.get("type", ""),
"link": doc.get("link", ""),
"summary": doc.get("summary", ""),
"status": doc.get("status", ""),
"tags": doc.get("tags", ""),
"updated": doc.get("updated", "")
})
# 构建消息
filter_desc = []
if tag:
filter_desc.append(f"标签 '{tag}'")
if status:
filter_desc.append(f"状态 '{status}'")
filter_text = "".join(filter_desc) if filter_desc else "全部"
return {
"results": formatted_results,
"count": len(formatted_results),
"message": f"{filter_text}文档共 {len(formatted_results)}"
}
except Exception as e:
return {
"results": [],
"count": 0,
"message": f"❌ 列出文档失败: {e}"
}
# 版本信息
__version__ = "1.3.0"
__all__ = [
"write_smart",
"append_smart",
"transfer_ownership",
"configure",
"get_config_status",
"search_docs",
"list_docs",
"UserConfig",
"FIRST_TIME_GUIDE"
]
@@ -0,0 +1,6 @@
{
"ownerId": "kn75sh5zhpf0spta18sqc7bd4s81dvr9",
"slug": "feishu-smart-doc-writer",
"version": "1.4.1",
"publishedAt": 1771834474198
}
@@ -0,0 +1 @@
{"openid": "ou_5b921cba0fd6e7c885276a02d730ec19", "permission_checked": true}
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
使用示例 - 展示如何在 OpenClaw Skill 中使用 feishu_smart_doc_writer
"""
# 示例 1: 在 Skill 中写入长文档
async def example_write_long_doc(ctx):
"""
在 OpenClaw Skill 中写入长文档
ctx 是 OpenClaw 提供的上下文对象
"""
from feishu_smart_doc_writer import FeishuDocWriter, ChunkConfig
# 创建写入器,传入 ctx
config = ChunkConfig(
chunk_size=2000, # 每块2000字符
show_progress=True, # 显示进度
max_retries=3 # 失败重试3次
)
writer = FeishuDocWriter(ctx, config)
# 准备长内容
long_content = """
# 项目策划文档
## 一、项目背景
这是项目的详细背景介绍...
## 二、技术方案
| 技术 | 说明 | 优势 |
|------|------|------|
| Python | 编程语言 | 生态丰富 |
| OpenClaw | 自动化框架 | 功能强大 |
## 三、实施计划
1. 第一阶段...
2. 第二阶段...
3. 第三阶段...
""" * 100 # 重复100次,生成约50000字符的长内容
# 写入文档
doc_url = await writer.write_document(
title="项目策划 - 完整版",
content=long_content,
folder_token=None # 可选:指定文件夹
)
print(f"✅ 文档创建成功: {doc_url}")
return doc_url
# 示例 2: 追加内容到现有文档
async def example_append_to_doc(ctx, doc_url):
"""
向已有文档追加内容
"""
from feishu_smart_doc_writer import FeishuDocWriter
writer = FeishuDocWriter(ctx)
# 追加内容
append_content = """
## 四、补充说明
这是后来补充的内容...
""" * 50
success = await writer.append_to_document(
doc_url=doc_url,
content=append_content
)
if success:
print("✅ 内容追加成功")
else:
print("❌ 内容追加失败")
return success
# 示例 3: 在 Skill 定义中使用
SKILL_DEFINITION = {
"name": "my_skill",
"tools": [
{
"name": "create_project_doc",
"description": "创建项目文档",
"handler": "create_project_doc_handler"
}
]
}
async def create_project_doc_handler(ctx, args):
"""
Skill 工具处理函数
"""
from feishu_smart_doc_writer import FeishuDocWriter
title = args.get("title", "未命名文档")
content = args.get("content", "")
writer = FeishuDocWriter(ctx)
doc_url = await writer.write_document(title, content)
return {
"doc_url": doc_url,
"message": "文档创建成功"
}
# 示例 4: 直接使用同步函数(简化版)
def example_sync_usage(ctx):
"""
同步方式使用(在不需要async的环境中)
"""
from feishu_smart_doc_writer import write_document_sync
doc_url = write_document_sync(
ctx=ctx,
title="同步创建的文档",
content="# 标题\n\n大量内容..." * 1000
)
return doc_url
if __name__ == "__main__":
print("Feishu Smart Doc Writer 使用示例")
print("=" * 50)
print()
print("在 OpenClaw Skill 中使用:")
print()
print("1. 导入:")
print(" from feishu_smart_doc_writer import FeishuDocWriter")
print()
print("2. 创建写入器:")
print(" writer = FeishuDocWriter(ctx)")
print()
print("3. 写入长文档:")
print(" doc_url = await writer.write_document(title, content)")
print()
print("4. 追加内容:")
print(" await writer.append_to_document(doc_url, content)")
@@ -0,0 +1,536 @@
#!/usr/bin/env python3
"""
Feishu Smart Doc Writer - 改进版
自动分块写入 + 自动转移所有权 + 自动更新索引
"""
import re
import time
import json
import asyncio
from typing import List, Optional
from dataclasses import dataclass
# 导入索引管理器
try:
from .index_manager import IndexManager, add_doc_to_index
except ImportError:
from index_manager import IndexManager, add_doc_to_index
@dataclass
class ChunkConfig:
"""分块配置"""
chunk_size: int = 2000 # 每块最大字符数
max_retries: int = 3 # 最大重试次数
retry_delay: float = 1.0 # 重试间隔(秒)
show_progress: bool = True # 显示进度
convert_tables: bool = True # 转换表格为文本
class ContentChunker:
"""内容分块器"""
def __init__(self, config: ChunkConfig = None):
self.config = config or ChunkConfig()
def chunk_content(self, content: str) -> List[str]:
"""
将长内容分割成多个小块
策略:按段落分割,如果段落超过限制,按句子分割
"""
chunks = []
current_chunk = ""
# 先处理表格
if self.config.convert_tables:
content = self._convert_tables(content)
# 按段落分割
paragraphs = self._split_paragraphs(content)
for para in paragraphs:
# 如果当前块加上新段落会超限
if len(current_chunk) + len(para) > self.config.chunk_size:
# 保存当前块
if current_chunk.strip():
chunks.append(current_chunk.strip())
# 如果单个段落就超限,需要进一步分割
if len(para) > self.config.chunk_size:
sub_chunks = self._split_large_paragraph(para)
chunks.extend(sub_chunks)
current_chunk = ""
else:
current_chunk = para
else:
current_chunk += "\n\n" + para if current_chunk else para
# 保存最后一块
if current_chunk.strip():
chunks.append(current_chunk.strip())
return chunks
def _convert_tables(self, content: str) -> str:
"""将Markdown表格转换为文本列表"""
table_pattern = r'\|[^\n]+\|\n\|[-:| ]+\|\n((?:\|[^\n]+\|\n)+)'
def convert_table(match):
table_text = match.group(0)
lines = table_text.strip().split('\n')
# 提取表头
header = [cell.strip() for cell in lines[0].split('|')[1:-1]]
# 提取数据行
result = ["【表格内容】"]
for line in lines[2:]: # 跳过表头和分隔线
cells = [cell.strip() for cell in line.split('|')[1:-1]]
if cells and any(cells): # 确保不是空行
row_text = ", ".join([f"{h}: {c}" for h, c in zip(header, cells)])
result.append(f"- {row_text}")
return "\n".join(result)
return re.sub(table_pattern, convert_table, content)
def _split_paragraphs(self, content: str) -> List[str]:
"""按段落分割,保留标题结构"""
lines = content.split('\n')
paragraphs = []
current_para = ""
for line in lines:
stripped = line.strip()
if not stripped:
if current_para.strip():
paragraphs.append(current_para.strip())
current_para = ""
continue
# 如果是标题,单独成段
if stripped.startswith('#'):
if current_para.strip():
paragraphs.append(current_para.strip())
current_para = ""
paragraphs.append(stripped)
else:
current_para += line + "\n"
if current_para.strip():
paragraphs.append(current_para.strip())
return paragraphs
def _split_large_paragraph(self, para: str) -> List[str]:
"""分割大段落(按句子)"""
chunks = []
sentences = re.split(r'([。!?.\n])', para)
current = ""
for i in range(0, len(sentences), 2):
sentence = sentences[i]
if i + 1 < len(sentences):
sentence += sentences[i + 1] # 加上标点
if len(current) + len(sentence) > self.config.chunk_size:
if current.strip():
chunks.append(current.strip())
current = sentence
else:
current += sentence
if current.strip():
chunks.append(current.strip())
return chunks
class FeishuDocWriter:
"""
飞书文档智能写入器
使用 OpenClaw 官方工具调用方式
"""
def __init__(self, ctx=None, config: ChunkConfig = None):
"""
初始化
Args:
ctx: OpenClaw 上下文对象(在 Skill 中传入)
config: 分块配置
"""
self.ctx = ctx
self.config = config or ChunkConfig()
self.chunker = ContentChunker(config)
async def write_document(self, title: str, content: str, folder_token: str = None) -> str:
"""
创建新文档并写入内容(自动分块)
Args:
title: 文档标题
content: 文档内容(支持长内容,自动分块)
folder_token: 可选的文件夹token
Returns:
文档URL
"""
if not self.ctx:
raise ValueError("需要提供 OpenClaw 上下文对象 (ctx)")
# 第一步:创建空文档(只传标题)
doc_token = await self._create_empty_doc(title, folder_token)
doc_url = f"https://feishu.cn/docx/{doc_token}"
if self.config.show_progress:
print(f"✅ 文档创建成功: {doc_url}")
# 第二步:分批追加内容
success = await self._write_content_in_chunks(doc_token, content)
if not success:
raise Exception("写入内容失败")
return doc_url
async def append_to_document(self, doc_url: str, content: str) -> bool:
"""
追加内容到现有文档(自动分块)
Args:
doc_url: 文档URL
content: 要追加的内容
Returns:
是否成功
"""
if not self.ctx:
raise ValueError("需要提供 OpenClaw 上下文对象 (ctx)")
doc_token = self._extract_token_from_url(doc_url)
return await self._write_content_in_chunks(doc_token, content)
async def _create_empty_doc(self, title: str, folder_token: str = None) -> str:
"""创建空文档,只传标题"""
try:
# 使用 OpenClaw 官方工具调用方式
result = await self.ctx.invoke_tool("feishu_doc.create", {
"title": title,
"folder_token": folder_token
})
# 提取 doc_token
if isinstance(result, dict):
doc_token = result.get("document_id") or result.get("doc_token")
if doc_token:
return doc_token
# 如果是字符串,尝试提取
if isinstance(result, str):
import re
match = re.search(r'docx/([a-zA-Z0-9]+)', result)
if match:
return match.group(1)
return result
raise Exception(f"无法解析文档token: {result}")
except Exception as e:
raise Exception(f"创建文档失败: {e}")
async def _write_content_in_chunks(self, doc_token: str, content: str) -> bool:
"""分批写入内容"""
chunks = self.chunker.chunk_content(content)
if self.config.show_progress:
print(f"📝 内容已分割为 {len(chunks)} 块,开始写入...")
for i, chunk in enumerate(chunks, 1):
if self.config.show_progress:
print(f" 写入第 {i}/{len(chunks)} 块 ({len(chunk)} 字符)...")
success = await self._append_chunk_with_retry(doc_token, chunk)
if not success:
print(f"❌ 第 {i} 块写入失败")
return False
# 添加小延迟,避免API限流
if i < len(chunks):
await asyncio.sleep(0.5)
if self.config.show_progress:
print(f"✅ 全部 {len(chunks)} 块写入完成")
return True
async def _append_chunk_with_retry(self, doc_token: str, chunk: str) -> bool:
"""带重试的追加内容"""
for attempt in range(self.config.max_retries):
try:
return await self._append_chunk(doc_token, chunk)
except Exception as e:
if self.config.show_progress:
print(f" 尝试 {attempt + 1}/{self.config.max_retries} 失败: {e}")
if attempt < self.config.max_retries - 1:
await asyncio.sleep(self.config.retry_delay * (attempt + 1))
else:
return False
return False
async def _append_chunk(self, doc_token: str, chunk: str) -> bool:
"""追加单块内容"""
try:
# 使用 OpenClaw 官方工具调用方式
await self.ctx.invoke_tool("feishu_doc.append", {
"doc_token": doc_token,
"content": chunk
})
return True
except Exception as e:
raise Exception(f"API调用失败: {e}")
def _extract_token_from_url(self, url: str) -> str:
"""从URL中提取doc_token"""
import re
match = re.search(r'docx/([a-zA-Z0-9]+)', url)
if match:
return match.group(1)
raise ValueError(f"无法从URL提取token: {url}")
async def _get_tenant_access_token(self) -> str:
"""获取飞书 tenant_access_token"""
import aiohttp
import json
import os
# 尝试从 OpenClaw 配置读取 App ID 和 Secret
app_id, app_secret = None, None
# 方法1: 尝试从环境变量读取
app_id = os.environ.get("FEISHU_APP_ID")
app_secret = os.environ.get("FEISHU_APP_SECRET")
# 方法2: 尝试从 OpenClaw 配置文件读取
if not app_id or not app_secret:
try:
config_paths = [
os.path.expanduser("~/.openclaw/openclaw.json"),
os.path.expanduser("~/.openclaw/config.json"),
]
for config_path in config_paths:
if os.path.exists(config_path):
with open(config_path, 'r') as f:
config = json.load(f)
feishu_config = config.get("channels", {}).get("feishu", {})
app_id = feishu_config.get("appId")
app_secret = feishu_config.get("appSecret")
if app_id and app_secret:
break
except Exception:
pass
if not app_id or not app_secret:
return ""
# 调用飞书 API 获取 token
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
payload = {
"app_id": app_id,
"app_secret": app_secret
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload) as resp:
result = await resp.json()
if result.get("code") == 0:
return result.get("tenant_access_token", "")
except Exception:
pass
return ""
async def transfer_ownership(self, doc_url: str, owner_openid: str) -> bool:
"""
转移文档所有权 - 直接调用飞书 API
API端点: POST /drive/v1/permissions/{token}/members/transfer_owner?type=docx
Args:
doc_url: 文档URL
owner_openid: 新所有者的openid (例如: ou_xxxxxxxx)
Returns:
是否成功
"""
import aiohttp
import json
doc_token = self._extract_token_from_url(doc_url)
# 获取 tenant_access_token
token = await self._get_tenant_access_token()
if not token:
if self.config.show_progress:
print(f"⚠️ 无法获取 tenant_access_token")
return False
# 调用飞书 API 转移所有权
url = f"https://open.feishu.cn/open-apis/drive/v1/permissions/{doc_token}/members/transfer_owner?type=docx"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"member_type": "openid",
"member_id": owner_openid
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=payload) as resp:
result = await resp.json()
if result.get("code") == 0:
if self.config.show_progress:
print(f"✅ 文档所有权已转移给 {owner_openid}")
return True
else:
error_msg = result.get("msg", "未知错误")
if self.config.show_progress:
print(f"⚠️ 所有权转移失败: {error_msg}")
return False
except Exception as e:
if self.config.show_progress:
print(f"⚠️ 所有权转移失败: {e}")
return False
async def write_document_with_transfer(
self,
title: str,
content: str,
folder_token: str = None,
owner_openid: str = None
) -> dict:
"""
创建文档并写入内容,完成后自动转移所有权并更新本地索引
Args:
title: 文档标题
content: 文档内容
folder_token: 可选的文件夹token
owner_openid: 新所有者的openid,如果提供则自动转移所有权
Returns:
{
"doc_url": "...",
"doc_token": "...",
"chunks_count": N,
"owner_transferred": True/False,
"index_updated": True/False
}
"""
# 1. 创建并写入文档
doc_url = await self.write_document(title, content, folder_token)
# 2. 提取 doc_token
doc_token = self._extract_token_from_url(doc_url)
# 3. 计算分块数
chunks = self.chunker.chunk_content(content)
# 4. 转移所有权(如果提供了 owner_openid
owner_transferred = False
if owner_openid:
owner_transferred = await self.transfer_ownership(doc_url, owner_openid)
# 5. 【关键】自动更新本地索引
index_updated = False
try:
# 生成摘要(取前100字)
summary = content[:100].replace('\n', ' ') + "..." if len(content) > 100 else content
# 自动分类标签
tags = self._auto_classify_content(content, title)
# 更新索引
index_updated = add_doc_to_index(
name=title,
url=doc_url,
token=doc_token,
summary=summary,
tags=tags,
owner=owner_openid or ""
)
if self.config.show_progress and index_updated:
print(f"✅ 文档索引已更新")
elif self.config.show_progress:
print(f"⚠️ 文档索引更新失败(不影响文档创建)")
except Exception as e:
if self.config.show_progress:
print(f"⚠️ 索引更新失败: {e}(不影响文档创建)")
return {
"doc_url": doc_url,
"doc_token": doc_token,
"chunks_count": len(chunks),
"owner_transferred": owner_transferred,
"index_updated": index_updated
}
def _auto_classify_content(self, content: str, title: str) -> List[str]:
"""根据内容自动分类"""
tags = []
text = (title + " " + content).lower()
# 关键词映射到标签
if any(k in text for k in ["ai", "人工智能", "模型", "gpt", "llm"]):
tags.append("AI技术")
if any(k in text for k in ["openclaw", "skill", "agent"]):
tags.append("OpenClaw")
if any(k in text for k in ["飞书", "文档", "feishu", "docx"]):
tags.append("飞书文档")
if any(k in text for k in ["电商", "tiktok", "alibaba", "玩具"]):
tags.append("电商")
if any(k in text for k in ["garmin", "strava", "骑行", "健康", "运动"]):
tags.append("健康运动")
if any(k in text for k in ["对话", "归档", "聊天记录"]):
tags.append("每日归档")
# 如果没有匹配到特定标签,添加通用标签
if not tags:
tags.append("其他")
return tags
# 同步包装函数(方便非异步环境使用)
def write_document_sync(ctx, title: str, content: str, folder_token: str = None, config: ChunkConfig = None) -> str:
"""同步方式写入文档"""
writer = FeishuDocWriter(ctx, config)
return asyncio.run(writer.write_document(title, content, folder_token))
def write_document_with_transfer_sync(
ctx,
title: str,
content: str,
folder_token: str = None,
owner_openid: str = None,
config: ChunkConfig = None
) -> dict:
"""同步方式写入文档并转移所有权"""
writer = FeishuDocWriter(ctx, config)
return asyncio.run(writer.write_document_with_transfer(title, content, folder_token, owner_openid))
def append_to_document_sync(ctx, doc_url: str, content: str, config: ChunkConfig = None) -> bool:
"""同步方式追加文档"""
writer = FeishuDocWriter(ctx, config)
return asyncio.run(writer.append_to_document(doc_url, content))
@@ -0,0 +1,349 @@
"""
Feishu 文档索引管理器
负责管理 memory/feishu-docs-index.md 文件
"""
import os
import re
import json
from datetime import datetime
from typing import List, Dict, Optional, Tuple
class IndexManager:
"""飞书文档索引管理器"""
DEFAULT_INDEX_PATH = os.path.expanduser("~/.openclaw/workspace/memory/feishu-docs-index.md")
def __init__(self, index_path: str = None):
self.index_path = index_path or self.DEFAULT_INDEX_PATH
self._ensure_index_exists()
def _ensure_index_exists(self):
"""确保索引文件存在"""
if not os.path.exists(self.index_path):
os.makedirs(os.path.dirname(self.index_path), exist_ok=True)
self._create_empty_index()
def _create_empty_index(self):
"""创建空索引文件"""
content = """# 飞书云文档索引
**用途:** 快速定位和管理所有飞书云文档
---
## 📊 文档列表
| 序号 | 文档名 | 类型 | 链接 | 内容摘要 | 状态 | 最后更新 | 标签 | 所有者 |
|------|--------|------|------|----------|------|----------|------|--------|
---
## 📂 按类型分类
### 项目管理
### 技术文档
### 每日归档
### 更新记录
### 数据分析
---
## 🔍 快速查找
*暂无关键词索引*
---
## 📝 使用说明
**添加新文档时:**
1. 复制表格中的一行
2. 填写所有字段
3. 更新分类索引
**查找文档时:**
1. 先搜索本文档中的关键词
2. 找到对应链接
3. 用 `feishu_doc` 工具读取内容
---
*创建时间:{date}*
*最后更新:{date}*
""".format(date=datetime.now().strftime("%Y-%m-%d"))
with open(self.index_path, 'w', encoding='utf-8') as f:
f.write(content)
def add_or_update_doc(self, name: str, doc_type: str, url: str, token: str,
summary: str = "", status: str = "已完成",
tags: List[str] = None, owner: str = "") -> bool:
"""
添加或更新文档到索引
Args:
name: 文档名称
doc_type: 文档类型 (docx, sheet, bitable 等)
url: 文档链接
token: 文档token
summary: 内容摘要
status: 文档状态
tags: 标签列表
owner: 所有者
Returns:
是否成功
"""
try:
# 读取现有索引
with open(self.index_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查文档是否已存在(通过token判断)
existing_index = self._find_doc_index(content, token)
# 获取当前序号
if existing_index:
doc_index = existing_index
else:
doc_index = self._get_next_index(content)
# 准备标签字符串
tags_str = ", ".join(tags) if tags else ""
# 准备表格行
now = datetime.now().strftime("%Y-%m-%d")
new_row = f"| {doc_index} | {name} | {doc_type} | {url} | {summary} | {status} | {now} | {tags_str} | {owner} |"
# 更新表格
if existing_index:
# 更新现有行
content = self._replace_table_row(content, token, new_row)
else:
# 添加新行
content = self._insert_table_row(content, new_row)
# 更新分类(如果新文档)
if not existing_index and tags:
content = self._update_categories(content, name, tags)
# 更新关键词索引
if not existing_index:
content = self._update_keywords(content, name, summary, tags)
# 更新最后更新时间
content = self._update_last_modified(content)
# 写回文件
with open(self.index_path, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception as e:
print(f"更新索引失败: {e}")
return False
def search_docs(self, keyword: str, search_in: List[str] = None) -> List[Dict]:
"""
搜索文档
Args:
keyword: 搜索关键词
search_in: 在哪些字段中搜索 (name, summary, tags)
Returns:
匹配的文档列表
"""
if search_in is None:
search_in = ["name", "summary", "tags"]
results = []
docs = self._parse_table()
keyword_lower = keyword.lower()
for doc in docs:
match = False
if "name" in search_in and keyword_lower in doc.get("name", "").lower():
match = True
if "summary" in search_in and keyword_lower in doc.get("summary", "").lower():
match = True
if "tags" in search_in and keyword_lower in doc.get("tags", "").lower():
match = True
if match:
results.append(doc)
return results
def list_docs(self, tag: str = None, status: str = None, limit: int = None) -> List[Dict]:
"""
列出文档
Args:
tag: 按标签筛选
status: 按状态筛选
limit: 限制数量
Returns:
文档列表
"""
docs = self._parse_table()
# 筛选
if tag:
docs = [d for d in docs if tag in d.get("tags", "")]
if status:
docs = [d for d in docs if d.get("status") == status]
# 限制数量
if limit:
docs = docs[:limit]
return docs
def get_doc_by_token(self, token: str) -> Optional[Dict]:
"""通过token获取文档信息"""
docs = self._parse_table()
for doc in docs:
if token in doc.get("link", ""):
return doc
return None
def _parse_table(self) -> List[Dict]:
"""解析索引表格"""
docs = []
try:
with open(self.index_path, 'r', encoding='utf-8') as f:
content = f.read()
# 找到表格部分
lines = content.split('\n')
in_table = False
for line in lines:
if line.startswith('| 序号 '):
in_table = True
continue
if in_table and line.startswith('|---'):
continue
if in_table and line.startswith('|') and not line.startswith('|------'):
# 解析表格行
parts = [p.strip() for p in line.split('|')[1:-1]]
if len(parts) >= 8:
# 兼容8列表格格式(序号、文档名、类型、链接、摘要、状态、更新时间、备注)
docs.append({
"index": parts[0],
"name": parts[1],
"type": parts[2],
"link": parts[3],
"summary": parts[4],
"status": parts[5],
"updated": parts[6],
"tags": parts[7], # 使用"备注"列作为标签
"owner": "" # 所有者信息暂时为空
})
except Exception as e:
print(f"解析索引失败: {e}")
return docs
def _find_doc_index(self, content: str, token: str) -> Optional[str]:
"""查找文档是否已存在,返回序号"""
pattern = r'\| (\d+) \| [^|]+ \| [^|]+ \| [^/]+/docx/' + re.escape(token) + r'[^|]* \|'
match = re.search(pattern, content)
if match:
return match.group(1)
return None
def _get_next_index(self, content: str) -> int:
"""获取下一个序号"""
pattern = r'\| (\d+) \|'
matches = re.findall(pattern, content)
if matches:
return max([int(m) for m in matches]) + 1
return 1
def _replace_table_row(self, content: str, token: str, new_row: str) -> str:
"""替换表格中的某一行"""
pattern = r'(\| \d+ \| [^|]+ \| [^|]+ \| [^/]+/docx/' + re.escape(token) + r'[^|]* \|[^\n]+)'
return re.sub(pattern, new_row, content)
def _insert_table_row(self, content: str, new_row: str) -> str:
"""插入新行到表格"""
# 在表格头部后插入
lines = content.split('\n')
insert_index = -1
for i, line in enumerate(lines):
if line.startswith('| 序号 '):
insert_index = i + 2 # 跳过表头和分隔线
break
if insert_index > 0:
lines.insert(insert_index, new_row)
return '\n'.join(lines)
def _update_categories(self, content: str, name: str, tags: List[str]) -> str:
"""更新分类列表"""
# 简单实现:在对应分类下添加文档名
# 这里可以扩展更复杂的逻辑
return content
def _update_keywords(self, content: str, name: str, summary: str, tags: List[str]) -> str:
"""更新关键词索引"""
# 提取关键词
keywords = []
if tags:
keywords.extend(tags)
# 这里可以添加更智能的关键词提取
# 暂时保持简单
return content
def _update_last_modified(self, content: str) -> str:
"""更新最后修改时间"""
now = datetime.now().strftime("%Y-%m-%d")
pattern = r'\*最后更新:[^*]+\*'
replacement = f'*最后更新:{now}*'
return re.sub(pattern, replacement, content)
# 便捷函数
def add_doc_to_index(name: str, url: str, token: str, summary: str = "",
tags: List[str] = None, owner: str = "") -> bool:
"""便捷函数:添加文档到索引"""
manager = IndexManager()
return manager.add_or_update_doc(
name=name,
doc_type="docx",
url=url,
token=token,
summary=summary,
status="已完成",
tags=tags,
owner=owner
)
def search_docs(keyword: str) -> List[Dict]:
"""便捷函数:搜索文档"""
manager = IndexManager()
return manager.search_docs(keyword)
def list_all_docs(tag: str = None) -> List[Dict]:
"""便捷函数:列出所有文档"""
manager = IndexManager()
return manager.list_docs(tag=tag)
@@ -0,0 +1,50 @@
{
"name": "feishu-smart-doc-writer",
"version": "1.4.1",
"description": "Feishu/Lark Smart Document Writer v1.4.1 - 飞书智能文档写入器. Auto-chunk writing, auto ownership transfer, document index management with search/list. Config OpenID on first use.",
"author": "OpenClaw User",
"license": "MIT",
"tags": [
"feishu",
"lark",
"飞书",
"document",
"docx",
"writer",
"chunk",
"smart",
"ownership-transfer",
"文档",
"写入"
],
"homepage": "https://github.com/openclaw/openclaw",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"keywords": [
"feishu",
"lark",
"飞书",
"document",
"writer",
"chunk",
"split",
"api-limit",
"ownership-transfer",
"文档写入",
"长文档"
],
"openclaw": {
"minVersion": "2026.2.0",
"tools": [
"write_smart",
"append_smart",
"search_docs",
"list_docs",
"transfer_ownership",
"configure",
"get_config_status"
]
}
}
+193
View File
@@ -0,0 +1,193 @@
{
"name": "feishu-smart-doc-writer",
"version": "1.4.1",
"description": "Feishu/Lark Smart Document Writer v1.4.1 - 飞书智能文档写入器. Auto-chunk writing, auto ownership transfer, document index management with search/list. Config OpenID on first use.",
"author": "OpenClaw",
"license": "MIT",
"tools": [
{
"name": "write_smart",
"description": "Smart create Feishu/Lark doc with auto-chunk writing and ownership transfer. Guides OpenID config on first use. 智能创建飞书文档,自动分块写入,自动转移所有权。首次使用时引导配置OpenID。",
"handler": "feishu_smart_doc_writer.write_smart",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Document title / 文档标题"
},
"content": {
"type": "string",
"description": "Document content (long content auto-chunked) / 文档内容(支持长内容,会自动分块处理)"
},
"folder_token": {
"type": "string",
"description": "Optional folder token / 可选的文件夹token"
},
"chunk_size": {
"type": "integer",
"description": "Max chars per chunk, default 2000 / 每块最大字符数,默认2000",
"default": 2000
},
"show_progress": {
"type": "boolean",
"description": "Show progress / 是否显示写入进度",
"default": true
}
},
"required": ["title", "content"]
},
"returns": {
"type": "object",
"properties": {
"doc_url": {"type": "string"},
"doc_token": {"type": "string"},
"chunks_count": {"type": "integer"},
"owner_transferred": {"type": "boolean"},
"need_config": {"type": "boolean", "description": "Need config / 是否需要配置"},
"message": {"type": "string"}
}
}
},
{
"name": "configure",
"description": "Configure Skill. Provide OpenID and confirm permission on first use. 配置Skill。首次使用时提供OpenID并确认权限已开通。",
"handler": "feishu_smart_doc_writer.configure",
"parameters": {
"type": "object",
"properties": {
"openid": {
"type": "string",
"description": "Your Feishu/Lark OpenID (format: ou_xxxxxxxx) / 你的飞书OpenID(格式:ou_xxxxxxxx"
},
"permission_checked": {
"type": "boolean",
"description": "Confirmed docs:permission.member:transfer permission granted / 是否已确认开通docs:permission.member:transfer权限",
"default": false
}
},
"required": ["openid"]
},
"returns": {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"openid": {"type": "string"},
"message": {"type": "string"}
}
}
},
{
"name": "append_smart",
"description": "Smart append content to Feishu/Lark doc with auto-chunk. 智能追加内容到飞书文档,自动分块。",
"handler": "feishu_smart_doc_writer.append_smart",
"parameters": {
"type": "object",
"properties": {
"doc_url": {"type": "string"},
"content": {"type": "string"},
"chunk_size": {"type": "integer", "default": 2000},
"show_progress": {"type": "boolean", "default": true}
},
"required": ["doc_url", "content"]
},
"returns": {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"chunks_count": {"type": "integer"},
"message": {"type": "string"}
}
}
},
{
"name": "transfer_ownership",
"description": "Transfer Feishu/Lark doc ownership. Requires docs:permission.member:transfer permission. 转移飞书文档所有权。需要docs:permission.member:transfer权限。",
"handler": "feishu_smart_doc_writer.transfer_ownership",
"parameters": {
"type": "object",
"properties": {
"doc_url": {"type": "string"},
"owner_openid": {"type": "string"}
},
"required": ["doc_url", "owner_openid"]
},
"returns": {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"message": {"type": "string"}
}
}
},
{
"name": "get_config_status",
"description": "Get current config status to check if OpenID is configured. 获取当前配置状态,查看是否已配置OpenID。",
"handler": "feishu_smart_doc_writer.get_config_status",
"parameters": {"type": "object", "properties": {}},
"returns": {
"type": "object",
"properties": {
"configured": {"type": "boolean"},
"openid": {"type": "string"},
"first_time": {"type": "boolean"},
"message": {"type": "string"}
}
}
},
{
"name": "search_docs",
"description": "Search local document index by keywords. Searches in document name, summary, and tags. 搜索本地文档索引,支持关键词搜索。",
"handler": "index_manager.search_docs",
"parameters": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "Search keyword / 搜索关键词"
}
},
"required": ["keyword"]
},
"returns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"link": {"type": "string"},
"summary": {"type": "string"},
"tags": {"type": "string"}
}
}
}
},
{
"name": "list_docs",
"description": "List all Feishu documents from local index. Optionally filter by tag. 列出所有飞书文档,可按标签筛选。",
"handler": "index_manager.list_all_docs",
"parameters": {
"type": "object",
"properties": {
"tag": {
"type": "string",
"description": "Optional tag filter / 可选的标签筛选"
}
}
},
"returns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"link": {"type": "string"},
"summary": {"type": "string"},
"tags": {"type": "string"}
}
}
}
}
],
"dependencies": []
}
@@ -0,0 +1,5 @@
{
"owner_openid": "ou_5b921cba0fd6e7c885276a02d730ec19",
"permission_noted": true,
"first_time": false
}