Skill: qiuwenxi416488212-ship-it/feishu-sheets-toolkit (version 1.0.0)

--- name: feishu-sheets description: | Feishu online spreadsheet (Sheets) operations including create, read, write, append data, manage worksheets. Use when user mentions Feishu Sheets, online spreadsheet, electronic spreadsheet (not Bitable/multi-dimensional table). Supports: create spreadsheet, write/read cell values, append rows, insert/delete rows/columns, manage worksheets. --- # Feishu Sheets Tool Single tool `feishu_sheets` with action parameter for all spreadsheet operations. ## Token Extraction From URL `https://xxx.feishu.cn/sheets/shtABC123` → `spreadsheet_token` = `shtABC123` ## Actions ### Create Spreadsheet ```json { "action": "create", "title": "New Spreadsheet" } ``` Optional folder: ```json { "action": "create", "title": "New Spreadsheet", "folder_token": "fldcnXXX" } ``` Returns: spreadsheet_token, url, title ### Write Values ```json { "action": "write", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx", "range": "A1:C3", "values": [["Name", "Age", "City"], ["Alice", 25, "Beijing"], ["Bob", 30, "Shanghai"]] } ``` ### Read Values ```json { "action": "read", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx", "range": "A1:C10" } ``` ### Append Values ```json { "action": "append", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx", "values": [["Charlie", 28, "Shenzhen"]] } ``` ### Insert Rows/Columns ```json { "action": "insert_dimension", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx", "dimension": "ROWS", "start_index": 5, "end_index": 7 } ``` ### Delete Rows/Columns ```json { "action": "delete_dimension", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx", "dimension": "ROWS", "start_index": 5, "end_index": 7 } ``` ### Get Spreadsheet Info ```json { "action": "get_info", "spreadsheet_token": "shtABC123" } ``` Returns: metadata including all sheet_ids and titles ### Add Worksheet ```json { "action": "add_sheet", "spreadsheet_token": "shtABC123", "title": "Sheet2" } ``` ### Delete Worksheet ```json { "action": "delete_sheet", "spreadsheet_token": "shtABC123", "sheet_id": "0bxxxx" } ``` ## Range Format - Cell: `A1`, `B5` - Range: `A1:C10`, `B2:D5` - Entire column: `A:A`, `B:D` - Entire row: `1:1`, `3:5` - With sheet_id: `0bxxxx!A1:C10` ## Sheet ID - From URL: `https://xxx.feishu.cn/sheets/shtABC123?sheet=0bxxxx` - From get_info action - Default first sheet often has simple id like `0bxxxx` ## Data Types Values can be: - String: `"Hello"` - Number: `123`, `45.67` - Formula: `{"type": "formula", "text": "=SUM(A1:A10)"}` - Link: `{"type": "url", "text": "Click here", "link": "https://..."}` ## Configuration ```yaml channels: feishu: tools: sheets: true # default: true ``` ## Permissions Required - `sheets:spreadsheet` - Create and manage spreadsheets - `sheets:spreadsheet:readonly` - Read spreadsheet data - `drive:drive` - Access cloud storage ## API Reference Base URL: `https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/` See references/api-reference.md for detailed API documentation. ## Code Implementation Python实现: eishu_sheets.py `python from feishu_sheets import FeishuSheets # 创建请求 fs = FeishuSheets() req = fs.build_create_request('New Sheet') # Token提取 [REDACTED_SECRET]'https://xxx.feishu.cn/sheets/shtABC123') `

path: feishu_enhanced.py
content: #!/usr/bin/env python3
"""Feishu Sheets Enhanced - 飞书表格增强"""

import requests
import json


