Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/app/skill_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ multi 模式支持按产品挑选:
cmd.Flags().String("mode", "", "skill 模式:mono | multi(不指定则交互询问)")
cmd.Flags().String("target", "all", "目标 Agent:all | "+supportedTargets())
cmd.Flags().String("source", "", "skill 源目录(默认自动查找二进制旁边或当前目录)")
cmd.Flags().Bool("yes", false, "跳过所有确认提示")
cmd.Flags().BoolP("yes", "y", false, "跳过所有确认提示")
cmd.Flags().StringSliceP("skill", "s", nil, "multi 模式:仅安装指定子 skill(可重复,接受短名 aitable 或全名 dingtalk-aitable)")
cmd.Flags().StringSliceP("exclude", "x", nil, "multi 模式:从全装中剔除指定子 skill(可重复,与 --skill 互斥)")
return cmd
Expand Down
50 changes: 50 additions & 0 deletions internal/cobracmd/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
package cobracmd

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
)

// ChildByName returns the child command with the given name, or nil.
Expand All @@ -42,6 +45,12 @@ func FlagChanged(cmd *cobra.Command, name string) bool {
}

// NewGroupCommand creates a non-leaf parent command that shows help when invoked.
//
// An action-less group node has no tool of its own; invoking it directly just
// lists its subcommands. For humans that means printed usage text. But when the
// caller explicitly requests JSON output (-f json), usage text breaks the
// "JSON-only" contract that agents/MCP consumers rely on, so we instead return a
// structured validation error naming the available subcommands. See issue #422.
func NewGroupCommand(use, short string) *cobra.Command {
return &cobra.Command{
Use: use,
Expand All @@ -50,11 +59,52 @@ func NewGroupCommand(use, short string) *cobra.Command {
TraverseChildren: true,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
if explicitJSONFormat(cmd) {
subs := visibleSubcommandNames(cmd)
msg := fmt.Sprintf("%q requires a subcommand", cmd.CommandPath())
if len(subs) > 0 {
msg = fmt.Sprintf("%s; available: %s", msg, strings.Join(subs, ", "))
}
return apperrors.NewValidation(msg)
}
return cmd.Help()
},
}
}

// explicitJSONFormat reports whether the user explicitly selected a JSON-family
// output format via -f/--format. It checks Changed so that a bare group
// invocation (relying on the default format) still gets human-readable help.
func explicitJSONFormat(cmd *cobra.Command) bool {
pf := cmd.Root().PersistentFlags()
if !pf.Changed("format") {
return false
}
f, err := pf.GetString("format")
if err != nil {
return false
}
switch strings.ToLower(strings.TrimSpace(f)) {
case "json", "ndjson":
return true
default:
return false
}
}

// visibleSubcommandNames returns the names of the command's non-hidden,
// non-help subcommands, for surfacing in the action-less JSON error.
func visibleSubcommandNames(cmd *cobra.Command) []string {
var names []string
for _, child := range cmd.Commands() {
if child.Hidden || child.Name() == "help" || !child.IsAvailableCommand() {
continue
}
names = append(names, child.Name())
}
return names
}

// NewHiddenGroupCommand creates a hidden non-leaf parent command.
func NewHiddenGroupCommand(use, short string) *cobra.Command {
cmd := NewGroupCommand(use, short)
Expand Down
6 changes: 3 additions & 3 deletions internal/helpers/aitable_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestAitableExportDataDryRunUsesWukongFormatFlags(t *testing.T) {
"--base-id", "BASE_001",
"--scope", "table",
"--table-id", "TBL_001",
"--format", "excel",
"--export-format", "excel",
"--timeout-ms", "1000",
})

Expand Down Expand Up @@ -234,7 +234,7 @@ func TestAitableExportDataRequiresFormatWhenCreatingTask(t *testing.T) {
})

if err := cmd.Execute(); err == nil {
t.Fatal("Execute() error = nil, want missing --format validation")
t.Fatal("Execute() error = nil, want missing --export-format validation")
}
if runner.last.Tool != "" {
t.Fatalf("tool = %q, want no invocation", runner.last.Tool)
Expand Down Expand Up @@ -304,7 +304,7 @@ func TestAitableExportDataLiveCallsExportDataOnce(t *testing.T) {
"--base-id", "BASE_001",
"--scope", "table",
"--table-id", "TBL_001",
"--format", "excel",
"--export-format", "excel",
"--timeout-ms", "1000",
})

