Skill: jolestar/feishu-openapi-skill (version 1.0.1)

--- name: feishu-openapi-skill description: Operate Feishu or Lark IM APIs through UXC with a curated OpenAPI schema, tenant-token bearer auth, and chat/message guardrails. --- # Feishu / Lark IM Skill Use this skill to run Feishu or Lark IM operations through `uxc` + OpenAPI. Reuse the `uxc` skill for shared execution, auth, and error-handling guidance. ## Prerequisites - `uxc` is installed and available in `PATH`. - Network access to `https://open.feishu.cn/open-apis` or `https://open.larksuite.com/open-apis`. - Access to the curated OpenAPI schema URL: - `https://raw.githubusercontent.com/holon-run/uxc/main/skills/feishu-openapi-skill/references/feishu-im.openapi.json` - A Feishu or Lark app with bot capability enabled. - A Feishu or Lark app `app_id` + `app_secret`, or a current `tenant_access_token` if you are using the manual fallback path. ## Scope This skill covers an IM-focused request/response surface: - chat lookup - chat member lookup - image and file upload for IM sends - message send and reply - selected message history reads - basic user lookup through contact APIs This skill does **not** cover: - docs, bitable, approval, or non-IM product families - the full Feishu or Lark Open Platform surface ## Subscribe Status Feishu and Lark expose event-delivery models beyond plain request/response APIs, including long-connection event delivery in the platform ecosystem. Current `uxc subscribe` status: - request/response IM operations are validated - inbound message intake is validated through the built-in `feishu-long-connection` transport - live validation confirmed real `im.message.receive_v1` events delivered into the subscribe sink for a `p2p` bot chat Important runtime notes: - `feishu-long-connection` is a provider-aware transport inside `uxc subscribe`; it is not a plain raw WebSocket stream - the runtime opens a temporary WebSocket URL from `/callback/ws/endpoint` - frames are protobuf binary messages, not text JSON - the runtime sends required event acknowledgements and ping control frames automatically ## Endpoint Choice This schema works against either Feishu or Lark Open Platform base URLs: - China / Feishu default: `https://open.feishu.cn/open-apis` - International / Lark alternative: `https://open.larksuite.com/open-apis` The fixed link example below uses Feishu. For Lark, use the same schema URL against the Lark base host. ## Authentication Feishu and Lark service-side APIs use `Authorization: Bearer <tenant_access_token>` for these operations. Preferred setup is to store `app_id` + `app_secret` as credential fields and let `uxc auth bootstrap` fetch and refresh the short-lived tenant token automatically. Feishu bootstrap-managed setup: ```bash uxc auth credential set feishu-tenant \ --auth-type bearer \ --field app_id=env:FEISHU_APP_ID \ --field app_secret=env:FEISHU_APP_SECRET uxc auth bootstrap set feishu-tenant \ --token-endpoint https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \ --header 'Content-Type=application/json; charset=utf-8' \ --request-json '{"app_id":"{{field:app_id}}","app_secret":"{{field:app_secret}}"}' \ --access-token-pointer /tenant_access_token \ --expires-in-pointer /expire \ --success-code-pointer /code \ --success-code-value 0 uxc auth binding add \ --id feishu-tenant \ --host open.feishu.cn \ --path-prefix /open-apis \ --scheme https \ --credential feishu-tenant \ --priority 100 ``` For Lark, use the same bootstrap shape against the Lark host and bind the credential to `open.larksuite.com`. To use long-connection subscribe, the credential still needs `app_id` and `app_secret` fields because the transport opens its own temporary event URL outside the normal bearer-token request path. Manual fallback if you already have a tenant token: ```bash curl -sS https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \ -H 'Content-Type: application/json; charset=utf-8' \ -d '{"app_id":"cli_xxx","app_secret":"xxxx"}' ``` Lark uses the same path shape on the Lark host: ```bash curl -sS https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal \ -H 'Content-Type: application/json; charset=utf-8' \ -d '{"app_id":"cli_xxx","app_secret":"xxxx"}' ``` Configure one bearer credential and bind it to the Feishu API host: ```bash uxc auth credential set feishu-tenant \ --auth-type bearer \ --secret-env FEISHU_TENANT_ACCESS_TOKEN uxc auth binding add \ --id feishu-tenant \ --host open.feishu.cn \ --path-prefix /open-apis \ --scheme https \ --credential feishu-tenant \ --priority 100 ``` For Lark, create the same binding against `open.larksuite.com`: ```bash uxc auth binding add \ --id lark-tenant \ --host open.larksuite.com \ --path-prefix /open-apis \ --scheme https \ --credential feishu-tenant \ --priority 100 ``` Inspect or pre-warm bootstrap state when auth looks wrong: ```bash uxc auth bootstrap info feishu-tenant uxc auth bootstrap refresh feishu-tenant ``` Validate the active binding when auth looks wrong: ```bash uxc auth binding match https://open.feishu.cn/open-apis ``` ## Core Workflow 1. Use the fixed link command by default: - `command -v feishu-openapi-cli` - If missing, create it: `uxc link feishu-openapi-cli https://open.feishu.cn/open-apis --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/feishu-openapi-skill/references/feishu-im.openapi.json` - `feishu-openapi-cli -h` 2. Inspect operation schema first: - `feishu-openapi-cli get:/im/v1/chats -h` - `feishu-openapi-cli post:/im/v1/images -h` - `feishu-openapi-cli post:/im/v1/files -h` - `feishu-openapi-cli post:/im/v1/messages -h` - `feishu-openapi-cli get:/im/v1/messages -h` 3. Prefer read/setup validation before writes: - `feishu-openapi-cli get:/im/v1/chats page_size=20` - `feishu-openapi-cli get:/im/v1/chats/{chat_id} chat_id=oc_xxx` - `feishu-openapi-cli get:/contact/v3/users/{user_id} user_id=ou_xxx user_id_type=open_id` 4. Execute with key/value or positional JSON: - key/value: `feishu-openapi-cli get:/im/v1/messages container_id_type=chat container_id=oc_xxx page_size=20` - multipart upload: `feishu-openapi-cli post:/im/v1/images image_type=message image=/tmp/example.png` - positional JSON: `feishu-openapi-cli post:/im/v1/messages receive_id_type=chat_id '{"receive_id":"oc_xxx","msg_type":"text","content":"{\"text\":\"Hello from UXC\"}"}'` 5. For inbound message intake, use `uxc subscribe` directly: - `uxc subscribe start https://open.feishu.cn/open-apis --transport feishu-long-connection --auth feishu-tenant --sink file:$HOME/.uxc/subscriptions/feishu.ndjson` - send a bot-visible message, then inspect the sink for `header.event_type = "im.message.receive_v1"` ## Operation Groups ### Chat Reads - `get:/im/v1/chats` - `get:/im/v1/chats/{chat_id}` - `get:/im/v1/chats/{chat_id}/members` ### Message Reads / Writes - `get:/im/v1/messages` - `get:/im/v1/messages/{message_id}` - `post:/im/v1/messages` - `post:/im/v1/messages/{message_id}/reply` ### Uploads - `post:/im/v1/images` - `post:/im/v1/files` ### User Lookup - `get:/contact/v3/users/{user_id}` - `post:/contact/v3/users/batch_get_id` ## Guardrails - Keep automation on the JSON output envelope; do not use `--text`. - Parse stable fields first: `ok`, `kind`, `protocol`, `data`, `error`. - Prefer `uxc auth bootstrap` over manual token management. Manual `tenant_access_token` setup is still supported as a fallback. - `feishu-long-connection` requires the app credential fields `app_id` and `app_secret`; a plain bearer-only credential is not enough for event intake. - `post:/im/v1/images` and `post:/im/v1/files` use `multipart/form-data`. File fields must be local path strings; help output marks them as multipart file fields. - `post:/im/v1/messages` requires the `receive_id_type` query parameter and the body `content` field is a JSON-encoded string, not a nested JSON object. - Upload first, then send by returned key: - image sends use `msg_type=image` with `content='{\"image_key\":\"img_xxx\"}'` - file sends use `msg_type=file` with `content='{\"file_key\":\"file_xxx\"}'` - `post:/im/v1/messages/{message_id}/reply` is for explicit replies to an existing message. Treat it as a high-risk write. - History reads only return chats and messages visible to the bot/app configuration. Auth success does not imply access to every chat. - Long-connection message intake is validated for Feishu bot chats; webhook-style callbacks and non-IM products are still out of scope. - `feishu-openapi-cli <operation> ...` is equivalent to `uxc https://open.feishu.cn/open-apis --schema-url <feishu_openapi_schema> <operation> ...`. ## References - Usage patterns: `references/usage-patterns.md` - Curated OpenAPI schema: `references/feishu-im.openapi.json` - Feishu Open Platform docs: https://open.feishu.cn/document/ - Lark Open Platform docs: https://open.larksuite.com/document/