class FeishuSheetsEnhanced:
    """飞书表格增强功能"""
    
    def __init__(self, app_id=None, app_secret=None):
        self.app_id = app_id
        self.app_secret = app_secret
        self.access_token = None
    
    def get_access_token(self):
        """获取Access Token"""
        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
        data = {
            "app_id": self.app_id,
            "app_secret": self.app_secret
        }
        resp = requests.post(url, json=data)
        if resp.status_code == 200:
            self.access_token = resp.json().get("tenant_access_token")
            return self.access_token
        return None
    
    def get_spreadsheet(self, spreadsheet_token):
        """获取表格元信息"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        resp = requests.get(url, headers=headers)
        return resp.json()
    
    def get_sheet_meta(self, spreadsheet_token, sheet_id):
        """获取Sheet元信息"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheet/{sheet_id}"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        resp = requests.get(url, headers=headers)
        return resp.json()
    
    def add_sheet(self, spreadsheet_token, title):
        """添加Sheet"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"properties": [{"title": title}]}
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def copy_sheet(self, spreadsheet_token, sheet_id, new_title):
        """复制Sheet"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/copy"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"sheet": {"sheet_id": sheet_id, "title": new_title}}
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def delete_sheet(self, spreadsheet_token, sheet_id):
        """删除Sheet"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        resp = requests.delete(url, headers=headers)
        return resp.json()
    
    def insert_dimension(self, spreadsheet_token, sheet_id, dimension, start_index, end_index):
        """插入行或列"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/insert_dimension"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {
            "dimension": dimension,
            "start_index": start_index,
            "end_index": end_index
        }
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def delete_dimension(self, spreadsheet_token, sheet_id, dimension, start_index, end_index):
        """删除行或列"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/delete_dimension"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {
            "dimension": dimension,
            "start_index": start_index,
            "end_index": end_index
        }
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def set_style(self, spreadsheet_token, sheet_id, range, style):
        """设置样式"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/style"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"range": range, "style": style}
        resp = requests.put(url, json=data, headers=headers)
        return resp.json()
    
    def protect_sheet(self, spreadsheet_token, sheet_id, permission):
        """保护Sheet"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/protection"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"permission": permission}
        resp = requests.put(url, json=data, headers=headers)
        return resp.json()
    
    def find_replace(self, spreadsheet_token, sheet_id, find, replace, match_case=False):
        """查找替换"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/find_replace"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {
            "find": find,
            "replacement": replace,
            "match_case": match_case
        }
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def create_condition_format(self, spreadsheet_token, sheet_id, range, condition):
        """创建条件格式"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/conditional_format"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"range": range, "condition": condition}
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()
    
    def get_comments(self, spreadsheet_token, sheet_id):
        """获取评论"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/comments"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        resp = requests.get(url, headers=headers)
        return resp.json()
    
    def add_comment(self, spreadsheet_token, sheet_id, cell, content):
        """添加评论"""
        url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheet/{spreadsheet_token}/sheets/{sheet_id}/comments"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        data = {"cell": cell, "message": content}
        resp = requests.post(url, json=data, headers=headers)
        return resp.json()


if __name__ == "__main__":
    print("Feishu Sheets Enhanced loaded")

path: feishu_sheets.py
content: #!/usr/bin/env python3
"""
Feishu Sheets 操作工具
飞书在线表格 (Sheets) 操作: 创建/读取/写入/追加/管理
注意: 这是操作在线电子表格,不是多维表格(Bitable)
"""

import os
import json
import re
from typing import Union, List, Dict, Any, Optional, Tuple