Expand Down
18 changes: 9 additions & 9 deletions internal/helpers/aitable_export_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ func newAitableExportDataCommand(runner executor.Runner) *cobra.Command {
Use: "data",
Short: i18n.T("导出数据"),
Long: i18n.T(`导出 AI 表格数据的统一入口。
不传 --task-id 时,根据 --scope / --format 创建新的导出任务,并同步等待结果;
不传 --task-id 时,根据 --scope / --export-format 创建新的导出任务,并同步等待结果;
若在等待窗口内完成,则直接返回 downloadUrl 和 fileName。
传入 --task-id 时,继续等待该任务,不会重新创建。

scope 可选值:all(整个 Base)、table(指定数据表)、view(指定视图)。
format 可选值:excel、attachment、excel_and_attachment、excel_with_inline_images。`),
Example: ` dws aitable export data --base-id BASE_ID --scope all --format excel
dws aitable export data --base-id BASE_ID --scope table --table-id TABLE_ID --format excel
dws aitable export data --base-id BASE_ID --scope view --table-id TABLE_ID --view-id VIEW_ID --format excel
export-format 可选值:excel、attachment、excel_and_attachment、excel_with_inline_images。`),
Example: ` dws aitable export data --base-id BASE_ID --scope all --export-format excel
dws aitable export data --base-id BASE_ID --scope table --table-id TABLE_ID --export-format excel
dws aitable export data --base-id BASE_ID --scope view --table-id TABLE_ID --view-id VIEW_ID --export-format excel
dws aitable export data --base-id BASE_ID --task-id TASK_ID
# 查询 baseId: dws aitable base list`,
Args: cobra.NoArgs,
Expand All @@ -64,8 +64,8 @@ format 可选值:excel、attachment、excel_and_attachment、excel_with_inline
cmd.Flags().String("base-id", "", i18n.T("Base ID (必填)"))
addAitableHiddenStringFlag(cmd, "base", "--base-id 的兼容别名")
cmd.Flags().String("scope", "", i18n.T("导出范围:all(整个 Base)、table(指定数据表)、view(指定视图)"))
cmd.Flags().String("format", "", i18n.T("导出格式:excel、attachment、excel_and_attachment、excel_with_inline_images"))
cmd.Flags().String("task-id", "", i18n.T("已有导出任务 ID,传入后继续等待(忽略 scope/format/table-id/view-id)"))
cmd.Flags().String("export-format", "", i18n.T("导出格式:excel、attachment、excel_and_attachment、excel_with_inline_images"))
cmd.Flags().String("task-id", "", i18n.T("已有导出任务 ID,传入后继续等待(忽略 scope/export-format/table-id/view-id)"))
cmd.Flags().String("table-id", "", i18n.T("Table ID,scope=table 或 scope=view 时必填"))
cmd.Flags().String("view-id", "", i18n.T("View ID,scope=view 时必填"))
cmd.Flags().Int("timeout-ms", 0, i18n.T("单次等待超时(毫秒),默认 30000,最大 30000"))
Expand All @@ -79,7 +79,7 @@ func runAitableExportData(cmd *cobra.Command, runner executor.Runner) error {
}
taskID, _ := cmd.Flags().GetString("task-id")
scope, _ := cmd.Flags().GetString("scope")
format, _ := cmd.Flags().GetString("format")
format, _ := cmd.Flags().GetString("export-format")
tableID, _ := cmd.Flags().GetString("table-id")
viewID, _ := cmd.Flags().GetString("view-id")
timeoutMS, _ := cmd.Flags().GetInt("timeout-ms")
Expand All @@ -93,7 +93,7 @@ func runAitableExportData(cmd *cobra.Command, runner executor.Runner) error {
return apperrors.NewValidation("--scope is required")
}
if strings.TrimSpace(format) == "" {
return apperrors.NewValidation("--format is required")
return apperrors.NewValidation("--export-format is required")
}
params["scope"] = scope
params["format"] = format
Expand Down
2 changes: 1 addition & 1 deletion internal/helpers/todo.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ func newTodoTaskDeleteCommand(runner executor.Runner) *cobra.Command {
preferLegacyLeaf(cmd)

cmd.Flags().String("task-id", "", i18n.T("待办任务 ID (必填)"))
cmd.Flags().Bool("yes", false, i18n.T("跳过确认直接删除"))
cmd.Flags().BoolP("yes", "y", false, i18n.T("跳过确认直接删除"))
return cmd
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
|---------|----------|----------|
| 查看几条数据 | `dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID>` | 不要默认 `--all` |
| 全量拉取/统计 | `dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --all` | 不要手动循环 cursor |
| 全量导出 | `dws aitable export data --base-id <BASE_ID> --scope all --format excel` | 不要 `--all` 拉全量再写文件 |
| 全量导出 | `dws aitable export data --base-id <BASE_ID> --scope all --export-format excel` | 不要 `--all` 拉全量再写文件 |
| 文件级导入 | `dws aitable import upload --base-id <BASE_ID> --file-name data.xlsx --file-size <字节数>` + `dws aitable import data --import-id <ID>` | 不要手动解析 xlsx 再逐条写入 |
| 批量写入多条不同数据 | `dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{"cells":{"<FIELD_ID>":"值"}}]'` | 不要一次超过 100 条 |
| 批量给多条记录写同一组值 | `dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{"recordId":"rec1","cells":{"<FIELD_ID>":"值"}},{"recordId":"rec2","cells":{"<FIELD_ID>":"值"}}]'` | 不要使用隐藏兼容命令 |
Expand All @@ -49,11 +49,11 @@

## 5. 导入导出与异步任务

- `export data` `--format` 是导出格式,不要在此命令上追加全局 `--format json`
- `export data` 的导出格式用 `--export-format`(如 `--export-format excel`);`--format` 在这里是全局输出格式,两者不要混用
- 创建导出任务:
```bash
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> \
--format excel --timeout-ms 1000
--export-format excel --timeout-ms 1000
```
- 续等已有导出任务:
```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

`export data` 为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。

> ⚠️ **`export data` `--format` 是导出格式**:需要导出 xlsx/附件时写 `--format excel` / `excel_and_attachment`。不要在这个命令上追加全局 `--format json`
> ⚠️ **`export data` 的导出格式用 `--export-format`**:需要导出 xlsx/附件时写 `--export-format excel` / `excel_and_attachment`。这里的 `--format` 是全局输出格式(json/table…),两者不要混用

```bash
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --export-format excel --timeout-ms 1000

# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
|---------|----------|----------|
| 查看几条数据 | `dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID>` | 不要默认 `--all` |
| 全量拉取/统计 | `dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --all` | 不要手动循环 cursor |
| 全量导出 | `dws aitable export data --base-id <BASE_ID> --scope all --format excel` | 不要 `--all` 拉全量再写文件 |
| 全量导出 | `dws aitable export data --base-id <BASE_ID> --scope all --export-format excel` | 不要 `--all` 拉全量再写文件 |
| 文件级导入 | `dws aitable import upload --base-id <BASE_ID> --file-name data.xlsx --file-size <字节数>` + `dws aitable import data --import-id <ID>` | 不要手动解析 xlsx 再逐条写入 |
| 批量写入多条不同数据 | `dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{"cells":{"<FIELD_ID>":"值"}}]'` | 不要一次超过 100 条 |
| 批量给多条记录写同一组值 | `dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> --records '[{"recordId":"rec1","cells":{"<FIELD_ID>":"值"}},{"recordId":"rec2","cells":{"<FIELD_ID>":"值"}}]'` | 不要使用隐藏兼容命令 |
Expand All @@ -49,11 +49,11 @@

## 5. 导入导出与异步任务

- `export data` `--format` 是导出格式,不要在此命令上追加全局 `--format json`
- `export data` 的导出格式用 `--export-format`(如 `--export-format excel`);`--format` 在这里是全局输出格式,两者不要混用
- 创建导出任务:
```bash
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> \
--format excel --timeout-ms 1000
--export-format excel --timeout-ms 1000
```
- 续等已有导出任务:
```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

`export data` 为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。

> ⚠️ **`export data` `--format` 是导出格式**:需要导出 xlsx/附件时写 `--format excel` / `excel_and_attachment`。不要在这个命令上追加全局 `--format json`
> ⚠️ **`export data` 的导出格式用 `--export-format`**:需要导出 xlsx/附件时写 `--export-format excel` / `excel_and_attachment`。这里的 `--format` 是全局输出格式(json/table…),两者不要混用

```bash
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --export-format excel --timeout-ms 1000

# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
Expand Down
Loading