path: agents/openai.yaml
content: interface:
  display_name: "Feishu / Lark IM"
  short_description: "Operate Feishu or Lark IM APIs via UXC + curated OpenAPI schema"
  default_prompt: "Use $feishu-openapi-skill to discover and execute Feishu or Lark IM operations through UXC with tenant-token bearer auth, chat/message scope, and content-string guardrails."


path: references/feishu-im.openapi.json
content: {
  "openapi": "3.0.3",
  "info": {
    "title": "Feishu / Lark IM API (Curated)",
    "version": "1.0.0",
    "description": "Curated Feishu / Lark IM and contact surface for UXC messaging workflows."
  },
  "servers": [
    {
      "url": "https://open.feishu.cn/open-apis"
    },
    {
      "url": "https://open.larksuite.com/open-apis"
    }
  ],
  "security": [
    {
      "FeishuBearerAuth": []
    }
  ],
  "paths": {
    "/im/v1/chats": {
      "get": {
        "summary": "List chats visible to the app",
        "parameters": [
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Number of chats to return"
          },
          {
            "name": "page_token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination token from a previous response"
          },
          {
            "name": "sort_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ByCreateTimeAsc",
                "ByCreateTimeDesc",
                "ByActiveTimeAsc",
                "ByActiveTimeDesc"
              ]
            },
            "description": "Sort order for the chat list"
          }
        ],
        "responses": {
          "200": {
            "description": "Chat list response"
          }
        }
      }
    },
    "/im/v1/chats/{chat_id}": {
      "get": {
        "summary": "Get details for one chat",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Feishu chat identifier"
          }
        ],
        "responses": {
          "200": {
            "description": "Chat detail response"
          }
        }
      }
    },
    "/im/v1/chats/{chat_id}/members": {
      "get": {
        "summary": "List members of a chat",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Feishu chat identifier"
          },
          {
            "name": "member_id_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open_id",
                "union_id",
                "user_id"
              ]
            },
            "description": "Identifier type for returned members"
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Number of members to return"
          },
          {
            "name": "page_token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination token from a previous response"
          }
        ],
        "responses": {
          "200": {
            "description": "Chat member list response"
          }
        }
      }
    },
    "/im/v1/messages": {
      "get": {
        "summary": "List historical messages for a container",
        "parameters": [
          {
            "name": "container_id_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "chat"
              ]
            },
            "description": "Container type used for the lookup"
          },
          {
            "name": "container_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Chat identifier whose history should be returned"
          },
          {
            "name": "sort_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ByCreateTimeAsc",
                "ByCreateTimeDesc"
              ]
            },
            "description": "Sort order for the message history"
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            },
            "description": "Number of messages to return"
          },
          {
            "name": "page_token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination token from a previous response"
          },
          {
            "name": "start_time",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Inclusive start time filter, typically epoch milliseconds as a string"
          },
          {
            "name": "end_time",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Inclusive end time filter, typically epoch milliseconds as a string"
          }
        ],
        "responses": {
          "200": {
            "description": "Message history response"
          }
        }
      },
      "post": {
        "summary": "Send a message to a user, chat, or thread target",
        "parameters": [
          {
            "name": "receive_id_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "open_id",
                "user_id",
                "union_id",
                "chat_id",
                "email"
              ]
            },
            "description": "Identifier type for the receive_id field"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Send message response"
          }
        }
      }
    },
    "/im/v1/messages/{message_id}": {
      "get": {
        "summary": "Get one message by ID",
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Feishu message identifier"
          }
        ],
        "responses": {
          "200": {
            "description": "Message detail response"
          }
        }
      }
    },
    "/im/v1/messages/{message_id}/reply": {
      "post": {
        "summary": "Reply to an existing message",
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Message identifier to reply to"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplyMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reply message response"
          }
        }
      }
    },
    "/im/v1/images": {
      "post": {
        "summary": "Upload an image and receive an image_key",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadImageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Image upload response"
          }
        }
      }
    },
    "/im/v1/files": {
      "post": {
        "summary": "Upload a file and receive a file_key",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File upload response"
          }
        }
      }
    },
    "/contact/v3/users/{user_id}": {
      "get": {
        "summary": "Get one user profile",
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "User identifier"
          },
          {
            "name": "user_id_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open_id",
                "union_id",
                "user_id"
              ]
            },
            "description": "Identifier type used for the path parameter"
          },
          {
            "name": "department_id_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "department_id",
                "open_department_id"
              ]
            },
            "description": "Preferred department ID type in the response"
          }
        ],
        "responses": {
          "200": {
            "description": "User profile response"
          }
        }
      }
    },
    "/contact/v3/users/batch_get_id": {
      "post": {
        "summary": "Resolve user IDs from email or mobile values",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchGetUserIdRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch user ID lookup response"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "FeishuBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tenant_access_token"
      }
    },
    "schemas": {
      "CreateMessageRequest": {
        "type": "object",
        "required": [
          "receive_id",
          "msg_type",
          "content"
        ],
        "properties": {
          "receive_id": {
            "type": "string",
            "description": "Target identifier whose type is defined by receive_id_type"
          },
          "msg_type": {
            "type": "string",
            "enum": [
              "text",
              "post",
              "image",
              "interactive",
              "share_chat",
              "share_user",
              "audio",
              "media",
              "file",
              "sticker"
            ],
            "description": "Message content type"
          },
          "content": {
            "type": "string",
            "description": "Stringified JSON payload expected by Feishu, for example {\"text\":\"hello\"}"
          },
          "uuid": {
            "type": "string",
            "description": "Optional client-generated idempotency token"
          }
        },
        "additionalProperties": false
      },
      "ReplyMessageRequest": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "description": "Stringified JSON payload expected by Feishu, for example {\"text\":\"reply\"}"
          },
          "msg_type": {
            "type": "string",
            "enum": [
              "text",
              "post",
              "image",
              "interactive",
              "share_chat",
              "share_user",
              "audio",
              "media",
              "file",
              "sticker"
            ],
            "description": "Message content type. Defaults vary by API behavior; set it explicitly."
          },
          "uuid": {
            "type": "string",
            "description": "Optional client-generated idempotency token"
          }
        },
        "additionalProperties": false
      },
      "UploadImageRequest": {
        "type": "object",
        "required": [
          "image_type",
          "image"
        ],
        "properties": {
          "image_type": {
            "type": "string",
            "enum": [
              "message"
            ],
            "description": "Image upload purpose. Use message for IM sends."
          },
          "image": {
            "type": "string",
            "format": "binary",
            "description": "Local image file path to upload"
          }
        },
        "additionalProperties": false
      },
      "UploadFileRequest": {
        "type": "object",
        "required": [
          "file_type",
          "file_name",
          "file"
        ],
        "properties": {
          "file_type": {
            "type": "string",
            "description": "File upload type. Use stream for general file uploads."
          },
          "file_name": {
            "type": "string",
            "description": "Original filename shown in Feishu"
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Local file path to upload"
          },
          "duration": {
            "type": "integer",
            "description": "Optional duration in milliseconds for audio or video uploads"
          }
        },
        "additionalProperties": false
      },
      "BatchGetUserIdRequest": {
        "type": "object",
        "properties": {
          "emails": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "description": "Email addresses to resolve"
          },
          "mobiles": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Mobile numbers to resolve"
          },
          "include_resigned": {
            "type": "boolean",
            "description": "Whether to include resigned users in the lookup"
          }
        },
        "additionalProperties": false
      }
    }
  }
}