class FeishuSheets:
    """飞书表格操作类"""
    
    def __init__(self, spreadsheet_token: str = None):
        self.spreadsheet_token = spreadsheet_token
        self.sheet_id = None
        
    # ============== Token提取 ==============
    @staticmethod
    def extract_token(url: str) -> str:
        """从URL提取spreadsheet_token"""
        # 例如: https://xxx.feishu.cn/sheets/shtABC123 -> shtABC123
        match = re.search(r'sheets/([a-zA-Z0-9]+)', url)
        if match:
            return match.group(1)
        return url
    
    @staticmethod
    def extract_sheet_id(url: str) -> str:
        """从URL提取sheet_id"""
        # 例如: ?sheet=0bxxxx -> 0bxxxx
        match = re.search(r'sheet=([a-zA-Z0-9]+)', url)
        if match:
            return match.group(1)
        return None
    
    # ============== 数据转换 ==============
    @staticmethod
    def prepare_values(data: Union[List, Dict]) -> List[List]:
        """准备写入的数据格式"""
        if isinstance(data, dict):
            # 字典转为单行
            return [list(data.values())]
        return data
    
    @staticmethod
    def parse_range(range_str: str, sheet_id: str = None) -> str:
        """解析区域格式"""
        if sheet_id and '!' not in range_str:
            return f'{sheet_id}!{range_str}'
        elif '!' in range_str:
            return range_str
        else:
            return f'{range_str}'
    
    # ============== 构建请求 ==============
    def build_create_request(self, title: str, folder_token: str = None) -> dict:
        """构建创建表格请求"""
        req = {"action": "create", "title": title}
        if folder_token:
            req["folder_token"] = folder_token
        return req
    
    def build_write_request(self, spreadsheet_token: str, sheet_id: str,
                          range_str: str, values: List[List]) -> dict:
        """构建写入请求"""
        return {
            "action": "write",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id,
            "range": range_str,
            "values": values
        }
    
    def build_read_request(self, spreadsheet_token: str, sheet_id: str,
                          range_str: str) -> dict:
        """构建读取请求"""
        return {
            "action": "read",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id,
            "range": range_str
        }
    
    def build_append_request(self, spreadsheet_token: str, sheet_id: str,
                           values: List[List]) -> dict:
        """构建追加请求"""
        return {
            "action": "append",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id,
            "values": values
        }
    
    def build_get_info_request(self, spreadsheet_token: str) -> dict:
        """构建获取信息请求"""
        return {"action": "get_info", "spreadsheet_token": spreadsheet_token}
    
    def build_add_sheet_request(self, spreadsheet_token: str, title: str) -> dict:
        """构建添加Sheet请求"""
        return {
            "action": "add_sheet",
            "spreadsheet_token": spreadsheet_token,
            "title": title
        }
    
    def build_delete_sheet_request(self, spreadsheet_token: str, sheet_id: str) -> dict:
        """构建删除Sheet请求"""
        return {
            "action": "delete_sheet",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id
        }
    
    def build_insert_dimension_request(self, spreadsheet_token: str, sheet_id: str,
                                      dimension: str, start_index: int, 
                                      end_index: int = None) -> dict:
        """构建插入行/列请求"""
        if end_index is None:
            end_index = start_index + 1
            
        return {
            "action": "insert_dimension",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id,
            "dimension": dimension,  # ROWS 或 COLS
            "start_index": start_index,
            "end_index": end_index
        }
    
    def build_delete_dimension_request(self, spreadsheet_token: str, sheet_id: str,
                                      dimension: str, start_index: int,
                                      end_index: int = None) -> dict:
        """构建删除行/列请求"""
        if end_index is None:
            end_index = start_index + 1
            
        return {
            "action": "delete_dimension",
            "spreadsheet_token": spreadsheet_token,
            "sheet_id": sheet_id,
            "dimension": dimension,
            "start_index": start_index,
            "end_index": end_index
        }
    
    # ============== 数据类型 ==============
    @staticmethod
    def make_formula(text: str) -> dict:
        """创建公式类型"""
        return {"type": "formula", "text": text}
    
    @staticmethod
    def make_link(text: str, url: str) -> dict:
        """创建链接类型"""
        return {"type": "url", "text": text, "link": url}
    
    # ============== 便捷方法 ==============
    def write_single_value(self, spreadsheet_token: str, sheet_id: str,
                          row: int, col: int, value: Any) -> dict:
        """写入单个值"""
        cell = f'{get_column_letter(col)}{row}'
        range_str = f'{sheet_id}!{cell}:{cell}'
        
        # 转换值为列表格式
        values = [[value]]
        
        return self.build_write_request(spreadsheet_token, sheet_id, range_str, values)


