---
name: feishu-ops
description: Use when a task needs to operate Feishu Open Platform APIs directly, especially calendar events and basic IM/Drive-adjacent automation. This skill provides fixed scripts for tenant token retrieval and Feishu calendar CRUD so the agent can call stable scripts instead of hand-writing ad hoc requests.
---

# Feishu Ops

Use this skill when the task needs direct Feishu Open Platform operations and CheapClaw does not already expose a dedicated tool.

## Delegation Contract

If an upper-layer bot delegates a task that uses this skill, it should explicitly provide:

- `app_id`
- `app_secret`
- `sender_id` from the triggering Feishu inbound message when the target is "the current message sender"
- `conversation_id` only if the task should send a follow-up message back to the same chat
- `calendar_id` if already known

Preferred source order:

1. First call `cheapclaw_get_current_bot_channel_config(channel="feishu")` if that tool is available in the current task.
2. If the tool returns Feishu credentials, use its `current_channel_config.app_id` and `current_channel_config.app_secret`.
3. Use the triggering Feishu message's `sender_id` as `user_open_id` when the target is "the current message sender".
4. Only if the tool is unavailable and the upper layer also did not pass credentials should you report missing parameters.

Interpretation rules:

- Treat Feishu `sender_id` as `user_open_id`.
- Never treat `conversation_id` / `chat_id` as a user identity.
- If the request is "create or inspect the current user's calendar" and the task was delegated without `sender_id`, stop and report missing parameters instead of guessing.
- If credentials are missing, stop and report missing parameters instead of trying to discover config files by yourself.

Current focus:
- get tenant access token
- get primary calendar
- list calendars
- list calendar events
- create calendar events
- update calendar events
- delete calendar events
- create docs
- append text into docs
- create spreadsheets
- read spreadsheet metadata
- write spreadsheet cells
- create bitable apps
- list bitable fields
- insert bitable records
- send interactive card messages to Feishu chats

## Real-world Notes

- The current scripts have been tested against a real CheapClaw Feishu bot config.
- When an upper-layer bot delegates a Feishu task that uses this skill, it should explicitly provide:
  - `app_id`
  - `app_secret`
  - `sender_id` from the triggering Feishu message when the task target is "the current user"
  - `conversation_id` only if a follow-up message should be sent back to the same chat
- Calendar APIs require application scopes. If you see `code=99991672`, the app is missing calendar permissions.
- Creating or updating events does **not** accept a raw ISO string directly in `start_time` / `end_time`.
  The accepted structure is:

```json
{
  "timestamp": "1774541280",
  "timezone": "Asia/Shanghai"
}
```

- The scripts accept either:
  - RFC3339 / ISO datetime with timezone offset, for example `2026-03-27T00:08:00+08:00`
  - or unix timestamp seconds
  and convert it to Feishu's required `{timestamp, timezone}` structure.
- Spreadsheet cell updates in Feishu Sheets use the **sheet_id** in the range, not the visible sheet title.
  Example: `026ff1!A1:B2` works, while `Sheet1!A1:B2` may return `sheetId not found`.
- Bitable record creation should use the real field names returned by the fields API, not guessed English aliases.
- For user-sent Feishu images/files that already arrived through CheapClaw, prefer the inbound `attachments[].local_path` supplied in social history or task context.
  Do **not** re-download them from `image_key` / `file_key` if a local path is already present.
- If you only see `image_key` / `file_key` without `local_path`, treat that as a transport/runtime problem and report it clearly instead of hand-writing ad hoc download code.
- A Feishu doc created by API can usually be opened with a URL shaped like:
  `https://<tenant-domain>.feishu.cn/docx/<document_id>`
  but the user must be logged into the correct Feishu account.
- `conversation_id/chat_id` is for sending replies, not for identifying a user's personal calendar.
- In current testing with a tenant token, passing `sender_id(open_id)` into the primary-calendar endpoint still returned the same primary calendar object. So the skill should accept `sender_id/user_open_id`, but must not promise that tenant-token calls can switch to a different personal calendar without further user-scoped auth.

## Inputs

The scripts expect:
- `--app-id`
- `--app-secret`