path: references/usage-patterns.md
content: # Feishu / Lark IM Skill - Usage Patterns

## Link Setup

```bash
command -v feishu-openapi-cli
uxc link feishu-openapi-cli https://open.feishu.cn/open-apis \
  --schema-url https://raw.githubusercontent.com/holon-run/uxc/main/skills/feishu-openapi-skill/references/feishu-im.openapi.json
feishu-openapi-cli -h
```

For international Lark tenants, use the same schema against `https://open.larksuite.com/open-apis`.

## Token Bootstrap

Preferred path: let `uxc` manage tenant-token bootstrap and refresh from app credentials.

```bash
uxc auth credential set feishu-tenant \
  --auth-type bearer \
  --field app_id=env:FEISHU_APP_ID \
  --field app_secret=env:FEISHU_APP_SECRET

uxc auth bootstrap set feishu-tenant \
  --token-endpoint https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \
  --header 'Content-Type=application/json; charset=utf-8' \
  --request-json '{"app_id":"{{field:app_id}}","app_secret":"{{field:app_secret}}"}' \
  --access-token-pointer /tenant_access_token \
  --expires-in-pointer /expire \
  --success-code-pointer /code \
  --success-code-value 0

uxc auth bootstrap info feishu-tenant
```

For long-connection subscribe, keep this same credential because the transport needs the stored `app_id` and `app_secret` fields to open a fresh temporary WebSocket URL.