# ============== 辅助函数 ==============
def get_column_letter(col_idx: int) -> str:
    """列索引转字母"""
    result = ""
    while col_idx > 0:
        col_idx -= 1
        result = chr(65 + col_idx % 26) + result
        col_idx //= 26
    return result


def parse_cell(cell: str) -> Tuple[int, int]:
    """解析单元格坐标"""
    import re
    match = re.match(r'([A-Z]+)(\d+)', cell.upper())
    if match:
        col = column_index_from_string(match.group(1))
        row = int(match.group(2))
        return row, col
    return 1, 1


def column_index_from_string(col_str: str) -> int:
    """字母转列索引"""
    result = 0
    for c in col_str.upper():
        result = result * 26 + (ord(c) - 64)
    return result


# ============== 与DataParser集成 ==============
def import_from_dataframe(df, spreadsheet_token: str = None) -> List[Dict]:
    """从DataFrame导入数据"""
    data = df.values.tolist()
    headers = df.columns.tolist()
    
    # 添加表头
    result = [headers] + data
    
    return result


# ============== 测试 ==============
if __name__ == '__main__':
    print('Feishu Sheets toolkit loaded')
    
    # 测试Token提取
    url = 'https://xxx.feishu.cn/sheets/shtABC123?sheet=0bxxxx'
    [REDACTED_SECRET])
    sheet_id = FeishuSheets.extract_sheet_id(url)
    print(f'[REDACTED_SECRET], Sheet ID: {sheet_id}')
    
    # 测试请求构建
    fs = FeishuSheets()
    
    # 创建
    req = fs.build_create_request('Test Sheet')
    print(f'Create: {req}')
    
    # 写入
    req = fs.build_write_request('shtABC', '0bxxxx', 'A1:C3', [['a','b','c'],[1,2,3]])
    print(f'Write: {req}')
    
    # 追加
    req = fs.build_append_request('shtABC', '0bxxxx', [['d',4,5]])
    print(f'Append: {req}')

path: README.md
content: # Feishu Sheets Toolkit
## 飞书表格操作工具箱 | 简化的飞书在线表格处理

<p align="center">
  <img src="https://img.shields.io/github/stars/XiLi/feishu-sheets-toolkit" alt="Stars">
  <img src="https://img.shields.io/github/license/XiLi/feishu-sheets-toolkit" alt="License">
</p>

---

## 项目简介

Feishu Sheets Toolkit (飞书表格工具箱) 是飞书在线电子表格的操作工具，帮助你自动化管理飞书Sheets。无论是数据同步、批量写入还是报表自动化，这个工具都能提升你的工作效率。

> **让飞书表格操作变得更简单!**

---

## 核心功能

| 功能 | 说明 |
|------|------|
| 🔑 Token提取 | 从URL提取表格ID |
| 📝 请求构建 | 创建/读取/写入/追加请求 |
| 🔗 数据类型 | 支持公式/链接 |
| 📤 批量操作 | 批量数据同步 |

---

## 快速开始

### Token提取

```python
from feishu_sheets import FeishuSheets

# 从URL提取Token
url = 'https://xxx.feishu.cn/sheets/shtABC123?sheet=0bxxxx'
[REDACTED_SECRET])     # shtABC123
sheet_id = FeishuSheets.extract_sheet_id(url)  # 0bxxxx
```

### 构建请求