You can usually obtain them from the current bot config or manifest if the upper-layer bot already knows that the current CheapClaw bot is connected to Feishu.

For personal-calendar tasks, the upper-layer bot should also pass:
- `sender_id`, treated as `user_open_id`

If a delegated task is missing credentials after trying `cheapclaw_get_current_bot_channel_config`, or is supposed to act on "the current user" but no `sender_id/user_open_id` is provided, stop and report missing parameters instead of guessing.

## Workflow

1. First obtain a tenant access token:

```bash
python scripts/get_tenant_token.py --app-id <APP_ID> --app-secret <APP_SECRET>
python scripts/get_primary_calendar.py --app-id <APP_ID> --app-secret <APP_SECRET> --user-open-id <SENDER_OPEN_ID>
```

2. Then use one of the calendar scripts:

```bash
python scripts/list_calendars.py --app-id <APP_ID> --app-secret <APP_SECRET>
python scripts/list_events.py --app-id <APP_ID> --app-secret <APP_SECRET> --calendar-id <CAL_ID>
python scripts/create_event.py --app-id <APP_ID> --app-secret <APP_SECRET> --calendar-id <CAL_ID> --summary "..." --start "2026-03-26T09:00:00+08:00" --end "2026-03-26T10:00:00+08:00"
python scripts/update_event.py --app-id <APP_ID> --app-secret <APP_SECRET> --calendar-id <CAL_ID> --event-id <EVENT_ID> --summary "..." --start "2026-03-26T10:00:00+08:00"
python scripts/delete_event.py --app-id <APP_ID> --app-secret <APP_SECRET> --calendar-id <CAL_ID> --event-id <EVENT_ID>
python scripts/create_doc.py --app-id <APP_ID> --app-secret <APP_SECRET> --title "CheapClaw Doc Smoke"
python scripts/write_doc_text.py --app-id <APP_ID> --app-secret <APP_SECRET> --document-id <DOC_ID> --text "hello from CheapClaw"
python scripts/create_sheet.py --app-id <APP_ID> --app-secret <APP_SECRET> --title "CheapClaw Sheet Smoke"
python scripts/get_sheet_meta.py --app-id <APP_ID> --app-secret <APP_SECRET> --spreadsheet-token <SHEET_TOKEN>
python scripts/write_sheet_values.py --app-id <APP_ID> --app-secret <APP_SECRET> --spreadsheet-token <SHEET_TOKEN> --range "026ff1!A1:B2" --values-json '[[\"A1\",\"B1\"],[\"A2\",\"B2\"]]'
python scripts/create_bitable.py --app-id <APP_ID> --app-secret <APP_SECRET> --name "CheapClaw Bitable Smoke"
python scripts/list_bitable_fields.py --app-id <APP_ID> --app-secret <APP_SECRET> --app-token <APP_TOKEN> --table-id <TABLE_ID>
python scripts/insert_bitable_record.py --app-id <APP_ID> --app-secret <APP_SECRET> --app-token <APP_TOKEN> --table-id <TABLE_ID> --fields-json '{\"文本\":\"hello\"}'
python scripts/send_card_message.py --app-id <APP_ID> --app-secret <APP_SECRET> --chat-id <CHAT_ID> --title "CheapClaw Card" --markdown "这是 **卡片** 内容"
```

## Few-shot

### Example 1: Verify the app is ready for calendar access

```bash
python scripts/get_primary_calendar.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --user-open-id <SENDER_OPEN_ID>
```

Expected success shape:

```json
{
  "code": 0,
  "data": {
    "calendar_id": "...",
    "summary": "...",
    "type": "primary"
  }
}
```

If you see `code=99991672`, stop and report that Feishu app permissions are not enabled yet.
If you are using a tenant token, do not assume this primary calendar is guaranteed to be the message sender's personal calendar; report that limitation clearly.

### Example 2: Create and then delete a smoke-test event

```bash
python scripts/create_event.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --calendar-id <CAL_ID> \
  --summary "CheapClaw smoke test" \
  --description "temporary test event" \
  --start "2026-03-27T09:00:00+08:00" \
  --end "2026-03-27T09:30:00+08:00"

python scripts/delete_event.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --calendar-id <CAL_ID> \
  --event-id <EVENT_ID>
```

