Skill: cxwos/dingtalk-bitable (version 1.0.0)

--- name: dingtalk-bitable description: "钉钉多维表格 API 集成 - 表格管理、数据 CRUD。Use when: user asks about DingTalk bitable, table, database, spreadsheet." metadata: { "openclaw": { "emoji": "📊", "requires": { "python": ["requests"] } } } --- # DingTalk Bitable 技能 钉钉多维表格（Bitable）API 集成，提供表格管理、数据增删改查等功能。 ## 功能 ### 📊 表格管理 - 获取表格列表 - 创建新表格 - 获取表格结构（字段/列） - 删除表格 ### 📝 数据操作 - 查询记录（支持筛选、排序） - 新增记录 - 更新记录 - 删除记录 - 批量操作 ### 🔍 高级功能 - 字段管理（添加/修改/删除字段） - 视图管理 - 数据导入导出 ## 配置 ### 钉钉应用权限 需要在钉钉开放平台申请以下权限： - 多维表格权限 - 应用访问权限 ### 凭证配置 技能自动从 openclaw.json 的钉钉 channel 配置中读取凭证，无需额外配置。 ## 工具 ### bitable_list 获取表格列表 **参数：** - `space_id` (可选): 空间 ID - `limit` (可选): 返回数量限制，默认 20 ### bitable_get_meta 获取表格元数据 **参数：** - `app_token` (必需): 表格应用 token（从 URL 获取） ### bitable_list_fields 获取表格字段列表 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID ### bitable_list_records 查询记录 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `filter` (可选): 筛选条件 - `sort` (可选): 排序字段 - `page_size` (可选): 每页数量，默认 100 - `page_token` (可选): 分页 token ### bitable_get_record 获取单条记录 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `record_id` (必需): 记录 ID ### bitable_create_record 新增记录 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `fields` (必需): 字段值（JSON 对象） ### bitable_update_record 更新记录 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `record_id` (必需): 记录 ID - `fields` (必需): 要更新的字段值 ### bitable_delete_record 删除记录 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `record_id` (必需): 记录 ID ### bitable_create_field 添加字段 **参数：** - `app_token` (必需): 表格应用 token - `table_id` (必需): 表格 ID - `field_name` (必需): 字段名称 - `field_type` (必需): 字段类型（Text/Number/Date/SingleSelect/MultiSelect/Checkbox/User/Phone/URL） ## 使用示例 ``` # 获取表格列表 bitable_list limit=10 # 获取表格元数据（从 URL /base/XXX 或 /wiki/XXX 获取 app_token） bitable_get_meta app_token="Stbqxxxxxxxxxxxxxx" # 查看表格字段 bitable_list_fields app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" # 查询记录 bitable_list_records app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" page_size=50 # 新增记录 bitable_create_record app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" fields='{"姓名":"张三","部门":"研发部","入职日期":"2026-03-19"}' # 更新记录 bitable_update_record app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" record_id="recxxxxxxxxxxxxxx" fields='{"部门":"产品部"}' # 删除记录 bitable_delete_record app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" record_id="recxxxxxxxxxxxxxx" # 添加字段 bitable_create_field app_token="Stbqxxxxxxxxxxxxxx" table_id="tblxxxxxxxxxxxxxx" field_name="手机号" field_type="Phone" ``` ## 字段类型说明 | 类型 | 说明 | 示例值 | |------|------|--------| | Text | 文本 | "张三" | | Number | 数字 | 123 | | Date | 日期 | 1710835200000 (毫秒时间戳) | | SingleSelect | 单选 | "选项 A" | | MultiSelect | 多选 | ["选项 A", "选项 B"] | | Checkbox | 复选框 | true | | User | 成员 | [{"id":"zhangsan"}] | | Phone | 手机号 | "13800138000" | | URL | 链接 | {"text":"显示文本","link":"https://..."} | ## 实现位置 技能实现位于：[REDACTED_SECRET] 包含文件： - `SKILL.md` - 技能定义 - `dingtalk_bitable.py` - API 实现 - `run.py` - 工具执行入口 - `manifest.json` - 工具定义 ## 错误处理 - 凭证无效：返回认证错误 - 权限不足：提示需要申请的权限 - API 限流：自动重试 - 表格不存在：返回明确的错误信息 ## 相关资源 - 钉钉开放平台文档：https://open.dingtalk.com/document/orgapp/overview-of-dingtalk-tables - API 参考：https://open.dingtalk.com/document/orgapp

