Files
修雨 4c5f1faeb1 feat: align open CLI with dws-wukong via cedar discovery version code (#509)
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
2026-06-26 21:49:38 +08:00

1827 lines
62 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"encoding/json"
"fmt"
"strings"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/i18n"
"github.com/spf13/cobra"
)
func runAitableHelperTool(cmd *cobra.Command, runner executor.Runner, tool string, params map[string]any) error {
return runAitableProductTool(cmd, runner, "aitable-helper", tool, params)
}
// newAitableRecordQueryEmptyCommand:dws aitable record query-empty
// 扫描一页记录,过滤出"完全没填用户字段"的空行。路由到 aitable-helper 的
// query_empty_records——该工具未必下发到 discovery,硬编码兜底保证 discovery
// 降级时命令仍可用(与 view lock/duplicate 等 helper 命令同一层)。
func newAitableRecordQueryEmptyCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "query-empty",
Short: i18n.T("查询完全没填用户字段的空行"),
Long: i18n.T(`按表内顺序扫描一页,过滤出"完全没填用户字段"的空行。
- 空行定义:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。
- --limit 是扫描预算(不是返回数):可能扫了 100 条但全部非空,本页返回空数组。
- 翻页:返回 nextCursor 非空时把它传回继续扫;nextCursor 为空才表示扫完全表。`),
Example: " dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID\n dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID --limit 50\n dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID --cursor <上次的nextCursor>",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
}
if cmd.Flags().Changed("limit") {
limit, _ := cmd.Flags().GetInt("limit")
if limit < 1 || limit > 100 {
return apperrors.NewValidation(fmt.Sprintf("--limit must be in [1, 100], got %d", limit))
}
params["limit"] = limit
}
if cursor := aitableStringFlag(cmd, "cursor"); cursor != "" {
params["cursor"] = cursor
}
return runAitableHelperTool(cmd, runner, "query_empty_records", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().Int("limit", 0, i18n.T("单页扫描预算 [1, 100],不传使用服务端默认值"))
cmd.Flags().String("cursor", "", i18n.T("翻页游标:传入上次返回的 nextCursor 继续扫描"))
return cmd
}
// newAitableDashboardArrangeCommand:dws aitable dashboard arrange
// 服务端智能重排仪表盘内图表布局。路由到 aitable-helper 的 align_dashboard。
func newAitableDashboardArrangeCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "arrange",
Short: i18n.T("自动重排仪表盘图表布局"),
Long: i18n.T(`对指定仪表盘做服务端智能布局:把图表按行铺满网格,避免某行只占半幅、留下大片空白。
返回 data 含 totalColumns、alignedChartCount 以及每个图表的新位置 layout。`),
Example: " dws aitable dashboard arrange --base-id BASE_ID --dashboard-id DASHBOARD_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
dashboardID, err := aitableRequiredFlag(cmd, "dashboard-id")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "align_dashboard", map[string]any{
"baseId": baseID,
"dashboardId": dashboardID,
})
},
}
preferLegacyLeaf(cmd)
addAitableDashboardIDFlags(cmd)
return cmd
}
func newAitableFieldSearchOptionsCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "search-options",
Short: i18n.T("搜索单选/多选字段选项"),
Example: " dws aitable field search-options --base-id BASE_ID --table-id TABLE_ID --field-id FIELD_ID --keyword 已完成",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
fieldID, err := aitableRequiredFlag(cmd, "field-id")
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"fieldId": fieldID,
}
if keyword := aitableStringFlag(cmd, "keyword"); keyword != "" {
params["keyword"] = keyword
}
if cmd.Flags().Changed("limit") {
limit, _ := cmd.Flags().GetInt("limit")
if limit <= 0 || limit > 3000 {
return apperrors.NewValidation(fmt.Sprintf("--limit must be in [1, 3000], got %d", limit))
}
params["limit"] = limit
}
return runAitableTool(cmd, runner, "search_field_options", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("field-id", "", i18n.T("目标字段 ID,必须是 singleSelect 或 multipleSelect 类型 (必填)"))
cmd.Flags().String("keyword", "", i18n.T("选项名称模糊搜索关键词"))
cmd.Flags().Int("limit", 0, i18n.T("返回 option 数量上限 [1,3000]"))
return cmd
}
func newAitableRecordHistoryListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "history-list",
Short: i18n.T("查询行记录变更历史"),
Example: " dws aitable record history-list --base-id BASE_ID --table-id TABLE_ID --record-id RECORD_ID --limit 50 --offset 0",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
recordID, err := aitableRequiredFlag(cmd, "record-id")
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"recordId": recordID,
}
if cmd.Flags().Changed("offset") {
offset, _ := cmd.Flags().GetInt("offset")
if offset < 0 {
return apperrors.NewValidation(fmt.Sprintf("--offset must be >= 0, got %d", offset))
}
params["offset"] = offset
}
if cmd.Flags().Changed("limit") {
limit, _ := cmd.Flags().GetInt("limit")
if limit < 1 || limit > 50 {
return apperrors.NewValidation(fmt.Sprintf("--limit must be in [1, 50], got %d", limit))
}
params["limit"] = limit
}
return runAitableHelperTool(cmd, runner, "query_record_history", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("record-id", "", i18n.T("Record ID (必填)"))
cmd.Flags().Int("offset", 0, i18n.T("分页偏移量,>= 0"))
cmd.Flags().Int("limit", 0, i18n.T("分页大小 [1, 50],不传使用服务端默认值"))
return cmd
}
func newAitableRecordShareURLCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "share-url",
Short: i18n.T("批量获取记录分享链接"),
Example: " dws aitable record share-url --base-id BASE_ID --table-id TABLE_ID --record-ids rec1,rec2 --view-id VIEW_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
recordIDsRaw, err := aitableRequiredFlag(cmd, "record-ids")
if err != nil {
return err
}
recordIDs := parseAitableCSVValues(recordIDsRaw)
if len(recordIDs) == 0 {
return apperrors.NewValidation("--record-ids must contain at least one record ID")
}
if len(recordIDs) > 20 {
return apperrors.NewValidation(fmt.Sprintf("--record-ids exceeds limit: got %d, max 20", len(recordIDs)))
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"recordIds": recordIDs,
}
if viewID := aitableStringFlag(cmd, "view-id"); viewID != "" {
params["viewId"] = viewID
}
return runAitableHelperTool(cmd, runner, "get_record_share_url", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("record-ids", "", i18n.T("Record ID 列表,逗号分隔,单次最多 20 条 (必填)"))
cmd.Flags().String("view-id", "", i18n.T("View ID,可选"))
return cmd
}
func newAitableRecordUpsertCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "upsert",
Short: i18n.T("批量创建或更新记录"),
Example: " dws aitable record upsert --base-id BASE_ID --table-id TABLE_ID --records '[{\"recordId\":\"rec1\",\"cells\":{\"fld\":\"x\"}},{\"cells\":{\"fld\":\"new\"}}]'",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
records, err := resolveAitableRecordsInput(cmd, "records", "fields")
if err != nil {
return err
}
if len(records) == 0 {
return apperrors.NewValidation("--records must contain at least one record")
}
if len(records) > 100 {
return apperrors.NewValidation(fmt.Sprintf("--records exceeds limit: got %d, max 100", len(records)))
}
return runAitableHelperTool(cmd, runner, "record_upsert", map[string]any{
"baseId": baseID,
"tableId": tableID,
"records": records,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("records", "", i18n.T("记录 JSON 数组 (必填,可改用 --records-file)"))
cmd.Flags().String("records-file", "", i18n.T("从文件读取 records JSON"))
addAitableHiddenStringFlag(cmd, "fields", "--records 的兼容别名")
return cmd
}
func newAitableRecordPrimaryDocGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "primary-doc-get",
Short: i18n.T("查询记录的主键文档"),
Example: " dws aitable record primary-doc-get --base-id BASE_ID --table-id TABLE_ID --record-id RECORD_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
recordID, err := aitableRequiredFlag(cmd, "record-id")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "get_primary_doc", map[string]any{
"baseId": baseID,
"tableId": tableID,
"recordId": recordID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("record-id", "", i18n.T("Record ID (必填)"))
return cmd
}
func newAitableRecordPrimaryDocCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "primary-doc-create",
Short: i18n.T("为记录创建主键文档"),
Example: " dws aitable record primary-doc-create --base-id BASE_ID --table-id TABLE_ID --field-id FIELD_ID --record-id RECORD_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, err := requiredAitableBaseTable(cmd)
if err != nil {
return err
}
fieldID, err := aitableRequiredFlag(cmd, "field-id")
if err != nil {
return err
}
recordID, err := aitableRequiredFlag(cmd, "record-id")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "create_primary_doc", map[string]any{
"baseId": baseID,
"tableId": tableID,
"fieldId": fieldID,
"recordId": recordID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableFlags(cmd)
cmd.Flags().String("field-id", "", i18n.T("PrimaryDoc 字段 ID (必填)"))
cmd.Flags().String("record-id", "", i18n.T("Record ID (必填)"))
return cmd
}
func newAitableViewGetCardCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "card", "card", nil, true)
}
func newAitableViewGetTimebarCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "timebar", "ganttTimebar", []string{"Gantt"}, false)
}
func newAitableViewGetAggregateCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "aggregate", "aggregate", []string{"Grid"}, false)
}
func newAitableViewGetFilterCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "filter", "filter", nil, false)
}
func newAitableViewGetSortCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "sort", "sort", nil, false)
}
func newAitableViewGetGroupCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "group", "group", nil, false)
}
func newAitableViewGetVisibleFieldsCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "visible-fields", "columns", nil, false)
}
func newAitableViewGetFieldWidthsCommand(runner executor.Runner) *cobra.Command {
return newAitableViewGetProjectionCommand(runner, "field-widths", "custom.widthMap", []string{"Grid"}, false)
}
func newAitableViewGetProjectionCommand(runner executor.Runner, use, blockKey string, allowedViewTypes []string, dynamicCard bool) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: i18n.T("获取视图 " + use + " 配置"),
Example: fmt.Sprintf(" dws aitable view get %s --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID", use),
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"viewIds": []string{viewID},
}
result, err := runAitableProductToolResult(cmd, runner, "aitable", "get_views", params)
if err != nil {
return err
}
if commandDryRun(cmd) {
return writeCommandPayload(cmd, result)
}
view, viewType, err := aitableViewFromResult(result, viewID)
if err != nil {
return err
}
key := blockKey
if dynamicCard {
key, err = aitableDispatchCardKey(viewType)
if err != nil {
return err
}
} else if err := aitableRequireViewType(viewType, use, allowedViewTypes); err != nil {
return err
}
data := aitableWalkViewPath(view, key)
if data == nil {
data = aitableDefaultViewProjection(use)
}
return writeCommandPayload(cmd, aitableProjectionEnvelope(result, data))
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
return cmd
}
func newAitableViewUpdateCardCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "card",
Short: i18n.T("更新视图 card 配置"),
Example: ` dws aitable view update card --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --cover-field-id fldXXX`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
noCover, _ := cmd.Flags().GetBool("no-cover")
coverFieldID := aitableStringFlag(cmd, "cover-field-id")
if noCover && coverFieldID != "" {
return apperrors.NewValidation("--no-cover 与 --cover-field-id 互斥,请只指定一个")
}
baseID, tableID, viewID, blockKey, err := aitableViewUpdatePreflight(cmd, runner, "card", nil, true)
if err != nil {
return err
}
typed := map[string]any{}
if noCover {
typed["coverFieldId"] = "NONE"
} else if coverFieldID != "" {
typed["coverFieldId"] = coverFieldID
}
aitableCollectStringFlag(cmd, "cover-resize-mode", "coverResizeMode", typed)
aitableCollectBoolFlag(cmd, "hidden-field-title", "hiddenFieldTitle", typed)
aitableCollectStringFlag(cmd, "cover-mode", "coverMode", typed)
aitableCollectBoolFlag(cmd, "display-field-name", "displayFieldName", typed)
block, err := aitableMergeJSONObjectFlag(cmd, typed)
if err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, blockKey, block, nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T("完整 card JSON 对象"))
cmd.Flags().String("cover-field-id", "", i18n.T("封面字段 ID"))
cmd.Flags().Bool("no-cover", false, i18n.T("清除封面字段"))
cmd.Flags().String("cover-resize-mode", "", i18n.T("封面裁剪模式: cover/contain/stretch"))
cmd.Flags().Bool("hidden-field-title", false, i18n.T("Kanban 是否隐藏字段标题"))
cmd.Flags().String("cover-mode", "", i18n.T("Gallery 封面模式"))
cmd.Flags().Bool("display-field-name", false, i18n.T("Gallery 是否显示字段名"))
return cmd
}
func newAitableViewUpdateTimebarCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "timebar",
Short: i18n.T("更新视图 timebar 配置"),
Example: ` dws aitable view update timebar --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --start-field fldStart`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, blockKey, err := aitableViewUpdatePreflight(cmd, runner, "ganttTimebar", []string{"Gantt"}, false)
if err != nil {
return err
}
typed := map[string]any{}
aitableCollectStringFlag(cmd, "start-field", "startField", typed)
aitableCollectStringFlag(cmd, "end-field", "endField", typed)
aitableCollectStringFlag(cmd, "display-field-id", "displayFieldId", typed)
aitableCollectStringFlag(cmd, "timeline-scale", "timelineScale", typed)
aitableCollectBoolFlag(cmd, "official-holiday", "officialHoliday", typed)
if raw := aitableStringFlag(cmd, "color-configs"); raw != "" {
arr, err := parseAitableJSONArray(raw, "color-configs")
if err != nil {
return err
}
typed["colorConfigs"] = arr
}
block, err := aitableMergeJSONObjectFlag(cmd, typed)
if err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, blockKey, block, nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T("完整 ganttTimebar JSON 对象"))
cmd.Flags().String("start-field", "", i18n.T("开始日期字段 ID"))
cmd.Flags().String("end-field", "", i18n.T("结束日期字段 ID"))
cmd.Flags().String("display-field-id", "", i18n.T("显示字段 ID"))
cmd.Flags().String("timeline-scale", "", i18n.T("时间轴粒度"))
cmd.Flags().String("color-configs", "", i18n.T("颜色配置 JSON 数组"))
cmd.Flags().Bool("official-holiday", false, i18n.T("是否显示法定节假日"))
return cmd
}
func newAitableViewUpdateAggregateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "aggregate",
Short: i18n.T("更新视图字段聚合统计"),
Example: ` dws aitable view update aggregate --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --field-id fldX --action SUM`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, blockKey, err := aitableViewUpdatePreflight(cmd, runner, "aggregate", []string{"Grid"}, false)
if err != nil {
return err
}
typed := map[string]any{}
fieldID := aitableStringFlag(cmd, "field-id")
action := aitableStringFlag(cmd, "action")
if (fieldID == "") != (action == "") {
return apperrors.NewValidation("--field-id and --action must be specified together")
}
if fieldID != "" {
typed[fieldID] = action
}
if raw := aitableStringFlag(cmd, "clear-field-id"); raw != "" {
for _, key := range parseAitableCSVValues(raw) {
typed[key] = nil
}
}
block, err := aitableMergeJSONObjectFlag(cmd, typed)
if err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, blockKey, block, nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T("aggregate JSON 对象"))
cmd.Flags().String("field-id", "", i18n.T("字段 ID"))
cmd.Flags().String("action", "", i18n.T("聚合动作"))
cmd.Flags().String("clear-field-id", "", i18n.T("要清除聚合的字段 ID,逗号分隔"))
return cmd
}
func newAitableViewUpdateFieldWidthsCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "field-widths",
Short: i18n.T("更新视图字段列宽"),
Example: ` dws aitable view update field-widths --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --field-id fldX --width 200`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, blockKey, err := aitableViewUpdatePreflight(cmd, runner, "fieldWidths", []string{"Grid"}, false)
if err != nil {
return err
}
typed := map[string]any{}
fieldID := aitableStringFlag(cmd, "field-id")
widthChanged := cmd.Flags().Changed("width")
if (fieldID == "") != !widthChanged {
return apperrors.NewValidation("--field-id and --width must be specified together")
}
if fieldID != "" {
width, _ := cmd.Flags().GetInt("width")
typed[fieldID] = width
}
block, err := aitableMergeJSONObjectFlag(cmd, typed)
if err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, blockKey, block, nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T("fieldWidths JSON 对象"))
cmd.Flags().String("field-id", "", i18n.T("字段 ID"))
cmd.Flags().Int("width", 0, i18n.T("列宽像素值"))
return cmd
}
func newAitableViewUpdateVisibleFieldsCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "visible-fields",
Short: i18n.T("更新视图可见字段列表"),
Example: ` dws aitable view update visible-fields --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --field-ids fld1,fld2`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
fieldIDs := parseAitableCSVValues(aitableStringFlag(cmd, "field-ids"))
if raw := aitableStringFlag(cmd, "json"); raw != "" {
arr, err := parseAitableJSONArray(raw, "json")
if err != nil {
return err
}
fieldIDs = fieldIDs[:0]
for _, item := range arr {
s, ok := item.(string)
if !ok {
return apperrors.NewValidation(fmt.Sprintf("--json array elements must be strings, got %T", item))
}
fieldIDs = append(fieldIDs, s)
}
}
if len(fieldIDs) == 0 {
return apperrors.NewValidation("must specify --field-ids or --json")
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, "visibleFieldIds", fieldIDs, nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("field-ids", "", i18n.T("可见字段 ID 列表,逗号分隔"))
cmd.Flags().String("json", "", i18n.T("可见字段 ID JSON 字符串数组"))
return cmd
}
func newAitableViewUpdateFilterCommand(runner executor.Runner) *cobra.Command {
return newAitableViewUpdateArrayCommand(runner, "filter", "filter")
}
func newAitableViewUpdateSortCommand(runner executor.Runner) *cobra.Command {
return newAitableViewUpdateArrayCommand(runner, "sort", "sort")
}
func newAitableViewUpdateGroupCommand(runner executor.Runner) *cobra.Command {
return newAitableViewUpdateArrayCommand(runner, "group", "group")
}
func newAitableViewUpdateArrayCommand(runner executor.Runner, use, blockKey string) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: i18n.T("更新视图 " + use + " 配置"),
Example: fmt.Sprintf(" dws aitable view update %s --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[]'", use),
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
raw, err := aitableRequiredFlag(cmd, "json")
if err != nil {
return err
}
parsed, err := parseAitableJSONValue(raw, "json")
if err != nil {
return err
}
cfg := map[string]any{blockKey: parsed}
if err := normalizeAitableViewConfigBlock(cmd, cfg); err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, blockKey, cfg[blockKey], nil)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T(use+" JSON 数组 (必填)"))
return cmd
}
func newAitableViewUpdateNameCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "name",
Short: i18n.T("重命名视图"),
Example: ` dws aitable view update name --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --name 新视图名`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
name, err := aitableRequiredFlag(cmd, "name")
if err != nil {
return err
}
return runAitableViewUpdateBlock(cmd, runner, baseID, tableID, viewID, "", nil, map[string]any{"newViewName": name})
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("name", "", i18n.T("新视图名称 (必填)"))
return cmd
}
func aitableViewUpdatePreflight(cmd *cobra.Command, runner executor.Runner, blockKey string, allowedViewTypes []string, dynamicCard bool) (string, string, string, string, error) {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return "", "", "", "", err
}
if commandDryRun(cmd) {
if dynamicCard {
return baseID, tableID, viewID, "kanbanCard", nil
}
return baseID, tableID, viewID, blockKey, nil
}
if !dynamicCard && len(allowedViewTypes) == 0 {
return baseID, tableID, viewID, blockKey, nil
}
result, err := runAitableProductToolResult(cmd, runner, "aitable", "get_views", map[string]any{
"baseId": baseID,
"tableId": tableID,
"viewIds": []string{viewID},
})
if err != nil {
return "", "", "", "", err
}
_, viewType, err := aitableViewFromResult(result, viewID)
if err != nil {
return "", "", "", "", err
}
if dynamicCard {
dispatched, err := aitableDispatchCardKey(viewType)
return baseID, tableID, viewID, dispatched, err
}
if err := aitableRequireViewType(viewType, blockKey, allowedViewTypes); err != nil {
return "", "", "", "", err
}
return baseID, tableID, viewID, blockKey, nil
}
func runAitableViewUpdateBlock(cmd *cobra.Command, runner executor.Runner, baseID, tableID, viewID, blockKey string, block any, extra map[string]any) error {
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"viewId": viewID,
}
for k, v := range extra {
params[k] = v
}
if blockKey != "" {
params["config"] = map[string]any{blockKey: block}
}
return runAitableTool(cmd, runner, "update_view", params)
}
func newAitableViewGetLockCommand(runner executor.Runner) *cobra.Command {
return newAitableViewHelperGetCommand(runner, "lock", i18n.T("获取视图锁定状态"), "get_view_lock_status")
}
func newAitableViewGetFrozenColsCommand(runner executor.Runner) *cobra.Command {
return newAitableViewHelperGetCommand(runner, "frozen-cols", i18n.T("获取视图冻结列数"), "get_frozen_columns_of_view")
}
func newAitableViewGetRowHeightCommand(runner executor.Runner) *cobra.Command {
return newAitableViewHelperGetCommand(runner, "row-height", i18n.T("获取视图行高"), "get_cell_height_of_view")
}
func newAitableViewGetFillColorRuleCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "fill-color-rule",
Short: i18n.T("获取视图数据高亮规则"),
Example: " dws aitable view get fill-color-rule --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
result, err := runAitableProductToolResult(cmd, runner, "aitable", "get_views", map[string]any{
"baseId": baseID,
"tableId": tableID,
"viewIds": []string{viewID},
})
if err != nil {
return err
}
if commandDryRun(cmd) {
return writeCommandPayload(cmd, result)
}
view, _, err := aitableViewFromResult(result, viewID)
if err != nil {
return err
}
data := aitableWalkViewPath(view, "conditionalFormats")
if data == nil {
data = []any{}
}
return writeCommandPayload(cmd, aitableProjectionEnvelope(result, data))
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
return cmd
}
func newAitableViewHelperGetCommand(runner executor.Runner, use, short, tool string) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: short,
Example: fmt.Sprintf(" dws aitable view get %s --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID", use),
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params, err := requiredAitableViewParams(cmd)
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, tool, params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
return cmd
}
func newAitableViewUpdateFrozenColsCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "frozen-cols",
Short: i18n.T("更新视图冻结列数"),
Example: " dws aitable view update frozen-cols --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --count 1",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if !cmd.Flags().Changed("count") {
return apperrors.NewValidation("--count is required")
}
count, _ := cmd.Flags().GetInt("count")
if count < 0 {
return apperrors.NewValidation(fmt.Sprintf("--count must be >= 0, got %d", count))
}
params, err := requiredAitableViewParams(cmd)
if err != nil {
return err
}
params["count"] = count
return runAitableHelperTool(cmd, runner, "set_frozen_columns_of_view", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().Int("count", 0, i18n.T("冻结列数,>= 0;0 表示取消冻结 (必填)"))
return cmd
}
func newAitableViewUpdateRowHeightCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "row-height",
Short: i18n.T("更新视图行高"),
Example: " dws aitable view update row-height --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --cell-height 56",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if !cmd.Flags().Changed("cell-height") {
return apperrors.NewValidation("--cell-height is required")
}
cellHeight, _ := cmd.Flags().GetInt("cell-height")
if cellHeight <= 0 {
return apperrors.NewValidation(fmt.Sprintf("--cell-height must be > 0, got %d", cellHeight))
}
params, err := requiredAitableViewParams(cmd)
if err != nil {
return err
}
params["cellHeight"] = cellHeight
return runAitableHelperTool(cmd, runner, "set_cell_height_of_view", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().Int("cell-height", 0, i18n.T("单元格高度,像素值 (必填)"))
return cmd
}
func newAitableViewUpdateFillColorRuleCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "fill-color-rule",
Short: i18n.T("更新视图数据高亮规则"),
Example: " dws aitable view update fill-color-rule --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --json '[]'",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
raw, err := aitableRequiredFlag(cmd, "json")
if err != nil {
return err
}
conditionalFormats, err := parseAitableJSONArray(raw, "json")
if err != nil {
return err
}
params, err := requiredAitableViewParams(cmd)
if err != nil {
return err
}
params["conditionalFormats"] = conditionalFormats
return runAitableTool(cmd, runner, "set_view_fill_color_rule", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("json", "", i18n.T("conditionalFormats JSON 数组 (必填)"))
return cmd
}
func newAitableViewLockCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "lock",
Short: i18n.T("锁定或解锁视图"),
Example: " dws aitable view lock --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --off",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
params, err := requiredAitableViewParams(cmd)
if err != nil {
return err
}
action := "lock"
if off, _ := cmd.Flags().GetBool("off"); off {
action = "unlock"
}
params["action"] = action
return runAitableHelperTool(cmd, runner, "lock_or_unlock_view", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().Bool("off", false, i18n.T("解锁视图;不传则锁定"))
return cmd
}
func newAitableViewDuplicateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "duplicate",
Short: i18n.T("复制视图"),
Example: " dws aitable view duplicate --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_ID --new-name 副本视图",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"tableId": tableID,
"sourceViewId": viewID,
}
if newName := aitableStringFlag(cmd, "new-name"); newName != "" {
params["newViewName"] = newName
}
return runAitableHelperTool(cmd, runner, "duplicate_view", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseTableViewFlags(cmd)
cmd.Flags().String("new-name", "", i18n.T("新视图名称"))
return cmd
}
func requiredAitableViewParams(cmd *cobra.Command) (map[string]any, error) {
baseID, tableID, viewID, err := requiredAitableBaseTableView(cmd)
if err != nil {
return nil, err
}
return map[string]any{
"baseId": baseID,
"tableId": tableID,
"viewId": viewID,
}, nil
}
func parseAitableJSONValue(raw, flagName string) (any, error) {
var value any
if err := json.Unmarshal([]byte(raw), &value); err != nil {
return nil, apperrors.NewValidation(fmt.Sprintf("--%s JSON parse failed: %v", flagName, err))
}
return value, nil
}
func aitableMergeJSONObjectFlag(cmd *cobra.Command, typed map[string]any) (map[string]any, error) {
block := map[string]any{}
if raw := aitableStringFlag(cmd, "json"); raw != "" {
parsed, err := parseAitableJSONObject(raw, "json")
if err != nil {
return nil, err
}
for k, v := range parsed {
block[k] = v
}
}
for k, v := range typed {
block[k] = v
}
if len(block) == 0 {
return nil, apperrors.NewValidation("must specify --json or at least one typed flag")
}
return block, nil
}
func aitableCollectStringFlag(cmd *cobra.Command, flagName, key string, dst map[string]any) {
if value := aitableStringFlag(cmd, flagName); value != "" {
dst[key] = value
}
}
func aitableCollectBoolFlag(cmd *cobra.Command, flagName, key string, dst map[string]any) {
if cmd.Flags().Changed(flagName) {
value, _ := cmd.Flags().GetBool(flagName)
dst[key] = value
}
}
var aitableKnownViewConfigKeys = map[string]bool{
"visibleFieldIds": true,
"filter": true,
"sort": true,
"group": true,
"fieldWidths": true,
"aggregate": true,
"kanbanCard": true,
"ganttTimebar": true,
"galleryCard": true,
}
var aitableRoutedViewConfigKeys = map[string]string{
"flags": "dws aitable view lock [--off]",
"frozenColCount": "dws aitable view update frozen-cols --count N",
"cellHeight": "dws aitable view update row-height --cell-height N",
"rowHeightLevel": "dws aitable view update row-height --cell-height N",
"conditionalFormats": "dws aitable view update fill-color-rule --json '[...]'",
}
func normalizeAitableViewConfigBlock(cmd *cobra.Command, cfg map[string]any) error {
var routed []string
var unknown []string
for key := range cfg {
if aitableKnownViewConfigKeys[key] {
continue
}
if _, ok := aitableRoutedViewConfigKeys[key]; ok {
routed = append(routed, key)
continue
}
unknown = append(unknown, key)
}
if len(routed) > 0 {
fmt.Fprintln(cmd.ErrOrStderr(), "以下 key 不能通过 view update --config 修改,请改用对应子命令:")
for _, key := range routed {
fmt.Fprintf(cmd.ErrOrStderr(), "- %s -> %s\n", key, aitableRoutedViewConfigKeys[key])
}
}
if len(unknown) > 0 {
fmt.Fprintf(cmd.ErrOrStderr(), "warning: config 中包含可能不被支持的 key: %v\n", unknown)
}
if value, ok := cfg["filter"]; ok && value != nil {
switch typed := value.(type) {
case []any:
for i, item := range typed {
typed[i] = normalizeAitableFilters(item)
}
cfg["filter"] = typed
case map[string]any:
cfg["filter"] = []any{normalizeAitableFilters(typed)}
default:
return apperrors.NewValidation(fmt.Sprintf("invalid config.filter: must be a JSON array or object, got %T", value))
}
}
for _, key := range []string{"sort", "group"} {
value, ok := cfg[key]
if !ok || value == nil {
continue
}
switch value.(type) {
case []any:
case map[string]any:
cfg[key] = []any{value}
default:
return apperrors.NewValidation(fmt.Sprintf("invalid config.%s: must be a JSON array, got %T", key, value))
}
}
return nil
}
func aitableViewFromResult(result executor.Result, viewID string) (map[string]any, string, error) {
content := aitableResultContent(result.Response)
data := content
if nested, ok := content["data"].(map[string]any); ok {
data = nested
}
views := aitableAnySlice(data["views"])
if len(views) == 0 {
views = aitableAnySlice(content["views"])
}
for _, item := range views {
view, ok := item.(map[string]any)
if !ok {
continue
}
if viewID == "" || aitableMapString(view, "viewId") == viewID {
return view, aitableMapString(view, "viewType"), nil
}
}
return nil, "", apperrors.NewValidation(fmt.Sprintf("view %q not found in get_views response", viewID))
}
func aitableResultContent(resp map[string]any) map[string]any {
if resp == nil {
return map[string]any{}
}
if content, ok := resp["content"].(map[string]any); ok {
return content
}
return resp
}
func aitableProjectionEnvelope(result executor.Result, data any) map[string]any {
content := aitableResultContent(result.Response)
out := make(map[string]any, len(content)+2)
for k, v := range content {
out[k] = v
}
if _, ok := out["status"]; !ok {
out["status"] = "success"
}
if _, ok := out["success"]; !ok {
out["success"] = true
}
out["data"] = data
return out
}
func aitableDispatchCardKey(viewType string) (string, error) {
switch strings.ToLower(strings.TrimSpace(viewType)) {
case "kanban":
return "kanbanCard", nil
case "gallery":
return "galleryCard", nil
default:
return "", apperrors.NewValidation(fmt.Sprintf("viewType %q does not support card; expected Kanban or Gallery", viewType))
}
}
func aitableRequireViewType(viewType, attr string, allowed []string) error {
if len(allowed) == 0 {
return nil
}
for _, want := range allowed {
if strings.EqualFold(viewType, want) {
return nil
}
}
return apperrors.NewValidation(fmt.Sprintf("viewType %q does not support %s; expected %s", viewType, attr, strings.Join(allowed, "/")))
}
func aitableWalkViewPath(view map[string]any, path string) any {
if path == "" {
return nil
}
var cur any = view
for _, part := range strings.Split(path, ".") {
m, ok := cur.(map[string]any)
if !ok {
return nil
}
cur = m[part]
if cur == nil {
return nil
}
}
return cur
}
func aitableDefaultViewProjection(use string) any {
switch use {
case "filter":
return map[string]any{"operator": "and", "operands": []any{}}
case "sort", "group", "visible-fields", "fill-color-rule":
return []any{}
default:
return map[string]any{}
}
}
func aitableAnySlice(value any) []any {
switch typed := value.(type) {
case []any:
return typed
case []map[string]any:
out := make([]any, 0, len(typed))
for _, item := range typed {
out = append(out, item)
}
return out
default:
return nil
}
}
func aitableMapString(m map[string]any, key string) string {
if value, ok := m[key].(string); ok {
return value
}
return ""
}
func newAitableWorkflowCommand(runner executor.Runner) *cobra.Command {
group := newAitableExtraGroup("workflow", i18n.T("自动化工作流管理"))
group.AddCommand(
newAitableWorkflowEnableCommand(runner),
newAitableWorkflowDisableCommand(runner),
newAitableWorkflowGetCommand(runner),
newAitableWorkflowListCommand(runner),
)
return group
}
func newAitableWorkflowEnableCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "enable",
Short: i18n.T("启用指定工作流"),
Example: " dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
workflowID, err := aitableRequiredFlag(cmd, "workflow-id")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "enable_workflow", map[string]any{
"baseId": baseID,
"workflowId": workflowID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("workflow-id", "", i18n.T("工作流 ID (必填)"))
return cmd
}
func newAitableWorkflowDisableCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "disable",
Short: i18n.T("禁用指定工作流"),
Example: " dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
workflowID, err := aitableRequiredFlag(cmd, "workflow-id")
if err != nil {
return err
}
if !confirmDeletePrompt(cmd, i18n.T("工作流"), workflowID) {
return nil
}
return runAitableHelperTool(cmd, runner, "disable_workflow", map[string]any{
"baseId": baseID,
"workflowId": workflowID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("workflow-id", "", i18n.T("工作流 ID (必填)"))
return cmd
}
func newAitableWorkflowGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: i18n.T("获取单个工作流详情"),
Example: " dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
workflowID, err := aitableRequiredFlag(cmd, "workflow-id")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "get_workflow", map[string]any{
"baseId": baseID,
"workflowId": workflowID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("workflow-id", "", i18n.T("工作流 ID (必填)"))
return cmd
}
func newAitableWorkflowListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: i18n.T("列出 Base 下的工作流"),
Example: " dws aitable workflow list --base-id BASE_ID --limit 50 --offset 100",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
params := map[string]any{"baseId": baseID}
if cmd.Flags().Changed("limit") {
limit, _ := cmd.Flags().GetInt("limit")
if limit < 1 || limit > 100 {
return apperrors.NewValidation(fmt.Sprintf("--limit must be in [1, 100], got %d", limit))
}
params["limit"] = limit
}
if cmd.Flags().Changed("offset") {
offset, _ := cmd.Flags().GetInt("offset")
if offset < 0 {
return apperrors.NewValidation(fmt.Sprintf("--offset must be >= 0, got %d", offset))
}
params["offset"] = offset
}
return runAitableHelperTool(cmd, runner, "list_workflows", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().Int("limit", 0, i18n.T("分页大小 [1, 100],不传使用服务端默认值"))
cmd.Flags().Int("offset", 0, i18n.T("分页偏移量,>= 0"))
return cmd
}
func newAitableAdvpermCommand(runner executor.Runner) *cobra.Command {
group := newAitableExtraGroup("advperm", i18n.T("高级权限管理"))
group.AddCommand(
newAitableAdvpermEnableCommand(runner),
newAitableAdvpermDisableCommand(runner),
newAitableAdvpermRoleListCommand(runner),
newAitableAdvpermRoleGetCommand(runner),
newAitableAdvpermRoleCreateCommand(runner),
newAitableAdvpermRoleUpdateCommand(runner),
newAitableAdvpermRoleDeleteCommand(runner),
)
return group
}
func newAitableAdvpermEnableCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "enable",
Short: i18n.T("开启高级权限总开关"),
Example: " dws aitable advperm enable --base-id BASE_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "set_advanced_permission", map[string]any{
"baseId": baseID,
"enabled": true,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
return cmd
}
func newAitableAdvpermDisableCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "disable",
Short: i18n.T("关闭高级权限总开关"),
Example: " dws aitable advperm disable --base-id BASE_ID --yes",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
if !confirmDeletePrompt(cmd, i18n.T("高级权限"), baseID) {
return nil
}
return runAitableHelperTool(cmd, runner, "set_advanced_permission", map[string]any{
"baseId": baseID,
"enabled": false,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
return cmd
}
func newAitableAdvpermRoleListCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "role-list",
Short: i18n.T("列出 Base 下所有角色"),
Example: " dws aitable advperm role-list --base-id BASE_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "list_roles", map[string]any{"baseId": baseID})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
return cmd
}
func newAitableAdvpermRoleGetCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "role-get",
Short: i18n.T("获取单个角色完整配置"),
Example: " dws aitable advperm role-get --base-id BASE_ID --role-id ROLE_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, roleID, err := requiredAitableRoleParams(cmd)
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "get_role", map[string]any{
"baseId": baseID,
"roleId": roleID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("role-id", "", i18n.T("角色 ID (必填)"))
return cmd
}
func newAitableAdvpermRoleCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "role-create",
Short: i18n.T("创建自定义角色"),
Example: " dws aitable advperm role-create --base-id BASE_ID --name 市场可读 --sub-roles '[{\"targetId\":\"tbl\",\"targetType\":\"sheet\",\"authLevel\":\"read\"}]'",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
name, err := aitableRequiredFlag(cmd, "name")
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"name": name,
}
if err := appendAitableRoleOptionalFlags(cmd, params); err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "create_role", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
addAitableRoleMutationFlags(cmd, true)
return cmd
}
func newAitableAdvpermRoleUpdateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "role-update",
Short: i18n.T("增量更新自定义角色配置"),
Example: " dws aitable advperm role-update --base-id BASE_ID --role-id ROLE_ID --name 新名字",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, roleID, err := requiredAitableRoleParams(cmd)
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"roleId": roleID,
}
if name := aitableStringFlag(cmd, "name"); name != "" {
params["name"] = name
}
if err := appendAitableRoleOptionalFlags(cmd, params); err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "patch_role", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("role-id", "", i18n.T("角色 ID (必填)"))
addAitableRoleMutationFlags(cmd, false)
return cmd
}
func newAitableAdvpermRoleDeleteCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "role-delete",
Short: i18n.T("删除自定义角色"),
Example: " dws aitable advperm role-delete --base-id BASE_ID --role-id ROLE_ID --yes",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, roleID, err := requiredAitableRoleParams(cmd)
if err != nil {
return err
}
if !confirmDeletePrompt(cmd, i18n.T("角色"), roleID) {
return nil
}
return runAitableHelperTool(cmd, runner, "delete_role", map[string]any{
"baseId": baseID,
"roleId": roleID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("role-id", "", i18n.T("角色 ID (必填)"))
return cmd
}
func requiredAitableRoleParams(cmd *cobra.Command) (string, string, error) {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return "", "", err
}
roleID, err := aitableRequiredFlag(cmd, "role-id")
if err != nil {
return "", "", err
}
return baseID, roleID, nil
}
func addAitableRoleMutationFlags(cmd *cobra.Command, nameRequired bool) {
label := i18n.T("角色名称")
if nameRequired {
label = i18n.T("角色名称 (必填)")
}
cmd.Flags().String("name", "", label)
cmd.Flags().String("role-type", "", i18n.T("角色类型"))
cmd.Flags().String("flow-type", "", i18n.T("流程类型"))
cmd.Flags().String("sub-roles", "", i18n.T("子角色配置 JSON 数组"))
}
func appendAitableRoleOptionalFlags(cmd *cobra.Command, params map[string]any) error {
if roleType := aitableStringFlag(cmd, "role-type"); roleType != "" {
params["roleType"] = roleType
}
if flowType := aitableStringFlag(cmd, "flow-type"); flowType != "" {
params["flowType"] = flowType
}
if subRolesRaw := aitableStringFlag(cmd, "sub-roles"); subRolesRaw != "" {
subRoles, err := parseAitableJSONArray(subRolesRaw, "sub-roles")
if err != nil {
return err
}
params["subRoles"] = subRoles
}
return nil
}
func newAitableSectionCommand(runner executor.Runner) *cobra.Command {
group := newAitableExtraGroup("section", i18n.T("文件夹与节点管理"))
group.AddCommand(
newAitableSectionCreateCommand(runner),
newAitableSectionRenameCommand(runner),
newAitableSectionDeleteCommand(runner),
newAitableSectionReorderCommand(runner),
newAitableSectionListEmptyCommand(runner),
newAitableSectionListNodesCommand(runner),
newAitableSectionMoveNodeCommand(runner),
)
return group
}
func newAitableSectionCreateCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: i18n.T("创建文件夹"),
Example: " dws aitable section create --base-id BASE_ID --name 我的文件夹 --parent-section-id SECTION_ID --index 0",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
name, err := aitableRequiredFlag(cmd, "name")
if err != nil {
return err
}
params := map[string]any{
"baseId": baseID,
"name": name,
}
if cmd.Flags().Changed("parent-section-id") {
parentSectionID, _ := cmd.Flags().GetString("parent-section-id")
params["parentSectionId"] = parentSectionID
}
if index, _ := cmd.Flags().GetInt("index"); index >= 0 {
params["index"] = index
}
return runAitableHelperTool(cmd, runner, "create_section", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("name", "", i18n.T("文件夹名称 (必填)"))
cmd.Flags().String("parent-section-id", "", i18n.T("父文件夹 ID;空字符串表示根目录"))
cmd.Flags().Int("index", -1, i18n.T("目标位置,0-based;不传则追加"))
return cmd
}
func newAitableSectionRenameCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "rename",
Short: i18n.T("重命名文件夹"),
Example: " dws aitable section rename --base-id BASE_ID --section-id SECTION_ID --new-name 新名称",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, sectionID, err := requiredAitableSectionParams(cmd)
if err != nil {
return err
}
newName, err := aitableRequiredFlag(cmd, "new-name")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "rename_section", map[string]any{
"baseId": baseID,
"sectionId": sectionID,
"newName": newName,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("section-id", "", i18n.T("文件夹 ID (必填)"))
cmd.Flags().String("new-name", "", i18n.T("新文件夹名称 (必填)"))
return cmd
}
func newAitableSectionDeleteCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "delete",
Short: i18n.T("删除文件夹"),
Example: " dws aitable section delete --base-id BASE_ID --section-id SECTION_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, sectionID, err := requiredAitableSectionParams(cmd)
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "delete_section", map[string]any{
"baseId": baseID,
"sectionId": sectionID,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("section-id", "", i18n.T("文件夹 ID (必填)"))
return cmd
}
func newAitableSectionReorderCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "reorder",
Short: i18n.T("调整文件夹顺序"),
Example: " dws aitable section reorder --base-id BASE_ID --section-id SECTION_ID --target-index 0",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, sectionID, err := requiredAitableSectionParams(cmd)
if err != nil {
return err
}
if !cmd.Flags().Changed("target-index") {
return apperrors.NewValidation("--target-index is required")
}
targetIndex, _ := cmd.Flags().GetInt("target-index")
if targetIndex < 0 {
return apperrors.NewValidation(fmt.Sprintf("--target-index must be >= 0, got %d", targetIndex))
}
return runAitableHelperTool(cmd, runner, "reorder_section", map[string]any{
"baseId": baseID,
"sectionId": sectionID,
"targetIndex": targetIndex,
})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("section-id", "", i18n.T("文件夹 ID (必填)"))
cmd.Flags().Int("target-index", -1, i18n.T("目标位置,0-based (必填)"))
return cmd
}
func newAitableSectionListEmptyCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list-empty",
Short: i18n.T("列出空文件夹"),
Example: " dws aitable section list-empty --base-id BASE_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "list_empty_sections", map[string]any{"baseId": baseID})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
return cmd
}
func newAitableSectionListNodesCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "list-nodes",
Short: i18n.T("列出全部节点"),
Example: " dws aitable section list-nodes --base-id BASE_ID",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
return runAitableHelperTool(cmd, runner, "list_nsheet_nodes", map[string]any{"baseId": baseID})
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
return cmd
}
func newAitableSectionMoveNodeCommand(runner executor.Runner) *cobra.Command {
cmd := &cobra.Command{
Use: "move-node",
Short: i18n.T("移动节点"),
Example: " dws aitable section move-node --base-id BASE_ID --node-id NODE_ID --new-parent-section-id SECTION_ID --target-index 0",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return err
}
nodeID, err := aitableRequiredFlag(cmd, "node-id")
if err != nil {
return err
}
if !cmd.Flags().Changed("new-parent-section-id") {
return apperrors.NewValidation("--new-parent-section-id is required; pass empty string to move to base root")
}
newParentSectionID, _ := cmd.Flags().GetString("new-parent-section-id")
params := map[string]any{
"baseId": baseID,
"nodeId": nodeID,
"newParentSectionId": newParentSectionID,
}
if targetIndex, _ := cmd.Flags().GetInt("target-index"); targetIndex >= 0 {
params["targetIndex"] = targetIndex
}
return runAitableHelperTool(cmd, runner, "move_nsheet_node", params)
},
}
preferLegacyLeaf(cmd)
addAitableBaseFlag(cmd)
cmd.Flags().String("node-id", "", i18n.T("要移动的节点 ID (必填)"))
cmd.Flags().String("new-parent-section-id", "", i18n.T("目标父文件夹 ID;空字符串表示根目录 (必填)"))
cmd.Flags().Int("target-index", -1, i18n.T("目标位置,0-based"))
return cmd
}
func requiredAitableSectionParams(cmd *cobra.Command) (string, string, error) {
baseID, err := aitableRequiredFlagOrFallback(cmd, "base-id", "base")
if err != nil {
return "", "", err
}
sectionID, err := aitableRequiredFlag(cmd, "section-id")
if err != nil {
return "", "", err
}
return baseID, sectionID, nil
}
func addAitableBaseFlag(cmd *cobra.Command) {
cmd.Flags().String("base-id", "", i18n.T("Base ID (必填)"))
addAitableHiddenStringFlag(cmd, "base", "--base-id 的兼容别名")
}
func newAitableExtraGroup(use, short string) *cobra.Command {
return &cobra.Command{
Use: use,
Short: short,
Args: cobra.NoArgs,
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
}