Use this pattern when you need to validate write permissions without leaving permanent data behind.

### Example 3: Create a spreadsheet and a bitable app

```bash
python scripts/create_sheet.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --title "CheapClaw Sheet Smoke"

python scripts/create_bitable.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --name "CheapClaw Bitable Smoke"
```

Expected success shape:

```json
{
  "code": 0,
  "data": {
    "spreadsheet": {
      "spreadsheet_token": "...",
      "url": "https://...feishu.cn/sheets/..."
    }
  }
}
```

and

```json
{
  "code": 0,
  "data": {
    "app": {
      "app_token": "...",
      "url": "https://...feishu.cn/base/..."
    }
  }
}
```

### Example 4: Write values into a spreadsheet

```bash
python scripts/get_sheet_meta.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --spreadsheet-token <SHEET_TOKEN>

python scripts/write_sheet_values.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --spreadsheet-token <SHEET_TOKEN> \
  --range "026ff1!A1:B2" \
  --values-json '[[\"CheapClaw\",\"2026-03-27\"],[\"status\",\"filled\"]]'
```

Use the returned `sheetId` such as `026ff1`, not the visible title `Sheet1`, when writing values.

### Example 5: Insert a record into bitable

```bash
python scripts/list_bitable_fields.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --app-token <APP_TOKEN> \
  --table-id <TABLE_ID>

python scripts/insert_bitable_record.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --app-token <APP_TOKEN> \
  --table-id <TABLE_ID> \
  --fields-json '{\"文本\":\"CheapClaw Bitable Smoke Row\",\"日期\":1774541250017}'
```

Always read fields first and use the real field names returned by the API.

### Example 6: Return created object links back to the user

If the current task has native CheapClaw messaging tools available, prefer sending a short text message with:

- object type
- title/name
- direct URL
- one-line note on what was created

If native send tools are **not** available in the current task, put the same information in `final_output`, and let the supervisor relay it.

Recommended response shape:

```text
已创建飞书表格：
- 标题：CheapClaw Sheet Smoke
- 链接：https://...feishu.cn/sheets/...
- 说明：已写入 A1:B2 的测试内容
```

### Example 7: Send an interactive card

```bash
python scripts/send_card_message.py \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --chat-id <CHAT_ID> \
  --title "CheapClaw Card Smoke" \
  --markdown "这是一条 **CheapClaw** 发送的飞书卡片消息。"
```

This path has been smoke-tested against a real Feishu chat.

## Rules

- Prefer these scripts over writing raw `requests` code inline.
- If the task is delegated from an upper-layer bot, require explicit credentials and the triggering `sender_id` for user-targeted calendar work.
- Treat Feishu `sender_id` as `user_open_id` when a task says "for the current user".
- Never treat `conversation_id` as a user identity.
- If the task is "create a calendar event for the current message sender" and no `sender_id/user_open_id` is provided, stop and report missing parameters.
- Return concise summaries plus any important IDs (`calendar_id`, `event_id`).
- When creating or updating events, always use absolute timestamps with timezone offsets.
- When you create a Feishu document, spreadsheet, or bitable app, prefer returning a direct URL rather than exporting a local file.
- If `cheapclaw_send_message` or `cheapclaw_send_file` is available in the current task, you may use it to send the resulting link back to the user.
- If those tools are not available, write the link, title, and brief content summary into `final_output`; the upper-layer supervisor can relay it.
- If the task only needs Feishu chat send/receive, prefer CheapClaw's existing channel capabilities instead of this skill.
- CheapClaw does not currently provide a native top-level Feishu card tool, but this skill now includes a tested script for sending simple interactive cards when `chat_id` is known.
- When a plain text link is enough, prefer plain text plus URL over cards to reduce rendering/debugging complexity.
- For docs and cloud objects, the most reliable return format is still plain text plus URL.
- If CheapClaw has already materialized an inbound Feishu attachment to `attachments[].local_path`, consume that local file directly and do not re-fetch by `image_key` / `file_key`.