path: dingtalk_bitable.py
content: #!/usr/bin/env python3
"""
钉钉多维表格 API 技能实现
提供表格管理、数据增删改查等功能
"""

import os
import json
import time
from datetime import datetime
from typing import Optional, List, Dict, Any
import requests


# ======================= 配置 =======================

DINGTALK_API_BASE = "https://api.dingtalk.com"

# 从环境变量或配置文件读取凭证
def get_dingtalk_credentials():
    """获取钉钉凭证"""
    # 优先从环境变量读取
    client_id = os.environ.get("DINGTALK_CLIENT_ID")
    client_secret = os.environ.get("DINGTALK_CLIENT_SECRET")
    
    # 如果环境变量没有，尝试从配置文件读取
    if not client_id or not client_secret:
        config_path = os.path.expanduser("~/.openclaw/openclaw.json")
        try:
            with open(config_path, "r", encoding="utf-8") as f:
                config = json.load(f)
                dingtalk_config = config.get("channels", {}).get("ddingtalk", {})
                accounts = dingtalk_config.get("accounts", {})
                default_account = accounts.get("default", {})
                client_id = default_account.get("clientId")
                client_secret = default_account.get("clientSecret")
        except Exception:
            pass
    
    if not client_id or not client_secret:
        raise ValueError("钉钉凭证未配置，请设置 DINGTALK_CLIENT_ID 和 DINGTALK_CLIENT_SECRET 环境变量")
    
    return client_id, client_secret


# ======================= Access Token 管理 =======================

_token_cache = {}

def get_access_token(client_id: str, client_secret: str) -> str:
    """获取钉钉 access_token"""
    cache_key = client_id
    current_time = time.time()
    
    # 检查缓存（提前 5 分钟过期）
    if cache_key in _token_cache:
        token_info = _token_cache[cache_key]
        if current_time < token_info["expire_time"] - 300:
            return token_info["token"]
    
    # 请求新 token
    url = f"{DINGTALK_API_BASE}/v1.0/oauth2/accessToken"
    headers = {"Content-Type": "application/json"}
    body = {
        "appKey": client_id,
        "appSecret": client_secret
    }
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    response.raise_for_status()
    result = response.json()
    
    if not result.get("accessToken"):
        raise ValueError(f"获取 access_token 失败：{result}")
    
    [REDACTED_SECRET]"accessToken"]
    expire_in = result.get("expireIn", 7200)  # 默认 2 小时
    
    # 缓存 token
    _token_cache[cache_key] = {
        "token": token,
        "expire_time": current_time + expire_in
    }
    
    return token


def dingtalk_api_request(method: str, path: str, body: Optional[Dict] = None, 
                         access_token: Optional[str] = None,
                         params: Optional[Dict] = None) -> Dict:
    """
    钉钉 API 统一调用方法
    
    Args:
        method: HTTP 方法 (GET/POST/PUT/DELETE)
        path: API 路径
        body: 请求体
        access_token: 可选的 access_token
        params: URL 查询参数
    
    Returns:
        API 响应结果
    """
    url = f"{DINGTALK_API_BASE}{path}"
    headers = {"Content-Type": "application/json"}
    
    if access_token:
        headers["x-acs-dingtalk-access-token"] = access_token
    
    if method.upper() == "GET":
        response = requests.get(url, headers=headers, params=params or body or {}, timeout=30)
    else:
        response = requests.request(
            method=method.upper(),
            url=url,
            headers=headers,
            json=body or {},
            params=params,
            timeout=30
        )
    
    response.raise_for_status()
    return response.json()


# ======================= 多维表格 API =======================

def bitable_list(space_id: Optional[str] = None, limit: int = 20) -> List[Dict]:
    """
    获取多维表格列表
    
    Args:
        space_id: 可选的空间 ID
        limit: 返回数量限制
    
    Returns:
        表格列表
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    params = {"limit": limit}
    if space_id:
        params["space_id"] = space_id
    
    try:
        result = dingtalk_api_request("GET", "/v1.0/bitable/apps", params=params, access_token=access_token)
        return result.get("result", {}).get("list", [])
    except Exception as e:
        return [{"error": f"获取表格列表失败：{str(e)}"}]


def bitable_get_meta(app_token: str) -> Dict:
    """
    获取表格元数据
    
    Args:
        app_token: 表格应用 token（从 URL 获取，如 /base/XXX 或 /wiki/XXX 中的 XXX）
    
    Returns:
        表格元数据（包含表格列表、名称等）
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    try:
        result = dingtalk_api_request("GET", f"/v1.0/bitable/apps/{app_token}", access_token=access_token)
        return result.get("result", {})
    except Exception as e:
        return {"error": f"获取表格元数据失败：{str(e)}"}


