diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 70a32f2909..5faa3739ef 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -2435,16 +2435,14 @@ func TestBaseRecordExecuteReadCreateDelete(t *testing.T) { 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, BaseRecordBatchCreate, []string{"+record-batch-create", "--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", `{"create_records":[{"Name":"Alice"},{"Name":"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"`) { + if got := stdout.String(); !strings.Contains(got, `"record_id_list"`) || !strings.Contains(got, `"rec_1"`) { t.Fatalf("stdout=%s", got) } }) diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index 5be5b4fdca..cdc6742e68 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -801,7 +801,8 @@ func TestBaseJSONExamplesLiveInFlagDescriptions(t *testing.T) { name: "record batch create json", shortcut: BaseRecordBatchCreate, wantHelp: []string{ - `batch create JSON object, e.g. {"fields":["Name","Status"],"rows":[["Task A","Todo"],["Task B",null]]}; rows follow fields order`, + "create_records contains one field map per record", + `{"create_records":[{"Name":"Task A","Status":"Todo"},{"Name":"Task B","Score":20}]}`, }, }, { @@ -850,8 +851,8 @@ func TestBaseRecordWriteHelpGuidesAgents(t *testing.T) { `{"Parent Link":[{"id":"rec_xxx"}]}`, "do not look for parent_record_id or a separate child-record API", "CellValue happy path: text/phone/url", - "select -> \"Todo\"", - "multi-select -> [\"Tag A\",\"Tag B\"]", + "select (multiple=false) -> \"Todo\"", + "select (multiple=true) -> [\"Tag A\",\"Tag B\"]", "datetime -> \"2026-03-24 10:00:00\"", "checkbox -> true/false", `ID-based CellValue: user/group/link fields use arrays like [{"id":"ou_xxx"}]`, @@ -865,11 +866,11 @@ func TestBaseRecordWriteHelpGuidesAgents(t *testing.T) { name: "record batch create", shortcut: BaseRecordBatchCreate, wantTips: []string{ - "Happy path fields: fields is the column order", - "rows is an array of row arrays", - "may use null for empty cells", + "Happy path field: create_records", + "create_records is an array of independent record field maps", + `{"create_records":[{"Name":"Task A","Status":"Todo"},{"Name":"Task B","Score":20}]}`, "use +field-list to confirm real writable fields", - "Batch create supports max 200 rows per call", + "Batch create supports max 200 records per call", "do not immediately +record-list the same table", "CellValue happy path: text/phone/url", `ID-based CellValue: user/group/link fields use arrays like [{"id":"ou_xxx"}]`, diff --git a/shortcuts/base/field_search_options.go b/shortcuts/base/field_search_options.go index ad9c471455..36080b06bb 100644 --- a/shortcuts/base/field_search_options.go +++ b/shortcuts/base/field_search_options.go @@ -27,7 +27,7 @@ var BaseFieldSearchOptions = common.Shortcut{ }, Tips: []string{ `Example: lark-cli base +field-search-options --base-token --table-id --field-id "Status" --keyword "Do"`, - "Use only for fields with options, such as select or multi-select fields.", + "Use only for select fields, whether multiple is false or true.", }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := validateLimitPageSizeAlias(runtime); err != nil { diff --git a/shortcuts/base/record_batch_create.go b/shortcuts/base/record_batch_create.go index 8f57753d8e..e6ec24af85 100644 --- a/shortcuts/base/record_batch_create.go +++ b/shortcuts/base/record_batch_create.go @@ -19,12 +19,13 @@ var BaseRecordBatchCreate = common.Shortcut{ Flags: []common.Flag{ baseTokenFlag(true), tableRefFlag(true), - {Name: "json", Desc: `batch create JSON object, e.g. {"fields":["Name","Status"],"rows":[["Task A","Todo"],["Task B",null]]}; rows follow fields order`, Required: true}, + {Name: "json", Desc: `batch create JSON object; create_records contains one field map per record, e.g. {"create_records":[{"Name":"Task A","Status":"Todo"},{"Name":"Task B","Score":20}]}`, Required: true}, }, Tips: append([]string{ - "Happy path fields: fields is the column order; rows is an array of row arrays; each row must match fields order and may use null for empty cells.", + "Happy path field: create_records is an array of independent record field maps.", + `Example: {"create_records":[{"Name":"Task A","Status":"Todo"},{"Name":"Task B","Score":20}]}.`, "Before writing, use +field-list to confirm real writable fields; do not write system fields, formula, lookup, or attachment fields as normal CellValue.", - "Batch create supports max 200 rows per call.", + "Batch create supports max 200 records per call.", "After batch-creating known helper rows, use the returned record IDs and your submitted rows; do not immediately +record-list the same table unless you need server-normalized formula/lookup values or failure diagnosis.", "Use the record-batch-create guide for command limits and edge cases.", }, recordCellValueHappyPathTips...), diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 005cdc8701..3b809a834c 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -19,7 +19,7 @@ const maxBatchGetSelectFieldCount = 100 const maxRecordSearchSelectFieldCount = 50 var recordCellValueHappyPathTips = []string{ - `CellValue happy path: text/phone/url -> "text"; number/currency/percent/rating -> 12.5; select -> "Todo"; multi-select -> ["Tag A","Tag B"]; datetime -> "2026-03-24 10:00:00"; checkbox -> true/false.`, + `CellValue happy path: text/phone/url -> "text"; number/currency/percent/rating -> 12.5; select (multiple=false) -> "Todo"; select (multiple=true) -> ["Tag A","Tag B"]; datetime -> "2026-03-24 10:00:00"; checkbox -> true/false.`, `ID-based CellValue: user/group/link fields use arrays like [{"id":"ou_xxx"}], [{"id":"oc_xxx"}], [{"id":"rec_xxx"}]; location uses {"lng":116.397428,"lat":39.90923}; null clears a cell when allowed.`, "Do not guess user/chat/linked-record IDs or location coordinates; resolve them first with the relevant contact/im/record lookup flow.", "Use lark-base-cell-value.md for complex CellValue shapes and special field types; do not invent values for fields not covered by the happy path.", diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index ede5711fc0..6d716f34e7 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -112,8 +112,7 @@ metadata: - 表名、字段名、视图名、workflow 配置中的名称必须来自真实返回;跨表场景还要读取目标表结构。 - 删除、角色更新、字段更新等高风险操作遵循 CLI 的 confirmation gate;目标不明确时先用 get/list 消歧。 - 批量写入单批最多 200 条;连续写同一表时串行执行,遇到 `1254291` 按短暂等待后重试处理。 -- `+record-batch-update` 使用 `update_records`,按 `record_id -> fields` 映射逐条提交字段值。 -- select/multiselect 写入未知选项可能触发平台新增选项;不是要新增时,先用 `+field-list` 或 `+field-search-options` 确认可选值。 +- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。 ## 表单与视图细节 diff --git a/skills/lark-base/references/lark-base-cell-value.md b/skills/lark-base/references/lark-base-cell-value.md index 3f3f0866cf..71447dc560 100644 --- a/skills/lark-base/references/lark-base-cell-value.md +++ b/skills/lark-base/references/lark-base-cell-value.md @@ -8,7 +8,7 @@ - `--json` 必须是 JSON 对象。 - `+record-upsert`:顶层直接传字段映射:`{"字段名或字段ID": CellValue}`。 -- `+record-batch-create`:`rows` 是 `CellValue[][]`,列顺序由 `fields` 决定。 +- `+record-batch-create`:使用 `create_records`,其每个元素都是 `Map`。 - `+record-batch-update`:使用 `update_records`,其每个 value 都是 `Map`。 - 一次 payload 里同一字段只用一种 key(字段名或字段 ID),不要重复。 - 写入前先 `+field-list` 获取字段 `type/style/multiple`,再构造值。 @@ -48,7 +48,7 @@ text 字段的 `style.type` 影响单元格检查逻辑: ### 2.3 select(单选/多选) -单选用选项名字符串;多选用选项名数组。选项名建议与字段配置一致;写入未知选项时平台可能自动新增选项,因此不要把自然语言近义词当成已有选项传入。 +`select` 字段用 `multiple` 区分单选和多选:`multiple=false` 时传选项名字符串,`multiple=true` 时传选项名数组。只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。 ```json { 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 95d6211d45..1585bfad7b 100644 --- a/skills/lark-base/references/lark-base-record-batch-create.md +++ b/skills/lark-base/references/lark-base-record-batch-create.md @@ -7,13 +7,13 @@ ## 适用场景(重点) - 适合导入 CSV / Excel、外部系统一次性写入新数据。 -- 先把输入数据映射到合适的字段类型,再组装 `fields + rows`。 +- 先把每条输入数据映射为独立的字段对象,再组装到 `create_records`。 ## 推荐命令 ```bash lark-cli base +record-batch-create --base-token --table-id \ - --json '{"fields":["标题","状态"],"rows":[["任务 A","Open"],["任务 B","Done"]]}' + --json '{"create_records":[{"标题":"任务 A","状态":"Open"},{"标题":"任务 B","状态":"Done"}]}' lark-cli base +record-batch-create --base-token --table-id --json @batch-create.json ``` @@ -34,23 +34,25 @@ lark-cli base +record-batch-create --base-token --table-id >` | 是 | 记录字段对象数组;每条记录可以提交不同字段,单次最多 200 条 | ## 返回重点 -返回 `fields`、`field_id_list`、`record_id_list`、`data`,其中 `data` 与 `fields` 列顺序对齐。 +返回 `record_id_list` 和可选的 `ignored_fields`。 ## 坑点 -- `fields` 与每行 `rows` 的列顺序必须一一对应。 -- 空单元格必须显式用 `null` 填充。 -- 单次最多 200 行,超出需分批写入。 -- select 写入未知选项时平台可能自动新增选项;如果不是要新增选项,先确认真实选项名。 +- 每个 `create_records` 元素都是独立的记录字段对象,只提交该记录需要写入的字段。 +- 单次最多 200 条,超出需分批写入。 +- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。 ## 参考 diff --git a/skills/lark-base/references/lark-base-record-upsert.md b/skills/lark-base/references/lark-base-record-upsert.md index 79d34a411b..30c28a8caa 100644 --- a/skills/lark-base/references/lark-base-record-upsert.md +++ b/skills/lark-base/references/lark-base-record-upsert.md @@ -55,7 +55,7 @@ lark-cli base +record-upsert --base-token --table-id --r ## 坑点 - 有 `--record-id` 就一定更新;不传就一定创建,不会自动查重或按业务键 upsert。 -- select 写入未知选项时平台可能自动新增选项;如果不是要新增选项,先用 `+field-list` / `+field-search-options` 确认真实选项名。 +- `select` 字段只支持写入字段中已有的选项;构造 CellValue 前先用 `+field-list` 或 `+field-search-options` 确认目标选项存在。 - 这是写入操作,执行前必须确认目标表和字段。 ## 参考 diff --git a/tests/cli_e2e/base/base_record_batch_update_workflow_test.go b/tests/cli_e2e/base/base_record_batch_update_workflow_test.go index 9b375925ff..7d8963efd1 100644 --- a/tests/cli_e2e/base/base_record_batch_update_workflow_test.go +++ b/tests/cli_e2e/base/base_record_batch_update_workflow_test.go @@ -35,7 +35,7 @@ func TestBaseRecordBatchUpdatePerRecordWorkflow(t *testing.T) { "base", "+record-batch-create", "--base-token", baseToken, "--table-id", tableID, - "--json", `{"fields":["Name","Status","Score"],"rows":[["alpha","Open",10],["beta","Open",15]]}`, + "--json", `{"create_records":[{"Name":"alpha","Status":"Open","Score":10},{"Name":"beta","Status":"Open","Score":15}]}`, }, DefaultAs: "bot", }) diff --git a/tests/cli_e2e/base/coverage.md b/tests/cli_e2e/base/coverage.md index 1a756d47fc..80c0b034c2 100644 --- a/tests/cli_e2e/base/coverage.md +++ b/tests/cli_e2e/base/coverage.md @@ -56,7 +56,7 @@ | ✕ | base +form-questions-list | shortcut | | none | form workflows not covered | | ✕ | base +form-questions-update | shortcut | | none | form workflows not covered | | ✕ | base +form-update | shortcut | | none | form workflows not covered | -| ✓ | base +record-batch-create | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.fields`; `--json.rows` | seeds heterogeneous live workflow records | +| ✓ | base +record-batch-create | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.create_records` | seeds heterogeneous live workflow records | | ✓ | base +record-batch-update | shortcut | base_record_batch_update_dryrun_test.go::TestBaseRecordBatchUpdatePerRecordDryRun; base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.update_records`; dry-run + live | heterogeneous select/number update with write-back verification | | ✕ | base +record-delete | shortcut | | none | record workflows not covered | | ✓ | base +record-get | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--record-id`; repeated `--field-id`; `--format json` | reads back select and number values after batch update |