Manual fallback:

For Feishu tenants:

```bash
curl -sS https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \
  -H 'Content-Type: application/json; charset=utf-8' \
  -d '{"app_id":"cli_xxx","app_secret":"xxxx"}'
```

For Lark tenants, call the same path on the Lark host:

```bash
curl -sS https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal \
  -H 'Content-Type: application/json; charset=utf-8' \
  -d '{"app_id":"cli_xxx","app_secret":"xxxx"}'
```

Store the resulting `tenant_access_token` in an environment variable before binding it into `uxc auth` if you are using the manual fallback.

## Auth Setup

```bash
uxc auth credential set feishu-tenant \
  --auth-type bearer \
  --secret-env FEISHU_TENANT_ACCESS_TOKEN

uxc auth binding add \
  --id feishu-tenant \
  --host open.feishu.cn \
  --path-prefix /open-apis \
  --scheme https \
  --credential feishu-tenant \
  --priority 100
```

## Read Examples

```bash
# List chats visible to the app
feishu-openapi-cli get:/im/v1/chats page_size=20

# Inspect one chat
feishu-openapi-cli get:/im/v1/chats/{chat_id} chat_id=oc_xxx

# List chat members
feishu-openapi-cli get:/im/v1/chats/{chat_id}/members chat_id=oc_xxx page_size=50

# Read recent messages from one chat
feishu-openapi-cli get:/im/v1/messages container_id_type=chat container_id=oc_xxx page_size=20

# Read one message by id
feishu-openapi-cli get:/im/v1/messages/{message_id} message_id=om_xxx

# Look up a user profile
feishu-openapi-cli get:/contact/v3/users/{user_id} user_id=ou_xxx user_id_type=open_id
```

