将 test/pre-mcp-discovery 分支上多位同学的能力改动整体合入 main, 服务发现端点保持生产 https://mcp.dingtalk.com,未携带分支上的预发改动。 主要内容: - 修雨域(chat / report / todo / contact / mail)对齐 wukong 基线 (#355) - DWS 内容生产能力增强 (#387)、文档导出与 OA 对齐 (#362 #388 #390) - envelope 注册 cli.Aliases 为 cobra aliases、命令结构与 JSON 解析优化 - skills 多包同步 wukong 对齐内容(attendance/calendar/minutes/oa/sheet 等) - product/oa 进一步对齐 wukong 排除项:未携带 566b4e1(test: point service discovery to pre-mcp.dingtalk.com); 以下 4 个常量保持生产:skill_command.go / auth/endpoints.go / cli/loader.go / market/registry.go。 Co-authored-by: john <32427341+wqyenjoy@users.noreply.github.com> Co-authored-by: wxianfeng <wang.fl1429@gmail.com>
645 lines
24 KiB
Go
645 lines
24 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"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cobracmd"
|
||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
func init() {
|
||
RegisterPublic(func() Handler {
|
||
return reportHandler{}
|
||
})
|
||
}
|
||
|
||
type reportHandler struct{}
|
||
|
||
func (reportHandler) Name() string {
|
||
return "report"
|
||
}
|
||
|
||
func (reportHandler) Command(runner executor.Runner) *cobra.Command {
|
||
root := &cobra.Command{
|
||
Use: "report",
|
||
Aliases: []string{"log"},
|
||
Short: "日志 / 模版 / 统计",
|
||
Long: `钉钉日志:模版、创建、详情、列表、统计。
|
||
|
||
子命令:
|
||
template 日志模版(list / detail)
|
||
create 创建日志
|
||
detail 获取日志详情
|
||
list 查询收到的日志列表
|
||
stats 获取日志统计数据
|
||
sent 查询已发送的日志列表`,
|
||
Args: cobra.NoArgs,
|
||
TraverseChildren: true,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
|
||
template := &cobra.Command{
|
||
Use: "template",
|
||
Short: "日志模版",
|
||
Args: cobra.NoArgs,
|
||
TraverseChildren: true,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
template.AddCommand(
|
||
newReportTemplateListCommand(runner),
|
||
newReportTemplateDetailCommand(runner),
|
||
)
|
||
|
||
root.AddCommand(
|
||
template,
|
||
newReportCreateCommand(runner),
|
||
newReportDetailCommand(runner),
|
||
newReportListCommand(runner),
|
||
newReportStatsCommand(runner),
|
||
newReportSentCommand(runner),
|
||
newReportCreatedCommand(runner),
|
||
)
|
||
return root
|
||
}
|
||
|
||
// ── flexTimeLayouts: supported date formats, most specific first ──
|
||
|
||
var flexTimeLayouts = []string{
|
||
time.RFC3339, // 2006-01-02T15:04:05+08:00
|
||
"2006-01-02T15:04:05Z", // UTC Z suffix
|
||
"2006-01-02T15:04:05-07:00", // with offset but no colon
|
||
"2006-01-02T15:04:05", // no timezone
|
||
"2006-01-02 15:04:05", // space-separated
|
||
"2006-01-02T15:04", // no seconds
|
||
"2006-01-02 15:04", // no seconds, space
|
||
"2006-01-02", // date only
|
||
"2006/01/02 15:04:05", // slash + time
|
||
"2006/01/02", // slash date
|
||
"20060102", // compact YYYYMMDD
|
||
}
|
||
|
||
// parseFlexTimeToMillis parses a date string using multiple formats and returns Unix milliseconds.
|
||
// Supports 11 formats for maximum compatibility with user input.
|
||
func parseFlexTimeToMillis(flagName, value string) (int64, error) {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return 0, apperrors.NewValidation(fmt.Sprintf(
|
||
"--%s is required\n hint: example: 2026-03-10T14:00:00+08:00", flagName))
|
||
}
|
||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||
if loc == nil {
|
||
loc = time.Local
|
||
}
|
||
for _, layout := range flexTimeLayouts {
|
||
t, err := time.ParseInLocation(layout, value, loc)
|
||
if err == nil {
|
||
return t.UnixMilli(), nil
|
||
}
|
||
}
|
||
return 0, apperrors.NewValidation(fmt.Sprintf(
|
||
"cannot parse time for --%s (input: %q)\n hint: supported formats: 2026-03-23T14:00:00+08:00, 2026-03-23 14:00:00, 2026-03-23",
|
||
flagName, value))
|
||
}
|
||
|
||
// validateTimeRange checks that endMs is strictly after startMs.
|
||
func validateTimeRange(startMs, endMs int64) error {
|
||
if endMs <= startMs {
|
||
return apperrors.NewValidation("--end must be after --start\n hint: swap the values or adjust the time range")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ── template list ──────────────────────────────────────────
|
||
|
||
func newReportTemplateListCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "获取当前用户可用的日志模版列表",
|
||
Example: " dws report template list",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
params := map[string]any{}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_available_report_templates", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_available_report_templates", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── template detail ────────────────────────────────────────
|
||
|
||
func newReportTemplateDetailCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "detail",
|
||
Short: "获取日志模版详情",
|
||
Example: " dws report template detail --name <templateName>",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
name, _ := cmd.Flags().GetString("name")
|
||
if name == "" {
|
||
return apperrors.NewValidation("--name is required")
|
||
}
|
||
params := map[string]any{
|
||
"report_template_name": name,
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_template_details_by_name", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_template_details_by_name", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("name", "", "模版名称 (必填)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── create ─────────────────────────────────────────────────
|
||
|
||
func newReportCreateCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "create",
|
||
Short: "创建日志",
|
||
Long: `按模版创建一条日志。--contents 为 JSON 数组,每项需含 key、sort、content、contentType、type,
|
||
与远程 create_report 一致;可先通过 report template list / template detail 取得 templateId 与控件定义。
|
||
|
||
注意:每个 contents 项的 key 必须精确等于模板的 field_name(中文/英文逐字匹配,不是控件 ID 或别名)。
|
||
key 与 field_name 不一致时钉钉 API 会返回 SYSTEM_ERROR (success=false),CLI 层不会预先拦截。
|
||
请先用 report template detail 查到 report_template_fields[].field_name 后再填。`,
|
||
Example: ` # Step 1:查模板,取 report_template_fields[].field_name 当作 contents[].key
|
||
dws report template detail --name "周报"
|
||
|
||
# Step 2:用上一步拿到的 field_name 作为 key 创建日志
|
||
dws report create --template-id TPL_ID --contents '[{"key":"<field_name>","sort":"0","content":"完成开发","contentType":"markdown","type":"1"}]'
|
||
|
||
# 同时通知到接收人单聊
|
||
dws report create --template-id TPL_ID --contents '[...]' --to-chat --to-user-ids userId1,userId2`,
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
tplID, _ := cmd.Flags().GetString("template-id")
|
||
if tplID == "" {
|
||
return apperrors.NewValidation("--template-id is required")
|
||
}
|
||
contentsJSON, _ := cmd.Flags().GetString("contents")
|
||
if contentsJSON == "" {
|
||
return apperrors.NewValidation("--contents is required")
|
||
}
|
||
var contents []map[string]any
|
||
if err := json.Unmarshal([]byte(contentsJSON), &contents); err != nil {
|
||
return apperrors.NewValidation(fmt.Sprintf("--contents JSON parse failed: %v", err))
|
||
}
|
||
ddFrom, _ := cmd.Flags().GetString("dd-from")
|
||
if ddFrom == "" {
|
||
ddFrom = "dws"
|
||
}
|
||
toChat, _ := cmd.Flags().GetBool("to-chat")
|
||
params := map[string]any{
|
||
"templateId": tplID,
|
||
"contents": contents,
|
||
"ddFrom": ddFrom,
|
||
"toChat": toChat,
|
||
}
|
||
if v, _ := cmd.Flags().GetString("to-user-ids"); v != "" {
|
||
params["toUserIds"] = parseUserIDs(v)
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "create_report", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "create_report", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("template-id", "", "日志模版 ID (必填)")
|
||
cmd.Flags().String("contents", "", "日志内容 JSON 数组 (必填),每项含 key/sort/content/contentType/type;key 必须精确等于模板 field_name (用 report template detail --name <模板名> 查询)")
|
||
cmd.Flags().String("dd-from", "dws", "创建来源标识")
|
||
cmd.Flags().Bool("to-chat", false, "是否发送到日志接收人单聊")
|
||
cmd.Flags().String("to-user-ids", "", "接收人 userId,逗号分隔 (可选)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── detail ─────────────────────────────────────────────────
|
||
|
||
func newReportDetailCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "detail",
|
||
Short: "获取日志详情",
|
||
Example: " dws report detail --report-id <reportId>",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
reportID, _ := cmd.Flags().GetString("report-id")
|
||
if reportID == "" {
|
||
return apperrors.NewValidation("--report-id is required")
|
||
}
|
||
params := map[string]any{
|
||
"report_id": reportID,
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_report_entry_details", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_report_entry_details", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("report-id", "", "日志 ID (必填)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── list (received reports) ────────────────────────────────
|
||
// Key fix: cursor defaults to 0, size defaults to 20, flexible date parsing
|
||
|
||
func newReportListCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "查询当前人收到的日志列表",
|
||
Example: ` dws report list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00"
|
||
dws report list --start "2026-03-10 00:00:00" --end "2026-03-10 23:59:59" --cursor 0 --size 20`,
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: withReportDeprecationWarning("list", "inbox list", reportListRunE(runner)),
|
||
}
|
||
cmd.Flags().String("start", "", "开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填)")
|
||
cmd.Flags().String("end", "", "结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填)")
|
||
cmd.Flags().Int("cursor", 0, "分页游标,首次传 0 (默认 0)")
|
||
cmd.Flags().Int("size", 20, "每页条数,最大 20 (默认 20)")
|
||
cmd.Flags().Int("limit", 0, "--size 的别名")
|
||
_ = cmd.Flags().MarkHidden("limit")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── stats ──────────────────────────────────────────────────
|
||
|
||
func newReportStatsCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "stats",
|
||
Short: "获取日志统计数据",
|
||
Example: " dws report stats --report-id <reportId>",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
reportID, _ := cmd.Flags().GetString("report-id")
|
||
if reportID == "" {
|
||
return apperrors.NewValidation("--report-id is required")
|
||
}
|
||
params := map[string]any{
|
||
"report_id": reportID,
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_report_statistics_by_id", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_report_statistics_by_id", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("report-id", "", "日志 ID (必填)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── sent (my created reports) ──────────────────────────────
|
||
// Key fix: cursor defaults to 0, size defaults to 20, start/end default to last 30 days
|
||
|
||
func newReportSentCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "sent",
|
||
Short: "查询当前人创建的日志列表",
|
||
Example: ` dws report sent
|
||
dws report sent --cursor 0 --size 20
|
||
dws report sent --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00"
|
||
dws report sent --template-name "日报"`,
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: withReportDeprecationWarning("sent", "outbox list", reportSentRunE(runner)),
|
||
}
|
||
cmd.Flags().Int("cursor", 0, "分页游标,首次传 0 (默认 0)")
|
||
cmd.Flags().Int("size", 20, "每页条数,最大 20 (默认 20)")
|
||
cmd.Flags().Int("limit", 0, "--size 的别名")
|
||
_ = cmd.Flags().MarkHidden("limit")
|
||
cmd.Flags().String("start", "", "创建开始时间 ISO-8601 (默认最近 30 天)")
|
||
cmd.Flags().String("end", "", "创建结束时间 ISO-8601 (默认最近 30 天)")
|
||
cmd.Flags().String("modified-start", "", "修改开始时间 ISO-8601 (可选)")
|
||
cmd.Flags().String("modified-end", "", "修改结束时间 ISO-8601 (可选)")
|
||
cmd.Flags().String("template-name", "", "日志模板名称 (可选,不传查全部)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── deprecation wrapper ────────────────────────────────────
|
||
// withReportDeprecationWarning prints a stderr warning that the legacy command
|
||
// is deprecated and then invokes the underlying RunE unchanged. The CLI exit
|
||
// code, stdout payload, and side effects remain identical to the canonical
|
||
// new-path command — only stderr gains the `[deprecated]` notice.
|
||
//
|
||
// Pair with the deprecated leaves wired in `(reportHandler).Command` and the
|
||
// post-merge `AttachReportLegacyInboxAlias` hook so every legacy invocation
|
||
// path carries the same notice.
|
||
func withReportDeprecationWarning(oldPath, newPath string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
|
||
return func(cmd *cobra.Command, args []string) error {
|
||
w := cmd.ErrOrStderr()
|
||
if w == nil {
|
||
w = os.Stderr
|
||
}
|
||
fmt.Fprintf(w, "[deprecated] `dws report %s` 已废弃,将在后续版本中移除。请改用 `dws report %s`。\n", oldPath, newPath)
|
||
return run(cmd, args)
|
||
}
|
||
}
|
||
|
||
// reportSentRunE implements the canonical "list reports I have sent" handler.
|
||
// Extracted so that the legacy `sent` and `created` aliases can share one body
|
||
// and each wrap it independently with the deprecation notice.
|
||
func reportSentRunE(runner executor.Runner) func(*cobra.Command, []string) error {
|
||
return func(cmd *cobra.Command, args []string) error {
|
||
// cursor defaults to 0, size defaults to 20
|
||
cursor, _ := cmd.Flags().GetInt("cursor")
|
||
size, _ := cmd.Flags().GetInt("size")
|
||
if v, _ := cmd.Flags().GetInt("limit"); v > 0 && !cmd.Flags().Changed("size") {
|
||
size = v
|
||
}
|
||
|
||
params := map[string]any{
|
||
"cursor": float64(cursor),
|
||
"size": float64(size),
|
||
}
|
||
|
||
// Default time range: last 30 days
|
||
now := time.Now()
|
||
startDefault := now.AddDate(0, 0, -30).Truncate(24 * time.Hour).Format(time.RFC3339)
|
||
endDefault := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Format(time.RFC3339)
|
||
|
||
startStr, _ := cmd.Flags().GetString("start")
|
||
if startStr == "" {
|
||
startStr = startDefault
|
||
}
|
||
endStr, _ := cmd.Flags().GetString("end")
|
||
if endStr == "" {
|
||
endStr = endDefault
|
||
}
|
||
|
||
startMs, err := parseFlexTimeToMillis("start", startStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
params["startTime"] = float64(startMs)
|
||
|
||
endMs, err := parseFlexTimeToMillis("end", endStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
params["endTime"] = float64(endMs)
|
||
|
||
if err := validateTimeRange(startMs, endMs); err != nil {
|
||
return err
|
||
}
|
||
|
||
// Optional modified time filters
|
||
if v, _ := cmd.Flags().GetString("modified-start"); v != "" {
|
||
ms, err := parseFlexTimeToMillis("modified-start", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
params["modifiedStartTime"] = float64(ms)
|
||
}
|
||
if v, _ := cmd.Flags().GetString("modified-end"); v != "" {
|
||
ms, err := parseFlexTimeToMillis("modified-end", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
params["modifiedEndTime"] = float64(ms)
|
||
}
|
||
|
||
// Optional template name filter
|
||
if v, _ := cmd.Flags().GetString("template-name"); v != "" {
|
||
params["report_template_name"] = v
|
||
}
|
||
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_send_report_list", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_send_report_list", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
}
|
||
}
|
||
|
||
// reportListRunE implements the canonical "list reports I have received" handler.
|
||
// Extracted so the legacy `list` and `inbox` aliases can share one body and
|
||
// each wrap it independently with the deprecation notice. The post-merge
|
||
// inbox-group hook (AttachReportLegacyInboxAlias) also reuses this body.
|
||
func reportListRunE(runner executor.Runner) func(*cobra.Command, []string) error {
|
||
return func(cmd *cobra.Command, args []string) error {
|
||
startStr, _ := cmd.Flags().GetString("start")
|
||
endStr, _ := cmd.Flags().GetString("end")
|
||
|
||
startMs, err := parseFlexTimeToMillis("start", startStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
endMs, err := parseFlexTimeToMillis("end", endStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateTimeRange(startMs, endMs); err != nil {
|
||
return err
|
||
}
|
||
|
||
// cursor defaults to 0, size defaults to 20
|
||
cursor, _ := cmd.Flags().GetInt("cursor")
|
||
size, _ := cmd.Flags().GetInt("size")
|
||
if v, _ := cmd.Flags().GetInt("limit"); v > 0 && !cmd.Flags().Changed("size") {
|
||
size = v
|
||
}
|
||
|
||
params := map[string]any{
|
||
"startTime": float64(startMs),
|
||
"endTime": float64(endMs),
|
||
"cursor": float64(cursor),
|
||
"size": float64(size),
|
||
}
|
||
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_received_report_list", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "report", "get_received_report_list", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
}
|
||
}
|
||
|
||
// ── created (deprecated alias for outbox list) ─────────────
|
||
// newReportCreatedCommand registers `dws report created` as a deprecated
|
||
// alias of `dws report outbox list`. Behaves byte-for-byte like `report sent`
|
||
// on stdout — the only difference is the deprecation notice on stderr names
|
||
// the user's invocation path so logs make it obvious which alias was used.
|
||
func newReportCreatedCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "created",
|
||
Short: "[deprecated] 已废弃,请改用 `dws report outbox list`",
|
||
Example: " dws report created --cursor 0 --size 20\n dws report created --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-10T23:59:59+08:00\"",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: withReportDeprecationWarning("created", "outbox list", reportSentRunE(runner)),
|
||
}
|
||
cmd.Flags().Int("cursor", 0, "分页游标,首次传 0 (默认 0)")
|
||
cmd.Flags().Int("size", 20, "每页条数,最大 20 (默认 20)")
|
||
cmd.Flags().Int("limit", 0, "--size 的别名")
|
||
_ = cmd.Flags().MarkHidden("limit")
|
||
cmd.Flags().String("start", "", "创建开始时间 ISO-8601 (默认最近 30 天)")
|
||
cmd.Flags().String("end", "", "创建结束时间 ISO-8601 (默认最近 30 天)")
|
||
cmd.Flags().String("modified-start", "", "修改开始时间 ISO-8601 (可选)")
|
||
cmd.Flags().String("modified-end", "", "修改结束时间 ISO-8601 (可选)")
|
||
cmd.Flags().String("template-name", "", "日志模板名称 (可选,不传查全部)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// AttachReportLegacyInboxAlias finds the dynamic-built `report inbox` group
|
||
// in the merged command tree and turns it into a dual-role command: when
|
||
// invoked with flags (and no subcommand), it executes the canonical inbox
|
||
// list handler with a deprecation notice on stderr. Sub-command invocation
|
||
// (e.g. `report inbox list ...`) keeps working unchanged.
|
||
//
|
||
// Why a post-merge hook: the envelope publishes `inbox` as a group whose
|
||
// only child is `list`. Hardcoded helpers cannot graft a same-named leaf in
|
||
// (MergeHardcodedLeaves rejects helper-leaf vs envelope-group as a shape
|
||
// mismatch), so we instead enrich the existing group's flags and RunE in
|
||
// place. The `runner` is the same executor that the helper leaves use,
|
||
// keeping behaviour identical to `report list` / `report inbox list`.
|
||
func AttachReportLegacyInboxAlias(commands []*cobra.Command, runner executor.Runner) {
|
||
for _, top := range commands {
|
||
if top == nil || top.Name() != "report" {
|
||
continue
|
||
}
|
||
var inbox *cobra.Command
|
||
for _, child := range top.Commands() {
|
||
if child != nil && child.Name() == "inbox" {
|
||
inbox = child
|
||
break
|
||
}
|
||
}
|
||
if inbox == nil {
|
||
return
|
||
}
|
||
// Inject the same flag surface used by `report list` so the
|
||
// legacy invocation `report inbox --start ... --end ...` parses.
|
||
// Skip flags that already exist (envelope may have registered
|
||
// some on the group) to avoid duplicate-flag panics.
|
||
registerIfAbsent := func(name string, register func()) {
|
||
if inbox.Flags().Lookup(name) == nil {
|
||
register()
|
||
}
|
||
}
|
||
registerIfAbsent("start", func() { inbox.Flags().String("start", "", "开始时间 ISO-8601 (必填)") })
|
||
registerIfAbsent("end", func() { inbox.Flags().String("end", "", "结束时间 ISO-8601 (必填)") })
|
||
registerIfAbsent("cursor", func() { inbox.Flags().Int("cursor", 0, "分页游标,首次传 0 (默认 0)") })
|
||
registerIfAbsent("size", func() { inbox.Flags().Int("size", 20, "每页条数 (默认 20)") })
|
||
registerIfAbsent("limit", func() {
|
||
inbox.Flags().Int("limit", 0, "--size 的别名")
|
||
_ = inbox.Flags().MarkHidden("limit")
|
||
})
|
||
registerIfAbsent("sender-user-ids", func() {
|
||
inbox.Flags().StringSlice("sender-user-ids", nil, "发送人 staffId 列表,逗号分隔 (可选)")
|
||
})
|
||
inbox.RunE = withReportDeprecationWarning("inbox", "inbox list", reportListRunE(runner))
|
||
// Group default Args was cobra.NoArgs which rejects positional args; we
|
||
// keep that constraint — the legacy path is `report inbox --start ...`
|
||
// with flags only, no positional arguments.
|
||
return
|
||
}
|
||
}
|
||
|
||
// ── helpers ────────────────────────────────────────────────
|
||
|
||
func parseUserIDs(s string) []string {
|
||
parts := strings.Split(s, ",")
|
||
out := make([]string, 0, len(parts))
|
||
for _, p := range parts {
|
||
p = strings.TrimSpace(p)
|
||
if p != "" {
|
||
out = append(out, p)
|
||
}
|
||
}
|
||
return out
|
||
}
|