```python
from feishu_sheets import FeishuSheets

fs = FeishuSheets()

# 创建表格
req = fs.build_create_request('销售数据表')

# 写入数据
req = fs.build_write_request(
    spreadsheet_token='shtABC123',
    sheet_id='0bxxxx',
    range='A1:C3',
    values=[
        ['日期', '销售额', '利润'],
        ['2026-01', 10000, 2000],
        ['2026-02', 15000, 3500]
    ]
)

# 追加数据
req = fs.build_append_request(
    spreadsheet_token='shtABC123',
    sheet_id='0bxxxx',
    values=[['2026-03', 12000, 1800]]
)
```

---

## 详细功能

### Token处理

```python
# 提取spreadsheet_token
[REDACTED_SECRET]'https://xxx.feishu.cn/sheets/shtABC123')
# 结果: shtABC123

# 提取sheet_id
sheet_id = FeishuSheets.extract_sheet_id('https://xxx.feishu.cn/sheets/shtABC123?sheet=0bxxxx')
# 结果: 0bxxxx
```

### 请求构建

```python
# 创建表格
req = fs.build_create_request('新表格')

# 读取数据
req = fs.build_read_request('shtABC', '0bxxxx', 'A1:C10')

# 添加Sheet
req = fs.build_add_sheet_request('shtABC', 'Sheet2')

# 删除Sheet
req = fs.build_delete_sheet_request('shtABC', '0bxxxx')
```

### 特殊数据类型

```python
# 公式
formula = fs.make_formula('=SUM(A1:A10)')

# 链接
link = fs.make_link('点击查看', 'https://example.com')
```

### 插入行/列

```python
# 插入行
req = fs.build_insert_dimension_request(
    spreadsheet_token='shtABC',
    sheet_id='0bxxxx',
    dimension='ROWS',
    start_index=5,
    end_index=7
)

# 删除列
req = fs.build_delete_dimension_request(
    spreadsheet_token='shtABC',
    sheet_id='0bxxxx',
    dimension='COLS',
    start_index=2,
    end_index=3
)
```

---

## 范围格式

| 格式 | 示例 |
|------|------|
| 单个单元格 | A1, B5 |
| 区域 | A1:C10, B2:D5 |
| 整列 | A:A, B:D |
| 整行 | 1:1, 3:5 |
| 带Sheet | 0bxxxx!A1:C10 |

---

## API参考

| 方法 | 功能 |
|------|------|
| extract_token() | 提取spreadsheet_token |
| extract_sheet_id() | 提取sheet_id |
| build_create_request() | 创建表格请求 |
| build_write_request() | 写入请求 |
| build_read_request() | 读取请求 |
| build_append_request() | 追加请求 |
| build_add_sheet_request() | 添加Sheet请求 |

---

## 使用前提

- 飞书开放平台应用权限
- 配置 channels.feishu

---

## 许可证

MIT License

---

## 🔧 OpenClaw / Claude Code 使用

本技能已集成到 OpenClaw 技能系统,可直接使用:

`python
# 在 OpenClaw 或 Claude Code 中使用
from ai_workflow import run_workflow
from chart_generator import ChartGenerator
from data_parser import DataParser
from database_ops import DatabaseOps
from excel_parser import ExcelParser
from feishu_sheets import FeishuSheets
`

或通过 skills 目录调用:

`python
import sys
sys.path.insert(0, 'path/to/skills')
`



path: .clawhub/origin.json
content: version: 1
registry: https://clawhub.ai
slug: feishu-sheets-skill
installedVersion: 1.0.0
installedAt: 1775164234870

path: references/api-reference.md
content: # Feishu Sheets API Reference

## Base URLs

- Sheets API v2: `https://open.feishu.cn/open-apis/sheets/v2/spreadsheets`
- Sheets API v3: `https://open.feishu.cn/open-apis/sheets/v3/spreadsheets`
- Auth API: `https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal`

## Authentication

All API calls require a tenant access token in the Authorization header:
```
Authorization: Bearer {tenant_access_token}
```

Get token by calling auth API with app_id and app_secret.

## Core APIs

### 1. Create Spreadsheet

**Endpoint:** `POST /sheets/v3/spreadsheets`