## Write Examples (Confirm Intent First)

```bash
# Upload an image and capture the returned image_key
feishu-openapi-cli post:/im/v1/images image_type=message image=/tmp/example.png

# Upload a file and capture the returned file_key
feishu-openapi-cli post:/im/v1/files file_type=stream file_name=report.txt file=/tmp/report.txt

# Send a text message to a chat
feishu-openapi-cli post:/im/v1/messages receive_id_type=chat_id '{"receive_id":"oc_xxx","msg_type":"text","content":"{\"text\":\"Hello from UXC\"}"}'

# Send an image message using a previously uploaded image_key
feishu-openapi-cli post:/im/v1/messages receive_id_type=chat_id '{"receive_id":"oc_xxx","msg_type":"image","content":"{\"image_key\":\"img_xxx\"}"}'

# Send a file message using a previously uploaded file_key
feishu-openapi-cli post:/im/v1/messages receive_id_type=chat_id '{"receive_id":"oc_xxx","msg_type":"file","content":"{\"file_key\":\"file_xxx\"}"}'

# Send a text message to a user by open_id
feishu-openapi-cli post:/im/v1/messages receive_id_type=open_id '{"receive_id":"ou_xxx","msg_type":"text","content":"{\"text\":\"Hello from UXC\"}"}'

# Reply to a message
feishu-openapi-cli post:/im/v1/messages/{message_id}/reply '{"message_id":"om_xxx","content":"{\"text\":\"Reply from UXC\"}","msg_type":"text"}'
```

## Long-Connection Subscribe

Use `uxc subscribe` directly for inbound Feishu/Lark message intake:

```bash
uxc subscribe start https://open.feishu.cn/open-apis \
  --transport feishu-long-connection \
  --auth feishu-tenant \
  --sink file:$HOME/.uxc/subscriptions/feishu.ndjson
```

What to expect:

- the sink starts with an `open` event containing the temporary Feishu WebSocket URL
- inbound bot-visible messages arrive as `data` events
- real Feishu IM events use `data.header.event_type`, for example:
  - `im.message.receive_v1`

Important notes:

