From 01d72bc143ad801889e9bfe86a01c4e6d9f91568 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Mon, 6 Apr 2026 20:23:38 +0800 Subject: [PATCH 01/12] feat(base): add record batch add/set shortcuts --- shortcuts/base/base_dryrun_ops_test.go | 2 + shortcuts/base/base_execute_test.go | 72 +++++++++++++++++++ shortcuts/base/base_shortcuts_test.go | 2 +- shortcuts/base/record_batch_add.go | 31 ++++++++ shortcuts/base/record_batch_set.go | 31 ++++++++ shortcuts/base/record_ops.go | 46 ++++++++++++ shortcuts/base/shortcuts.go | 2 + skills/lark-base/SKILL.md | 14 ++-- .../references/lark-base-record-batch-add.md | 55 ++++++++++++++ .../references/lark-base-record-batch-set.md | 59 +++++++++++++++ .../lark-base/references/lark-base-record.md | 2 + 11 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 shortcuts/base/record_batch_add.go create mode 100644 shortcuts/base/record_batch_set.go create mode 100644 skills/lark-base/references/lark-base-record-batch-add.md create mode 100644 skills/lark-base/references/lark-base-record-batch-set.md diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index 3898826b7..d6039b953 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -75,6 +75,8 @@ func TestDryRunRecordOps(t *testing.T) { nil, nil, ) assertDryRunContains(t, dryRunRecordUpsert(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records") + assertDryRunContains(t, dryRunRecordBatchAdd(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") + assertDryRunContains(t, dryRunRecordBatchSet(ctx, upsertCreateRT), "PATCH /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") rt := newBaseTestRuntime( map[string]string{"base-token": "app_x", "table-id": "tbl_1", "record-id": "rec_1", "json": `{"Name":"B"}`}, diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 46ec996d9..74607f919 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -552,6 +552,78 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { } }) + t.Run("batch add", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "fields": []interface{}{"Name"}, + "record_id_list": []interface{}{"rec_1", "rec_2"}, + "data": []interface{}{[]interface{}{"Alice"}, []interface{}{"Bob"}}, + }, + }, + }) + if err := runShortcut(t, BaseRecordBatchAdd, []string{"+record-batch-add", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"fields":["Name"],"rows":[["Alice"],["Bob"]]}`}, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"rec_1"`) || !strings.Contains(got, `"Alice"`) { + t.Fatalf("stdout=%s", got) + } + }) + + t.Run("batch set", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + reg.Register(&httpmock.Stub{ + Method: "PATCH", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "has_more": false, + "record_id_list": []interface{}{"rec_1"}, + "update": map[string]interface{}{"Status": "Done"}, + }, + }, + }) + if err := runShortcut(t, BaseRecordBatchSet, []string{"+record-batch-set", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Status":"Done"}}`}, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"update"`) || !strings.Contains(got, `"Done"`) { + t.Fatalf("stdout=%s", got) + } + }) + + t.Run("batch set passthrough", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + updateStub := &httpmock.Stub{ + Method: "PATCH", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "record_id_list": []interface{}{"rec_1"}, + }, + }, + } + reg.Register(updateStub) + if err := runShortcut(t, BaseRecordBatchSet, []string{"+record-batch-set", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Name":"Alice","Status":"Done"}}`}, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"rec_1"`) { + t.Fatalf("stdout=%s", got) + } + body := string(updateStub.CapturedBody) + if !strings.Contains(body, `"record_id_list":["rec_1"]`) || !strings.Contains(body, `"patch":{"Name":"Alice","Status":"Done"}`) { + t.Fatalf("request body=%s", body) + } + }) + t.Run("delete", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index a6f1c61d0..cda960be1 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -114,7 +114,7 @@ func TestShortcutsCatalog(t *testing.T) { "+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-group", "+view-set-group", "+view-get-sort", "+view-set-sort", "+view-get-timebar", "+view-set-timebar", "+view-get-card", "+view-set-card", "+view-rename", - "+record-list", "+record-get", "+record-upsert", "+record-upload-attachment", "+record-delete", + "+record-list", "+record-get", "+record-upsert", "+record-batch-add", "+record-batch-set", "+record-upload-attachment", "+record-delete", "+record-history-list", "+base-get", "+base-copy", "+base-create", "+role-create", "+role-delete", "+role-update", "+role-list", "+role-get", "+advperm-enable", "+advperm-disable", diff --git a/shortcuts/base/record_batch_add.go b/shortcuts/base/record_batch_add.go new file mode 100644 index 000000000..1c1fb03de --- /dev/null +++ b/shortcuts/base/record_batch_add.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 BaseRecordBatchAdd = common.Shortcut{ + Service: "base", + Command: "+record-batch-add", + Description: "Batch add records", + Risk: "write", + Scopes: []string{"base:record:create"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + tableRefFlag(true), + {Name: "json", Desc: "batch add JSON object, e.g. {\"fields\":[],\"rows\":[]}", Required: true}, + }, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + return validateRecordJSON(runtime) + }, + DryRun: dryRunRecordBatchAdd, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeRecordBatchAdd(runtime) + }, +} diff --git a/shortcuts/base/record_batch_set.go b/shortcuts/base/record_batch_set.go new file mode 100644 index 000000000..bfea77ae4 --- /dev/null +++ b/shortcuts/base/record_batch_set.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 BaseRecordBatchSet = common.Shortcut{ + Service: "base", + Command: "+record-batch-set", + Description: "Batch set records", + Risk: "write", + Scopes: []string{"base:record:update"}, + AuthTypes: authTypes(), + Flags: []common.Flag{ + baseTokenFlag(true), + tableRefFlag(true), + {Name: "json", Desc: "batch set JSON object, passed through as request body, e.g. {\"record_id_list\":[\"rec_xxx\"],\"patch\":{\"field_id_or_name\":\"value\"}}", Required: true}, + }, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + return validateRecordJSON(runtime) + }, + DryRun: dryRunRecordBatchSet, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeRecordBatchSet(runtime) + }, +} diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 280b1c580..40b29cfe4 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -51,6 +51,24 @@ func dryRunRecordUpsert(_ context.Context, runtime *common.RuntimeContext) *comm Set("table_id", baseTableID(runtime)) } +func dryRunRecordBatchAdd(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + body, _ := parseJSONObject(runtime.Str("json"), "json") + return common.NewDryRunAPI(). + POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). + Body(body). + Set("base_token", runtime.Str("base-token")). + Set("table_id", baseTableID(runtime)) +} + +func dryRunRecordBatchSet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + body, _ := parseJSONObject(runtime.Str("json"), "json") + return common.NewDryRunAPI(). + PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). + Body(body). + Set("base_token", runtime.Str("base-token")). + Set("table_id", baseTableID(runtime)) +} + func dryRunRecordDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return common.NewDryRunAPI(). DELETE("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/:record_id"). @@ -128,6 +146,34 @@ func executeRecordUpsert(runtime *common.RuntimeContext) error { return nil } +func executeRecordBatchAdd(runtime *common.RuntimeContext) error { + body, err := parseJSONObject(runtime.Str("json"), "json") + if err != nil { + return err + } + result, err := baseV3Raw(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) + data, err := handleBaseAPIResult(result, err, "batch add records") + if err != nil { + return err + } + runtime.Out(data, nil) + return nil +} + +func executeRecordBatchSet(runtime *common.RuntimeContext) error { + body, err := parseJSONObject(runtime.Str("json"), "json") + if err != nil { + return err + } + result, err := baseV3Raw(runtime, "PATCH", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) + data, err := handleBaseAPIResult(result, err, "batch set records") + if err != nil { + return err + } + runtime.Out(data, nil) + return nil +} + func executeRecordDelete(runtime *common.RuntimeContext) error { _, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", runtime.Str("record-id")), nil, nil) if err != nil { diff --git a/shortcuts/base/shortcuts.go b/shortcuts/base/shortcuts.go index fea4ebd7e..43909059d 100644 --- a/shortcuts/base/shortcuts.go +++ b/shortcuts/base/shortcuts.go @@ -37,6 +37,8 @@ func Shortcuts() []common.Shortcut { BaseRecordList, BaseRecordGet, BaseRecordUpsert, + BaseRecordBatchAdd, + BaseRecordBatchSet, BaseRecordUploadAttachment, BaseRecordDelete, BaseRecordHistoryList, diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 23f4abc87..3f5560836 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -36,17 +36,17 @@ metadata: - 不要把 `+record-list` 当聚合分析引擎 - 不要没读 guide 就直接创建 formula / lookup 字段 - 不要凭自然语言猜表名、字段名、公式表达式里的字段引用 -- 不要把系统字段、formula 字段、lookup 字段当成 `+record-upsert` 的写入目标 +- 不要把系统字段、formula 字段、lookup 字段当成 `+record-upsert / +record-batch-add / +record-batch-set` 的写入目标 - 不要在 Base 场景改走 `lark-cli api GET /open-apis/bitable/v1/...` - 不要因为 wiki 解析结果里的 `obj_type=bitable` 就去找 `bitable.*`;在本 CLI 里应继续使用 `lark-cli base +...` ## Base 基本心智模型 1. **Base 字段分三类** - - **存储字段**:真实存用户输入的数据,通常适合 `+record-upsert` 写入,例如文本、数字、日期、单选、多选、人员、关联。**附件字段例外**:对 agent 而言,文件上传必须走 `+record-upload-attachment`。 + - **存储字段**:真实存用户输入的数据,通常适合 `+record-upsert / +record-batch-add / +record-batch-set` 写入,例如文本、数字、日期、单选、多选、人员、关联。**附件字段例外**:对 agent 而言,文件上传必须走 `+record-upload-attachment`。 - **系统字段**:平台自动维护,只读,典型包括创建时间、最后更新时间、创建人、修改人、自动编号。 - **计算字段**:通过表达式或跨表规则推导,只读,典型包括 **公式字段(formula)** 和 **查找引用字段(lookup)**。 -2. **写记录前先判断字段类别** — 只有存储字段可直接写;公式 / lookup / 创建时间 / 更新时间 / 创建人 / 修改人 / 自动编号都应视为只读输出字段,不能拿来做 `+record-upsert` 入参。 +2. **写记录前先判断字段类别** — 只有存储字段可直接写;公式 / lookup / 创建时间 / 更新时间 / 创建人 / 修改人 / 自动编号都应视为只读输出字段,不能拿来做 `+record-upsert / +record-batch-add / +record-batch-set` 入参。 3. **Base 不只是存表数据,也能内建计算** — 用户提出“统计、比较、排名、文本拼接、日期差、跨表汇总、状态判断”等需求时,不能默认导出数据后手算;要先判断是否应通过 `+data-query` 或公式字段在 Base 内完成。 ## 分析路径决策 @@ -101,7 +101,7 @@ metadata: ## 核心规则 -1. **只使用原子命令** — 使用 `+table-list / +table-get / +field-create / +record-upsert / +view-set-filter / +record-history-list / +base-get` 这类一命令一动作的写法,不使用旧聚合式 `+table / +field / +record / +view / +history / +workspace` +1. **只使用原子命令** — 使用 `+table-list / +table-get / +field-create / +record-upsert / +record-batch-add / +record-batch-set / +view-set-filter / +record-history-list / +base-get` 这类一命令一动作的写法,不使用旧聚合式 `+table / +field / +record / +view / +history / +workspace` 2. **写记录前先读字段结构** — 先调用 `+field-list` 获取字段结构,再读 [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) 确认各字段类型的写入值格式 3. **写字段前先看字段属性规范** — 先读 [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) 确认 `+field-create/+field-update` 的 JSON 结构 4. **筛选查询按视图能力执行** — 先读 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) 和 [lark-base-record-list.md](references/lark-base-record-list.md),通过 `+view-set-filter` + `+record-list` 组合完成筛选读取 @@ -134,6 +134,8 @@ metadata: | 创建 / 更新 lookup 字段 | `lark-cli base +field-create` / `+field-update` | `type=lookup`;先读 lookup guide,再创建 / 更新,默认先判断 formula 是否更合适 | | 列表 / 获取记录 | `lark-cli base +record-list` / `+record-get` | 原子命令,如果需要`聚合计算`,`分组统计` 推荐走 `+data-query` | | 创建 / 更新记录 | `lark-cli base +record-upsert` | `--table-id [--record-id] --json` | +| 批量新增记录 | `lark-cli base +record-batch-add` | `--table-id --json`(`json.fields + json.rows`) | +| 批量更新指定记录 | `lark-cli base +record-batch-set` | `--table-id --json`(`json.record_id_list + json.patch`) | | 聚合分析 / 比较排序 / 求最值 / 筛选统计 | `lark-cli base +data-query` | 不要用 `+record-list` 拉全量数据再手动计算,需使用 `+data-query` 走服务端计算 | | 配置 / 查询视图 | `lark-cli base +view-*` | `list/get/create/delete/get-*/set-*/rename` | | 查看记录历史 | `lark-cli base +record-history-list` | 按表和记录查询变更历史 | @@ -267,6 +269,8 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} - [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) — `+field-create/+field-update` JSON 规范(推荐) - [role-config.md](references/role-config.md) — 角色权限配置详解 - [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — `+record-upsert` 值格式规范(推荐) +- [lark-base-record-batch-add.md](references/lark-base-record-batch-add.md) — `+record-batch-add` JSON 结构与 raw schema +- [lark-base-record-batch-set.md](references/lark-base-record-batch-set.md) — `+record-batch-set` JSON 结构与 raw schema - [formula-field-guide.md](references/formula-field-guide.md) — formula 字段写法、函数约束、CurrentValue 规则、跨表计算模式(强烈推荐) - [lookup-field-guide.md](references/lookup-field-guide.md) — lookup 字段配置规则、where/aggregate 约束、与 formula 的取舍 - [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) — 视图筛选配置 @@ -293,7 +297,7 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} |----------|------| | [`table commands`](references/lark-base-table.md) | `+table-list / +table-get / +table-create / +table-update / +table-delete` | | [`field commands`](references/lark-base-field.md) | `+field-list / +field-get / +field-create / +field-update / +field-delete / +field-search-options` | -| [`record commands`](references/lark-base-record.md) | `+record-list / +record-get / +record-upsert / +record-upload-attachment / +record-delete` | +| [`record commands`](references/lark-base-record.md) | `+record-list / +record-get / +record-upsert / +record-batch-add / +record-batch-set / +record-upload-attachment / +record-delete` | | [`view commands`](references/lark-base-view.md) | `+view-list / +view-get / +view-create / +view-delete / +view-get-* / +view-set-* / +view-rename` | | [`data-query commands`](references/lark-base-data-query.md) | `+data-query` | | [`history commands`](references/lark-base-history.md) | `+record-history-list` | diff --git a/skills/lark-base/references/lark-base-record-batch-add.md b/skills/lark-base/references/lark-base-record-batch-add.md new file mode 100644 index 000000000..30394e4f3 --- /dev/null +++ b/skills/lark-base/references/lark-base-record-batch-add.md @@ -0,0 +1,55 @@ +# base +record-batch-add + +> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。 + +批量新增记录。 + +## 推荐命令 + +```bash +lark-cli base +record-batch-add \ + --base-token app_xxx \ + --table-id tbl_xxx \ + --json '{"fields":["标题","状态"],"rows":[["任务 A","Open"],["任务 B","Done"]]}' +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--table-id ` | 是 | 表 ID 或表名 | +| `--json ` | 是 | 批量新增请求体,必须是 JSON 对象 | + +## API 入参详情 + +**HTTP 方法和路径:** + +``` +POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch +``` + +## `--json` Raw JSON Schema + +```json +{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100,"description":"Field id or name"},"minItems":1,"maxItems":200},"rows":{"type":"array","items":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string","description":"text field cell, example: \"one string and [one url](https://foo.bar)\""},{"type":"number","description":"number field cell, can be any float64 value"},{"type":"array","items":{"type":"string","description":"option name"},"description":"select field cell, example: [\"option_1\", \"option_2\"]"},{"type":"string","description":"datetime field cell. accepts common datetime strings and timestamp-like values. Prefer \"YYYY-MM-DD HH:mm:ss\" in requests because it is the most stable format and matches the API output. Example: \"2026-01-01 19:30:00\""},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"record id"}},"required":["id"],"additionalProperties":false},"description":"link field cell, example: [{\"id\": \"rec_123\"}]"},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"user id"}},"required":["id"],"additionalProperties":false},"description":"user field cell, example: [{\"id\": \"ou_123\"}]"},{"type":"object","properties":{"lng":{"type":"number","description":"Longitude"},"lat":{"type":"number","description":"Latitude"}},"required":["lng","lat"],"additionalProperties":false,"description":"location field cell, example: {\"lng\": 113.94765, \"lat\": 22.528533}"},{"type":"boolean","description":"checkbox field cell"},{"type":"array","items":{"type":"object","properties":{"file_token":{"type":"string","minLength":0,"maxLength":50},"name":{"type":"string","minLength":1,"maxLength":255},"mime_type":{"type":"string","maxLength":255,"description":"deprecated field"},"size":{"type":"integer","minimum":0,"description":"deprecated field"},"image_width":{"type":"integer","minimum":0,"description":"deprecated field"},"image_height":{"type":"integer","minimum":0,"description":"deprecated field"},"deprecated_set_attachment":{"type":"boolean","description":"deprecated field"}},"required":["file_token","name"],"additionalProperties":false},"description":"attachment field cell. temporary compatibility for attachment writes."},{"type":"null"}]},{"type":"null"}]}},"minItems":1,"maxItems":200}},"required":["fields","rows"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} +``` + +## 返回重点 + +- 返回对象键: + - `fields` + - `field_id_list` + - `record_id_list` + - `data` + - `ignored_fields`(可选) + +## 坑点 + +- ⚠️ `--json` 必须是对象。 +- ⚠️ `fields` 与 `rows` 列顺序必须一一对应。 + +## 参考 + +- [lark-base-record.md](lark-base-record.md) — record 索引页 +- [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md) — 记录值格式规范 diff --git a/skills/lark-base/references/lark-base-record-batch-set.md b/skills/lark-base/references/lark-base-record-batch-set.md new file mode 100644 index 000000000..ee8b6ec84 --- /dev/null +++ b/skills/lark-base/references/lark-base-record-batch-set.md @@ -0,0 +1,59 @@ +# base +record-batch-set + +> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。 + +批量更新记录(将同一份 `patch` 批量应用到一批 `record_id_list`)。 + +## 推荐命令 + +```bash +lark-cli base +record-batch-set \ + --base-token app_xxx \ + --table-id tbl_xxx \ + --json '{"record_id_list":["rec_xxx"],"patch":{"field_id_or_name":"value"}}' +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--table-id ` | 是 | 表 ID 或表名 | +| `--json ` | 是 | 批量更新请求体,必须是 JSON 对象 | + +## 生成 `patch` 前必看 + +- 先阅读 [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md),按字段类型构造 `patch` 的 value,避免类型不匹配。 + +## API 入参详情 + +**HTTP 方法和路径:** + +``` +PATCH /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch +``` + +## `--json` Raw JSON Schema + +```json +{"type":"object","properties":{"record_id_list":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":200},"patch":{"type":"object"}},"required":["record_id_list","patch"],"additionalProperties":true,"$schema":"http://json-schema.org/draft-07/schema#"} +``` + +## 返回重点 + +- 返回对象(`OpenAPIBatchPatchResult`): + - `has_more`(`boolean`,必返) + - `record_id_list`(`string[]`,必返) + - `update`(`object`,必返;可能为空对象) + - `ignored_fields`(`{id,name,reason}[]`,可选) + +## 坑点 + +- ⚠️ `--json` 必须是对象。 +- ⚠️ 该接口是“同值批量更新”:同一请求内所有 `record_id_list` 都会应用同一份 `patch`。 +- ⚠️ `record_id_list` 最大 200 条,超过会被接口校验拒绝。 +- ⚠️ 命令不会自动做字段/行映射转换,传什么就发什么。 + +## 参考 + +- [lark-base-record.md](lark-base-record.md) — record 索引页 diff --git a/skills/lark-base/references/lark-base-record.md b/skills/lark-base/references/lark-base-record.md index 15b29393f..85c3295e4 100644 --- a/skills/lark-base/references/lark-base-record.md +++ b/skills/lark-base/references/lark-base-record.md @@ -11,6 +11,8 @@ record 相关命令索引。 | [lark-base-record-list.md](lark-base-record-list.md) | `+record-list` | 分页列记录 | | [lark-base-record-get.md](lark-base-record-get.md) | `+record-get` | 获取单条记录 | | [lark-base-record-upsert.md](lark-base-record-upsert.md) | `+record-upsert` | 创建或更新记录 | +| [lark-base-record-batch-add.md](lark-base-record-batch-add.md) | `+record-batch-add` | 按 `fields/rows` 批量新增记录 | +| [lark-base-record-batch-set.md](lark-base-record-batch-set.md) | `+record-batch-set` | 批量更新记录 | | [lark-base-record-upload-attachment.md](lark-base-record-upload-attachment.md) | `+record-upload-attachment` | 上传本地文件到附件字段并更新记录 | | [lark-base-record-delete.md](lark-base-record-delete.md) | `+record-delete` | 删除记录 | From 1c5db0df5beebb3fa57d3153f51ad1d72241e4d2 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Wed, 8 Apr 2026 15:25:30 +0800 Subject: [PATCH 02/12] docs: clarify record batch add/set input guidance --- skills/lark-base/SKILL.md | 7 ++++--- .../references/lark-base-record-batch-add.md | 15 +++++++++++---- .../references/lark-base-record-batch-set.md | 8 ++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 3f5560836..03e972794 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -134,7 +134,7 @@ metadata: | 创建 / 更新 lookup 字段 | `lark-cli base +field-create` / `+field-update` | `type=lookup`;先读 lookup guide,再创建 / 更新,默认先判断 formula 是否更合适 | | 列表 / 获取记录 | `lark-cli base +record-list` / `+record-get` | 原子命令,如果需要`聚合计算`,`分组统计` 推荐走 `+data-query` | | 创建 / 更新记录 | `lark-cli base +record-upsert` | `--table-id [--record-id] --json` | -| 批量新增记录 | `lark-cli base +record-batch-add` | `--table-id --json`(`json.fields + json.rows`) | +| 批量新增记录 | `lark-cli base +record-batch-add` | 适合大量新增写入(如 CSV/Excel 导入);先按 record-value 规范整理为 `json.fields + json.rows` | | 批量更新指定记录 | `lark-cli base +record-batch-set` | `--table-id --json`(`json.record_id_list + json.patch`) | | 聚合分析 / 比较排序 / 求最值 / 筛选统计 | `lark-cli base +data-query` | 不要用 `+record-list` 拉全量数据再手动计算,需使用 `+data-query` 走服务端计算 | | 配置 / 查询视图 | `lark-cli base +view-*` | `list/get/create/delete/get-*/set-*/rename` | @@ -158,6 +158,7 @@ metadata: - **Base token 口径统一**:统一使用 `--base-token` - **`+xxx-list` 调用纪律**:`+table-list / +field-list / +record-list / +view-list / +record-history-list / +role-list / +dashboard-list / +dashboard-block-list / +workflow-list` 禁止并发调用;批量执行时只能串行 - **`+record-list` 分页规则**:`--limit` 最大 `200`。先拉首批并检查返回 `has_more`;仅当 `has_more=true` 且用户明确需要更多数据(如“全部导出/全量明细/继续下一页”)时再继续翻页。用户只要样例或前 N 条时,不要继续拉全量 +- **`+record-batch-add` 适用边界**:优先用于大量新增写入(如 CSV/Excel 导入)。当输入是长表格或长文本时,先按 [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) 做字段映射和类型规范化,再组装 `fields + rows` 调用命令写入 - **字段可写性先判断**:存储字段才可写;公式 / lookup / 系统字段默认只读,写记录时应跳过 - **公式能力要主动想到**:用户说“算一下”“生成标签”“判断是否异常”“跨表汇总”“按日期差预警”时,要先判断是否应该建公式字段,而不是只返回手工分析方案 - **lookup 不是默认首选**:lookup 只在用户明确要求或确实更适合固定查找模型时使用;常规计算、跨表聚合和条件判断优先 formula @@ -269,8 +270,8 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} - [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) — `+field-create/+field-update` JSON 规范(推荐) - [role-config.md](references/role-config.md) — 角色权限配置详解 - [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — `+record-upsert` 值格式规范(推荐) -- [lark-base-record-batch-add.md](references/lark-base-record-batch-add.md) — `+record-batch-add` JSON 结构与 raw schema -- [lark-base-record-batch-set.md](references/lark-base-record-batch-set.md) — `+record-batch-set` JSON 结构与 raw schema +- [lark-base-record-batch-add.md](references/lark-base-record-batch-add.md) — `+record-batch-add` 用法与 `--json` 结构 +- [lark-base-record-batch-set.md](references/lark-base-record-batch-set.md) — `+record-batch-set` 用法与 `--json` 结构 - [formula-field-guide.md](references/formula-field-guide.md) — formula 字段写法、函数约束、CurrentValue 规则、跨表计算模式(强烈推荐) - [lookup-field-guide.md](references/lookup-field-guide.md) — lookup 字段配置规则、where/aggregate 约束、与 formula 的取舍 - [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) — 视图筛选配置 diff --git a/skills/lark-base/references/lark-base-record-batch-add.md b/skills/lark-base/references/lark-base-record-batch-add.md index 30394e4f3..7d686a271 100644 --- a/skills/lark-base/references/lark-base-record-batch-add.md +++ b/skills/lark-base/references/lark-base-record-batch-add.md @@ -4,6 +4,11 @@ 批量新增记录。 +## 适用场景(重点) + +- 适合大量新增写入场景,例如导入 CSV / Excel、外部系统一次性灌入新数据。 +- 当输入是长表格或长文本数据时,先按 [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md) 做字段映射和类型规范化,再组装 `fields + rows` 调用本命令写入。 + ## 推荐命令 ```bash @@ -29,11 +34,11 @@ lark-cli base +record-batch-add \ POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch ``` -## `--json` Raw JSON Schema +## `--json` 结构 -```json -{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","minLength":1,"maxLength":100,"description":"Field id or name"},"minItems":1,"maxItems":200},"rows":{"type":"array","items":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string","description":"text field cell, example: \"one string and [one url](https://foo.bar)\""},{"type":"number","description":"number field cell, can be any float64 value"},{"type":"array","items":{"type":"string","description":"option name"},"description":"select field cell, example: [\"option_1\", \"option_2\"]"},{"type":"string","description":"datetime field cell. accepts common datetime strings and timestamp-like values. Prefer \"YYYY-MM-DD HH:mm:ss\" in requests because it is the most stable format and matches the API output. Example: \"2026-01-01 19:30:00\""},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"record id"}},"required":["id"],"additionalProperties":false},"description":"link field cell, example: [{\"id\": \"rec_123\"}]"},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"user id"}},"required":["id"],"additionalProperties":false},"description":"user field cell, example: [{\"id\": \"ou_123\"}]"},{"type":"object","properties":{"lng":{"type":"number","description":"Longitude"},"lat":{"type":"number","description":"Latitude"}},"required":["lng","lat"],"additionalProperties":false,"description":"location field cell, example: {\"lng\": 113.94765, \"lat\": 22.528533}"},{"type":"boolean","description":"checkbox field cell"},{"type":"array","items":{"type":"object","properties":{"file_token":{"type":"string","minLength":0,"maxLength":50},"name":{"type":"string","minLength":1,"maxLength":255},"mime_type":{"type":"string","maxLength":255,"description":"deprecated field"},"size":{"type":"integer","minimum":0,"description":"deprecated field"},"image_width":{"type":"integer","minimum":0,"description":"deprecated field"},"image_height":{"type":"integer","minimum":0,"description":"deprecated field"},"deprecated_set_attachment":{"type":"boolean","description":"deprecated field"}},"required":["file_token","name"],"additionalProperties":false},"description":"attachment field cell. temporary compatibility for attachment writes."},{"type":"null"}]},{"type":"null"}]}},"minItems":1,"maxItems":200}},"required":["fields","rows"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} -``` +- 对象形态:`{"fields":[...],"rows":[...]}`。 +- `fields`:字段 id 或字段名数组。 +- `rows`:二维数组,每一行按 `fields` 的同序列给值。 ## 返回重点 @@ -47,7 +52,9 @@ POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch ## 坑点 - ⚠️ `--json` 必须是对象。 +- ⚠️ 写 `rows` 前必须先阅读 [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md),按字段类型填值,禁止按自然语言猜测 value 结构。 - ⚠️ `fields` 与 `rows` 列顺序必须一一对应。 +- ⚠️ 单次最多 200 行,超出需分批写入。 ## 参考 diff --git a/skills/lark-base/references/lark-base-record-batch-set.md b/skills/lark-base/references/lark-base-record-batch-set.md index ee8b6ec84..d919fc05b 100644 --- a/skills/lark-base/references/lark-base-record-batch-set.md +++ b/skills/lark-base/references/lark-base-record-batch-set.md @@ -33,11 +33,11 @@ lark-cli base +record-batch-set \ PATCH /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch ``` -## `--json` Raw JSON Schema +## `--json` 结构 -```json -{"type":"object","properties":{"record_id_list":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":200},"patch":{"type":"object"}},"required":["record_id_list","patch"],"additionalProperties":true,"$schema":"http://json-schema.org/draft-07/schema#"} -``` +- 对象形态:`{"record_id_list":[...],"patch":{...}}`。 +- `record_id_list`:要更新的记录 ID 列表(单次最多 200 条)。 +- `patch`:同一份字段更新对象,会应用到 `record_id_list` 内所有记录。 ## 返回重点 From 6d38c9ec07ebaebb3c74abf7ac2c431481ee7c8a Mon Sep 17 00:00:00 2001 From: kongenpei Date: Wed, 8 Apr 2026 15:51:58 +0800 Subject: [PATCH 03/12] docs: mark base shortcut references as required before calling --- skills/lark-base/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 03e972794..06a8d9798 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -267,9 +267,9 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} ## 参考文档 -- [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) — `+field-create/+field-update` JSON 规范(推荐) +- [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) — `+field-create/+field-update` 调用前必看,各类型 field JSON 规范 - [role-config.md](references/role-config.md) — 角色权限配置详解 -- [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — `+record-upsert` 值格式规范(推荐) +- [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — record 写入(`+record-upsert / +record-batch-add / +record-batch-set`)调用前必看,各类型 record JSON 规范 - [lark-base-record-batch-add.md](references/lark-base-record-batch-add.md) — `+record-batch-add` 用法与 `--json` 结构 - [lark-base-record-batch-set.md](references/lark-base-record-batch-set.md) — `+record-batch-set` 用法与 `--json` 结构 - [formula-field-guide.md](references/formula-field-guide.md) — formula 字段写法、函数约束、CurrentValue 规则、跨表计算模式(强烈推荐) From 8d9b2c2be0d18ca3b9c8219c7f9b38d2b985ca5b Mon Sep 17 00:00:00 2001 From: kongenpei Date: Wed, 8 Apr 2026 17:52:07 +0800 Subject: [PATCH 04/12] fix(base): remove stale token stub calls in batch record tests --- shortcuts/base/base_execute_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 74607f919..9f3d6055b 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -554,7 +554,6 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { t.Run("batch add", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) - registerTokenStub(reg) reg.Register(&httpmock.Stub{ Method: "POST", URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", @@ -577,7 +576,6 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { t.Run("batch set", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) - registerTokenStub(reg) reg.Register(&httpmock.Stub{ Method: "PATCH", URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", @@ -600,7 +598,6 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { t.Run("batch set passthrough", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) - registerTokenStub(reg) updateStub := &httpmock.Stub{ Method: "PATCH", URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", From ebb0ea93d6332f6dd294520000486f5d8a556210 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Wed, 8 Apr 2026 21:13:25 +0800 Subject: [PATCH 05/12] feat(base): rename record batch add/set to create/update --- shortcuts/base/base_dryrun_ops_test.go | 4 +- shortcuts/base/base_execute_test.go | 12 ++--- shortcuts/base/base_shortcuts_test.go | 2 +- ...rd_batch_add.go => record_batch_create.go} | 12 ++--- ...rd_batch_set.go => record_batch_update.go} | 12 ++--- shortcuts/base/record_ops.go | 12 ++--- shortcuts/base/shortcuts.go | 4 +- skills/lark-base/SKILL.md | 22 ++++----- ...dd.md => lark-base-record-batch-create.md} | 48 ++++++++++++------- ...et.md => lark-base-record-batch-update.md} | 43 +++++++++++------ .../lark-base/references/lark-base-record.md | 4 +- 11 files changed, 101 insertions(+), 74 deletions(-) rename shortcuts/base/{record_batch_add.go => record_batch_create.go} (64%) rename shortcuts/base/{record_batch_set.go => record_batch_update.go} (59%) rename skills/lark-base/references/{lark-base-record-batch-add.md => lark-base-record-batch-create.md} (50%) rename skills/lark-base/references/{lark-base-record-batch-set.md => lark-base-record-batch-update.md} (51%) diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index d6039b953..c24304a2e 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -75,8 +75,8 @@ func TestDryRunRecordOps(t *testing.T) { nil, nil, ) assertDryRunContains(t, dryRunRecordUpsert(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records") - assertDryRunContains(t, dryRunRecordBatchAdd(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") - assertDryRunContains(t, dryRunRecordBatchSet(ctx, upsertCreateRT), "PATCH /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") + assertDryRunContains(t, dryRunRecordBatchCreate(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") + assertDryRunContains(t, dryRunRecordBatchUpdate(ctx, upsertCreateRT), "PATCH /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") rt := newBaseTestRuntime( map[string]string{"base-token": "app_x", "table-id": "tbl_1", "record-id": "rec_1", "json": `{"Name":"B"}`}, diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 9f3d6055b..e91464468 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -552,7 +552,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { } }) - t.Run("batch add", func(t *testing.T) { + t.Run("batch create", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ Method: "POST", @@ -566,7 +566,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { }, }, }) - if err := runShortcut(t, BaseRecordBatchAdd, []string{"+record-batch-add", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"fields":["Name"],"rows":[["Alice"],["Bob"]]}`}, factory, stdout); err != nil { + if err := runShortcut(t, BaseRecordBatchCreate, []string{"+record-batch-create", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"fields":["Name"],"rows":[["Alice"],["Bob"]]}`}, factory, stdout); err != nil { t.Fatalf("err=%v", err) } if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"rec_1"`) || !strings.Contains(got, `"Alice"`) { @@ -574,7 +574,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { } }) - t.Run("batch set", func(t *testing.T) { + t.Run("batch update", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ Method: "PATCH", @@ -588,7 +588,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { }, }, }) - if err := runShortcut(t, BaseRecordBatchSet, []string{"+record-batch-set", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Status":"Done"}}`}, factory, stdout); err != nil { + if err := runShortcut(t, BaseRecordBatchUpdate, []string{"+record-batch-update", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Status":"Done"}}`}, factory, stdout); err != nil { t.Fatalf("err=%v", err) } if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"update"`) || !strings.Contains(got, `"Done"`) { @@ -596,7 +596,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { } }) - t.Run("batch set passthrough", func(t *testing.T) { + t.Run("batch update passthrough", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) updateStub := &httpmock.Stub{ Method: "PATCH", @@ -609,7 +609,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { }, } reg.Register(updateStub) - if err := runShortcut(t, BaseRecordBatchSet, []string{"+record-batch-set", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Name":"Alice","Status":"Done"}}`}, factory, stdout); err != nil { + if err := runShortcut(t, BaseRecordBatchUpdate, []string{"+record-batch-update", "--base-token", "app_x", "--table-id", "tbl_x", "--json", `{"record_id_list":["rec_1"],"patch":{"Name":"Alice","Status":"Done"}}`}, factory, stdout); err != nil { t.Fatalf("err=%v", err) } if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"rec_1"`) { diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index cda960be1..c390afac4 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -114,7 +114,7 @@ func TestShortcutsCatalog(t *testing.T) { "+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-group", "+view-set-group", "+view-get-sort", "+view-set-sort", "+view-get-timebar", "+view-set-timebar", "+view-get-card", "+view-set-card", "+view-rename", - "+record-list", "+record-get", "+record-upsert", "+record-batch-add", "+record-batch-set", "+record-upload-attachment", "+record-delete", + "+record-list", "+record-get", "+record-upsert", "+record-batch-create", "+record-batch-update", "+record-upload-attachment", "+record-delete", "+record-history-list", "+base-get", "+base-copy", "+base-create", "+role-create", "+role-delete", "+role-update", "+role-list", "+role-get", "+advperm-enable", "+advperm-disable", diff --git a/shortcuts/base/record_batch_add.go b/shortcuts/base/record_batch_create.go similarity index 64% rename from shortcuts/base/record_batch_add.go rename to shortcuts/base/record_batch_create.go index 1c1fb03de..b5c01325c 100644 --- a/shortcuts/base/record_batch_add.go +++ b/shortcuts/base/record_batch_create.go @@ -9,23 +9,23 @@ import ( "github.com/larksuite/cli/shortcuts/common" ) -var BaseRecordBatchAdd = common.Shortcut{ +var BaseRecordBatchCreate = common.Shortcut{ Service: "base", - Command: "+record-batch-add", - Description: "Batch add records", + Command: "+record-batch-create", + Description: "Batch create records", Risk: "write", Scopes: []string{"base:record:create"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), tableRefFlag(true), - {Name: "json", Desc: "batch add JSON object, e.g. {\"fields\":[],\"rows\":[]}", Required: true}, + {Name: "json", Desc: "batch create JSON object, e.g. {\"fields\":[],\"rows\":[]}", Required: true}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return validateRecordJSON(runtime) }, - DryRun: dryRunRecordBatchAdd, + DryRun: dryRunRecordBatchCreate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { - return executeRecordBatchAdd(runtime) + return executeRecordBatchCreate(runtime) }, } diff --git a/shortcuts/base/record_batch_set.go b/shortcuts/base/record_batch_update.go similarity index 59% rename from shortcuts/base/record_batch_set.go rename to shortcuts/base/record_batch_update.go index bfea77ae4..8ba8dc327 100644 --- a/shortcuts/base/record_batch_set.go +++ b/shortcuts/base/record_batch_update.go @@ -9,23 +9,23 @@ import ( "github.com/larksuite/cli/shortcuts/common" ) -var BaseRecordBatchSet = common.Shortcut{ +var BaseRecordBatchUpdate = common.Shortcut{ Service: "base", - Command: "+record-batch-set", - Description: "Batch set records", + Command: "+record-batch-update", + Description: "Batch update records", Risk: "write", Scopes: []string{"base:record:update"}, AuthTypes: authTypes(), Flags: []common.Flag{ baseTokenFlag(true), tableRefFlag(true), - {Name: "json", Desc: "batch set JSON object, passed through as request body, e.g. {\"record_id_list\":[\"rec_xxx\"],\"patch\":{\"field_id_or_name\":\"value\"}}", Required: true}, + {Name: "json", Desc: "batch update JSON object, passed through as request body, e.g. {\"record_id_list\":[\"recXXX\"],\"patch\":{\"field_id_or_name\":\"value\"}}", Required: true}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return validateRecordJSON(runtime) }, - DryRun: dryRunRecordBatchSet, + DryRun: dryRunRecordBatchUpdate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { - return executeRecordBatchSet(runtime) + return executeRecordBatchUpdate(runtime) }, } diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 40b29cfe4..0689d958f 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -51,7 +51,7 @@ func dryRunRecordUpsert(_ context.Context, runtime *common.RuntimeContext) *comm Set("table_id", baseTableID(runtime)) } -func dryRunRecordBatchAdd(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { +func dryRunRecordBatchCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, _ := parseJSONObject(runtime.Str("json"), "json") return common.NewDryRunAPI(). POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). @@ -60,7 +60,7 @@ func dryRunRecordBatchAdd(_ context.Context, runtime *common.RuntimeContext) *co Set("table_id", baseTableID(runtime)) } -func dryRunRecordBatchSet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { +func dryRunRecordBatchUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, _ := parseJSONObject(runtime.Str("json"), "json") return common.NewDryRunAPI(). PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). @@ -146,13 +146,13 @@ func executeRecordUpsert(runtime *common.RuntimeContext) error { return nil } -func executeRecordBatchAdd(runtime *common.RuntimeContext) error { +func executeRecordBatchCreate(runtime *common.RuntimeContext) error { body, err := parseJSONObject(runtime.Str("json"), "json") if err != nil { return err } result, err := baseV3Raw(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) - data, err := handleBaseAPIResult(result, err, "batch add records") + data, err := handleBaseAPIResult(result, err, "batch create records") if err != nil { return err } @@ -160,13 +160,13 @@ func executeRecordBatchAdd(runtime *common.RuntimeContext) error { return nil } -func executeRecordBatchSet(runtime *common.RuntimeContext) error { +func executeRecordBatchUpdate(runtime *common.RuntimeContext) error { body, err := parseJSONObject(runtime.Str("json"), "json") if err != nil { return err } result, err := baseV3Raw(runtime, "PATCH", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) - data, err := handleBaseAPIResult(result, err, "batch set records") + data, err := handleBaseAPIResult(result, err, "batch update records") if err != nil { return err } diff --git a/shortcuts/base/shortcuts.go b/shortcuts/base/shortcuts.go index 43909059d..248cb47d5 100644 --- a/shortcuts/base/shortcuts.go +++ b/shortcuts/base/shortcuts.go @@ -37,8 +37,8 @@ func Shortcuts() []common.Shortcut { BaseRecordList, BaseRecordGet, BaseRecordUpsert, - BaseRecordBatchAdd, - BaseRecordBatchSet, + BaseRecordBatchCreate, + BaseRecordBatchUpdate, BaseRecordUploadAttachment, BaseRecordDelete, BaseRecordHistoryList, diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 06a8d9798..f31c2b79e 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -36,17 +36,17 @@ metadata: - 不要把 `+record-list` 当聚合分析引擎 - 不要没读 guide 就直接创建 formula / lookup 字段 - 不要凭自然语言猜表名、字段名、公式表达式里的字段引用 -- 不要把系统字段、formula 字段、lookup 字段当成 `+record-upsert / +record-batch-add / +record-batch-set` 的写入目标 +- 不要把系统字段、formula 字段、lookup 字段当成 `+record-upsert / +record-batch-create / +record-batch-update` 的写入目标 - 不要在 Base 场景改走 `lark-cli api GET /open-apis/bitable/v1/...` - 不要因为 wiki 解析结果里的 `obj_type=bitable` 就去找 `bitable.*`;在本 CLI 里应继续使用 `lark-cli base +...` ## Base 基本心智模型 1. **Base 字段分三类** - - **存储字段**:真实存用户输入的数据,通常适合 `+record-upsert / +record-batch-add / +record-batch-set` 写入,例如文本、数字、日期、单选、多选、人员、关联。**附件字段例外**:对 agent 而言,文件上传必须走 `+record-upload-attachment`。 + - **存储字段**:真实存用户输入的数据,通常适合 `+record-upsert / +record-batch-create / +record-batch-update` 写入,例如文本、数字、日期、单选、多选、人员、关联。**附件字段例外**:对 agent 而言,文件上传必须走 `+record-upload-attachment`。 - **系统字段**:平台自动维护,只读,典型包括创建时间、最后更新时间、创建人、修改人、自动编号。 - **计算字段**:通过表达式或跨表规则推导,只读,典型包括 **公式字段(formula)** 和 **查找引用字段(lookup)**。 -2. **写记录前先判断字段类别** — 只有存储字段可直接写;公式 / lookup / 创建时间 / 更新时间 / 创建人 / 修改人 / 自动编号都应视为只读输出字段,不能拿来做 `+record-upsert / +record-batch-add / +record-batch-set` 入参。 +2. **写记录前先判断字段类别** — 只有存储字段可直接写;公式 / lookup / 创建时间 / 更新时间 / 创建人 / 修改人 / 自动编号都应视为只读输出字段,不能拿来做 `+record-upsert / +record-batch-create / +record-batch-update` 入参。 3. **Base 不只是存表数据,也能内建计算** — 用户提出“统计、比较、排名、文本拼接、日期差、跨表汇总、状态判断”等需求时,不能默认导出数据后手算;要先判断是否应通过 `+data-query` 或公式字段在 Base 内完成。 ## 分析路径决策 @@ -101,7 +101,7 @@ metadata: ## 核心规则 -1. **只使用原子命令** — 使用 `+table-list / +table-get / +field-create / +record-upsert / +record-batch-add / +record-batch-set / +view-set-filter / +record-history-list / +base-get` 这类一命令一动作的写法,不使用旧聚合式 `+table / +field / +record / +view / +history / +workspace` +1. **只使用原子命令** — 使用 `+table-list / +table-get / +field-create / +record-upsert / +record-batch-create / +record-batch-update / +view-set-filter / +record-history-list / +base-get` 这类一命令一动作的写法,不使用旧聚合式 `+table / +field / +record / +view / +history / +workspace` 2. **写记录前先读字段结构** — 先调用 `+field-list` 获取字段结构,再读 [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) 确认各字段类型的写入值格式 3. **写字段前先看字段属性规范** — 先读 [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) 确认 `+field-create/+field-update` 的 JSON 结构 4. **筛选查询按视图能力执行** — 先读 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) 和 [lark-base-record-list.md](references/lark-base-record-list.md),通过 `+view-set-filter` + `+record-list` 组合完成筛选读取 @@ -134,8 +134,8 @@ metadata: | 创建 / 更新 lookup 字段 | `lark-cli base +field-create` / `+field-update` | `type=lookup`;先读 lookup guide,再创建 / 更新,默认先判断 formula 是否更合适 | | 列表 / 获取记录 | `lark-cli base +record-list` / `+record-get` | 原子命令,如果需要`聚合计算`,`分组统计` 推荐走 `+data-query` | | 创建 / 更新记录 | `lark-cli base +record-upsert` | `--table-id [--record-id] --json` | -| 批量新增记录 | `lark-cli base +record-batch-add` | 适合大量新增写入(如 CSV/Excel 导入);先按 record-value 规范整理为 `json.fields + json.rows` | -| 批量更新指定记录 | `lark-cli base +record-batch-set` | `--table-id --json`(`json.record_id_list + json.patch`) | +| 批量创建记录 | `lark-cli base +record-batch-create` | 适合大量创建写入(如 CSV/Excel 导入);先按 record-value 规范整理为 `json.fields + json.rows` | +| 批量更新指定记录 | `lark-cli base +record-batch-update` | `--table-id --json`(`json.record_id_list + json.patch`) | | 聚合分析 / 比较排序 / 求最值 / 筛选统计 | `lark-cli base +data-query` | 不要用 `+record-list` 拉全量数据再手动计算,需使用 `+data-query` 走服务端计算 | | 配置 / 查询视图 | `lark-cli base +view-*` | `list/get/create/delete/get-*/set-*/rename` | | 查看记录历史 | `lark-cli base +record-history-list` | 按表和记录查询变更历史 | @@ -158,7 +158,7 @@ metadata: - **Base token 口径统一**:统一使用 `--base-token` - **`+xxx-list` 调用纪律**:`+table-list / +field-list / +record-list / +view-list / +record-history-list / +role-list / +dashboard-list / +dashboard-block-list / +workflow-list` 禁止并发调用;批量执行时只能串行 - **`+record-list` 分页规则**:`--limit` 最大 `200`。先拉首批并检查返回 `has_more`;仅当 `has_more=true` 且用户明确需要更多数据(如“全部导出/全量明细/继续下一页”)时再继续翻页。用户只要样例或前 N 条时,不要继续拉全量 -- **`+record-batch-add` 适用边界**:优先用于大量新增写入(如 CSV/Excel 导入)。当输入是长表格或长文本时,先按 [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) 做字段映射和类型规范化,再组装 `fields + rows` 调用命令写入 +- **`+record-batch-create` 适用边界**:优先用于大量创建写入(如 CSV/Excel 导入)。当输入是长表格或长文本时,先按 [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) 做字段映射和类型规范化,再组装 `fields + rows` 调用命令写入 - **字段可写性先判断**:存储字段才可写;公式 / lookup / 系统字段默认只读,写记录时应跳过 - **公式能力要主动想到**:用户说“算一下”“生成标签”“判断是否异常”“跨表汇总”“按日期差预警”时,要先判断是否应该建公式字段,而不是只返回手工分析方案 - **lookup 不是默认首选**:lookup 只在用户明确要求或确实更适合固定查找模型时使用;常规计算、跨表聚合和条件判断优先 formula @@ -269,9 +269,9 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} - [lark-base-shortcut-field-properties.md](references/lark-base-shortcut-field-properties.md) — `+field-create/+field-update` 调用前必看,各类型 field JSON 规范 - [role-config.md](references/role-config.md) — 角色权限配置详解 -- [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — record 写入(`+record-upsert / +record-batch-add / +record-batch-set`)调用前必看,各类型 record JSON 规范 -- [lark-base-record-batch-add.md](references/lark-base-record-batch-add.md) — `+record-batch-add` 用法与 `--json` 结构 -- [lark-base-record-batch-set.md](references/lark-base-record-batch-set.md) — `+record-batch-set` 用法与 `--json` 结构 +- [lark-base-shortcut-record-value.md](references/lark-base-shortcut-record-value.md) — record 写入(`+record-upsert / +record-batch-create / +record-batch-update`)调用前必看,各类型 record JSON 规范 +- [lark-base-record-batch-create.md](references/lark-base-record-batch-create.md) — `+record-batch-create` 用法与 `--json` 结构 +- [lark-base-record-batch-update.md](references/lark-base-record-batch-update.md) — `+record-batch-update` 用法与 `--json` 结构 - [formula-field-guide.md](references/formula-field-guide.md) — formula 字段写法、函数约束、CurrentValue 规则、跨表计算模式(强烈推荐) - [lookup-field-guide.md](references/lookup-field-guide.md) — lookup 字段配置规则、where/aggregate 约束、与 formula 的取舍 - [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md) — 视图筛选配置 @@ -298,7 +298,7 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} |----------|------| | [`table commands`](references/lark-base-table.md) | `+table-list / +table-get / +table-create / +table-update / +table-delete` | | [`field commands`](references/lark-base-field.md) | `+field-list / +field-get / +field-create / +field-update / +field-delete / +field-search-options` | -| [`record commands`](references/lark-base-record.md) | `+record-list / +record-get / +record-upsert / +record-batch-add / +record-batch-set / +record-upload-attachment / +record-delete` | +| [`record commands`](references/lark-base-record.md) | `+record-list / +record-get / +record-upsert / +record-batch-create / +record-batch-update / +record-upload-attachment / +record-delete` | | [`view commands`](references/lark-base-view.md) | `+view-list / +view-get / +view-create / +view-delete / +view-get-* / +view-set-* / +view-rename` | | [`data-query commands`](references/lark-base-data-query.md) | `+data-query` | | [`history commands`](references/lark-base-history.md) | `+record-history-list` | diff --git a/skills/lark-base/references/lark-base-record-batch-add.md b/skills/lark-base/references/lark-base-record-batch-create.md similarity index 50% rename from skills/lark-base/references/lark-base-record-batch-add.md rename to skills/lark-base/references/lark-base-record-batch-create.md index 7d686a271..3c817363a 100644 --- a/skills/lark-base/references/lark-base-record-batch-add.md +++ b/skills/lark-base/references/lark-base-record-batch-create.md @@ -1,59 +1,73 @@ -# base +record-batch-add +# base +record-batch-create > **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。 -批量新增记录。 +批量创建记录。 ## 适用场景(重点) -- 适合大量新增写入场景,例如导入 CSV / Excel、外部系统一次性灌入新数据。 +- 适合大量创建写入场景,例如导入 CSV / Excel、外部系统一次性写入新数据。 - 当输入是长表格或长文本数据时,先按 [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md) 做字段映射和类型规范化,再组装 `fields + rows` 调用本命令写入。 ## 推荐命令 ```bash -lark-cli base +record-batch-add \ - --base-token app_xxx \ - --table-id tbl_xxx \ +lark-cli base +record-batch-create \ + --base-token XXXXXX \ + --table-id tblXXX \ --json '{"fields":["标题","状态"],"rows":[["任务 A","Open"],["任务 B","Done"]]}' ``` +```bash +lark-cli base +record-batch-create \ + --base-token XXXXXX \ + --table-id tblXXX \ + --json @batch-create.json +``` + ## 参数 | 参数 | 必填 | 说明 | |------|------|------| | `--base-token ` | 是 | Base Token | | `--table-id ` | 是 | 表 ID 或表名 | -| `--json ` | 是 | 批量新增请求体,必须是 JSON 对象 | +| `--json ` | 是 | 批量创建请求体,必须是 JSON 对象。支持直接传 JSON 字符串,或 `@` 从文件读取 | ## API 入参详情 **HTTP 方法和路径:** -``` +```http POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch ``` ## `--json` 结构 -- 对象形态:`{"fields":[...],"rows":[...]}`。 -- `fields`:字段 id 或字段名数组。 -- `rows`:二维数组,每一行按 `fields` 的同序列给值。 +对象形态:`{"fields":[...],"rows":[...]}`。 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `fields` | `string[]` | 是 | 字段 ID 或字段名数组 | +| `rows` | `any[][]` | 是 | 二维数组,每一行按 `fields` 同序给值;单次最多 200 行 | ## 返回重点 -- 返回对象键: - - `fields` - - `field_id_list` - - `record_id_list` - - `data` - - `ignored_fields`(可选) +`data` 为多行二维数组,与 `+record-list` 返回的多行数据结构一致(按 `fields` 列顺序对齐)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `fields` | `string[]` | 返回的字段名数组 | +| `field_id_list` | `string[]` | 返回的字段 ID 数组 | +| `record_id_list` | `string[]` | 新创建记录 ID 列表 | +| `data` | `any[][]` | 与 `fields` 对齐的多行数据 | +| `ignored_fields` | `array` | 可选,表示被忽略的字段信息 | ## 坑点 - ⚠️ `--json` 必须是对象。 - ⚠️ 写 `rows` 前必须先阅读 [lark-base-shortcut-record-value.md](lark-base-shortcut-record-value.md),按字段类型填值,禁止按自然语言猜测 value 结构。 - ⚠️ `fields` 与 `rows` 列顺序必须一一对应。 +- ⚠️ 空单元格可以显式用 `null` 填充。 - ⚠️ 单次最多 200 行,超出需分批写入。 ## 参考 diff --git a/skills/lark-base/references/lark-base-record-batch-set.md b/skills/lark-base/references/lark-base-record-batch-update.md similarity index 51% rename from skills/lark-base/references/lark-base-record-batch-set.md rename to skills/lark-base/references/lark-base-record-batch-update.md index d919fc05b..6cc5608ef 100644 --- a/skills/lark-base/references/lark-base-record-batch-set.md +++ b/skills/lark-base/references/lark-base-record-batch-update.md @@ -1,4 +1,4 @@ -# base +record-batch-set +# base +record-batch-update (batch update) > **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。 @@ -7,10 +7,17 @@ ## 推荐命令 ```bash -lark-cli base +record-batch-set \ - --base-token app_xxx \ - --table-id tbl_xxx \ - --json '{"record_id_list":["rec_xxx"],"patch":{"field_id_or_name":"value"}}' +lark-cli base +record-batch-update \ + --base-token XXXXXX \ + --table-id tblXXX \ + --json '{"record_id_list":["recXXX"],"patch":{"field_id_or_name":"value"}}' +``` + +```bash +lark-cli base +record-batch-update \ + --base-token XXXXXX \ + --table-id tblXXX \ + --json @batch-update.json ``` ## 参数 @@ -19,7 +26,7 @@ lark-cli base +record-batch-set \ |------|------|------| | `--base-token ` | 是 | Base Token | | `--table-id ` | 是 | 表 ID 或表名 | -| `--json ` | 是 | 批量更新请求体,必须是 JSON 对象 | +| `--json ` | 是 | 批量更新请求体,必须是 JSON 对象。支持直接传 JSON 字符串,或 `@` 从文件读取 | ## 生成 `patch` 前必看 @@ -29,23 +36,29 @@ lark-cli base +record-batch-set \ **HTTP 方法和路径:** -``` +```http PATCH /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch ``` ## `--json` 结构 -- 对象形态:`{"record_id_list":[...],"patch":{...}}`。 -- `record_id_list`:要更新的记录 ID 列表(单次最多 200 条)。 -- `patch`:同一份字段更新对象,会应用到 `record_id_list` 内所有记录。 +对象形态:`{"record_id_list":[...],"patch":{...}}`。 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `record_id_list` | `string[]` | 是 | 要更新的记录 ID 列表(单次最多 200 条) | +| `patch` | `object` | 是 | 同一份字段更新对象,会应用到 `record_id_list` 内所有记录 | ## 返回重点 -- 返回对象(`OpenAPIBatchPatchResult`): - - `has_more`(`boolean`,必返) - - `record_id_list`(`string[]`,必返) - - `update`(`object`,必返;可能为空对象) - - `ignored_fields`(`{id,name,reason}[]`,可选) +返回结构如下(其中 `update` 可与 `+record-list` 的单行字段对象结构对齐): + +| 字段 | 类型 | 说明 | +|------|------|------| +| `has_more` | `boolean` | 是否还有剩余数据 | +| `record_id_list` | `string[]` | 本次更新到的记录 ID 列表 | +| `update` | `object` | 本次应用的字段更新结果;可能为空对象 | +| `ignored_fields` | `{id,name,reason}[]` | 可选,被忽略字段信息 | ## 坑点 diff --git a/skills/lark-base/references/lark-base-record.md b/skills/lark-base/references/lark-base-record.md index 85c3295e4..322a818ff 100644 --- a/skills/lark-base/references/lark-base-record.md +++ b/skills/lark-base/references/lark-base-record.md @@ -11,8 +11,8 @@ record 相关命令索引。 | [lark-base-record-list.md](lark-base-record-list.md) | `+record-list` | 分页列记录 | | [lark-base-record-get.md](lark-base-record-get.md) | `+record-get` | 获取单条记录 | | [lark-base-record-upsert.md](lark-base-record-upsert.md) | `+record-upsert` | 创建或更新记录 | -| [lark-base-record-batch-add.md](lark-base-record-batch-add.md) | `+record-batch-add` | 按 `fields/rows` 批量新增记录 | -| [lark-base-record-batch-set.md](lark-base-record-batch-set.md) | `+record-batch-set` | 批量更新记录 | +| [lark-base-record-batch-create.md](lark-base-record-batch-create.md) | `+record-batch-create` | 按 `fields/rows` 批量创建记录 | +| [lark-base-record-batch-update.md](lark-base-record-batch-update.md) | `+record-batch-update` | 批量更新记录 | | [lark-base-record-upload-attachment.md](lark-base-record-upload-attachment.md) | `+record-upload-attachment` | 上传本地文件到附件字段并更新记录 | | [lark-base-record-delete.md](lark-base-record-delete.md) | `+record-delete` | 删除记录 | From b8290e44a8c970957240b5a08e5227133ffc556b Mon Sep 17 00:00:00 2001 From: kongenpei Date: Thu, 9 Apr 2026 00:12:48 +0800 Subject: [PATCH 06/12] refactor(base): remove noop record json validators --- shortcuts/base/record_batch_create.go | 3 --- shortcuts/base/record_batch_update.go | 3 --- shortcuts/base/record_ops.go | 4 ---- shortcuts/base/record_upsert.go | 3 --- 4 files changed, 13 deletions(-) diff --git a/shortcuts/base/record_batch_create.go b/shortcuts/base/record_batch_create.go index b5c01325c..9de49b41c 100644 --- a/shortcuts/base/record_batch_create.go +++ b/shortcuts/base/record_batch_create.go @@ -21,9 +21,6 @@ var BaseRecordBatchCreate = common.Shortcut{ tableRefFlag(true), {Name: "json", Desc: "batch create JSON object, e.g. {\"fields\":[],\"rows\":[]}", Required: true}, }, - Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { - return validateRecordJSON(runtime) - }, DryRun: dryRunRecordBatchCreate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { return executeRecordBatchCreate(runtime) diff --git a/shortcuts/base/record_batch_update.go b/shortcuts/base/record_batch_update.go index 8ba8dc327..ba85e3df3 100644 --- a/shortcuts/base/record_batch_update.go +++ b/shortcuts/base/record_batch_update.go @@ -21,9 +21,6 @@ var BaseRecordBatchUpdate = common.Shortcut{ tableRefFlag(true), {Name: "json", Desc: "batch update JSON object, passed through as request body, e.g. {\"record_id_list\":[\"recXXX\"],\"patch\":{\"field_id_or_name\":\"value\"}}", Required: true}, }, - Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { - return validateRecordJSON(runtime) - }, DryRun: dryRunRecordBatchUpdate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { return executeRecordBatchUpdate(runtime) diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 0689d958f..dd977b0b9 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -92,10 +92,6 @@ func dryRunRecordHistoryList(_ context.Context, runtime *common.RuntimeContext) Set("base_token", runtime.Str("base-token")) } -func validateRecordJSON(runtime *common.RuntimeContext) error { - return nil -} - func executeRecordList(runtime *common.RuntimeContext) error { offset := runtime.Int("offset") if offset < 0 { diff --git a/shortcuts/base/record_upsert.go b/shortcuts/base/record_upsert.go index 0ff68309e..e495791c8 100644 --- a/shortcuts/base/record_upsert.go +++ b/shortcuts/base/record_upsert.go @@ -22,9 +22,6 @@ var BaseRecordUpsert = common.Shortcut{ recordRefFlag(false), {Name: "json", Desc: "record JSON object", Required: true}, }, - Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { - return validateRecordJSON(runtime) - }, DryRun: dryRunRecordUpsert, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { return executeRecordUpsert(runtime) From 7f66ef8040cb61349b9b637fad5c434042c6a99b Mon Sep 17 00:00:00 2001 From: kongenpei Date: Thu, 9 Apr 2026 01:06:25 +0800 Subject: [PATCH 07/12] test(base): align record validate test with nil hooks --- shortcuts/base/base_shortcuts_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index c390afac4..df15b3f8d 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -234,18 +234,20 @@ func TestBaseTableValidate(t *testing.T) { } func TestBaseRecordValidate(t *testing.T) { - ctx := context.Background() if BaseRecordList.Validate != nil { t.Fatalf("record list validate should be nil after removing --fields") } if BaseRecordGet.Validate != nil { t.Fatalf("record get validate should be nil after removing --fields") } - if err := BaseRecordUpsert.Validate(ctx, newBaseTestRuntime(map[string]string{"base-token": "b", "table-id": "tbl_1", "json": `{"Name":"A"}`}, nil, nil)); err != nil { - t.Fatalf("upsert validate err=%v", err) + if BaseRecordUpsert.Validate != nil { + t.Fatalf("record upsert validate should be nil") + } + if BaseRecordBatchCreate.Validate != nil { + t.Fatalf("record batch create validate should be nil") } - if err := BaseRecordUpsert.Validate(ctx, newBaseTestRuntime(map[string]string{"base-token": "b", "table-id": "tbl_1", "json": "{"}, nil, nil)); err != nil { - t.Fatalf("invalid record json should bypass CLI validate, err=%v", err) + if BaseRecordBatchUpdate.Validate != nil { + t.Fatalf("record batch update validate should be nil") } } From f7bb4485d4ea9d67b17ea108e2e29a616989ce3d Mon Sep 17 00:00:00 2001 From: kongenpei Date: Thu, 9 Apr 2026 10:01:52 +0800 Subject: [PATCH 08/12] fix: align base record batch shortcuts with openapi routes --- shortcuts/base/base_dryrun_ops_test.go | 4 ++-- shortcuts/base/base_execute_test.go | 10 +++++----- shortcuts/base/record_ops.go | 8 ++++---- .../references/lark-base-record-batch-create.md | 2 +- .../references/lark-base-record-batch-update.md | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index c24304a2e..b86ff3cf2 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -75,8 +75,8 @@ func TestDryRunRecordOps(t *testing.T) { nil, nil, ) assertDryRunContains(t, dryRunRecordUpsert(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records") - assertDryRunContains(t, dryRunRecordBatchCreate(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") - assertDryRunContains(t, dryRunRecordBatchUpdate(ctx, upsertCreateRT), "PATCH /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch") + assertDryRunContains(t, dryRunRecordBatchCreate(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch_create") + assertDryRunContains(t, dryRunRecordBatchUpdate(ctx, upsertCreateRT), "POST /open-apis/base/v3/bases/app_x/tables/tbl_1/records/batch_update") rt := newBaseTestRuntime( map[string]string{"base-token": "app_x", "table-id": "tbl_1", "record-id": "rec_1", "json": `{"Name":"B"}`}, diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index e91464468..672382cfb 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -556,7 +556,7 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ Method: "POST", - URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch_create", Body: map[string]interface{}{ "code": 0, "data": map[string]interface{}{ @@ -577,8 +577,8 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { t.Run("batch update", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ - Method: "PATCH", - URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch_update", Body: map[string]interface{}{ "code": 0, "data": map[string]interface{}{ @@ -599,8 +599,8 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { t.Run("batch update passthrough", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) updateStub := &httpmock.Stub{ - Method: "PATCH", - URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch", + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/records/batch_update", Body: map[string]interface{}{ "code": 0, "data": map[string]interface{}{ diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index dd977b0b9..ea8d1d00d 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -54,7 +54,7 @@ func dryRunRecordUpsert(_ context.Context, runtime *common.RuntimeContext) *comm func dryRunRecordBatchCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, _ := parseJSONObject(runtime.Str("json"), "json") return common.NewDryRunAPI(). - POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). + POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_create"). Body(body). Set("base_token", runtime.Str("base-token")). Set("table_id", baseTableID(runtime)) @@ -63,7 +63,7 @@ func dryRunRecordBatchCreate(_ context.Context, runtime *common.RuntimeContext) func dryRunRecordBatchUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, _ := parseJSONObject(runtime.Str("json"), "json") return common.NewDryRunAPI(). - PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch"). + POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_update"). Body(body). Set("base_token", runtime.Str("base-token")). Set("table_id", baseTableID(runtime)) @@ -147,7 +147,7 @@ func executeRecordBatchCreate(runtime *common.RuntimeContext) error { if err != nil { return err } - result, err := baseV3Raw(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) + result, err := baseV3Raw(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch_create"), nil, body) data, err := handleBaseAPIResult(result, err, "batch create records") if err != nil { return err @@ -161,7 +161,7 @@ func executeRecordBatchUpdate(runtime *common.RuntimeContext) error { if err != nil { return err } - result, err := baseV3Raw(runtime, "PATCH", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch"), nil, body) + result, err := baseV3Raw(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records", "batch_update"), nil, body) data, err := handleBaseAPIResult(result, err, "batch update records") if err != nil { return err diff --git a/skills/lark-base/references/lark-base-record-batch-create.md b/skills/lark-base/references/lark-base-record-batch-create.md index 3c817363a..803883c25 100644 --- a/skills/lark-base/references/lark-base-record-batch-create.md +++ b/skills/lark-base/references/lark-base-record-batch-create.md @@ -38,7 +38,7 @@ lark-cli base +record-batch-create \ **HTTP 方法和路径:** ```http -POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch +POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_create ``` ## `--json` 结构 diff --git a/skills/lark-base/references/lark-base-record-batch-update.md b/skills/lark-base/references/lark-base-record-batch-update.md index 6cc5608ef..bad73fb70 100644 --- a/skills/lark-base/references/lark-base-record-batch-update.md +++ b/skills/lark-base/references/lark-base-record-batch-update.md @@ -37,7 +37,7 @@ lark-cli base +record-batch-update \ **HTTP 方法和路径:** ```http -PATCH /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch +POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_update ``` ## `--json` 结构 From 42d023900d11c8d3e73c8c11a56fc4448fdbb7f7 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Thu, 9 Apr 2026 17:55:50 +0800 Subject: [PATCH 09/12] fix(base): pass parse context for record batch JSON parsing --- shortcuts/base/record_ops.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 21e5d782f..42d2aa219 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -53,7 +53,8 @@ func dryRunRecordUpsert(_ context.Context, runtime *common.RuntimeContext) *comm } func dryRunRecordBatchCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { - body, _ := parseJSONObject(runtime.Str("json"), "json") + pc := newParseCtx(runtime) + body, _ := parseJSONObject(pc, runtime.Str("json"), "json") return common.NewDryRunAPI(). POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_create"). Body(body). @@ -62,7 +63,8 @@ func dryRunRecordBatchCreate(_ context.Context, runtime *common.RuntimeContext) } func dryRunRecordBatchUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { - body, _ := parseJSONObject(runtime.Str("json"), "json") + pc := newParseCtx(runtime) + body, _ := parseJSONObject(pc, runtime.Str("json"), "json") return common.NewDryRunAPI(). POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_update"). Body(body). @@ -145,7 +147,8 @@ func executeRecordUpsert(runtime *common.RuntimeContext) error { } func executeRecordBatchCreate(runtime *common.RuntimeContext) error { - body, err := parseJSONObject(runtime.Str("json"), "json") + pc := newParseCtx(runtime) + body, err := parseJSONObject(pc, runtime.Str("json"), "json") if err != nil { return err } @@ -159,7 +162,8 @@ func executeRecordBatchCreate(runtime *common.RuntimeContext) error { } func executeRecordBatchUpdate(runtime *common.RuntimeContext) error { - body, err := parseJSONObject(runtime.Str("json"), "json") + pc := newParseCtx(runtime) + body, err := parseJSONObject(pc, runtime.Str("json"), "json") if err != nil { return err } From b722ffb0459f72021acbe7a06a4cf4511e5a9ca3 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Thu, 9 Apr 2026 20:56:43 +0800 Subject: [PATCH 10/12] docs: move base record batch JSON guidance to tips --- shortcuts/base/record_batch_create.go | 6 +++++- shortcuts/base/record_batch_update.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/shortcuts/base/record_batch_create.go b/shortcuts/base/record_batch_create.go index 9de49b41c..623a2caaf 100644 --- a/shortcuts/base/record_batch_create.go +++ b/shortcuts/base/record_batch_create.go @@ -19,7 +19,11 @@ var BaseRecordBatchCreate = common.Shortcut{ Flags: []common.Flag{ baseTokenFlag(true), tableRefFlag(true), - {Name: "json", Desc: "batch create JSON object, e.g. {\"fields\":[],\"rows\":[]}", Required: true}, + {Name: "json", Desc: "batch create JSON object", Required: true}, + }, + Tips: []string{ + `Example: --json '{"fields":["Title","Status"],"rows":[["Task A","Open"],["Task B","Done"]]}'`, + "Agent hint: use the lark-base skill's record-batch-create guide for usage and limits.", }, DryRun: dryRunRecordBatchCreate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { diff --git a/shortcuts/base/record_batch_update.go b/shortcuts/base/record_batch_update.go index ba85e3df3..a06fb177e 100644 --- a/shortcuts/base/record_batch_update.go +++ b/shortcuts/base/record_batch_update.go @@ -19,7 +19,11 @@ var BaseRecordBatchUpdate = common.Shortcut{ Flags: []common.Flag{ baseTokenFlag(true), tableRefFlag(true), - {Name: "json", Desc: "batch update JSON object, passed through as request body, e.g. {\"record_id_list\":[\"recXXX\"],\"patch\":{\"field_id_or_name\":\"value\"}}", Required: true}, + {Name: "json", Desc: "batch update JSON object", Required: true}, + }, + Tips: []string{ + `Example: --json '{"record_id_list":["recXXX"],"patch":{"Status":"Done"}}'`, + "Agent hint: use the lark-base skill's record-batch-update guide for usage and limits.", }, DryRun: dryRunRecordBatchUpdate, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { From ac91e3a44960eef84b2df8d66e277e2321043996 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Fri, 10 Apr 2026 13:26:10 +0800 Subject: [PATCH 11/12] refactor: remove noop record validate --- shortcuts/base/base_shortcuts_test.go | 22 ---------------------- shortcuts/base/record_ops.go | 4 ---- shortcuts/base/record_upsert.go | 3 --- 3 files changed, 29 deletions(-) diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index c68901aa5..cbc98b55d 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -233,28 +233,6 @@ func TestBaseTableValidate(t *testing.T) { } } -func TestBaseRecordValidate(t *testing.T) { - ctx := context.Background() - if BaseRecordList.Validate != nil { - t.Fatalf("record list validate should be nil after removing --fields") - } - if BaseRecordGet.Validate != nil { - t.Fatalf("record get validate should be nil after removing --fields") - } - if BaseRecordUpsert.Validate == nil { - t.Fatalf("record upsert validate should be set") - } - if err := BaseRecordUpsert.Validate(ctx, newBaseTestRuntime(map[string]string{"base-token": "b", "table-id": "tbl_1", "json": "{"}, nil, nil)); err != nil { - t.Fatalf("record upsert validate should bypass malformed json, err=%v", err) - } - if BaseRecordBatchCreate.Validate != nil { - t.Fatalf("record batch create validate should be nil") - } - if BaseRecordBatchUpdate.Validate != nil { - t.Fatalf("record batch update validate should be nil") - } -} - func TestBaseViewValidate(t *testing.T) { ctx := context.Background() if err := BaseViewCreate.Validate(ctx, newBaseTestRuntime(map[string]string{"base-token": "b", "table-id": "tbl_1", "json": `{"name":"Main"}`}, nil, nil)); err != nil { diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 8fba6f8c2..42d2aa219 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -95,10 +95,6 @@ func dryRunRecordHistoryList(_ context.Context, runtime *common.RuntimeContext) Set("base_token", runtime.Str("base-token")) } -func validateRecordJSON(runtime *common.RuntimeContext) error { - return nil -} - func executeRecordList(runtime *common.RuntimeContext) error { offset := runtime.Int("offset") if offset < 0 { diff --git a/shortcuts/base/record_upsert.go b/shortcuts/base/record_upsert.go index 13045a4d6..09211f3be 100644 --- a/shortcuts/base/record_upsert.go +++ b/shortcuts/base/record_upsert.go @@ -26,9 +26,6 @@ var BaseRecordUpsert = common.Shortcut{ `Example: --json '{"Name":"Alice"}'`, "Agent hint: use the lark-base skill's record-upsert guide for usage and limits.", }, - Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { - return validateRecordJSON(runtime) - }, DryRun: dryRunRecordUpsert, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { return executeRecordUpsert(runtime) From 9d350a6fc632e751ca8bf2772255c644f668f008 Mon Sep 17 00:00:00 2001 From: kongenpei Date: Fri, 10 Apr 2026 17:05:58 +0800 Subject: [PATCH 12/12] docs: remove has_more from batch update guide --- skills/lark-base/references/lark-base-record-batch-update.md | 1 - 1 file changed, 1 deletion(-) diff --git a/skills/lark-base/references/lark-base-record-batch-update.md b/skills/lark-base/references/lark-base-record-batch-update.md index bad73fb70..7014e2dfa 100644 --- a/skills/lark-base/references/lark-base-record-batch-update.md +++ b/skills/lark-base/references/lark-base-record-batch-update.md @@ -55,7 +55,6 @@ POST /open-apis/base/v3/bases/:base_token/tables/:table_id/records/batch_update | 字段 | 类型 | 说明 | |------|------|------| -| `has_more` | `boolean` | 是否还有剩余数据 | | `record_id_list` | `string[]` | 本次更新到的记录 ID 列表 | | `update` | `object` | 本次应用的字段更新结果;可能为空对象 | | `ignored_fields` | `{id,name,reason}[]` | 可选,被忽略字段信息 |