Compare commits

..

3 Commits

Author SHA1 Message Date
liangshuo-1
6840bb7415 chore(release): v1.0.35 (#995)
Change-Id: I6ddc8cfc029c684deb5de4f210357e19ade083e1
2026-05-20 19:46:10 +08:00
caojie0621
ce485eb3f5 fix(sheets): declare metadata scope for info shortcut (#994) 2026-05-20 19:43:21 +08:00
YangJunzhou-01
c98a49f2a3 docs(im): clarify media key formats for message media flags (#991)
* docs(im): clarify media path restrictions

* docs(im): clarify file key formats for message file flags

Change-Id: I329ca0db9e7a01b774846d522d1b2a64da74233c

---------

Co-authored-by: mtsui-cmyk <mervyntsui@gmail.com>
2026-05-20 17:39:14 +08:00
11 changed files with 132 additions and 49 deletions

View File

@@ -2,6 +2,24 @@
All notable changes to this project will be documented in this file.
## [v1.0.35] - 2026-05-20
### Features
- **markdown**: Support wiki node target in `+create` (#883)
- **markdown**: Add `+diff` shortcut (#876)
- **base**: Add form `+detail` / `+submit` shortcuts (#759)
- **skills**: Add incremental skills sync (#965)
- **doc**: Warn before overwrite when document contains whiteboard or file blocks (#825)
### Documentation
- **im**: Clarify media key formats for message media flags (#991)
- **im**: Add media-preview reference (#990)
- **drive**: Migrate `docs +search` to `drive +search` and fix `creator_ids` owner semantic (#951)
- **drive**: Prefer local comments for drive reviews (#981)
- **wiki**: Add wiki base fast path (#982)
## [v1.0.34] - 2026-05-19
### Features
@@ -774,6 +792,7 @@ Bundled AI agent skills for intelligent assistance:
- Bilingual documentation (English & Chinese).
- CI/CD pipelines: linting, testing, coverage reporting, and automated releases.
[v1.0.35]: https://github.com/larksuite/cli/releases/tag/v1.0.35
[v1.0.34]: https://github.com/larksuite/cli/releases/tag/v1.0.34
[v1.0.33]: https://github.com/larksuite/cli/releases/tag/v1.0.33
[v1.0.32]: https://github.com/larksuite/cli/releases/tag/v1.0.32

View File

@@ -1,6 +1,6 @@
{
"name": "@larksuite/cli",
"version": "1.0.34",
"version": "1.0.35",
"description": "The official CLI for Lark/Feishu open platform",
"bin": {
"lark-cli": "scripts/run.js"

View File

@@ -29,11 +29,11 @@ var ImMessagesReply = common.Shortcut{
{Name: "content", Desc: "(one of --content/--text/--markdown/--image/--file/--video/--audio required) message content JSON"},
{Name: "text", Desc: "plain text message (auto-wrapped as JSON)"},
{Name: "markdown", Desc: "markdown text (auto-wrapped as post format with style optimization; image URLs auto-resolved)"},
{Name: "image", Desc: "image_key, local file path"},
{Name: "file", Desc: "file_key, local file path"},
{Name: "video", Desc: "video file_key, local file path; must be used together with --video-cover"},
{Name: "video-cover", Desc: "video cover image_key, local file path; required when using --video"},
{Name: "audio", Desc: "audio file_key, local file path"},
{Name: "image", Desc: "image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
{Name: "file", Desc: "file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
{Name: "video", Desc: "video file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); must be used together with --video-cover"},
{Name: "video-cover", Desc: "video cover image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); required when using --video"},
{Name: "audio", Desc: "audio file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
{Name: "reply-in-thread", Type: "bool", Desc: "reply in thread (message appears in thread stream instead of main chat)"},
{Name: "idempotency-key", Desc: "idempotency key (prevents duplicate sends)"},
},

View File

@@ -33,11 +33,11 @@ var ImMessagesSend = common.Shortcut{
{Name: "text", Desc: "plain text message (auto-wrapped as JSON)"},
{Name: "markdown", Desc: "markdown text (auto-wrapped as post format with style optimization; image URLs auto-resolved)"},
{Name: "idempotency-key", Desc: "idempotency key (prevents duplicate sends)"},
{Name: "image", Desc: "image_key, local file path"},
{Name: "file", Desc: "file_key, local file path"},
{Name: "video", Desc: "video file_key, local file path; must be used together with --video-cover"},
{Name: "video-cover", Desc: "video cover image_key, local file path; required when using --video"},
{Name: "audio", Desc: "audio file_key, local file path"},
{Name: "image", Desc: "image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
{Name: "file", Desc: "file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
{Name: "video", Desc: "video file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); must be used together with --video-cover"},
{Name: "video-cover", Desc: "video cover image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); required when using --video"},
{Name: "audio", Desc: "audio file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
chatFlag := runtime.Str("chat-id")

View File

@@ -6,9 +6,11 @@ package im
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/larksuite/cli/internal/vfs/localfileio"
"github.com/larksuite/cli/shortcuts/common"
)
func TestValidateMediaFlagPath(t *testing.T) {
@@ -49,3 +51,37 @@ func TestValidateMediaFlagPath(t *testing.T) {
})
}
}
func TestIMMediaFlagDescriptionsDocumentPathRestrictions(t *testing.T) {
shortcuts := []struct {
name string
flags []common.Flag
}{
{name: "messages-send", flags: ImMessagesSend.Flags},
{name: "messages-reply", flags: ImMessagesReply.Flags},
}
mediaFlags := []string{"image", "file", "video", "video-cover", "audio"}
for _, sc := range shortcuts {
for _, flagName := range mediaFlags {
t.Run(sc.name+"/"+flagName, func(t *testing.T) {
desc := findFlagDesc(t, sc.flags, flagName)
for _, want := range []string{"URL", "cwd-relative local path", "absolute paths", ".. are rejected"} {
if !strings.Contains(desc, want) {
t.Fatalf("%s --%s description = %q, want it to mention %q", sc.name, flagName, desc, want)
}
}
})
}
}
}
func findFlagDesc(t *testing.T, flags []common.Flag, name string) string {
t.Helper()
for _, flag := range flags {
if flag.Name == name {
return flag.Desc
}
}
t.Fatalf("flag %q not found", name)
return ""
}

View File

@@ -7,6 +7,7 @@ import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
@@ -29,6 +30,15 @@ func TestSheetCreateSheetValidateMissingToken(t *testing.T) {
}
}
func TestSheetInfoRequiresSpreadsheetMetaAndReadScopes(t *testing.T) {
t.Parallel()
want := []string{"sheets:spreadsheet.meta:read", "sheets:spreadsheet:read"}
if !reflect.DeepEqual(SheetInfo.Scopes, want) {
t.Fatalf("SheetInfo scopes = %v, want %v", SheetInfo.Scopes, want)
}
}
func TestSheetManageValidateRejectsURLAndTokenTogether(t *testing.T) {
t.Parallel()

View File

@@ -22,9 +22,9 @@ import (
var SheetInfo = common.Shortcut{
Service: "sheets",
Command: "+info",
Description: "View spreadsheet and sheet information",
Description: "View spreadsheet metadata and sheet information",
Risk: "read",
Scopes: []string{"sheets:spreadsheet:read"},
Scopes: []string{"sheets:spreadsheet.meta:read", "sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "url", Desc: "spreadsheet URL"},

View File

@@ -27,7 +27,7 @@ When using `--as user`, the reply is sent as the authorized end user and require
| Reply with plain text exactly as written | `--text` | Wrapped directly to `{"text":"..."}` |
| Reply with simple Markdown and accept conversion | `--markdown` | Automatically converted to `post` JSON |
| Precisely control the reply payload | `--content` | You provide the exact JSON |
| Reply with media | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads local files automatically |
| Reply with media | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads URLs, or cwd-relative local files automatically |
### `--text` vs `--markdown`
@@ -138,24 +138,30 @@ lark-cli im +messages-reply --message-id om_xxx --text "Received" --idempotency-
lark-cli im +messages-reply --message-id om_xxx --markdown $'## Test\n\nhello' --dry-run
```
## Media Input Rules
- Media flags accept an existing key (`img_xxx` / `file_xxx`), an `http://` or `https://` URL, or a local file path.
- Local paths must be relative to the current working directory and stay within it after resolving `..` and symlinks.
- Absolute paths such as `/tmp/photo.png` are rejected. Run the command from the file's directory and pass `./photo.png`, or copy the file into the current directory first.
## Parameters
| Parameter | Required | Description |
|------|------|------|
| `--message-id <id>` | Yes | ID of the message being replied to (`om_xxx`) |
| Parameter | Required | Description |
|------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--message-id <id>` | Yes | ID of the message being replied to (`om_xxx`) |
| `--msg-type <type>` | No | Message type (default `text`). If you use `--text` / `--markdown` / media flags, the effective type is inferred automatically. Explicitly setting a conflicting `--msg-type` fails validation |
| `--content <json>` | One content option | Exact reply content as JSON. The JSON must match the effective `--msg-type` |
| `--text <string>` | One content option | Plain text reply. Best default when you need exact text and formatting preservation |
| `--markdown <string>` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization |
| `--image <path\|key>` | One content option | Local image path or `image_key` (`img_xxx`) |
| `--file <path\|key>` | One content option | Local file path or `file_key` (`file_xxx`) |
| `--video <path\|key>` | One content option | Local video path or `file_key`; **must be used together with `--video-cover`** |
| `--video-cover <path\|key>` | **Required with `--video`** | Video cover image path or `image_key` (`img_xxx`) |
| `--audio <path\|key>` | One content option | Local audio path or `file_key` |
| `--reply-in-thread` | No | Reply inside the thread. The reply appears in the target message's thread instead of the main chat stream |
| `--idempotency-key <key>` | No | Idempotency key; the same key sends only one reply within 1 hour |
| `--as <identity>` | No | Identity type: `bot` or `user` (default `bot`) |
| `--dry-run` | No | Print the request only, do not execute it |
| `--content <json>` | One content option | Exact reply content as JSON. The JSON must match the effective `--msg-type` |
| `--text <string>` | One content option | Plain text reply. Best default when you need exact text and formatting preservation |
| `--markdown <string>` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization |
| `--image <path\|url\|key>` | One content option | Cwd-relative local image path, URL, or `image_key` (`img_xxx`) |
| `--file <path\|url\|key>` | One content option | Cwd-relative local file path, URL, or `file_key` (`file_xxx`) |
| `--video <path\|url\|key>` | One content option | Cwd-relative local video path, URL, or `file_key` (`file_xxx`); **must be used together with `--video-cover`** |
| `--video-cover <path\|url\|key>` | **Required with `--video`** | Cwd-relative local cover image path, URL, or `image_key` (`img_xxx`) |
| `--audio <path\|url\|key>` | One content option | Cwd-relative local audio path, URL, or `file_key` (`file_xxx`) |
| `--reply-in-thread` | No | Reply inside the thread. The reply appears in the target message's thread instead of the main chat stream |
| `--idempotency-key <key>` | No | Idempotency key; the same key sends only one reply within 1 hour |
| `--as <identity>` | No | Identity type: `bot` or `user` (default `bot`) |
| `--dry-run` | No | Print the request only, do not execute it |
> **Mutual exclusivity rule:** `--text`, `--markdown`, `--content`, and `--image`/`--file`/`--video`/`--audio` cannot be used together. Media flags are also mutually exclusive with each other.
>
@@ -211,7 +217,7 @@ The reply appears in the target message's thread and does not show up in the mai
- When using `--content`, you are responsible for making the JSON structure match the effective `msg_type`
- `--reply-in-thread` adds `reply_in_thread=true` to the API request
- `--reply-in-thread` is mainly meaningful in chats that support thread replies
- `--image`/`--file`/`--video`/`--audio`/`--video-cover` support local file paths; the shortcut uploads first and then sends the reply; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`)
- `--image`/`--file`/`--video`/`--audio`/`--video-cover` support existing keys, URLs, and cwd-relative local file paths; the shortcut uploads local paths and URLs first, then sends the reply; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`)
- If the provided media value starts with `img_` or `file_`, it is treated as an existing key and used directly
- `--markdown` always sends `msg_type=post`
- If you explicitly set `--msg-type` and it conflicts with the chosen content flag, validation fails

View File

@@ -27,7 +27,7 @@ When using `--as user`, the message is sent as the authorized end user and requi
| Send plain text exactly as written | `--text` | Wrapped directly to `{"text":"..."}`; no Markdown conversion |
| Send simple Markdown and accept Feishu-style rendering | `--markdown` | Automatically converted to `post` JSON |
| Precisely control the final payload | `--content` | You provide the exact JSON for `text` / `post` / `interactive` / `share_*` / media payloads |
| Send image / file / video / audio | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads local files automatically |
| Send image / file / video / audio | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads URLs, or cwd-relative local files automatically |
### `--text` vs `--markdown`
@@ -144,24 +144,30 @@ lark-cli im +messages-send --chat-id oc_xxx --text "Hello" --idempotency-key my-
lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry-run
```
## Media Input Rules
- Media flags accept an existing key (`img_xxx` / `file_xxx`), an `http://` or `https://` URL, or a local file path.
- Local paths must be relative to the current working directory and stay within it after resolving `..` and symlinks.
- Absolute paths such as `/tmp/photo.png` are rejected. Run the command from the file's directory and pass `./photo.png`, or copy the file into the current directory first.
## Parameters
| Parameter | Required | Description |
|------|------|------|
| `--chat-id <id>` | One of two | Group chat ID (`oc_xxx`) |
| `--user-id <id>` | One of two | User open_id (`ou_xxx`) for direct messages |
| `--text <string>` | One content option | Plain text message. Best default for exact text and preserved formatting. Automatically wrapped as `{"text":"..."}` |
| `--markdown <string>` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization; not full Markdown passthrough |
| `--content <json>` | One content option | Exact message content JSON string; use this when you need full control over `msg_type` and payload. The JSON must match the effective `--msg-type` |
| `--image <path\|key>` | One content option | Local image path or `image_key` (`img_xxx`). Local paths are uploaded automatically |
| `--file <path\|key>` | One content option | Local file path or `file_key` (`file_xxx`). Local paths are uploaded automatically |
| `--video <path\|key>` | One content option | Local video path or `file_key`. Local paths are uploaded automatically. **Must be paired with `--video-cover`** |
| `--video-cover <path\|key>` | **Required with `--video`** | Video cover image path or `image_key` (`img_xxx`). Local paths are uploaded automatically |
| `--audio <path\|key>` | One content option | Local audio path or `file_key`. Local paths are uploaded automatically |
| Parameter | Required | Description |
|------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--chat-id <id>` | One of two | Group chat ID (`oc_xxx`) |
| `--user-id <id>` | One of two | User open_id (`ou_xxx`) for direct messages |
| `--text <string>` | One content option | Plain text message. Best default for exact text and preserved formatting. Automatically wrapped as `{"text":"..."}` |
| `--markdown <string>` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization; not full Markdown passthrough |
| `--content <json>` | One content option | Exact message content JSON string; use this when you need full control over `msg_type` and payload. The JSON must match the effective `--msg-type` |
| `--image <path\|url\|key>` | One content option | Cwd-relative local image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically |
| `--file <path\|url\|key>` | One content option | Cwd-relative local file path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically |
| `--video <path\|url\|key>` | One content option | Cwd-relative local video path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically. **Must be paired with `--video-cover`** |
| `--video-cover <path\|url\|key>` | **Required with `--video`** | Cwd-relative local cover image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically |
| `--audio <path\|url\|key>` | One content option | Cwd-relative local audio path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically |
| `--msg-type <type>` | No | Message type (default `text`). If you use `--text` / `--markdown` / media flags, the effective type is inferred automatically. Explicitly setting a conflicting `--msg-type` fails validation |
| `--idempotency-key <key>` | No | Idempotency key; the same key sends only one message within 1 hour |
| `--as <identity>` | No | Identity type: `bot` or `user` (default `bot`) |
| `--dry-run` | No | Print the request only, do not execute it |
| `--idempotency-key <key>` | No | Idempotency key; the same key sends only one message within 1 hour |
| `--as <identity>` | No | Identity type: `bot` or `user` (default `bot`) |
| `--dry-run` | No | Print the request only, do not execute it |
> **Mutual exclusivity rule:** `--text`, `--markdown`, `--content`, and `--image`/`--file`/`--video`/`--audio` cannot be used together. Media flags are also mutually exclusive with each other.
>
@@ -211,7 +217,7 @@ lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry
- `--chat-id` and `--user-id` are mutually exclusive; you must provide exactly one
- `--content` must be valid JSON
- When using `--content`, you are responsible for making the JSON structure match the effective `msg_type`
- `--image`/`--file`/`--video`/`--audio` support local file paths; the shortcut uploads first and then sends the message; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`)
- `--image`/`--file`/`--video`/`--audio` support existing keys, URLs, and cwd-relative local file paths; the shortcut uploads local paths and URLs first, then sends the message; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`)
- If the provided media value starts with `img_` or `file_`, it is treated as an existing key and used directly
- `--markdown` always sends `msg_type=post`, even if you do not explicitly set `--msg-type post`
- If you explicitly set `--msg-type` and it conflicts with the chosen content flag, validation fails

View File

@@ -186,7 +186,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli sheets +<verb> [flags]`
| Shortcut | 说明 |
|----------|------|
| [`+create`](references/lark-sheets-spreadsheet-management.md#create) | Create a spreadsheet (optional header row and initial data) |
| [`+info`](references/lark-sheets-spreadsheet-management.md#info) | View spreadsheet and sheet information |
| [`+info`](references/lark-sheets-spreadsheet-management.md#info) | View spreadsheet metadata and sheet information |
| [`+export`](references/lark-sheets-spreadsheet-management.md#export) | Export a spreadsheet (async task polling + optional download) |
### Sheet Management

View File

@@ -5,7 +5,7 @@
这份 reference 汇总电子表格对象级操作:
- `+create`:创建电子表格
- `+info`:查看电子表格和工作表信息
- `+info`:查看电子表格元信息和工作表列表
- `+export`:导出电子表格
<a id="create"></a>
@@ -60,8 +60,14 @@ lark-cli sheets +create --title "测试表" --dry-run
用于:
- 从表格 URL / token 获取 `spreadsheet_token`
- 获取电子表格标题、URL、所有者等元信息
- 列出工作表的 `sheet_id`、标题、行列数、冻结状态等信息
权限说明:
- 该 shortcut 声明了 `sheets:spreadsheet.meta:read``sheets:spreadsheet:read`,本地 scope preflight 要求两者同时满足
- `spreadsheet` 元信息来自 `spreadsheets/:token` 查询,工作表列表来自额外的 `spreadsheets/:token/sheets/query` 查询
```bash
# 传 URL支持 wiki URL
lark-cli sheets +info --url "https://example.larksuite.com/sheets/shtxxxxxxxx"