- this transport is provider-aware; it is not a generic raw WebSocket JSON stream
- the runtime handles temporary URL bootstrap, protobuf binary frames, event acknowledgements, and ping control frames automatically
- use a credential that still has `app_id` and `app_secret` fields, even if request/response calls are already using bootstrap-managed bearer auth

## Fallback Equivalence

- `feishu-openapi-cli <operation> ...` is equivalent to
  `uxc https://open.feishu.cn/open-apis --schema-url <feishu_openapi_schema> <operation> ...`.


path: scripts/validate.sh
content: #!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
SKILL_DIR="${ROOT_DIR}/skills/feishu-openapi-skill"
SKILL_FILE="${SKILL_DIR}/SKILL.md"
OPENAI_FILE="${SKILL_DIR}/agents/openai.yaml"
USAGE_FILE="${SKILL_DIR}/references/usage-patterns.md"
SCHEMA_FILE="${SKILL_DIR}/references/feishu-im.openapi.json"

fail() {
  printf '[validate] error: %s\n' "$*" >&2
  exit 1
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}

need_cmd rg
need_cmd jq

for file in "${SKILL_FILE}" "${OPENAI_FILE}" "${USAGE_FILE}" "${SCHEMA_FILE}"; do
  [[ -f "${file}" ]] || fail "missing required file: ${file}"
done

jq -e '.openapi and .paths' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'invalid OpenAPI schema JSON or missing .openapi/.paths'
jq -e '.paths["/im/v1/chats"]' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'OpenAPI schema missing /im/v1/chats path'
jq -e '.paths["/im/v1/images"]' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'OpenAPI schema missing /im/v1/images path'
jq -e '.paths["/im/v1/files"]' "${SCHEMA_FILE}" >/dev/null 2>&1 || fail 'OpenAPI schema missing /im/v1/files path'

rg -q '^name:\s*feishu-openapi-skill\s*$' "${SKILL_FILE}" || fail 'invalid skill name'
rg -q '^description:\s*.+' "${SKILL_FILE}" || fail 'missing description'

rg -q 'command -v feishu-openapi-cli' "${SKILL_FILE}" || fail 'missing link-first command check'
rg -q 'uxc link feishu-openapi-cli https://open.feishu.cn/open-apis --schema-url ' "${SKILL_FILE}" || fail 'missing fixed link create command with schema-url'
rg -q 'feishu-openapi-cli -h' "${SKILL_FILE}" || fail 'missing help-first host discovery example'
rg -q 'feishu-openapi-cli get:/im/v1/chats -h' "${SKILL_FILE}" || fail 'missing operation-level help example'
rg -q 'feishu-openapi-cli post:/im/v1/images -h' "${SKILL_FILE}" || fail 'missing image upload help example'
rg -q 'feishu-openapi-cli post:/im/v1/files -h' "${SKILL_FILE}" || fail 'missing file upload help example'
rg -q -- '--auth-type bearer' "${SKILL_FILE}" || fail 'missing bearer auth setup'
rg -q 'tenant_access_token' "${SKILL_FILE}" || fail 'missing tenant access token guidance'
rg -q 'uxc auth binding match https://open.feishu.cn/open-apis' "${SKILL_FILE}" || fail 'missing binding match check'
rg -q 'receive_id_type' "${SKILL_FILE}" || fail 'missing receive_id_type guardrail'
rg -q 'content` field is a JSON-encoded string' "${SKILL_FILE}" || fail 'missing content-string guardrail'
rg -q 'multipart/form-data' "${SKILL_FILE}" || fail 'missing multipart upload guardrail'
rg -q 'positional JSON' "${SKILL_FILE}" || fail 'missing positional JSON guidance'

if rg -q -- "--args\\s+'\\{" "${SKILL_FILE}" "${USAGE_FILE}"; then
  fail 'found banned legacy JSON argument pattern'
fi

rg -q '^\s*display_name:\s*"Feishu / Lark IM"\s*$' "${OPENAI_FILE}" || fail 'missing display_name'
rg -q '^\s*short_description:\s*".+"\s*$' "${OPENAI_FILE}" || fail 'missing short_description'
rg -q '^\s*default_prompt:\s*".*\$feishu-openapi-skill.*"\s*$' "${OPENAI_FILE}" || fail 'default_prompt must mention $feishu-openapi-skill'

echo "skills/feishu-openapi-skill validation passed"


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