**Request Body:**
```json
{
  "title": "Spreadsheet Name",
  "folder_token": "fldcnXXX"  // optional
}
```

**Response:**
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "spreadsheet": {
      "title": "Spreadsheet Name",
      "spreadsheet_token": "shtcnXXX",
      "url": "https://xxx.feishu.cn/sheets/shtcnXXX"
    }
  }
}
```

### 2. Get Spreadsheet Info

**Endpoint:** `GET /sheets/v2/spreadsheets/{spreadsheet_token}/metainfo`

**Response:**
```json
{
  "code": 0,
  "data": {
    "properties": {
      "title": "Spreadsheet Name"
    },
    "sheets": [
      {
        "sheet_id": "0bxxxx",
        "title": "Sheet1",
        "grid_properties": {
          "row_count": 1000,
          "column_count": 20
        }
      }
    ]
  }
}
```

### 3. Read Values

**Endpoint:** `GET /sheets/v2/spreadsheets/{spreadsheet_token}/values/{range}`

**Range Formats:**
- `A1:C10` - Cell range
- `0bxxxx!A1:C10` - With sheet_id
- `Sheet1!A1:C10` - With sheet title

**Response:**
```json
{
  "code": 0,
  "data": {
    "range": "0bxxxx!A1:C3",
    "values": [
      ["Name", "Age", "City"],
      ["Alice", 25, "Beijing"],
      ["Bob", 30, "Shanghai"]
    ]
  }
}
```

### 4. Write Values

**Endpoint:** `PUT /sheets/v2/spreadsheets/{spreadsheet_token}/values`

**Request Body:**
```json
{
  "valueRange": {
    "range": "0bxxxx!A1:C3",
    "values": [
      ["Name", "Age", "City"],
      ["Alice", 25, "Beijing"],
      ["Bob", 30, "Shanghai"]
    ]
  }
}
```

### 5. Append Values

**Endpoint:** `POST /sheets/v2/spreadsheets/{spreadsheet_token}/values_append`

**Request Body:**
```json
{
  "valueRange": {
    "range": "0bxxxx",
    "values": [
      ["Charlie", 28, "Shenzhen"]
    ]
  }
}
```

### 6. Insert Rows/Columns

**Endpoint:** `POST /sheets/v2/spreadsheets/{spreadsheet_token}/insert_dimension_range`

**Request Body:**
```json
{
  "dimension": {
    "sheetId": "0bxxxx",
    "majorDimension": "ROWS",  // or "COLUMNS"
    "startIndex": 5,
    "endIndex": 7
  }
}
```

### 7. Delete Rows/Columns

**Endpoint:** `DELETE /sheets/v2/spreadsheets/{spreadsheet_token}/dimension_range`

**Request Body:** Same as insert

### 8. Add Worksheet

**Endpoint:** `POST /sheets/v2/spreadsheets/{spreadsheet_token}/sheets_batch_update`

**Request Body:**
```json
{
  "requests": [{
    "addSheet": {
      "properties": {
        "title": "New Sheet"
      }
    }
  }]
}
```

### 9. Delete Worksheet

**Endpoint:** `POST /sheets/v2/spreadsheets/{spreadsheet_token}/sheets_batch_update`

**Request Body:**
```json
{
  "requests": [{
    "deleteSheet": {
      "sheetId": "0bxxxx"
    }
  }]
}
```

## Data Types

### Cell Values

**String:**
```json
"Hello World"
```

**Number:**
```json
123
45.67
```

**Formula:**
```json
{
  "type": "formula",
  "text": "=SUM(A1:A10)"
}
```

**Hyperlink:**
```json
{
  "type": "url",
  "text": "Click here",
  "link": "https://example.com"
}
```

## Error Codes

| Code | Message | Description |
|------|---------|-------------|
| 0 | success | Request successful |
| 10001 | bad request | Invalid parameters |
| 10002 | unauthorized | Invalid or expired token |
| 10003 | forbidden | Insufficient permissions |
| 10004 | not found | Spreadsheet or sheet not found |
| 10005 | internal error | Server error |

## Rate Limits

- Default: 100 requests per minute per app
- Bulk operations (batch_update): 20 requests per minute

## Best Practices

1. **Reuse token:** Cache tenant_access_token for up to 2 hours
2. **Batch operations:** Use batch_update for multiple changes
3. **Range selection:** Be specific with ranges to reduce data transfer
4. **Error handling:** Always check response code before processing data
5. **Sheet ID:** Use sheet_id (not title) for reliable operations


path: scripts/feishu_sheets.py
content: #!/usr/bin/env python3
"""
Feishu Sheets API Client
Supports: create, read, write, append, insert/delete rows/columns
"""

import os
import sys
import json
import requests
from typing import List, Dict, Any, Optional

# API Base URLs
BASE_URL = "https://open.feishu.cn/open-apis"
SHEETS_API = f"{BASE_URL}/sheets/v2/spreadsheets"
AUTH_API = f"{BASE_URL}/auth/v3/tenant_access_token/internal"

class FeishuSheetsClient:
    def __init__(self):
        self.app_id = os.getenv("FEISHU_APP_ID")
        self.app_secret = os.getenv("FEISHU_APP_SECRET")
        self._token = None
        
    def _get_token(self) -> str:
        """Get tenant access token"""
        if self._token:
            return self._token
            
        resp = requests.post(AUTH_API, json={
            "app_id": self.app_id,
            "app_secret": self.app_secret
        })
        resp.raise_for_status()
        data = resp.json()
        
        if data.get("code") != 0:
            raise Exception(f"Auth failed: {data}")
            
        self._token = data["tenant_access_token"]
        return self._token
        
    def _headers(self) -> Dict[str, str]:
        return {
            "Authorization": f"Bearer {self._get_token()}",
            "Content-Type": "application/json"
        }
        
    def create_spreadsheet(self, title: str, folder_token: Optional[str] = None) -> Dict:
        """Create a new spreadsheet"""
        url = f"{BASE_URL}/sheets/v3/spreadsheets"
        payload = {"title": title}
        if folder_token:
            payload["folder_token"] = folder_token
            
        resp = requests.post(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def get_spreadsheet_info(self, spreadsheet_token: str) -> Dict:
        """Get spreadsheet metadata including sheets"""
        url = f"{SHEETS_API}/{spreadsheet_token}/metainfo"
        resp = requests.get(url, headers=self._headers())
        resp.raise_for_status()
        return resp.json()
        
    def read_values(self, spreadsheet_token: str, sheet_id: str, range_str: str) -> Dict:
        """Read values from a range"""
        range_param = f"{sheet_id}!{range_str}" if sheet_id else range_str
        url = f"{SHEETS_API}/{spreadsheet_token}/values/{range_param}"
        resp = requests.get(url, headers=self._headers())
        resp.raise_for_status()
        return resp.json()
        
    def write_values(self, spreadsheet_token: str, sheet_id: str, 
                     range_str: str, values: List[List[Any]]) -> Dict:
        """Write values to a range"""
        url = f"{SHEETS_API}/{spreadsheet_token}/values"
        range_param = f"{sheet_id}!{range_str}" if sheet_id else range_str
        payload = {
            "valueRange": {
                "range": range_param,
                "values": values
            }
        }
        resp = requests.put(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def append_values(self, spreadsheet_token: str, sheet_id: str, 
                      values: List[List[Any]]) -> Dict:
        """Append values to the end of sheet"""
        url = f"{SHEETS_API}/{spreadsheet_token}/values_append"
        payload = {
            "valueRange": {
                "range": sheet_id,
                "values": values
            }
        }
        resp = requests.post(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def insert_dimension(self, spreadsheet_token: str, sheet_id: str,
                        dimension: str, start_index: int, end_index: int) -> Dict:
        """Insert rows or columns"""
        url = f"{SHEETS_API}/{spreadsheet_token}/insert_dimension_range"
        payload = {
            "dimension": {
                "sheetId": sheet_id,
                "majorDimension": dimension,  # ROWS or COLUMNS
                "startIndex": start_index,
                "endIndex": end_index
            }
        }
        resp = requests.post(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def delete_dimension(self, spreadsheet_token: str, sheet_id: str,
                        dimension: str, start_index: int, end_index: int) -> Dict:
        """Delete rows or columns"""
        url = f"{SHEETS_API}/{spreadsheet_token}/dimension_range"
        payload = {
            "dimension": {
                "sheetId": sheet_id,
                "majorDimension": dimension,
                "startIndex": start_index,
                "endIndex": end_index
            }
        }
        resp = requests.delete(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def add_sheet(self, spreadsheet_token: str, title: str) -> Dict:
        """Add a new worksheet"""
        url = f"{SHEETS_API}/{spreadsheet_token}/sheets_batch_update"
        payload = {
            "requests": [{
                "addSheet": {
                    "properties": {"title": title}
                }
            }]
        }
        resp = requests.post(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()
        
    def delete_sheet(self, spreadsheet_token: str, sheet_id: str) -> Dict:
        """Delete a worksheet"""
        url = f"{SHEETS_API}/{spreadsheet_token}/sheets_batch_update"
        payload = {
            "requests": [{
                "deleteSheet": {"sheetId": sheet_id}
            }]
        }
        resp = requests.post(url, headers=self._headers(), json=payload)
        resp.raise_for_status()
        return resp.json()


def main():
    """CLI entry point for tool calls"""
    if len(sys.argv) < 2:
        print(json.dumps({"error": "No action specified"}))
        sys.exit(1)
        
    action = sys.argv[1]
    client = FeishuSheetsClient()
    
    try:
        if action == "create":
            title = sys.argv[2]
            folder = sys.argv[3] if len(sys.argv) > 3 else None
            result = client.create_spreadsheet(title, folder)
            
        elif action == "get_info":
            [REDACTED_SECRET]
            result = client.get_spreadsheet_info(token)
            
        elif action == "read":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            range_str = sys.argv[4]
            result = client.read_values(token, sheet_id, range_str)
            
        elif action == "write":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            range_str = sys.argv[4]
            values = json.loads(sys.argv[5])
            result = client.write_values(token, sheet_id, range_str, values)
            
        elif action == "append":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            values = json.loads(sys.argv[4])
            result = client.append_values(token, sheet_id, values)
            
        elif action == "insert_dimension":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            dimension = sys.argv[4]
            start = int(sys.argv[5])
            end = int(sys.argv[6])
            result = client.insert_dimension(token, sheet_id, dimension, start, end)
            
        elif action == "delete_dimension":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            dimension = sys.argv[4]
            start = int(sys.argv[5])
            end = int(sys.argv[6])
            result = client.delete_dimension(token, sheet_id, dimension, start, end)
            
        elif action == "add_sheet":
            [REDACTED_SECRET]
            title = sys.argv[3]
            result = client.add_sheet(token, title)
            
        elif action == "delete_sheet":
            [REDACTED_SECRET]
            sheet_id = sys.argv[3]
            result = client.delete_sheet(token, sheet_id)
            
        else:
            result = {"error": f"Unknown action: {action}"}
            
        print(json.dumps(result, ensure_ascii=False, indent=2))
        
    except Exception as e:
        print(json.dumps({"error": str(e)}, ensure_ascii=False))
        sys.exit(1)


if __name__ == "__main__":
    main()


path: _meta.json
content: ownerId: kn710qwp94vjc36crngn15sfq9819j6c
slug: feishu-sheets-skill
version: 1.0.0
publishedAt: 1771220240776

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)