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.
1827 lines
62 KiB
Go
1827 lines
62 KiB
Go
// 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()
|
||
},
|
||
}
|
||
}
|