def bitable_list_fields(app_token: str, table_id: str) -> List[Dict]:
    """
    获取表格字段列表
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
    
    Returns:
        字段列表
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    try:
        result = dingtalk_api_request(
            "GET", 
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/fields",
            access_token=access_token
        )
        return result.get("result", {}).get("items", [])
    except Exception as e:
        return [{"error": f"获取字段列表失败：{str(e)}"}]


def bitable_list_records(app_token: str, table_id: str, 
                         filter: Optional[str] = None,
                         sort: Optional[str] = None,
                         page_size: int = 100,
                         page_token: Optional[str] = None) -> Dict:
    """
    查询记录
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        filter: 筛选条件（JSON 字符串）
        sort: 排序字段（JSON 字符串）
        page_size: 每页数量，默认 100
        page_token: 分页 token
    
    Returns:
        记录列表和分页信息
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    params = {
        "pageSize": page_size
    }
    
    if page_token:
        params["pageToken"] = page_token
    
    body = {}
    if filter:
        try:
            body["filter"] = json.loads(filter)
        except json.JSONDecodeError:
            body["filter"] = filter
    
    if sort:
        try:
            body["sort"] = json.loads(sort)
        except json.JSONDecodeError:
            body["sort"] = sort
    
    try:
        result = dingtalk_api_request(
            "POST",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/records/search",
            body=body,
            params=params,
            access_token=access_token
        )
        return result.get("result", {})
    except Exception as e:
        return {"error": f"查询记录失败：{str(e)}"}


def bitable_get_record(app_token: str, table_id: str, record_id: str) -> Dict:
    """
    获取单条记录
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        record_id: 记录 ID
    
    Returns:
        记录详情
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    try:
        result = dingtalk_api_request(
            "GET",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/records/{record_id}",
            access_token=access_token
        )
        return result.get("result", {})
    except Exception as e:
        return {"error": f"获取记录失败：{str(e)}"}


def bitable_create_record(app_token: str, table_id: str, fields: Dict) -> str:
    """
    新增记录
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        fields: 字段值（字典）
    
    Returns:
        创建的记录 ID
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    body = {"fields": fields}
    
    try:
        result = dingtalk_api_request(
            "POST",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/records",
            body=body,
            access_token=access_token
        )
        return result.get("result", {}).get("recordId", "")
    except Exception as e:
        return f"创建失败：{str(e)}"


def bitable_update_record(app_token: str, table_id: str, record_id: str, fields: Dict) -> bool:
    """
    更新记录
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        record_id: 记录 ID
        fields: 要更新的字段值
    
    Returns:
        更新结果
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    body = {"fields": fields}
    
    try:
        result = dingtalk_api_request(
            "PUT",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/records/{record_id}",
            body=body,
            access_token=access_token
        )
        return result.get("success", False)
    except Exception as e:
        return False


def bitable_delete_record(app_token: str, table_id: str, record_id: str) -> bool:
    """
    删除记录
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        record_id: 记录 ID
    
    Returns:
        删除结果
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    try:
        result = dingtalk_api_request(
            "DELETE",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/records/{record_id}",
            access_token=access_token
        )
        return result.get("success", False)
    except Exception:
        return False


def bitable_create_field(app_token: str, table_id: str, 
                         field_name: str, field_type: str) -> str:
    """
    添加字段
    
    Args:
        app_token: 表格应用 token
        table_id: 表格 ID
        field_name: 字段名称
        field_type: 字段类型 (Text/Number/Date/SingleSelect/MultiSelect/Checkbox/User/Phone/URL)
    
    Returns:
        创建的字段 ID
    """
    client_id, client_secret = get_dingtalk_credentials()
    access_token = get_access_token(client_id, client_secret)
    
    # 字段类型映射
    type_map = {
        "Text": 1,
        "Number": 2,
        "SingleSelect": 3,
        "MultiSelect": 4,
        "Date": 5,
        "Checkbox": 7,
        "User": 11,
        "Phone": 13,
        "URL": 15,
    }
    
    body = {
        "name": field_name,
        "type": type_map.get(field_type, 1)
    }
    
    try:
        result = dingtalk_api_request(
            "POST",
            f"/v1.0/bitable/apps/{app_token}/tables/{table_id}/fields",
            body=body,
            access_token=access_token
        )
        return result.get("result", {}).get("fieldId", "")
    except Exception as e:
        return f"创建失败：{str(e)}"


