From 85f8fda709f21399807a90f752c046d23c2dd859 Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 17:35:53 +0800 Subject: [PATCH 1/7] feat(base): add base block shortcuts --- shortcuts/base/base_block_create.go | 35 ++++ shortcuts/base/base_block_delete.go | 30 ++++ shortcuts/base/base_block_list.go | 31 ++++ shortcuts/base/base_block_move.go | 33 ++++ shortcuts/base/base_block_ops.go | 158 ++++++++++++++++++ shortcuts/base/base_block_rename.go | 31 ++++ shortcuts/base/base_dryrun_ops_test.go | 23 +++ shortcuts/base/base_shortcuts_test.go | 26 +++ shortcuts/base/shortcuts.go | 5 + skills/lark-base/SKILL.md | 38 +++-- .../references/lark-base-base-block-create.md | 44 +++++ .../references/lark-base-base-block-delete.md | 35 ++++ .../references/lark-base-base-block-list.md | 38 +++++ .../references/lark-base-base-block-move.md | 50 ++++++ .../references/lark-base-base-block-rename.md | 34 ++++ .../references/lark-base-base-block.md | 23 +++ tests/cli_e2e/base/base_block_dryrun_test.go | 152 +++++++++++++++++ tests/cli_e2e/base/coverage.md | 12 +- 18 files changed, 784 insertions(+), 14 deletions(-) create mode 100644 shortcuts/base/base_block_create.go create mode 100644 shortcuts/base/base_block_delete.go create mode 100644 shortcuts/base/base_block_list.go create mode 100644 shortcuts/base/base_block_move.go create mode 100644 shortcuts/base/base_block_ops.go create mode 100644 shortcuts/base/base_block_rename.go create mode 100644 skills/lark-base/references/lark-base-base-block-create.md create mode 100644 skills/lark-base/references/lark-base-base-block-delete.md create mode 100644 skills/lark-base/references/lark-base-base-block-list.md create mode 100644 skills/lark-base/references/lark-base-base-block-move.md create mode 100644 skills/lark-base/references/lark-base-base-block-rename.md create mode 100644 skills/lark-base/references/lark-base-base-block.md create mode 100644 tests/cli_e2e/base/base_block_dryrun_test.go diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go new file mode 100644 index 000000000..35d3627f2 --- /dev/null +++ b/shortcuts/base/base_block_create.go @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseBaseBlockCreate = common.Shortcut{ + Service: "base", + Command: "+base-block-create", + Description: "Create a base block", + Risk: "write", + Scopes: []string{"base:app:update"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + {Name: "type", Desc: "resource type", Required: true, Enum: baseBlockTypeEnums}, + {Name: "name", Desc: "base block name", Required: true}, + {Name: "parent-id", Desc: "folder base block id; when omitted, create at the base root"}, + }, + Tips: []string{ + "Use this for Base container entries. Use table/field/record/docx/dashboard/workflow commands for content inside the created entity.", + }, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + return validateBaseBlockCreate(runtime) + }, + DryRun: dryRunBaseBlockCreate, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeBaseBlockCreate(runtime) + }, +} diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go new file mode 100644 index 000000000..434c755f4 --- /dev/null +++ b/shortcuts/base/base_block_delete.go @@ -0,0 +1,30 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseBaseBlockDelete = common.Shortcut{ + Service: "base", + Command: "+base-block-delete", + Description: "Delete a base block", + Risk: "high-risk-write", + Scopes: []string{"base:app:update"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + baseBlockIDFlag(true), + }, + Tips: []string{ + "Recursive folder deletion is not supported. If a folder is not empty, move or delete its children first.", + }, + DryRun: dryRunBaseBlockDelete, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeBaseBlockDelete(runtime) + }, +} diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go new file mode 100644 index 000000000..011d413ae --- /dev/null +++ b/shortcuts/base/base_block_list.go @@ -0,0 +1,31 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseBaseBlockList = common.Shortcut{ + Service: "base", + Command: "+base-block-list", + Description: "List base blocks in a base", + Risk: "read", + Scopes: []string{"base:app:read"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + {Name: "parent-id", Desc: "optional folder base block id; when omitted, list all base blocks"}, + }, + Tips: []string{ + "Base blocks are entries managed by the Base container, such as folder, table, docx, dashboard, and workflow.", + "Dashboard blocks are chart/widget blocks inside a dashboard; use +dashboard-block-* for those.", + }, + DryRun: dryRunBaseBlockList, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeBaseBlockList(runtime) + }, +} diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go new file mode 100644 index 000000000..1e7f051b5 --- /dev/null +++ b/shortcuts/base/base_block_move.go @@ -0,0 +1,33 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseBaseBlockMove = common.Shortcut{ + Service: "base", + Command: "+base-block-move", + Description: "Move a base block", + Risk: "write", + Scopes: []string{"base:app:update"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + baseBlockIDFlag(true), + {Name: "parent-id", Desc: "target folder base block id; when omitted, move to the base root"}, + {Name: "before-id", Desc: "place before this sibling base block id"}, + {Name: "after-id", Desc: "place after this sibling base block id"}, + }, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + return validateBaseBlockMove(runtime) + }, + DryRun: dryRunBaseBlockMove, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeBaseBlockMove(runtime) + }, +} diff --git a/shortcuts/base/base_block_ops.go b/shortcuts/base/base_block_ops.go new file mode 100644 index 000000000..4fbfaad23 --- /dev/null +++ b/shortcuts/base/base_block_ops.go @@ -0,0 +1,158 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + "strings" + + "github.com/larksuite/cli/shortcuts/common" +) + +var baseBlockTypeEnums = []string{"folder", "table", "docx", "dashboard", "workflow"} + +func baseBlockIDFlag(required bool) common.Flag { + return common.Flag{Name: "block-id", Desc: "base block id", Required: required} +} + +func dryRunBaseBlockList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + return common.NewDryRunAPI(). + POST("/open-apis/base/v3/bases/:base_token/blocks/list"). + Body(buildBaseBlockListBody(runtime)). + Set("base_token", runtime.Str("base-token")) +} + +func dryRunBaseBlockCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + return common.NewDryRunAPI(). + POST("/open-apis/base/v3/bases/:base_token/blocks"). + Body(buildBaseBlockCreateBody(runtime)). + Set("base_token", runtime.Str("base-token")) +} + +func dryRunBaseBlockMove(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + return common.NewDryRunAPI(). + POST("/open-apis/base/v3/bases/:base_token/blocks/:block_id/move"). + Body(buildBaseBlockMoveBody(runtime)). + Set("base_token", runtime.Str("base-token")). + Set("block_id", runtime.Str("block-id")) +} + +func dryRunBaseBlockRename(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + return common.NewDryRunAPI(). + POST("/open-apis/base/v3/bases/:base_token/blocks/:block_id/rename"). + Body(map[string]interface{}{"name": strings.TrimSpace(runtime.Str("name"))}). + Set("base_token", runtime.Str("base-token")). + Set("block_id", runtime.Str("block-id")) +} + +func dryRunBaseBlockDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + return common.NewDryRunAPI(). + DELETE("/open-apis/base/v3/bases/:base_token/blocks/:block_id"). + Set("base_token", runtime.Str("base-token")). + Set("block_id", runtime.Str("block-id")) +} + +func validateBaseBlockCreate(runtime *common.RuntimeContext) error { + if strings.TrimSpace(runtime.Str("name")) == "" { + return common.FlagErrorf("--name must not be blank") + } + if strings.TrimSpace(runtime.Str("type")) == "" { + return common.FlagErrorf("--type must not be blank") + } + return nil +} + +func validateBaseBlockMove(runtime *common.RuntimeContext) error { + if strings.TrimSpace(runtime.Str("before-id")) != "" && strings.TrimSpace(runtime.Str("after-id")) != "" { + return common.FlagErrorf("--before-id and --after-id are mutually exclusive") + } + return nil +} + +func validateBaseBlockRename(runtime *common.RuntimeContext) error { + if strings.TrimSpace(runtime.Str("name")) == "" { + return common.FlagErrorf("--name must not be blank") + } + return nil +} + +func executeBaseBlockList(runtime *common.RuntimeContext) error { + data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", "list"), nil, buildBaseBlockListBody(runtime)) + if err != nil { + return err + } + runtime.Out(data, nil) + return nil +} + +func executeBaseBlockCreate(runtime *common.RuntimeContext) error { + data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks"), nil, buildBaseBlockCreateBody(runtime)) + if err != nil { + return err + } + runtime.Out(map[string]interface{}{"block": data, "created": true}, nil) + return nil +} + +func executeBaseBlockMove(runtime *common.RuntimeContext) error { + data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id"), "move"), nil, buildBaseBlockMoveBody(runtime)) + if err != nil { + return err + } + runtime.Out(map[string]interface{}{"block": data, "moved": true}, nil) + return nil +} + +func executeBaseBlockRename(runtime *common.RuntimeContext) error { + data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id"), "rename"), nil, map[string]interface{}{ + "name": strings.TrimSpace(runtime.Str("name")), + }) + if err != nil { + return err + } + runtime.Out(map[string]interface{}{"block": data, "renamed": true}, nil) + return nil +} + +func executeBaseBlockDelete(runtime *common.RuntimeContext) error { + data, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id")), nil, nil) + if err != nil { + return err + } + runtime.Out(map[string]interface{}{"block": data, "deleted": true}, nil) + return nil +} + +func buildBaseBlockListBody(runtime *common.RuntimeContext) map[string]interface{} { + body := map[string]interface{}{} + if parentID := strings.TrimSpace(runtime.Str("parent-id")); parentID != "" { + body["parent_id"] = parentID + } + return body +} + +func buildBaseBlockCreateBody(runtime *common.RuntimeContext) map[string]interface{} { + body := map[string]interface{}{ + "type": strings.TrimSpace(runtime.Str("type")), + "name": strings.TrimSpace(runtime.Str("name")), + } + if parentID := strings.TrimSpace(runtime.Str("parent-id")); parentID != "" { + body["parent_id"] = parentID + } + return body +} + +func buildBaseBlockMoveBody(runtime *common.RuntimeContext) map[string]interface{} { + body := map[string]interface{}{"parent_id": nil} + if parentID := strings.TrimSpace(runtime.Str("parent-id")); parentID != "" { + body["parent_id"] = parentID + } + if beforeID := strings.TrimSpace(runtime.Str("before-id")); beforeID != "" { + body["before_id"] = beforeID + } + if afterID := strings.TrimSpace(runtime.Str("after-id")); afterID != "" { + body["after_id"] = afterID + } + return body +} diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go new file mode 100644 index 000000000..bc732d23b --- /dev/null +++ b/shortcuts/base/base_block_rename.go @@ -0,0 +1,31 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseBaseBlockRename = common.Shortcut{ + Service: "base", + Command: "+base-block-rename", + Description: "Rename a base block", + Risk: "write", + Scopes: []string{"base:app:update"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + baseBlockIDFlag(true), + {Name: "name", Desc: "new base block name", Required: true}, + }, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + return validateBaseBlockRename(runtime) + }, + DryRun: dryRunBaseBlockRename, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeBaseBlockRename(runtime) + }, +} diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index f25b99ac2..82410c760 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -32,6 +32,29 @@ func TestDryRunTableOps(t *testing.T) { assertDryRunContains(t, dryRunTableDelete(ctx, rt), "DELETE /open-apis/base/v3/bases/app_x/tables/tbl_1") } +func TestDryRunBaseBlockOps(t *testing.T) { + ctx := context.Background() + + listRT := newBaseTestRuntime(map[string]string{"base-token": "app_x"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockList(ctx, listRT), "POST /open-apis/base/v3/bases/app_x/blocks/list") + + listFolderRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "parent-id": "bfl_1"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockList(ctx, listFolderRT), "POST /open-apis/base/v3/bases/app_x/blocks/list", `"parent_id":"bfl_1"`) + + createRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "type": "docx", "name": "Spec", "parent-id": "bfl_1"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockCreate(ctx, createRT), "POST /open-apis/base/v3/bases/app_x/blocks", `"type":"docx"`, `"name":"Spec"`, `"parent_id":"bfl_1"`) + + moveRootRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "block-id": "blk_1"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockMove(ctx, moveRootRT), "POST /open-apis/base/v3/bases/app_x/blocks/blk_1/move", `"parent_id":null`) + + moveAfterRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "block-id": "blk_1", "parent-id": "bfl_1", "after-id": "blk_0"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockMove(ctx, moveAfterRT), "POST /open-apis/base/v3/bases/app_x/blocks/blk_1/move", `"parent_id":"bfl_1"`, `"after_id":"blk_0"`) + + renameRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "block-id": "blk_1", "name": "New name"}, nil, nil) + assertDryRunContains(t, dryRunBaseBlockRename(ctx, renameRT), "POST /open-apis/base/v3/bases/app_x/blocks/blk_1/rename", `"name":"New name"`) + assertDryRunContains(t, dryRunBaseBlockDelete(ctx, renameRT), "DELETE /open-apis/base/v3/bases/app_x/blocks/blk_1") +} + func TestDryRunFieldOps(t *testing.T) { ctx := context.Background() diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index 9f75ac763..ac143d078 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -133,6 +133,7 @@ func TestViewSetVisibleFieldsValidateHook(t *testing.T) { func TestShortcutsCatalog(t *testing.T) { shortcuts := Shortcuts() want := []string{ + "+base-block-list", "+base-block-create", "+base-block-move", "+base-block-rename", "+base-block-delete", "+table-list", "+table-get", "+table-create", "+table-update", "+table-delete", "+field-list", "+field-get", "+field-create", "+field-update", "+field-delete", "+field-search-options", "+view-list", "+view-get", "+view-create", "+view-delete", "+view-get-filter", "+view-set-filter", "+view-get-visible-fields", "+view-set-visible-fields", "+view-get-group", "+view-set-group", "+view-get-sort", "+view-set-sort", "+view-get-timebar", "+view-set-timebar", "+view-get-card", "+view-set-card", "+view-rename", @@ -188,6 +189,7 @@ func TestBaseDeleteShortcutsRisk(t *testing.T) { BaseFormQuestionsDelete.Command: BaseFormQuestionsDelete.Risk, BaseDashboardDelete.Command: BaseDashboardDelete.Risk, BaseDashboardBlockDelete.Command: BaseDashboardBlockDelete.Risk, + BaseBaseBlockDelete.Command: BaseBaseBlockDelete.Risk, BaseRoleDelete.Command: BaseRoleDelete.Risk, } @@ -222,6 +224,30 @@ func TestBaseFieldUpdateHelpHidesReadGuideFlag(t *testing.T) { } } +func TestBaseBlockMoveRejectsBeforeAndAfter(t *testing.T) { + runtime := newBaseTestRuntime( + map[string]string{"before-id": "blk_before", "after-id": "blk_after"}, + nil, + nil, + ) + err := validateBaseBlockMove(runtime) + if err == nil || !strings.Contains(err.Error(), "--before-id and --after-id are mutually exclusive") { + t.Fatalf("err=%v", err) + } +} + +func TestBaseBlockCreateAndRenameRequireName(t *testing.T) { + createRT := newBaseTestRuntime(map[string]string{"type": "folder", "name": " "}, nil, nil) + if err := validateBaseBlockCreate(createRT); err == nil || !strings.Contains(err.Error(), "--name must not be blank") { + t.Fatalf("create err=%v", err) + } + + renameRT := newBaseTestRuntime(map[string]string{"name": " "}, nil, nil) + if err := validateBaseBlockRename(renameRT); err == nil || !strings.Contains(err.Error(), "--name must not be blank") { + t.Fatalf("rename err=%v", err) + } +} + func TestBaseRecordReadHelpGuidesAgents(t *testing.T) { tests := []struct { name string diff --git a/shortcuts/base/shortcuts.go b/shortcuts/base/shortcuts.go index c98ccff7e..cc52efa22 100644 --- a/shortcuts/base/shortcuts.go +++ b/shortcuts/base/shortcuts.go @@ -8,6 +8,11 @@ import "github.com/larksuite/cli/shortcuts/common" // Shortcuts returns all base shortcuts. func Shortcuts() []common.Shortcut { return []common.Shortcut{ + BaseBaseBlockList, + BaseBaseBlockCreate, + BaseBaseBlockMove, + BaseBaseBlockRename, + BaseBaseBlockDelete, BaseTableList, BaseTableGet, BaseTableCreate, diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 371191767..5308179c6 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -55,6 +55,7 @@ metadata: | 大模块 | 处理什么问题 | 包含的小模块 / 能力 | |------|-------------|-------------------| | Base 模块 | 管理 Base 本体,或从链接进入 Base 场景 | `base-create / base-get / base-copy`,Base / Wiki 链接解析 | +| Base Block 模块 | 管理 Base 容器里的 folder/table/docx/dashboard/workflow 入口 | `base-block-*` | | 表与数据模块 | 管理 Base 内部结构与日常数据操作 | `table / field / record / view` | | 公式 / Lookup 模块 | 处理派生字段、条件判断、跨表计算、固定查找引用 | `formula / lookup` 字段创建与更新 | | 数据分析模块 | 做一次性筛选、分组、聚合分析 | `data-query` | @@ -75,12 +76,27 @@ metadata: | `+base-get` | 获取 Base 信息 | [`lark-base-base-get.md`](references/lark-base-base-get.md)、[`lark-base-workspace.md`](references/lark-base-workspace.md) | 适合确认 Base 本体信息,不替代表/字段结构读取 | | `+base-copy` | 复制已有 Base | [`lark-base-base-copy.md`](references/lark-base-base-copy.md)、[`lark-base-workspace.md`](references/lark-base-workspace.md) | 写入操作;执行前先读 reference;复制成功后应主动返回新 Base 标识信息 | -### 2.3 表与数据模块 +### 2.3 Base Block 模块 + +用于管理 Base 容器里的资源入口,包括 folder、table、docx、dashboard、workflow。 +模块索引:[`references/lark-base-base-block.md`](references/lark-base-base-block.md) + +> `base-block` 是 Base 容器条目;`dashboard-block` 是仪表盘内部图表/组件。不要混用。 + +| 命令 | 用途 / 何时使用 | 必读 reference | 路由提醒 | +|------|------------------|----------------|----------| +| `+base-block-list` | 列出 Base 容器里的 block;可用 `--parent-id` 列出某个 folder 下的条目 | [`lark-base-base-block-list.md`](references/lark-base-base-block-list.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | CLI 不暴露分页;不传 `--parent-id` 时列出全部 | +| `+base-block-create` | 创建 folder/table/docx/dashboard/workflow | [`lark-base-base-block-create.md`](references/lark-base-base-block-create.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填;`--parent-id` 不传表示根层级 | +| `+base-block-move` | 移动 block 到根层级或 folder,并可调整 before/after 顺序 | [`lark-base-base-block-move.md`](references/lark-base-base-block-move.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--before-id` 和 `--after-id` 互斥;`--parent-id` 不传表示根层级 | +| `+base-block-rename` | 重命名 Base 容器里的 block | [`lark-base-base-block-rename.md`](references/lark-base-base-block-rename.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填 | +| `+base-block-delete` | 删除 Base 容器里的 block | [`lark-base-base-block-delete.md`](references/lark-base-base-block-delete.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | 高风险操作;不支持递归删除非空 folder | + +### 2.4 表与数据模块 这是最常用的大模块,包含 `table / field / record / view` 四类子模块。 补充示例:[`references/examples.md`](references/examples.md),适合需要串联 table / record / view 完整操作链路时再读。 -#### 2.3.1 Table 子模块 +#### 2.4.1 Table 子模块 子模块索引:[`references/lark-base-table.md`](references/lark-base-table.md) @@ -89,7 +105,7 @@ metadata: | `+table-list / +table-get` | 列出数据表,或获取单个表详情 | [`lark-base-table-list.md`](references/lark-base-table-list.md)、[`lark-base-table-get.md`](references/lark-base-table-get.md) | `+table-list` 只能串行执行;`+table-get` 适合删除/修改前确认目标 | | `+table-create / +table-update / +table-delete` | 创建、更新或删除数据表 | [`lark-base-table-create.md`](references/lark-base-table-create.md)、[`lark-base-table-update.md`](references/lark-base-table-update.md)、[`lark-base-table-delete.md`](references/lark-base-table-delete.md) | 创建适合一次性建表;更新前先确认目标表;删除时用户已明确目标可直接执行并带 `--yes` | -#### 2.3.2 Field 子模块 +#### 2.4.2 Field 子模块 普通字段管理走这里;如果字段类型是 `formula` 或 `lookup`,转到下方“公式 / Lookup 模块”。 子模块索引:[`references/lark-base-field.md`](references/lark-base-field.md) @@ -100,7 +116,7 @@ metadata: | `+field-create / +field-update / +field-delete` | 创建、更新或删除普通字段 | [`lark-base-field-create.md`](references/lark-base-field-create.md)、[`lark-base-field-update.md`](references/lark-base-field-update.md)、[`lark-base-field-delete.md`](references/lark-base-field-delete.md)、[`lark-base-shortcut-field-properties.md`](references/lark-base-shortcut-field-properties.md) | 写字段前先看字段属性规范;如果涉及类型转换,直接按 `+field-update` 中的字段类型变更规则执行,只在安全白名单内考虑原地转换;如果类型是 `formula / lookup`,先转去读对应 guide;更新或删除时用户已明确目标可直接执行并带 `--yes` | | `+field-search-options` | 查询字段可选项 | [`lark-base-field-search-options.md`](references/lark-base-field-search-options.md) | 适合单选/多选等选项型字段 | -#### 2.3.3 Record 子模块 +#### 2.4.3 Record 子模块 子模块索引:[`references/lark-base-record.md`](references/lark-base-record.md)、[`references/lark-base-history.md`](references/lark-base-history.md) @@ -115,7 +131,7 @@ metadata: | `+record-history-list` | 查询指定记录的变更历史 | [`lark-base-record-history-list.md`](references/lark-base-record-history-list.md) | 按 `table-id + record-id` 查询,不支持整表扫描;`+record-history-list` 只能串行执行 | | `+record-share-link-create` | 为一条或多条记录生成分享链接 | [`lark-base-record-share-link-create.md`](references/lark-base-record-share-link-create.md) | 单次最多 100 条;重复 record_id 会自动去重;适合分享单条记录或批量分享场景 | -#### 2.3.4 View 子模块 +#### 2.4.4 View 子模块 子模块索引:[`references/lark-base-view.md`](references/lark-base-view.md) @@ -130,7 +146,7 @@ metadata: | `+view-get-card / +view-set-card` | 读取或配置卡片视图 | [`lark-base-view-get-card.md`](references/lark-base-view-get-card.md)、[`lark-base-view-set-card.md`](references/lark-base-view-set-card.md) | 适合卡片展示场景 | | `+view-get-timebar / +view-set-timebar` | 读取或配置时间轴视图 | [`lark-base-view-get-timebar.md`](references/lark-base-view-get-timebar.md)、[`lark-base-view-set-timebar.md`](references/lark-base-view-set-timebar.md) | 适合时间线展示场景 | -### 2.4 公式 / Lookup 模块 +### 2.5 公式 / Lookup 模块 只要用户诉求涉及派生指标、条件判断、文本处理、日期差、跨表计算、跨表筛选后取值,都要先判断是否进入本模块。 @@ -144,7 +160,7 @@ metadata: | `+field-create`(`type=lookup`) | 创建 lookup 字段 | [`lookup-field-guide.md`](references/lookup-field-guide.md)、[`lark-base-field-create.md`](references/lark-base-field-create.md)、[`lark-base-shortcut-field-properties.md`](references/lark-base-shortcut-field-properties.md) | 没读 guide 前不要直接创建 | | `+field-update`(`type=lookup`) | 更新 lookup 字段 | [`lookup-field-guide.md`](references/lookup-field-guide.md)、[`lark-base-field-update.md`](references/lark-base-field-update.md)、[`lark-base-shortcut-field-properties.md`](references/lark-base-shortcut-field-properties.md) | 跨表时还要拿目标表结构 | -### 2.5 数据分析模块 +### 2.6 数据分析模块 用于一次性分析和临时聚合查询。用户要的是“这次算出来的结果”,而不是把结果沉淀成字段时,优先进入本模块。 @@ -158,7 +174,7 @@ metadata: |------|------------------|----------------|----------| | `+data-query` | 做分组统计、SUM / AVG / COUNT / MAX / MIN、条件筛选后的聚合分析 | [`lark-base-data-query.md`](references/lark-base-data-query.md) | 字段名必须精确匹配真实字段名;不要用 `+record-list` / `+record-search` 拉全量再手算;`+data-query` 不返回原始记录;使用前先确认权限和字段类型是否受支持 | -### 2.6 Workflow 模块 +### 2.7 Workflow 模块 这是高约束模块。执行任何 workflow 命令前,都必须先读对应命令文档和 schema。 模块索引:[`references/lark-base-workflow.md`](references/lark-base-workflow.md) @@ -169,7 +185,7 @@ metadata: | `+workflow-create / +workflow-update` | 创建或更新 workflow | [`lark-base-workflow-create.md`](references/lark-base-workflow-create.md)、[`lark-base-workflow-update.md`](references/lark-base-workflow-update.md)、[`lark-base-workflow-schema.md`](references/lark-base-workflow-schema.md) | 先读 schema;禁止凭自然语言猜 `type`;先确认真实表名和字段名 | | `+workflow-enable / +workflow-disable` | 启用或停用 workflow | [`lark-base-workflow-enable.md`](references/lark-base-workflow-enable.md)、[`lark-base-workflow-disable.md`](references/lark-base-workflow-disable.md)、[`lark-base-workflow-schema.md`](references/lark-base-workflow-schema.md) | 启用或停用前先确认目标 workflow;`workflow_id` 与 `table_id` 需按前缀区分 | -### 2.7 Dashboard 模块 +### 2.8 Dashboard 模块 当用户提到“仪表盘、dashboard、数据看板、图表、可视化、block、组件、添加组件、创建图表”等关键词时,进入本模块,并先阅读 [`lark-base-dashboard.md`](references/lark-base-dashboard.md)。 @@ -180,7 +196,7 @@ metadata: | `+dashboard-block-list / +dashboard-block-get` | 列出图表组件,或获取单个 block 详情 | [`lark-base-dashboard-block-list.md`](references/lark-base-dashboard-block-list.md)、[`lark-base-dashboard-block-get.md`](references/lark-base-dashboard-block-get.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md)、[`dashboard-block-data-config.md`](references/dashboard-block-data-config.md) | `+dashboard-block-list` 只能串行执行;查看配置细节时读 block config 文档 | | `+dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` | 创建、更新或删除图表组件 | [`lark-base-dashboard-block-create.md`](references/lark-base-dashboard-block-create.md)、[`lark-base-dashboard-block-update.md`](references/lark-base-dashboard-block-update.md)、[`lark-base-dashboard-block-delete.md`](references/lark-base-dashboard-block-delete.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md)、[`dashboard-block-data-config.md`](references/dashboard-block-data-config.md) | 涉及 `data_config`、图表类型、filter 时要读 block config 文档;删除前先确认目标 | -### 2.8 表单模块 +### 2.9 表单模块 用于管理表单本体和表单题目。 模块索引:[`references/lark-base-form.md`](references/lark-base-form.md)、[`references/lark-base-form-questions.md`](references/lark-base-form-questions.md) @@ -195,7 +211,7 @@ metadata: | `+form-questions-list` | 列出表单题目 | [`lark-base-form-questions-list.md`](references/lark-base-form-questions-list.md) | 适合查看已有题目结构 | | `+form-questions-create / +form-questions-update / +form-questions-delete` | 创建、更新或删除题目 | [`lark-base-form-questions-create.md`](references/lark-base-form-questions-create.md)、[`lark-base-form-questions-update.md`](references/lark-base-form-questions-update.md)、[`lark-base-form-questions-delete.md`](references/lark-base-form-questions-delete.md) | 先确认 `form-id`;更新或删除前先确认题目目标 | -### 2.9 权限与角色模块 +### 2.10 权限与角色模块 用于启用高级权限,以及管理 Base 自定义角色。 涉及 `+advperm-enable / +advperm-disable / +role-*` 时,操作用户必须为 Base 管理员,否则会返回权限错误。 diff --git a/skills/lark-base/references/lark-base-base-block-create.md b/skills/lark-base/references/lark-base-base-block-create.md new file mode 100644 index 000000000..e70279f3e --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block-create.md @@ -0,0 +1,44 @@ +# base +base-block-create + +在 Base 容器里创建一个 block 条目。支持 `folder`、`table`、`docx`、`dashboard`、`workflow`。 + +## 推荐命令 + +```bash +lark-cli base +base-block-create \ + --base-token app_xxx \ + --type folder \ + --name "项目资料" + +lark-cli base +base-block-create \ + --base-token app_xxx \ + --type docx \ + --name "需求文档" \ + --parent-id blk_folder +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--type ` | 是 | `folder` / `table` / `docx` / `dashboard` / `workflow` | +| `--name ` | 是 | 新 block 名称 | +| `--parent-id ` | 否 | 父 folder 的 base block id;不传时创建在根层级 | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回重点 + +- 返回 `block` 和 `created=true`。 +- `block.id` 是 Base 容器里的 block id。 +- 如果创建 docx,返回中可能包含 `docx_token`,后续 docx 内容操作使用该 token。 + +## 坑点 + +- `--name` 必填,不能依赖默认名称。 +- 创建的是 Base 容器里的入口及对应资源;资源内容仍需用 table/docx/dashboard/workflow 对应命令继续操作。 +- 创建 workflow/dashboard 时,初始配置可能为空,后续需要再用对应模块补全。 + +## 参考 + +- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-delete.md b/skills/lark-base/references/lark-base-base-block-delete.md new file mode 100644 index 000000000..3b745ed62 --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block-delete.md @@ -0,0 +1,35 @@ +# base +base-block-delete + +删除 Base 容器里的 block。 + +## 推荐命令 + +```bash +lark-cli base +base-block-delete \ + --base-token app_xxx \ + --block-id blk_xxx \ + --yes +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--block-id ` | 是 | 要删除的 base block id | +| `--yes` | 是 | 高风险写入确认 | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回重点 + +- 返回 `block` 和 `deleted=true`。 + +## 坑点 + +- 不支持递归删除 folder。删除非空 folder 前,先移动或删除其子项。 +- 删除的是 Base 容器里的 block。不同类型底层资源是否会被物理删除,以后端语义为准。 +- 删除前建议先用 `+base-block-list` 确认 `block-id`。 + +## 参考 + +- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-list.md b/skills/lark-base/references/lark-base-base-block-list.md new file mode 100644 index 000000000..ca54f0540 --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block-list.md @@ -0,0 +1,38 @@ +# base +base-block-list + +列出 Base 容器管理的 block 条目,可按 folder 过滤直属子项。 + +## 推荐命令 + +```bash +lark-cli base +base-block-list \ + --base-token app_xxx + +lark-cli base +base-block-list \ + --base-token app_xxx \ + --parent-id blk_folder +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--parent-id ` | 否 | folder 的 base block id;不传时列出全部 Base block | +| `--format ` | 否 | 输出格式:json / pretty / table / csv / ndjson | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回重点 + +- 返回后端 `blocks` 与 `total`。 +- 每个 block 至少包含 `id`、`type`、`name`、`parent_id`。 +- docx 类型可能额外包含 `docx_token`,用于后续 docx 文档操作。 + +## 坑点 + +- CLI 不暴露 `limit/offset`。如果结果超过后端当前上限,需要先调整 Base 结构或等待后端能力扩展。 +- `+base-block-list` 是 Base 容器列表,不是表记录列表,也不是仪表盘组件列表。 + +## 参考 + +- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-move.md b/skills/lark-base/references/lark-base-base-block-move.md new file mode 100644 index 000000000..d2c4fc8a3 --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block-move.md @@ -0,0 +1,50 @@ +# base +base-block-move + +移动 Base 容器里的 block,可移动到根层级或某个 folder 下,并可指定同级顺序。 + +## 推荐命令 + +```bash +# 移动到根层级 +lark-cli base +base-block-move \ + --base-token app_xxx \ + --block-id blk_xxx + +# 移动到文件夹里 +lark-cli base +base-block-move \ + --base-token app_xxx \ + --block-id blk_xxx \ + --parent-id blk_folder + +# 移动到某个同级条目之后 +lark-cli base +base-block-move \ + --base-token app_xxx \ + --block-id blk_xxx \ + --parent-id blk_folder \ + --after-id blk_sibling +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--block-id ` | 是 | 要移动的 base block id | +| `--parent-id ` | 否 | 目标 folder 的 base block id;不传表示移动到根层级 | +| `--before-id ` | 否 | 放到该同级 block 前 | +| `--after-id ` | 否 | 放到该同级 block 后 | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回重点 + +- 返回 `block` 和 `moved=true`。 + +## 坑点 + +- `--before-id` 与 `--after-id` 互斥,不能同时传。 +- 不传 `--parent-id` 表示根层级,不需要也不要输入 `null`。 +- 移动 folder 是移动整个 folder 入口,子项仍归属该 folder。 + +## 参考 + +- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-rename.md b/skills/lark-base/references/lark-base-base-block-rename.md new file mode 100644 index 000000000..ba727c494 --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block-rename.md @@ -0,0 +1,34 @@ +# base +base-block-rename + +重命名 Base 容器里的 block。 + +## 推荐命令 + +```bash +lark-cli base +base-block-rename \ + --base-token app_xxx \ + --block-id blk_xxx \ + --name "新的名称" +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--block-id ` | 是 | 要重命名的 base block id | +| `--name ` | 是 | 新名称 | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回重点 + +- 返回 `block` 和 `renamed=true`。 + +## 坑点 + +- `--name` 必填,且不能为空白字符串。 +- 这是重命名 Base 容器入口;具体资源内部标题是否同步,以后端语义为准。 + +## 参考 + +- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block.md b/skills/lark-base/references/lark-base-base-block.md new file mode 100644 index 000000000..12b9bc13e --- /dev/null +++ b/skills/lark-base/references/lark-base-base-block.md @@ -0,0 +1,23 @@ +# base +base-block-* + +管理 Base 容器里的一级资源入口。Base block 是 Base 侧边栏/容器管理的条目,包括 folder、table、docx、dashboard、workflow。 + +> 注意:`base-block` 和 `dashboard-block` 不是同一个概念。`base-block` 是 Base 容器条目;`dashboard-block` 是仪表盘内部的图表/组件。 + +## 命令选择 + +| 目标 | 命令 | 参考 | +|------|------|------| +| 列出 Base 容器条目 | `+base-block-list` | [lark-base-base-block-list.md](lark-base-base-block-list.md) | +| 创建 folder/table/docx/dashboard/workflow 条目 | `+base-block-create` | [lark-base-base-block-create.md](lark-base-base-block-create.md) | +| 移动条目到根或文件夹、调整同级位置 | `+base-block-move` | [lark-base-base-block-move.md](lark-base-base-block-move.md) | +| 重命名条目 | `+base-block-rename` | [lark-base-base-block-rename.md](lark-base-base-block-rename.md) | +| 删除条目 | `+base-block-delete` | [lark-base-base-block-delete.md](lark-base-base-block-delete.md) | + +## 通用约束 + +- `--block-id` 是 Base 容器里的 block id,不是 docx token,也不是 dashboard 内部 chart/widget id。 +- `--parent-id` 是目标 folder 的 base block id;不传表示根层级。 +- 当前 CLI 不暴露分页参数。Base block 总数上限由后端控制,默认一次返回完整列表。 +- 当前不支持递归删除文件夹。删除非空 folder 时,先移动或删除其子项。 +- 创建出的 docx/dashboard/workflow/table 的具体内容,需要继续用对应模块命令操作。 diff --git a/tests/cli_e2e/base/base_block_dryrun_test.go b/tests/cli_e2e/base/base_block_dryrun_test.go new file mode 100644 index 000000000..ce9753602 --- /dev/null +++ b/tests/cli_e2e/base/base_block_dryrun_test.go @@ -0,0 +1,152 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + "testing" + "time" + + clie2e "github.com/larksuite/cli/tests/cli_e2e" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" +) + +func TestBaseBlockDryRun(t *testing.T) { + setBaseDryRunConfigEnv(t) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + t.Cleanup(cancel) + + t.Run("list all", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-list", + "--base-token", "app_x", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/list", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "POST", gjson.Get(out, "api.0.method").String(), out) + require.False(t, gjson.Get(out, "api.0.body.parent_id").Exists(), out) + }) + + t.Run("list folder", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-list", + "--base-token", "app_x", + "--parent-id", "blk_folder", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/list", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "blk_folder", gjson.Get(out, "api.0.body.parent_id").String(), out) + }) + + t.Run("create", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-create", + "--base-token", "app_x", + "--type", "docx", + "--name", "Spec", + "--parent-id", "blk_folder", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "POST", gjson.Get(out, "api.0.method").String(), out) + require.Equal(t, "docx", gjson.Get(out, "api.0.body.type").String(), out) + require.Equal(t, "Spec", gjson.Get(out, "api.0.body.name").String(), out) + require.Equal(t, "blk_folder", gjson.Get(out, "api.0.body.parent_id").String(), out) + }) + + t.Run("move root", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-move", + "--base-token", "app_x", + "--block-id", "blk_a", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/blk_a/move", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "POST", gjson.Get(out, "api.0.method").String(), out) + require.True(t, gjson.Get(out, "api.0.body.parent_id").Exists(), out) + require.Equal(t, "Null", gjson.Get(out, "api.0.body.parent_id").Type.String(), out) + }) + + t.Run("move after", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-move", + "--base-token", "app_x", + "--block-id", "blk_a", + "--parent-id", "blk_folder", + "--after-id", "blk_b", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/blk_a/move", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "blk_folder", gjson.Get(out, "api.0.body.parent_id").String(), out) + require.Equal(t, "blk_b", gjson.Get(out, "api.0.body.after_id").String(), out) + }) + + t.Run("rename", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-rename", + "--base-token", "app_x", + "--block-id", "blk_a", + "--name", "Renamed", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/blk_a/rename", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "POST", gjson.Get(out, "api.0.method").String(), out) + require.Equal(t, "Renamed", gjson.Get(out, "api.0.body.name").String(), out) + }) + + t.Run("delete", func(t *testing.T) { + result, err := clie2e.RunCmd(ctx, clie2e.Request{ + Args: []string{ + "base", "+base-block-delete", + "--base-token", "app_x", + "--block-id", "blk_a", + "--dry-run", + }, + DefaultAs: "bot", + }) + require.NoError(t, err) + result.AssertExitCode(t, 0) + out := result.Stdout + require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/blk_a", gjson.Get(out, "api.0.url").String(), out) + require.Equal(t, "DELETE", gjson.Get(out, "api.0.method").String(), out) + }) +} diff --git a/tests/cli_e2e/base/coverage.md b/tests/cli_e2e/base/coverage.md index b1b7f80a1..ec7d2f0a7 100644 --- a/tests/cli_e2e/base/coverage.md +++ b/tests/cli_e2e/base/coverage.md @@ -1,12 +1,13 @@ # Base CLI E2E Coverage ## Metrics -- Denominator: 73 leaf commands -- Covered: 10 -- Coverage: 13.7% +- Denominator: 78 leaf commands +- Covered: 15 +- Coverage: 19.2% ## Summary - TestBase_BasicWorkflow: proves `+base-create`, `+base-get`, `+table-create`, `+table-get`, and `+table-list`; key `t.Run(...)` proof points are `get base as bot`, `get table as bot`, and `list tables and find created table as bot`. +- TestBaseBlockDryRun: proves the five `+base-block-*` shortcuts request shapes without touching live data. - TestBase_RoleWorkflow: proves `+advperm-enable`, `+role-create`, `+role-list`, `+role-get`, and `+role-update`; key `t.Run(...)` proof points are `list as bot`, `get as bot`, and `update as bot`. - Cleanup note: `+table-delete` and `+role-delete` only run in cleanup and are intentionally left uncovered. - Blocked area: dashboard, field, form, record, view, and workflow operations still lack deterministic create/read/update workflows in this suite. @@ -20,6 +21,11 @@ | ✕ | base +base-copy | shortcut | | none | no copy workflow yet | | ✓ | base +base-create | shortcut | base/helpers_test.go::createBaseWithRetry | `--name`; `--time-zone` | helper asserts created base token | | ✓ | base +base-get | shortcut | base_basic_workflow_test.go::TestBase_BasicWorkflow/get base as bot | `--base-token` | | +| ✓ | base +base-block-create | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/create | `--base-token`; `--type`; `--name`; `--parent-id`; dry-run only | request shape only | +| ✓ | base +base-block-delete | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/delete | `--base-token`; `--block-id`; dry-run only | request shape only | +| ✓ | base +base-block-list | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/list all,list folder | `--base-token`; optional `--parent-id`; dry-run only | request shape only | +| ✓ | base +base-block-move | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/move root,move after | `--base-token`; `--block-id`; optional `--parent-id`; `--after-id`; dry-run only | request shape only | +| ✓ | base +base-block-rename | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/rename | `--base-token`; `--block-id`; `--name`; dry-run only | request shape only | | ✕ | base +dashboard-arrange | shortcut | | none | dashboard workflows not covered | | ✕ | base +dashboard-block-create | shortcut | | none | dashboard workflows not covered | | ✕ | base +dashboard-block-delete | shortcut | | none | dashboard workflows not covered | From ad8570b3bd6257c4a5daeda7c969cbd393684efb Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 17:39:25 +0800 Subject: [PATCH 2/7] fix(base): use block scopes for base block shortcuts --- shortcuts/base/base_block_create.go | 2 +- shortcuts/base/base_block_delete.go | 2 +- shortcuts/base/base_block_list.go | 2 +- shortcuts/base/base_block_move.go | 2 +- shortcuts/base/base_block_rename.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go index 35d3627f2..552315805 100644 --- a/shortcuts/base/base_block_create.go +++ b/shortcuts/base/base_block_create.go @@ -14,7 +14,7 @@ var BaseBaseBlockCreate = common.Shortcut{ Command: "+base-block-create", Description: "Create a base block", Risk: "write", - Scopes: []string{"base:app:update"}, + Scopes: []string{"base:block:write"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go index 434c755f4..7b370b83c 100644 --- a/shortcuts/base/base_block_delete.go +++ b/shortcuts/base/base_block_delete.go @@ -14,7 +14,7 @@ var BaseBaseBlockDelete = common.Shortcut{ Command: "+base-block-delete", Description: "Delete a base block", Risk: "high-risk-write", - Scopes: []string{"base:app:update"}, + Scopes: []string{"base:block:write"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go index 011d413ae..1930202ca 100644 --- a/shortcuts/base/base_block_list.go +++ b/shortcuts/base/base_block_list.go @@ -14,7 +14,7 @@ var BaseBaseBlockList = common.Shortcut{ Command: "+base-block-list", Description: "List base blocks in a base", Risk: "read", - Scopes: []string{"base:app:read"}, + Scopes: []string{"base:block:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go index 1e7f051b5..a2a5de7f7 100644 --- a/shortcuts/base/base_block_move.go +++ b/shortcuts/base/base_block_move.go @@ -14,7 +14,7 @@ var BaseBaseBlockMove = common.Shortcut{ Command: "+base-block-move", Description: "Move a base block", Risk: "write", - Scopes: []string{"base:app:update"}, + Scopes: []string{"base:block:write"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go index bc732d23b..7a6983cf4 100644 --- a/shortcuts/base/base_block_rename.go +++ b/shortcuts/base/base_block_rename.go @@ -14,7 +14,7 @@ var BaseBaseBlockRename = common.Shortcut{ Command: "+base-block-rename", Description: "Rename a base block", Risk: "write", - Scopes: []string{"base:app:update"}, + Scopes: []string{"base:block:write"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), From 1a9b59ed08f1e0bb7979cdb2b4a6c0fd66018965 Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 17:41:29 +0800 Subject: [PATCH 3/7] fix(base): split base block shortcut scopes --- shortcuts/base/base_block_create.go | 2 +- shortcuts/base/base_block_delete.go | 2 +- shortcuts/base/base_block_move.go | 2 +- shortcuts/base/base_block_rename.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go index 552315805..a0fde57dd 100644 --- a/shortcuts/base/base_block_create.go +++ b/shortcuts/base/base_block_create.go @@ -14,7 +14,7 @@ var BaseBaseBlockCreate = common.Shortcut{ Command: "+base-block-create", Description: "Create a base block", Risk: "write", - Scopes: []string{"base:block:write"}, + Scopes: []string{"base:block:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go index 7b370b83c..bbbd16ce1 100644 --- a/shortcuts/base/base_block_delete.go +++ b/shortcuts/base/base_block_delete.go @@ -14,7 +14,7 @@ var BaseBaseBlockDelete = common.Shortcut{ Command: "+base-block-delete", Description: "Delete a base block", Risk: "high-risk-write", - Scopes: []string{"base:block:write"}, + Scopes: []string{"base:block:delete"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go index a2a5de7f7..97c2fc98d 100644 --- a/shortcuts/base/base_block_move.go +++ b/shortcuts/base/base_block_move.go @@ -14,7 +14,7 @@ var BaseBaseBlockMove = common.Shortcut{ Command: "+base-block-move", Description: "Move a base block", Risk: "write", - Scopes: []string{"base:block:write"}, + Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go index 7a6983cf4..2b157f0e4 100644 --- a/shortcuts/base/base_block_rename.go +++ b/shortcuts/base/base_block_rename.go @@ -14,7 +14,7 @@ var BaseBaseBlockRename = common.Shortcut{ Command: "+base-block-rename", Description: "Rename a base block", Risk: "write", - Scopes: []string{"base:block:write"}, + Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), From ec039ccd4c48468a6d378c414516dad3aebc3939 Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 17:49:43 +0800 Subject: [PATCH 4/7] docs(base): consolidate base block help --- shortcuts/base/base_block_create.go | 4 +- shortcuts/base/base_block_delete.go | 3 ++ shortcuts/base/base_block_list.go | 4 +- shortcuts/base/base_block_move.go | 5 ++ shortcuts/base/base_block_rename.go | 4 ++ skills/lark-base/SKILL.md | 10 ++-- .../references/lark-base-base-block-create.md | 44 ---------------- .../references/lark-base-base-block-delete.md | 35 ------------- .../references/lark-base-base-block-list.md | 38 -------------- .../references/lark-base-base-block-move.md | 50 ------------------- .../references/lark-base-base-block-rename.md | 34 ------------- .../references/lark-base-base-block.md | 28 ++++++++--- 12 files changed, 43 insertions(+), 216 deletions(-) delete mode 100644 skills/lark-base/references/lark-base-base-block-create.md delete mode 100644 skills/lark-base/references/lark-base-base-block-delete.md delete mode 100644 skills/lark-base/references/lark-base-base-block-list.md delete mode 100644 skills/lark-base/references/lark-base-base-block-move.md delete mode 100644 skills/lark-base/references/lark-base-base-block-rename.md diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go index a0fde57dd..0ff1ab8ee 100644 --- a/shortcuts/base/base_block_create.go +++ b/shortcuts/base/base_block_create.go @@ -23,7 +23,9 @@ var BaseBaseBlockCreate = common.Shortcut{ {Name: "parent-id", Desc: "folder base block id; when omitted, create at the base root"}, }, Tips: []string{ - "Use this for Base container entries. Use table/field/record/docx/dashboard/workflow commands for content inside the created entity.", + "Creates a Base container entry for folder, table, docx, dashboard, or workflow.", + "Do not pass null for --parent-id. Omit it to create at the root level.", + "Created resources still use their own commands for content operations, such as table/field/record/docx/dashboard/workflow commands.", }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return validateBaseBlockCreate(runtime) diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go index bbbd16ce1..b44933f97 100644 --- a/shortcuts/base/base_block_delete.go +++ b/shortcuts/base/base_block_delete.go @@ -21,7 +21,10 @@ var BaseBaseBlockDelete = common.Shortcut{ baseBlockIDFlag(true), }, Tips: []string{ + "Deletes the Base container entry identified by --block-id.", "Recursive folder deletion is not supported. If a folder is not empty, move or delete its children first.", + "Different block types may have independent backing resources; deletion follows backend Base block semantics.", + "Use +base-block-list first when you need to confirm the target block id.", }, DryRun: dryRunBaseBlockDelete, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go index 1930202ca..42e8856ff 100644 --- a/shortcuts/base/base_block_list.go +++ b/shortcuts/base/base_block_list.go @@ -18,10 +18,12 @@ var BaseBaseBlockList = common.Shortcut{ AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), - {Name: "parent-id", Desc: "optional folder base block id; when omitted, list all base blocks"}, + {Name: "parent-id", Desc: "folder base block id; when omitted, list all base blocks"}, }, Tips: []string{ "Base blocks are entries managed by the Base container, such as folder, table, docx, dashboard, and workflow.", + "This command returns the full backend list. It intentionally does not expose limit or offset.", + "Pass --parent-id to list only direct children of a folder base block.", "Dashboard blocks are chart/widget blocks inside a dashboard; use +dashboard-block-* for those.", }, DryRun: dryRunBaseBlockList, diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go index 97c2fc98d..371cd6130 100644 --- a/shortcuts/base/base_block_move.go +++ b/shortcuts/base/base_block_move.go @@ -23,6 +23,11 @@ var BaseBaseBlockMove = common.Shortcut{ {Name: "before-id", Desc: "place before this sibling base block id"}, {Name: "after-id", Desc: "place after this sibling base block id"}, }, + Tips: []string{ + "Omit --parent-id to move the block to the base root; do not pass null.", + "--before-id and --after-id are mutually exclusive.", + "When moving a folder, its children remain under that folder.", + }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return validateBaseBlockMove(runtime) }, diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go index 2b157f0e4..04319299a 100644 --- a/shortcuts/base/base_block_rename.go +++ b/shortcuts/base/base_block_rename.go @@ -21,6 +21,10 @@ var BaseBaseBlockRename = common.Shortcut{ baseBlockIDFlag(true), {Name: "name", Desc: "new base block name", Required: true}, }, + Tips: []string{ + "Renames the Base container entry identified by --block-id.", + "Use +base-block-list first when you need to resolve the target block id from a visible name.", + }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return validateBaseBlockRename(runtime) }, diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 5308179c6..9f3169c0d 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -85,11 +85,11 @@ metadata: | 命令 | 用途 / 何时使用 | 必读 reference | 路由提醒 | |------|------------------|----------------|----------| -| `+base-block-list` | 列出 Base 容器里的 block;可用 `--parent-id` 列出某个 folder 下的条目 | [`lark-base-base-block-list.md`](references/lark-base-base-block-list.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | CLI 不暴露分页;不传 `--parent-id` 时列出全部 | -| `+base-block-create` | 创建 folder/table/docx/dashboard/workflow | [`lark-base-base-block-create.md`](references/lark-base-base-block-create.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填;`--parent-id` 不传表示根层级 | -| `+base-block-move` | 移动 block 到根层级或 folder,并可调整 before/after 顺序 | [`lark-base-base-block-move.md`](references/lark-base-base-block-move.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--before-id` 和 `--after-id` 互斥;`--parent-id` 不传表示根层级 | -| `+base-block-rename` | 重命名 Base 容器里的 block | [`lark-base-base-block-rename.md`](references/lark-base-base-block-rename.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填 | -| `+base-block-delete` | 删除 Base 容器里的 block | [`lark-base-base-block-delete.md`](references/lark-base-base-block-delete.md)、[`lark-base-base-block.md`](references/lark-base-base-block.md) | 高风险操作;不支持递归删除非空 folder | +| `+base-block-list` | 列出 Base 容器里的 block;可用 `--parent-id` 列出某个 folder 下的条目 | [`lark-base-base-block.md`](references/lark-base-base-block.md) | CLI 不暴露分页;不传 `--parent-id` 时列出全部 | +| `+base-block-create` | 创建 folder/table/docx/dashboard/workflow | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填;`--parent-id` 不传表示根层级 | +| `+base-block-move` | 移动 block 到根层级或 folder,并可调整 before/after 顺序 | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--before-id` 和 `--after-id` 互斥;`--parent-id` 不传表示根层级 | +| `+base-block-rename` | 重命名 Base 容器里的 block | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填 | +| `+base-block-delete` | 删除 Base 容器里的 block | [`lark-base-base-block.md`](references/lark-base-base-block.md) | 高风险操作;不支持递归删除非空 folder | ### 2.4 表与数据模块 diff --git a/skills/lark-base/references/lark-base-base-block-create.md b/skills/lark-base/references/lark-base-base-block-create.md deleted file mode 100644 index e70279f3e..000000000 --- a/skills/lark-base/references/lark-base-base-block-create.md +++ /dev/null @@ -1,44 +0,0 @@ -# base +base-block-create - -在 Base 容器里创建一个 block 条目。支持 `folder`、`table`、`docx`、`dashboard`、`workflow`。 - -## 推荐命令 - -```bash -lark-cli base +base-block-create \ - --base-token app_xxx \ - --type folder \ - --name "项目资料" - -lark-cli base +base-block-create \ - --base-token app_xxx \ - --type docx \ - --name "需求文档" \ - --parent-id blk_folder -``` - -## 参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `--base-token ` | 是 | Base Token | -| `--type ` | 是 | `folder` / `table` / `docx` / `dashboard` / `workflow` | -| `--name ` | 是 | 新 block 名称 | -| `--parent-id ` | 否 | 父 folder 的 base block id;不传时创建在根层级 | -| `--dry-run` | 否 | 预览 API 调用,不执行 | - -## 返回重点 - -- 返回 `block` 和 `created=true`。 -- `block.id` 是 Base 容器里的 block id。 -- 如果创建 docx,返回中可能包含 `docx_token`,后续 docx 内容操作使用该 token。 - -## 坑点 - -- `--name` 必填,不能依赖默认名称。 -- 创建的是 Base 容器里的入口及对应资源;资源内容仍需用 table/docx/dashboard/workflow 对应命令继续操作。 -- 创建 workflow/dashboard 时,初始配置可能为空,后续需要再用对应模块补全。 - -## 参考 - -- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-delete.md b/skills/lark-base/references/lark-base-base-block-delete.md deleted file mode 100644 index 3b745ed62..000000000 --- a/skills/lark-base/references/lark-base-base-block-delete.md +++ /dev/null @@ -1,35 +0,0 @@ -# base +base-block-delete - -删除 Base 容器里的 block。 - -## 推荐命令 - -```bash -lark-cli base +base-block-delete \ - --base-token app_xxx \ - --block-id blk_xxx \ - --yes -``` - -## 参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `--base-token ` | 是 | Base Token | -| `--block-id ` | 是 | 要删除的 base block id | -| `--yes` | 是 | 高风险写入确认 | -| `--dry-run` | 否 | 预览 API 调用,不执行 | - -## 返回重点 - -- 返回 `block` 和 `deleted=true`。 - -## 坑点 - -- 不支持递归删除 folder。删除非空 folder 前,先移动或删除其子项。 -- 删除的是 Base 容器里的 block。不同类型底层资源是否会被物理删除,以后端语义为准。 -- 删除前建议先用 `+base-block-list` 确认 `block-id`。 - -## 参考 - -- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-list.md b/skills/lark-base/references/lark-base-base-block-list.md deleted file mode 100644 index ca54f0540..000000000 --- a/skills/lark-base/references/lark-base-base-block-list.md +++ /dev/null @@ -1,38 +0,0 @@ -# base +base-block-list - -列出 Base 容器管理的 block 条目,可按 folder 过滤直属子项。 - -## 推荐命令 - -```bash -lark-cli base +base-block-list \ - --base-token app_xxx - -lark-cli base +base-block-list \ - --base-token app_xxx \ - --parent-id blk_folder -``` - -## 参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `--base-token ` | 是 | Base Token | -| `--parent-id ` | 否 | folder 的 base block id;不传时列出全部 Base block | -| `--format ` | 否 | 输出格式:json / pretty / table / csv / ndjson | -| `--dry-run` | 否 | 预览 API 调用,不执行 | - -## 返回重点 - -- 返回后端 `blocks` 与 `total`。 -- 每个 block 至少包含 `id`、`type`、`name`、`parent_id`。 -- docx 类型可能额外包含 `docx_token`,用于后续 docx 文档操作。 - -## 坑点 - -- CLI 不暴露 `limit/offset`。如果结果超过后端当前上限,需要先调整 Base 结构或等待后端能力扩展。 -- `+base-block-list` 是 Base 容器列表,不是表记录列表,也不是仪表盘组件列表。 - -## 参考 - -- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-move.md b/skills/lark-base/references/lark-base-base-block-move.md deleted file mode 100644 index d2c4fc8a3..000000000 --- a/skills/lark-base/references/lark-base-base-block-move.md +++ /dev/null @@ -1,50 +0,0 @@ -# base +base-block-move - -移动 Base 容器里的 block,可移动到根层级或某个 folder 下,并可指定同级顺序。 - -## 推荐命令 - -```bash -# 移动到根层级 -lark-cli base +base-block-move \ - --base-token app_xxx \ - --block-id blk_xxx - -# 移动到文件夹里 -lark-cli base +base-block-move \ - --base-token app_xxx \ - --block-id blk_xxx \ - --parent-id blk_folder - -# 移动到某个同级条目之后 -lark-cli base +base-block-move \ - --base-token app_xxx \ - --block-id blk_xxx \ - --parent-id blk_folder \ - --after-id blk_sibling -``` - -## 参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `--base-token ` | 是 | Base Token | -| `--block-id ` | 是 | 要移动的 base block id | -| `--parent-id ` | 否 | 目标 folder 的 base block id;不传表示移动到根层级 | -| `--before-id ` | 否 | 放到该同级 block 前 | -| `--after-id ` | 否 | 放到该同级 block 后 | -| `--dry-run` | 否 | 预览 API 调用,不执行 | - -## 返回重点 - -- 返回 `block` 和 `moved=true`。 - -## 坑点 - -- `--before-id` 与 `--after-id` 互斥,不能同时传。 -- 不传 `--parent-id` 表示根层级,不需要也不要输入 `null`。 -- 移动 folder 是移动整个 folder 入口,子项仍归属该 folder。 - -## 参考 - -- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block-rename.md b/skills/lark-base/references/lark-base-base-block-rename.md deleted file mode 100644 index ba727c494..000000000 --- a/skills/lark-base/references/lark-base-base-block-rename.md +++ /dev/null @@ -1,34 +0,0 @@ -# base +base-block-rename - -重命名 Base 容器里的 block。 - -## 推荐命令 - -```bash -lark-cli base +base-block-rename \ - --base-token app_xxx \ - --block-id blk_xxx \ - --name "新的名称" -``` - -## 参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `--base-token ` | 是 | Base Token | -| `--block-id ` | 是 | 要重命名的 base block id | -| `--name ` | 是 | 新名称 | -| `--dry-run` | 否 | 预览 API 调用,不执行 | - -## 返回重点 - -- 返回 `block` 和 `renamed=true`。 - -## 坑点 - -- `--name` 必填,且不能为空白字符串。 -- 这是重命名 Base 容器入口;具体资源内部标题是否同步,以后端语义为准。 - -## 参考 - -- [lark-base-base-block.md](lark-base-base-block.md) — base block 总览 diff --git a/skills/lark-base/references/lark-base-base-block.md b/skills/lark-base/references/lark-base-base-block.md index 12b9bc13e..312635ea8 100644 --- a/skills/lark-base/references/lark-base-base-block.md +++ b/skills/lark-base/references/lark-base-base-block.md @@ -6,18 +6,30 @@ ## 命令选择 -| 目标 | 命令 | 参考 | -|------|------|------| -| 列出 Base 容器条目 | `+base-block-list` | [lark-base-base-block-list.md](lark-base-base-block-list.md) | -| 创建 folder/table/docx/dashboard/workflow 条目 | `+base-block-create` | [lark-base-base-block-create.md](lark-base-base-block-create.md) | -| 移动条目到根或文件夹、调整同级位置 | `+base-block-move` | [lark-base-base-block-move.md](lark-base-base-block-move.md) | -| 重命名条目 | `+base-block-rename` | [lark-base-base-block-rename.md](lark-base-base-block-rename.md) | -| 删除条目 | `+base-block-delete` | [lark-base-base-block-delete.md](lark-base-base-block-delete.md) | +| 目标 | 命令 | 关键参数 | +|------|------|----------| +| 列出 Base 容器条目 | `+base-block-list` | `--base-token`,可选 `--parent-id` | +| 创建 folder/table/docx/dashboard/workflow 条目 | `+base-block-create` | `--base-token`、`--type`、`--name`,可选 `--parent-id` | +| 移动条目到根或文件夹、调整同级位置 | `+base-block-move` | `--base-token`、`--block-id`,可选 `--parent-id`、`--before-id`、`--after-id` | +| 重命名条目 | `+base-block-rename` | `--base-token`、`--block-id`、`--name` | +| 删除条目 | `+base-block-delete` | `--base-token`、`--block-id`、`--yes` | ## 通用约束 - `--block-id` 是 Base 容器里的 block id,不是 docx token,也不是 dashboard 内部 chart/widget id。 -- `--parent-id` 是目标 folder 的 base block id;不传表示根层级。 +- `--parent-id` 是目标 folder 的 base block id;创建和移动时不传表示根层级;list 时不传表示列出全部。 - 当前 CLI 不暴露分页参数。Base block 总数上限由后端控制,默认一次返回完整列表。 +- 移动时 `--before-id` 和 `--after-id` 互斥。 - 当前不支持递归删除文件夹。删除非空 folder 时,先移动或删除其子项。 - 创建出的 docx/dashboard/workflow/table 的具体内容,需要继续用对应模块命令操作。 + +## 示例 + +```bash +lark-cli base +base-block-list --base-token app_xxx +lark-cli base +base-block-create --base-token app_xxx --type folder --name "项目资料" +lark-cli base +base-block-create --base-token app_xxx --type docx --name "需求文档" --parent-id blk_folder +lark-cli base +base-block-move --base-token app_xxx --block-id blk_docx --parent-id blk_folder --after-id blk_table +lark-cli base +base-block-rename --base-token app_xxx --block-id blk_docx --name "新的名称" +lark-cli base +base-block-delete --base-token app_xxx --block-id blk_docx --yes +``` From b180c2aba094fb0d9f1576c53b5095798a8d692f Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 17:55:04 +0800 Subject: [PATCH 5/7] docs(base): simplify block help wording --- shortcuts/base/base_block_create.go | 8 ++++---- shortcuts/base/base_block_delete.go | 6 +++--- shortcuts/base/base_block_list.go | 8 ++++---- shortcuts/base/base_block_move.go | 10 +++++----- shortcuts/base/base_block_ops.go | 2 +- shortcuts/base/base_block_rename.go | 6 +++--- skills/lark-base/references/lark-base-base-block.md | 6 +++--- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/shortcuts/base/base_block_create.go b/shortcuts/base/base_block_create.go index 0ff1ab8ee..8e6601b91 100644 --- a/shortcuts/base/base_block_create.go +++ b/shortcuts/base/base_block_create.go @@ -12,18 +12,18 @@ import ( var BaseBaseBlockCreate = common.Shortcut{ Service: "base", Command: "+base-block-create", - Description: "Create a base block", + Description: "Create a block", Risk: "write", Scopes: []string{"base:block:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), {Name: "type", Desc: "resource type", Required: true, Enum: baseBlockTypeEnums}, - {Name: "name", Desc: "base block name", Required: true}, - {Name: "parent-id", Desc: "folder base block id; when omitted, create at the base root"}, + {Name: "name", Desc: "block name", Required: true}, + {Name: "parent-id", Desc: "folder block id; when omitted, create at root"}, }, Tips: []string{ - "Creates a Base container entry for folder, table, docx, dashboard, or workflow.", + "Creates a folder, table, docx, dashboard, or workflow entry.", "Do not pass null for --parent-id. Omit it to create at the root level.", "Created resources still use their own commands for content operations, such as table/field/record/docx/dashboard/workflow commands.", }, diff --git a/shortcuts/base/base_block_delete.go b/shortcuts/base/base_block_delete.go index b44933f97..f6cca13e2 100644 --- a/shortcuts/base/base_block_delete.go +++ b/shortcuts/base/base_block_delete.go @@ -12,7 +12,7 @@ import ( var BaseBaseBlockDelete = common.Shortcut{ Service: "base", Command: "+base-block-delete", - Description: "Delete a base block", + Description: "Delete a block", Risk: "high-risk-write", Scopes: []string{"base:block:delete"}, AuthTypes: authTypes(), @@ -21,9 +21,9 @@ var BaseBaseBlockDelete = common.Shortcut{ baseBlockIDFlag(true), }, Tips: []string{ - "Deletes the Base container entry identified by --block-id.", + "Deletes the block identified by --block-id.", "Recursive folder deletion is not supported. If a folder is not empty, move or delete its children first.", - "Different block types may have independent backing resources; deletion follows backend Base block semantics.", + "Different block types may have independent backing resources; deletion follows backend semantics.", "Use +base-block-list first when you need to confirm the target block id.", }, DryRun: dryRunBaseBlockDelete, diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go index 42e8856ff..853e6cfd9 100644 --- a/shortcuts/base/base_block_list.go +++ b/shortcuts/base/base_block_list.go @@ -12,18 +12,18 @@ import ( var BaseBaseBlockList = common.Shortcut{ Service: "base", Command: "+base-block-list", - Description: "List base blocks in a base", + Description: "List blocks in a base", Risk: "read", Scopes: []string{"base:block:read"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), - {Name: "parent-id", Desc: "folder base block id; when omitted, list all base blocks"}, + {Name: "parent-id", Desc: "folder block id; when omitted, list all blocks"}, }, Tips: []string{ - "Base blocks are entries managed by the Base container, such as folder, table, docx, dashboard, and workflow.", + "Blocks are entries in the base sidebar, such as folder, table, docx, dashboard, and workflow.", "This command returns the full backend list. It intentionally does not expose limit or offset.", - "Pass --parent-id to list only direct children of a folder base block.", + "Pass --parent-id to list only direct children of a folder.", "Dashboard blocks are chart/widget blocks inside a dashboard; use +dashboard-block-* for those.", }, DryRun: dryRunBaseBlockList, diff --git a/shortcuts/base/base_block_move.go b/shortcuts/base/base_block_move.go index 371cd6130..6916f5277 100644 --- a/shortcuts/base/base_block_move.go +++ b/shortcuts/base/base_block_move.go @@ -12,19 +12,19 @@ import ( var BaseBaseBlockMove = common.Shortcut{ Service: "base", Command: "+base-block-move", - Description: "Move a base block", + Description: "Move a block", Risk: "write", Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), baseBlockIDFlag(true), - {Name: "parent-id", Desc: "target folder base block id; when omitted, move to the base root"}, - {Name: "before-id", Desc: "place before this sibling base block id"}, - {Name: "after-id", Desc: "place after this sibling base block id"}, + {Name: "parent-id", Desc: "target folder block id; when omitted, move to root"}, + {Name: "before-id", Desc: "place before this sibling block id"}, + {Name: "after-id", Desc: "place after this sibling block id"}, }, Tips: []string{ - "Omit --parent-id to move the block to the base root; do not pass null.", + "Omit --parent-id to move the block to root; do not pass null.", "--before-id and --after-id are mutually exclusive.", "When moving a folder, its children remain under that folder.", }, diff --git a/shortcuts/base/base_block_ops.go b/shortcuts/base/base_block_ops.go index 4fbfaad23..a8b233372 100644 --- a/shortcuts/base/base_block_ops.go +++ b/shortcuts/base/base_block_ops.go @@ -13,7 +13,7 @@ import ( var baseBlockTypeEnums = []string{"folder", "table", "docx", "dashboard", "workflow"} func baseBlockIDFlag(required bool) common.Flag { - return common.Flag{Name: "block-id", Desc: "base block id", Required: required} + return common.Flag{Name: "block-id", Desc: "block id", Required: required} } func dryRunBaseBlockList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { diff --git a/shortcuts/base/base_block_rename.go b/shortcuts/base/base_block_rename.go index 04319299a..75f0344b9 100644 --- a/shortcuts/base/base_block_rename.go +++ b/shortcuts/base/base_block_rename.go @@ -12,17 +12,17 @@ import ( var BaseBaseBlockRename = common.Shortcut{ Service: "base", Command: "+base-block-rename", - Description: "Rename a base block", + Description: "Rename a block", Risk: "write", Scopes: []string{"base:block:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), baseBlockIDFlag(true), - {Name: "name", Desc: "new base block name", Required: true}, + {Name: "name", Desc: "new block name", Required: true}, }, Tips: []string{ - "Renames the Base container entry identified by --block-id.", + "Renames the block identified by --block-id.", "Use +base-block-list first when you need to resolve the target block id from a visible name.", }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { diff --git a/skills/lark-base/references/lark-base-base-block.md b/skills/lark-base/references/lark-base-base-block.md index 312635ea8..c974e9fe2 100644 --- a/skills/lark-base/references/lark-base-base-block.md +++ b/skills/lark-base/references/lark-base-base-block.md @@ -1,6 +1,6 @@ # base +base-block-* -管理 Base 容器里的一级资源入口。Base block 是 Base 侧边栏/容器管理的条目,包括 folder、table、docx、dashboard、workflow。 +管理 Base 容器里的一级资源入口。这里的 block 是 Base 侧边栏/容器管理的条目,包括 folder、table、docx、dashboard、workflow。 > 注意:`base-block` 和 `dashboard-block` 不是同一个概念。`base-block` 是 Base 容器条目;`dashboard-block` 是仪表盘内部的图表/组件。 @@ -17,8 +17,8 @@ ## 通用约束 - `--block-id` 是 Base 容器里的 block id,不是 docx token,也不是 dashboard 内部 chart/widget id。 -- `--parent-id` 是目标 folder 的 base block id;创建和移动时不传表示根层级;list 时不传表示列出全部。 -- 当前 CLI 不暴露分页参数。Base block 总数上限由后端控制,默认一次返回完整列表。 +- `--parent-id` 是目标 folder 的 block id;创建和移动时不传表示根层级;list 时不传表示列出全部。 +- 当前 CLI 不暴露分页参数。block 总数上限由后端控制,默认一次返回完整列表。 - 移动时 `--before-id` 和 `--after-id` 互斥。 - 当前不支持递归删除文件夹。删除非空 folder 时,先移动或删除其子项。 - 创建出的 docx/dashboard/workflow/table 的具体内容,需要继续用对应模块命令操作。 From 230b00c380540d78506884da09377e01af15ec9c Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 18:11:17 +0800 Subject: [PATCH 6/7] test(base): cover base block shortcut execution --- shortcuts/base/base_execute_test.go | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 0a9a1d709..b6679a0b9 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -410,6 +410,102 @@ func decodeCapturedJSONBody(t *testing.T, stub *httpmock.Stub) map[string]interf return body } +func TestBaseBlockExecuteShortcuts(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + listStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/blocks/list", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{"items": []interface{}{}, "total": 0}, + }, + } + createStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/blocks", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{"block_id": "blk_doc", "type": "docx", "name": "Spec"}, + }, + } + moveStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/blocks/blk_doc/move", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{"block_id": "blk_doc", "parent_id": "bfl_1"}, + }, + } + renameStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/blocks/blk_doc/rename", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{"block_id": "blk_doc", "name": "Final Spec"}, + }, + } + deleteStub := &httpmock.Stub{ + Method: "DELETE", + URL: "/open-apis/base/v3/bases/app_x/blocks/blk_doc", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{"block_id": "blk_doc"}, + }, + } + for _, stub := range []*httpmock.Stub{listStub, createStub, moveStub, renameStub, deleteStub} { + reg.Register(stub) + } + + if err := runShortcut(t, BaseBaseBlockList, []string{"+base-block-list", "--base-token", "app_x", "--parent-id", "bfl_1"}, factory, stdout); err != nil { + t.Fatalf("list err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"total": 0`) { + t.Fatalf("list stdout=%s", got) + } + if body := decodeCapturedJSONBody(t, listStub); body["parent_id"] != "bfl_1" { + t.Fatalf("list body=%#v", body) + } + + if err := runShortcut(t, BaseBaseBlockCreate, []string{"+base-block-create", "--base-token", "app_x", "--type", "docx", "--name", " Spec ", "--parent-id", "bfl_1"}, factory, stdout); err != nil { + t.Fatalf("create err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"created": true`) || !strings.Contains(got, `"blk_doc"`) { + t.Fatalf("create stdout=%s", got) + } + createBody := decodeCapturedJSONBody(t, createStub) + if createBody["type"] != "docx" || createBody["name"] != "Spec" || createBody["parent_id"] != "bfl_1" { + t.Fatalf("create body=%#v", createBody) + } + + if err := runShortcut(t, BaseBaseBlockMove, []string{"+base-block-move", "--base-token", "app_x", "--block-id", "blk_doc", "--parent-id", "bfl_1", "--after-id", "blk_prev"}, factory, stdout); err != nil { + t.Fatalf("move err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"moved": true`) { + t.Fatalf("move stdout=%s", got) + } + moveBody := decodeCapturedJSONBody(t, moveStub) + if moveBody["parent_id"] != "bfl_1" || moveBody["after_id"] != "blk_prev" { + t.Fatalf("move body=%#v", moveBody) + } + + if err := runShortcut(t, BaseBaseBlockRename, []string{"+base-block-rename", "--base-token", "app_x", "--block-id", "blk_doc", "--name", " Final Spec "}, factory, stdout); err != nil { + t.Fatalf("rename err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"renamed": true`) || !strings.Contains(got, `"Final Spec"`) { + t.Fatalf("rename stdout=%s", got) + } + if body := decodeCapturedJSONBody(t, renameStub); body["name"] != "Final Spec" { + t.Fatalf("rename body=%#v", body) + } + + if err := runShortcut(t, BaseBaseBlockDelete, []string{"+base-block-delete", "--base-token", "app_x", "--block-id", "blk_doc", "--yes"}, factory, stdout); err != nil { + t.Fatalf("delete err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"deleted": true`) || !strings.Contains(got, `"blk_doc"`) { + t.Fatalf("delete stdout=%s", got) + } +} + func TestBaseHistoryExecute(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ From e4b84470e39739de74a25c7ce03f6dac40d62d96 Mon Sep 17 00:00:00 2001 From: zgz2048 Date: Fri, 22 May 2026 18:58:55 +0800 Subject: [PATCH 7/7] feat(base): filter base block list by type --- shortcuts/base/base_block_list.go | 2 ++ shortcuts/base/base_block_ops.go | 21 ++++++++++++++++++++ shortcuts/base/base_dryrun_ops_test.go | 2 +- shortcuts/base/base_execute_test.go | 14 +++++++++---- skills/lark-base/SKILL.md | 17 ++++++++-------- tests/cli_e2e/base/base_block_dryrun_test.go | 2 ++ tests/cli_e2e/base/coverage.md | 6 +++--- 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/shortcuts/base/base_block_list.go b/shortcuts/base/base_block_list.go index 853e6cfd9..3eb0313f7 100644 --- a/shortcuts/base/base_block_list.go +++ b/shortcuts/base/base_block_list.go @@ -18,11 +18,13 @@ var BaseBaseBlockList = common.Shortcut{ AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), + {Name: "type", Desc: "filter by resource type", Enum: baseBlockTypeEnums}, {Name: "parent-id", Desc: "folder block id; when omitted, list all blocks"}, }, Tips: []string{ "Blocks are entries in the base sidebar, such as folder, table, docx, dashboard, and workflow.", "This command returns the full backend list. It intentionally does not expose limit or offset.", + "Pass --type to list only one resource type.", "Pass --parent-id to list only direct children of a folder.", "Dashboard blocks are chart/widget blocks inside a dashboard; use +dashboard-block-* for those.", }, diff --git a/shortcuts/base/base_block_ops.go b/shortcuts/base/base_block_ops.go index a8b233372..706368c81 100644 --- a/shortcuts/base/base_block_ops.go +++ b/shortcuts/base/base_block_ops.go @@ -82,6 +82,7 @@ func executeBaseBlockList(runtime *common.RuntimeContext) error { if err != nil { return err } + filterBaseBlockListData(data, strings.TrimSpace(runtime.Str("type"))) runtime.Out(data, nil) return nil } @@ -132,6 +133,26 @@ func buildBaseBlockListBody(runtime *common.RuntimeContext) map[string]interface return body } +func filterBaseBlockListData(data map[string]interface{}, blockType string) { + if blockType == "" { + return + } + blocks, ok := data["blocks"].([]interface{}) + if !ok { + return + } + filtered := make([]interface{}, 0, len(blocks)) + for _, block := range blocks { + blockMap, ok := block.(map[string]interface{}) + if !ok || blockMap["type"] != blockType { + continue + } + filtered = append(filtered, block) + } + data["blocks"] = filtered + data["total"] = len(filtered) +} + func buildBaseBlockCreateBody(runtime *common.RuntimeContext) map[string]interface{} { body := map[string]interface{}{ "type": strings.TrimSpace(runtime.Str("type")), diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index 82410c760..938e76bb9 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -38,7 +38,7 @@ func TestDryRunBaseBlockOps(t *testing.T) { listRT := newBaseTestRuntime(map[string]string{"base-token": "app_x"}, nil, nil) assertDryRunContains(t, dryRunBaseBlockList(ctx, listRT), "POST /open-apis/base/v3/bases/app_x/blocks/list") - listFolderRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "parent-id": "bfl_1"}, nil, nil) + listFolderRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "parent-id": "bfl_1", "type": "docx"}, nil, nil) assertDryRunContains(t, dryRunBaseBlockList(ctx, listFolderRT), "POST /open-apis/base/v3/bases/app_x/blocks/list", `"parent_id":"bfl_1"`) createRT := newBaseTestRuntime(map[string]string{"base-token": "app_x", "type": "docx", "name": "Spec", "parent-id": "bfl_1"}, nil, nil) diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index b6679a0b9..cbf20ac19 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -417,7 +417,13 @@ func TestBaseBlockExecuteShortcuts(t *testing.T) { URL: "/open-apis/base/v3/bases/app_x/blocks/list", Body: map[string]interface{}{ "code": 0, - "data": map[string]interface{}{"items": []interface{}{}, "total": 0}, + "data": map[string]interface{}{ + "blocks": []interface{}{ + map[string]interface{}{"id": "blk_doc", "type": "docx", "name": "Spec"}, + map[string]interface{}{"id": "blk_folder", "type": "folder", "name": "Folder"}, + }, + "total": 2, + }, }, } createStub := &httpmock.Stub{ @@ -456,13 +462,13 @@ func TestBaseBlockExecuteShortcuts(t *testing.T) { reg.Register(stub) } - if err := runShortcut(t, BaseBaseBlockList, []string{"+base-block-list", "--base-token", "app_x", "--parent-id", "bfl_1"}, factory, stdout); err != nil { + if err := runShortcut(t, BaseBaseBlockList, []string{"+base-block-list", "--base-token", "app_x", "--parent-id", "bfl_1", "--type", "docx"}, factory, stdout); err != nil { t.Fatalf("list err=%v", err) } - if got := stdout.String(); !strings.Contains(got, `"total": 0`) { + if got := stdout.String(); !strings.Contains(got, `"total": 1`) || !strings.Contains(got, `"blk_doc"`) || strings.Contains(got, `"blk_folder"`) { t.Fatalf("list stdout=%s", got) } - if body := decodeCapturedJSONBody(t, listStub); body["parent_id"] != "bfl_1" { + if body := decodeCapturedJSONBody(t, listStub); body["parent_id"] != "bfl_1" || body["type"] != nil { t.Fatalf("list body=%#v", body) } diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 9f3169c0d..b7171e9a0 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -78,18 +78,17 @@ metadata: ### 2.3 Base Block 模块 -用于管理 Base 容器里的资源入口,包括 folder、table、docx、dashboard、workflow。 -模块索引:[`references/lark-base-base-block.md`](references/lark-base-base-block.md) +管理 Base 里的 block,类型有 `folder/table/docx/dashboard/workflow`。`+base-block-list` 返回的 `id` 对于 table/dashboard/workflow 就是对应资源 ID,可直接用于调用 table/dashboard/workflow 命令。docx block 会返回 docx token,把这个 token 交给 docx CLI/skill 继续操作 docx 文档内容。 > `base-block` 是 Base 容器条目;`dashboard-block` 是仪表盘内部图表/组件。不要混用。 -| 命令 | 用途 / 何时使用 | 必读 reference | 路由提醒 | -|------|------------------|----------------|----------| -| `+base-block-list` | 列出 Base 容器里的 block;可用 `--parent-id` 列出某个 folder 下的条目 | [`lark-base-base-block.md`](references/lark-base-base-block.md) | CLI 不暴露分页;不传 `--parent-id` 时列出全部 | -| `+base-block-create` | 创建 folder/table/docx/dashboard/workflow | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填;`--parent-id` 不传表示根层级 | -| `+base-block-move` | 移动 block 到根层级或 folder,并可调整 before/after 顺序 | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--before-id` 和 `--after-id` 互斥;`--parent-id` 不传表示根层级 | -| `+base-block-rename` | 重命名 Base 容器里的 block | [`lark-base-base-block.md`](references/lark-base-base-block.md) | `--name` 必填 | -| `+base-block-delete` | 删除 Base 容器里的 block | [`lark-base-base-block.md`](references/lark-base-base-block.md) | 高风险操作;不支持递归删除非空 folder | +| 命令 | 用途 / 何时使用 | 路由提醒 | +|------|------------------|----------| +| `+base-block-list` | 列出所有 folder/table/docx/dashboard/workflow,可按 folder 或 type 过滤 | | +| `+base-block-create` | 创建 folder/table/docx/dashboard/workflow block | 具体参数看 `--help` | +| `+base-block-move` | 移动 block 或调整同级顺序 | 具体定位参数看 `--help` | +| `+base-block-rename` | 重命名 block | | +| `+base-block-delete` | 删除 block | 高风险操作;执行前看 `--help` | ### 2.4 表与数据模块 diff --git a/tests/cli_e2e/base/base_block_dryrun_test.go b/tests/cli_e2e/base/base_block_dryrun_test.go index ce9753602..fab8c4235 100644 --- a/tests/cli_e2e/base/base_block_dryrun_test.go +++ b/tests/cli_e2e/base/base_block_dryrun_test.go @@ -42,6 +42,7 @@ func TestBaseBlockDryRun(t *testing.T) { "base", "+base-block-list", "--base-token", "app_x", "--parent-id", "blk_folder", + "--type", "docx", "--dry-run", }, DefaultAs: "bot", @@ -51,6 +52,7 @@ func TestBaseBlockDryRun(t *testing.T) { out := result.Stdout require.Equal(t, "/open-apis/base/v3/bases/app_x/blocks/list", gjson.Get(out, "api.0.url").String(), out) require.Equal(t, "blk_folder", gjson.Get(out, "api.0.body.parent_id").String(), out) + require.False(t, gjson.Get(out, "api.0.body.type").Exists(), out) }) t.Run("create", func(t *testing.T) { diff --git a/tests/cli_e2e/base/coverage.md b/tests/cli_e2e/base/coverage.md index ec7d2f0a7..b3b2435b5 100644 --- a/tests/cli_e2e/base/coverage.md +++ b/tests/cli_e2e/base/coverage.md @@ -2,8 +2,8 @@ ## Metrics - Denominator: 78 leaf commands -- Covered: 15 -- Coverage: 19.2% +- Covered: 18 +- Coverage: 23.1% ## Summary - TestBase_BasicWorkflow: proves `+base-create`, `+base-get`, `+table-create`, `+table-get`, and `+table-list`; key `t.Run(...)` proof points are `get base as bot`, `get table as bot`, and `list tables and find created table as bot`. @@ -23,7 +23,7 @@ | ✓ | base +base-get | shortcut | base_basic_workflow_test.go::TestBase_BasicWorkflow/get base as bot | `--base-token` | | | ✓ | base +base-block-create | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/create | `--base-token`; `--type`; `--name`; `--parent-id`; dry-run only | request shape only | | ✓ | base +base-block-delete | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/delete | `--base-token`; `--block-id`; dry-run only | request shape only | -| ✓ | base +base-block-list | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/list all,list folder | `--base-token`; optional `--parent-id`; dry-run only | request shape only | +| ✓ | base +base-block-list | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/list all,list folder | `--base-token`; optional `--parent-id`; optional `--type`; dry-run only | request shape only | | ✓ | base +base-block-move | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/move root,move after | `--base-token`; `--block-id`; optional `--parent-id`; `--after-id`; dry-run only | request shape only | | ✓ | base +base-block-rename | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/rename | `--base-token`; `--block-id`; `--name`; dry-run only | request shape only | | ✕ | base +dashboard-arrange | shortcut | | none | dashboard workflows not covered |