# ======================= 工具函数 =======================

def format_field(field: Dict) -> str:
    """格式化字段显示"""
    name = field.get("name", "")
    type_ = field.get("type", "")
    field_id = field.get("fieldId", "")
    return f"{field_id}: {name} ({type_})"


def format_record(record: Dict, fields: Optional[List] = None) -> str:
    """格式化记录显示"""
    record_id = record.get("recordId", "")
    fields_data = record.get("fields", {})
    
    lines = [f"📄 记录 ID: {record_id}"]
    for key, value in fields_data.items():
        lines.append(f"   {key}: {value}")
    
    return "\n".join(lines)


# ======================= 主函数（供技能调用） =======================

def main():
    """测试函数"""
    print("钉钉多维表格 API 技能测试")
    
    try:
        # 测试获取表格列表
        tables = bitable_list(limit=5)
        print(f"\n多维表格 ({len(tables)}):")
        for table in tables:
            print(f"  - {table.get('name', '未知')} ({table.get('appId', '')})")
            
    except Exception as e:
        print(f"测试失败：{e}")


if __name__ == "__main__":
    main()


path: manifest.json
content: {
  "name": "dingtalk-bitable",
  "version": "1.0.0",
  "description": "钉钉多维表格 API 集成 - 表格管理、数据 CRUD",
  "tools": [
    {
      "name": "bitable_list",
      "description": "获取多维表格列表",
      "parameters": {
        "type": "object",
        "properties": {
          "space_id": {
            "type": "string",
            "description": "可选的空间 ID"
          },
          "limit": {
            "type": "number",
            "description": "返回数量限制，默认 20"
          }
        }
      }
    },
    {
      "name": "bitable_get_meta",
      "description": "获取表格元数据（从 URL /base/XXX 或 /wiki/XXX 获取 app_token）",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token（从 URL 获取）"
          }
        },
        "required": ["app_token"]
      }
    },
    {
      "name": "bitable_list_fields",
      "description": "获取表格字段列表",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          }
        },
        "required": ["app_token", "table_id"]
      }
    },
    {
      "name": "bitable_list_records",
      "description": "查询记录（支持筛选、排序、分页）",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "filter": {
            "type": "string",
            "description": "筛选条件（JSON 字符串）"
          },
          "sort": {
            "type": "string",
            "description": "排序字段（JSON 字符串）"
          },
          "page_size": {
            "type": "number",
            "description": "每页数量，默认 100"
          },
          "page_token": {
            "type": "string",
            "description": "分页 token"
          }
        },
        "required": ["app_token", "table_id"]
      }
    },
    {
      "name": "bitable_get_record",
      "description": "获取单条记录",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "record_id": {
            "type": "string",
            "description": "记录 ID"
          }
        },
        "required": ["app_token", "table_id", "record_id"]
      }
    },
    {
      "name": "bitable_create_record",
      "description": "新增记录",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "fields": {
            "type": "object",
            "description": "字段值（字典）"
          }
        },
        "required": ["app_token", "table_id", "fields"]
      }
    },
    {
      "name": "bitable_update_record",
      "description": "更新记录",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "record_id": {
            "type": "string",
            "description": "记录 ID"
          },
          "fields": {
            "type": "object",
            "description": "要更新的字段值"
          }
        },
        "required": ["app_token", "table_id", "record_id", "fields"]
      }
    },
    {
      "name": "bitable_delete_record",
      "description": "删除记录",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "record_id": {
            "type": "string",
            "description": "记录 ID"
          }
        },
        "required": ["app_token", "table_id", "record_id"]
      }
    },
    {
      "name": "bitable_create_field",
      "description": "添加字段",
      "parameters": {
        "type": "object",
        "properties": {
          "app_token": {
            "type": "string",
            "description": "表格应用 token"
          },
          "table_id": {
            "type": "string",
            "description": "表格 ID"
          },
          "field_name": {
            "type": "string",
            "description": "字段名称"
          },
          "field_type": {
            "type": "string",
            "description": "字段类型 (Text/Number/Date/SingleSelect/MultiSelect/Checkbox/User/Phone/URL)",
            "default": "Text"
          }
        },
        "required": ["app_token", "table_id", "field_name"]
      }
    }
  ],
  "runtime": {
    "type": "python",
    "entry": "run.py",
    "requirements": ["requests"]
  }
}


path: run.py
content: #!/usr/bin/env python3
"""
钉钉多维表格技能 - 工具执行入口
"""

import sys
import json
import os

# 添加当前目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from dingtalk_bitable import (
    bitable_list,
    bitable_get_meta,
    bitable_list_fields,
    bitable_list_records,
    bitable_get_record,
    bitable_create_record,
    bitable_update_record,
    bitable_delete_record,
    bitable_create_field,
    format_field,
    format_record,
)


def parse_args():
    """解析命令行参数"""
    if len(sys.argv) < 2:
        print("用法：python run.py <tool_name> [args...]")
        print("可用工具：bitable_list, bitable_get_meta, bitable_list_fields,")
        print("         bitable_list_records, bitable_get_record, bitable_create_record,")
        print("         bitable_update_record, bitable_delete_record, bitable_create_field")
        sys.exit(1)
    
    tool_name = sys.argv[1]
    args = {}
    
    # 解析 JSON 参数
    for arg in sys.argv[2:]:
        if "=" in arg:
            key, value = arg.split("=", 1)
            # 尝试解析 JSON
            try:
                args[key] = json.loads(value)
            except json.JSONDecodeError:
                args[key] = value
    
    return tool_name, args


def main():
    tool_name, args = parse_args()
    
    result = None
    
    try:
        if tool_name == "bitable_list":
            space_id = args.get("space_id")
            limit = args.get("limit", 20)
            result = bitable_list(space_id=space_id, limit=limit)
        
        elif tool_name == "bitable_get_meta":
            app_token = args.get("app_token")
            if not app_token:
                raise ValueError("缺少必需参数：app_token")
            result = bitable_get_meta(app_token)
        
        elif tool_name == "bitable_list_fields":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            if not app_token or not table_id:
                raise ValueError("缺少必需参数：app_token, table_id")
            result = bitable_list_fields(app_token, table_id)
        
        elif tool_name == "bitable_list_records":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            filter_cond = args.get("filter")
            sort_cond = args.get("sort")
            page_size = args.get("page_size", 100)
            page_token = args.get("page_token")
            if not app_token or not table_id:
                raise ValueError("缺少必需参数：app_token, table_id")
            result = bitable_list_records(
                app_token, table_id,
                filter=filter_cond,
                sort=sort_cond,
                page_size=page_size,
                page_token=page_token
            )
        
        elif tool_name == "bitable_get_record":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            record_id = args.get("record_id")
            if not app_token or not table_id or not record_id:
                raise ValueError("缺少必需参数：app_token, table_id, record_id")
            result = bitable_get_record(app_token, table_id, record_id)
        
        elif tool_name == "bitable_create_record":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            fields = args.get("fields")
            if not app_token or not table_id or not fields:
                raise ValueError("缺少必需参数：app_token, table_id, fields")
            record_id = bitable_create_record(app_token, table_id, fields)
            result = {"recordId": record_id, "success": bool(record_id)}
        
        elif tool_name == "bitable_update_record":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            record_id = args.get("record_id")
            fields = args.get("fields")
            if not app_token or not table_id or not record_id or not fields:
                raise ValueError("缺少必需参数：app_token, table_id, record_id, fields")
            success = bitable_update_record(app_token, table_id, record_id, fields)
            result = {"success": success}
        
        elif tool_name == "bitable_delete_record":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            record_id = args.get("record_id")
            if not app_token or not table_id or not record_id:
                raise ValueError("缺少必需参数：app_token, table_id, record_id")
            success = bitable_delete_record(app_token, table_id, record_id)
            result = {"success": success}
        
        elif tool_name == "bitable_create_field":
            app_token = args.get("app_token")
            table_id = args.get("table_id")
            field_name = args.get("field_name")
            field_type = args.get("field_type", "Text")
            if not app_token or not table_id or not field_name:
                raise ValueError("缺少必需参数：app_token, table_id, field_name")
            field_id = bitable_create_field(app_token, table_id, field_name, field_type)
            result = {"fieldId": field_id, "success": bool(field_id)}
        
        else:
            print(f"未知工具：{tool_name}")
            sys.exit(1)
        
        # 输出结果
        print(json.dumps(result, ensure_ascii=False, indent=2))
        
    except Exception as e:
        error_result = {"error": str(e)}
        print(json.dumps(error_result, ensure_ascii=False, indent=2))
        sys.exit(1)


if __name__ == "__main__":
    main()


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