- Move the eventId lookup hint into Long description - Keep example commands free of shell comments to pass example policy gate
3192 lines
139 KiB
Go
3192 lines
139 KiB
Go
package helpers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
// ──────────────────────────────────────────────────────────
|
||
// dws calendar — 日历产品命令组
|
||
// ──────────────────────────────────────────────────────────
|
||
|
||
// calendarInfoHintSubCmd builds a hidden disambiguation subcommand that prints
|
||
// a warning-level "Did you mean" hint to stderr (instead of returning an Error)
|
||
// and exits 0. Scoped to calendar.go on purpose so the shared cmdutil.HintSubCmd
|
||
// used by other products keeps returning errors as before.
|
||
//
|
||
// The `suggestion` argument should be the bare corrected command (no leading
|
||
// "use:" / "hint:" prefix); the helper wraps it with the standard "Did you
|
||
// mean:" line to keep all calendar hints consistent.
|
||
func calendarInfoHintSubCmd(use, suggestion string) *cobra.Command {
|
||
c := hintSubCmd(use, suggestion)
|
||
c.DisableFlagParsing = true
|
||
c.RunE = func(cmd *cobra.Command, args []string) error {
|
||
fmt.Fprintf(os.Stderr, "warning: command %q does not exist\n hint: %s\t %s\n more: %s \n",
|
||
cmd.Parent().CommandPath()+" "+use,
|
||
suggestion,
|
||
"[MUST] use --help to see command detail",
|
||
"'dws calendar --help' to see all available commands")
|
||
return nil
|
||
}
|
||
return c
|
||
}
|
||
|
||
// installUnknownVerbFallback makes `group` emit a consistent warning-style
|
||
// "Did you mean" hint whenever the caller types an unknown subcommand under
|
||
// that group, regardless of whether extra flags follow. This is a blanket
|
||
// safety net that covers every verb we never thought to pre-register via
|
||
// calendarInfoHintSubCmd (e.g. `dws calendar room query --min-duration 30`).
|
||
//
|
||
// Two Cobra knobs make this work together:
|
||
// - FParseErrWhitelist.UnknownFlags=true stops pflag from aborting with
|
||
// "unknown flag: --xxx" before RunE ever runs.
|
||
// - Args=cobra.ArbitraryArgs lets Cobra pass the bad verb through as the
|
||
// first positional arg instead of rejecting it.
|
||
//
|
||
// If the user types a *known* subcommand, Cobra still dispatches to that
|
||
// child's RunE as usual; this fallback only fires when resolution stops at
|
||
// `group` with leftover args.
|
||
func installUnknownVerbFallback(group *cobra.Command) {
|
||
group.FParseErrWhitelist.UnknownFlags = true
|
||
group.Args = cobra.ArbitraryArgs
|
||
|
||
// Override HelpFunc so that `<group> <unknown-verb> --help` also shows
|
||
// the "unknown subcommand" error instead of silently printing help.
|
||
// Cobra intercepts --help before RunE, so without this the fallback
|
||
// would never fire when --help is present.
|
||
origHelp := group.HelpFunc()
|
||
group.SetHelpFunc(func(cmd *cobra.Command, args []string) {
|
||
if cmd == group {
|
||
// HelpFunc receives os.Args[1:] (full arg slice without binary).
|
||
// Strip tokens matching the resolved command path to get actual
|
||
// leftover args that should be checked for unknown verbs.
|
||
depth := len(strings.Fields(cmd.CommandPath())) - 1
|
||
leftover := stripCommandPrefix(args, depth)
|
||
if bad := findUnknownVerb(cmd, leftover); bad != "" {
|
||
printUnknownSubcmdError(cmd, bad)
|
||
return
|
||
}
|
||
origHelp(cmd, args)
|
||
return
|
||
}
|
||
// For non-group commands, render base help then apply the safety
|
||
// annotation. Recursion safety hinges on NOT calling
|
||
// cmd.Root().HelpFunc(): in test trees calendar IS the root, so that
|
||
// would re-enter this wrapper. origHelp was captured before any
|
||
// wrapping and is the plain cobra renderer.
|
||
origHelp(cmd, args)
|
||
cli.RenderSafetyAnnotation(cmd)
|
||
})
|
||
|
||
prev := group.RunE
|
||
group.RunE = func(cmd *cobra.Command, args []string) error {
|
||
// Unknown flags whitelisted by pflag may leak into args. Pick the first
|
||
// non-flag token as the offending verb.
|
||
if bad := findUnknownVerb(cmd, args); bad != "" {
|
||
printUnknownSubcmdError(cmd, bad)
|
||
return nil
|
||
}
|
||
// No unknown verb found. Since FParseErrWhitelist.UnknownFlags silently
|
||
// swallows bad flags, scan the original os.Args for flags unregistered
|
||
// on this command and report them explicitly.
|
||
if flag := findUnknownFlag(cmd); flag != "" {
|
||
fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n", flag)
|
||
fmt.Fprintf(os.Stderr, " hint: Run '%s --help' to see available options\n", cmd.CommandPath())
|
||
return nil
|
||
}
|
||
if prev != nil {
|
||
return prev(cmd, args)
|
||
}
|
||
return cmd.Help()
|
||
}
|
||
}
|
||
|
||
// findUnknownVerb returns the first positional arg that is not a registered
|
||
// subcommand (or alias) of cmd. Returns "" if all args are flags or known.
|
||
func findUnknownVerb(cmd *cobra.Command, args []string) string {
|
||
for _, a := range args {
|
||
if strings.HasPrefix(a, "-") {
|
||
continue
|
||
}
|
||
isKnown := false
|
||
for _, c := range cmd.Commands() {
|
||
if c.Name() == a {
|
||
isKnown = true
|
||
break
|
||
}
|
||
for _, alias := range c.Aliases {
|
||
if alias == a {
|
||
isKnown = true
|
||
break
|
||
}
|
||
}
|
||
if isKnown {
|
||
break
|
||
}
|
||
}
|
||
if !isKnown {
|
||
return a
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// printUnknownSubcmdError prints the standard "unknown subcommand" error to
|
||
// stderr with available commands and a did-you-mean hint.
|
||
func printUnknownSubcmdError(cmd *cobra.Command, bad string) {
|
||
var available []string
|
||
for _, c := range cmd.Commands() {
|
||
if !c.Hidden && c.Name() != "help" {
|
||
available = append(available, c.Name())
|
||
}
|
||
}
|
||
fmt.Fprintf(os.Stderr, "Error: unknown subcommand %q for %q\n", bad, cmd.CommandPath())
|
||
fmt.Fprintf(os.Stderr, " available: %s\n", strings.Join(available, ", "))
|
||
if s := cmd.SuggestionsFor(bad); len(s) > 0 {
|
||
fmt.Fprintf(os.Stderr, " hint: did you mean %q\n", cmd.CommandPath()+" "+s[0])
|
||
} else {
|
||
fmt.Fprintf(os.Stderr, " hint: %s --help\n", cmd.CommandPath())
|
||
}
|
||
}
|
||
|
||
// stripCommandPrefix strips the first `depth` non-flag tokens from args.
|
||
// This is needed because Cobra's HelpFunc receives os.Args[1:] (the full arg
|
||
// slice without the binary name), including the resolved command path tokens.
|
||
// depth should be len(strings.Fields(cmd.CommandPath())) - 1.
|
||
func stripCommandPrefix(args []string, depth int) []string {
|
||
skipped := 0
|
||
for i, a := range args {
|
||
if skipped >= depth {
|
||
return args[i:]
|
||
}
|
||
if !strings.HasPrefix(a, "-") {
|
||
skipped++
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// findUnknownFlag scans os.Args for flags that are not registered on cmd.
|
||
// Returns the first offending flag token (e.g. "--today") or "".
|
||
func findUnknownFlag(cmd *cobra.Command) string {
|
||
depth := len(strings.Fields(cmd.CommandPath())) - 1
|
||
leftover := stripCommandPrefix(os.Args[1:], depth)
|
||
for i := 0; i < len(leftover); i++ {
|
||
a := leftover[i]
|
||
if a == "--" {
|
||
break
|
||
}
|
||
if strings.HasPrefix(a, "--") {
|
||
name := a[2:]
|
||
if eqIdx := strings.Index(name, "="); eqIdx >= 0 {
|
||
name = name[:eqIdx]
|
||
}
|
||
if name == "help" {
|
||
continue
|
||
}
|
||
if cmd.Flags().Lookup(name) == nil {
|
||
return a
|
||
}
|
||
} else if strings.HasPrefix(a, "-") && a != "-" {
|
||
ch := a[1:2]
|
||
if ch == "h" {
|
||
continue
|
||
}
|
||
if cmd.Flags().ShorthandLookup(ch) == nil {
|
||
return a
|
||
}
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func newCalendarCommand() *cobra.Command {
|
||
// Product-level Agent routing Decl (migrated from selection/calendar.json
|
||
// products.calendar). Catalog assembly stamps provenance contract_final.
|
||
contract.RegisterProductDecl(contract.ProductDecl{
|
||
ID: "calendar",
|
||
Selection: contract.ProductSelectionDecl{
|
||
AgentSummary: "管理日历本、日程、参与人、附件、会议室,并查询人员或会议室闲忙状态。",
|
||
UseWhen: []string{
|
||
"需要查询或管理钉钉日历、日程、参与人、会议室和闲忙信息时。",
|
||
},
|
||
AvoidWhen: []string{
|
||
"目标是聊天消息、待办或审批,而不是日历与日程资源时。",
|
||
},
|
||
},
|
||
})
|
||
root := &cobra.Command{
|
||
Use: "calendar",
|
||
Short: "日历日程 / 会议室 / 闲忙",
|
||
Long: `管理钉钉日历:日程、参会人、会议室、闲忙、附件、日历本、访问权限。调用前必须先使用 --help 查看参数结构。
|
||
|
||
命令结构:
|
||
dws calendar event [list|get|create|update|delete|suggest|respond|instances|share-info] 日程管理
|
||
dws calendar attendee [list|add|delete] 参会人管理
|
||
dws calendar room [search|add|delete|list-groups] 会议室管理
|
||
dws calendar busy search 闲忙查询 (可查人、查会议室)
|
||
dws calendar attachment add 日程附件管理
|
||
dws calendar book [list|get|search|update] 日历本管理
|
||
dws calendar acl [list|add|delete] 日历访问权限管理`,
|
||
RunE: groupRunE,
|
||
}
|
||
|
||
// ── event: 日程 ─────────────────────────────────────────────
|
||
|
||
eventCmd := &cobra.Command{Use: "event", Short: "日程管理", RunE: groupRunE}
|
||
|
||
eventListCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "查询日程列表",
|
||
Long: `查询当前用户可访问的日程列表。**注意** 不传 --start/--end 时将查询今天的日程(00:00:00 ~ 23:59:59)。
|
||
**calendar_id**: 不指定calendar_id时,默认查询当前用户主日历下的日程。也可指定calendar_id查询他人共享或者已订阅的日历。calendar_id可通过 dws calendar book list 查询。
|
||
**权限**:查询共享日历下的日程时,至少要有reader权限。
|
||
`,
|
||
Example: ` dws calendar event list
|
||
dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||
dws calendar event list --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --limit 50
|
||
dws calendar event list --calendar-id primary
|
||
dws calendar event list --cursor "<nextCursor从上一次查询结果获取>"
|
||
dws calendar event list --format json`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
toolArgs := map[string]any{}
|
||
var startTime, endTime int64
|
||
var now time.Time
|
||
if v := flagOrFallback(cmd, "start", "time-min", "min-time", "start-time", "startTime", "start_time", "start-date", "startDate"); v != "" {
|
||
var err error
|
||
startTime, err = parseISOTimeToMillis("start", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["startTime"] = startTime
|
||
} else {
|
||
now = time.Now()
|
||
startTime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).UnixMilli()
|
||
toolArgs["startTime"] = startTime
|
||
}
|
||
if v := flagOrFallback(cmd, "end", "time-max", "max-time", "end-time", "endTime", "end_time", "end-date", "endDate"); v != "" {
|
||
var err error
|
||
endTime, err = parseISOTimeToMillis("end", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["endTime"] = endTime
|
||
} else {
|
||
if now.IsZero() {
|
||
now = time.Now()
|
||
}
|
||
endTime = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).UnixMilli()
|
||
toolArgs["endTime"] = endTime
|
||
}
|
||
if err := validateTimeRange(startTime, endTime); err != nil {
|
||
return err
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "cursor", "next-cursor", "nextCursor", "page-token", "pageToken", "next-token"); v != "" {
|
||
toolArgs["cursor"] = v
|
||
}
|
||
if lim, _ := cmd.Flags().GetInt("limit"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("max-results"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("maxResults"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("page-size"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("size"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("count"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
}
|
||
return callSortedCalendarEvents(cmd, "list_calendar_events", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventListCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_calendar_events",
|
||
CanonicalPath: "calendar.list_calendar_events",
|
||
CLIPath: "calendar event list",
|
||
PrimaryCLIPath: "calendar event list",
|
||
},
|
||
Description: "查询当前用户日程列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_calendar_events"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询当前用户日程列表",
|
||
UseWhen: []string{"需要按起止时间检索指定日历本下的日程列表时(未传起止默认查当天)"},
|
||
AvoidWhen: []string{
|
||
"已知 eventId 要详情时改用 dws calendar event get",
|
||
"要创建日程时改用 dws calendar event create",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event list --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T18:00:00+08:00\"",
|
||
"dws calendar event list --calendar-id primary",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "end", Property: "endTime"},
|
||
{Name: "start", Property: "startTime"},
|
||
},
|
||
},
|
||
})
|
||
|
||
eventGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "获取日程详情",
|
||
Example: ` dws calendar event get --id EVENT_ID # 查询 eventId: dws calendar event list
|
||
dws calendar event get --id EVENT_ID --calendar-id primary`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"eventId": eventID}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("get_calendar_detail", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventGetCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "get_calendar_detail",
|
||
CanonicalPath: "calendar.get_calendar_detail",
|
||
CLIPath: "calendar event get",
|
||
PrimaryCLIPath: "calendar event get",
|
||
},
|
||
Description: "查询日程详情",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "get_calendar_detail"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询日程详情",
|
||
UseWhen: []string{"已知 eventId,需要查看日程明细时"},
|
||
AvoidWhen: []string{
|
||
"要按时间查列表时改用 dws calendar event list",
|
||
"要创建/删除时改用 create/delete",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event get --id <EVENT_ID>",
|
||
"dws calendar event get --id <EVENT_ID> --calendar-id primary",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "eventId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
eventCreateCmd := &cobra.Command{
|
||
Use: "create",
|
||
Short: "创建日程",
|
||
Long: `创建日程。
|
||
|
||
周期日程说明:--recurrence-* 不是彼此独立的参数。一旦指定任一 --recurrence-* 标志,就必须一次性提供**完整**的循环规则,
|
||
至少包含 --recurrence-type、--recurrence-interval(>0) 与 --recurrence-range-type,否则命令会被拒绝执行。`,
|
||
Example: ` dws calendar event create --title "Q1 复盘会" \
|
||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||
dws calendar event create --title "周会" \
|
||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||
--attendees userId1,userId2
|
||
dws calendar event create --title "项目评审" \
|
||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||
--rooms roomId1,roomId2 # roomId 必须来自 dws calendar room search 返回
|
||
dws calendar event create --title "每日站会" \
|
||
--start "2026-03-10T09:00:00+08:00" --end "2026-03-10T09:30:00+08:00" \
|
||
--recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 10
|
||
dws calendar event create --title "团队周会" \
|
||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||
--calendar-id <SHARED_CALENDAR_ID> # 在指定日历本(如共享日历)下创建日程`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
title, err := mustFlagOrFallback(cmd, "title", "summary")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
startStr, err := mustFlagOrFallback(cmd, "start", "start-time", "startTime", "start_time", "start-date", "startDate")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
endStr, err := mustFlagOrFallback(cmd, "end", "end-time", "endTime", "end_time", "end-date", "endDate")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"summary": title,
|
||
"startDateTime": startStr,
|
||
"endDateTime": endStr,
|
||
}
|
||
if v, _ := cmd.Flags().GetString("timezone"); v != "" {
|
||
toolArgs["timeZone"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "desc", "description"); v != "" {
|
||
toolArgs["description"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "attendees", "users", "user-ids", "userIds", "user", "attendee", "attendee-ids", "attendeeIds", "participants", "participant-ids", "participantIds", "members"); v != "" {
|
||
toolArgs["attendees"] = parseCSVValues(v)
|
||
}
|
||
if v, _ := cmd.Flags().GetString("open-dingtalk-ids"); v != "" {
|
||
toolArgs["openDingTalkIds"] = parseCSVValues(v)
|
||
}
|
||
if v := flagOrFallback(cmd, "rooms", "room-ids", "roomIds"); v != "" {
|
||
toolArgs["roomIds"] = parseCSVValues(v)
|
||
}
|
||
recurrence, err := buildRecurrence(cmd)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if recurrence != nil {
|
||
toolArgs["recurrence"] = recurrence
|
||
}
|
||
if v, _ := cmd.Flags().GetString("rich-text-desc"); v != "" {
|
||
toolArgs["richTextDescription"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("location"); v != "" {
|
||
toolArgs["location"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "free-busy", "freeBusy", "freebusy"); v != "" {
|
||
toolArgs["freeBusy"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("remind-minutes"); v != "" {
|
||
toolArgs["reminders"] = buildReminders(v)
|
||
}
|
||
return callMCPTool("create_calendar_event", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventCreateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "create_calendar_event",
|
||
CanonicalPath: "calendar.create_calendar_event",
|
||
CLIPath: "calendar event create",
|
||
PrimaryCLIPath: "calendar event create",
|
||
},
|
||
Description: "创建日程",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "create_calendar_event"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "创建日程",
|
||
UseWhen: []string{"需要创建新日程(时间、可选参会人/提醒/会议室/循环规则)并提取 eventId 时"},
|
||
AvoidWhen: []string{
|
||
"只需查询日程列表或详情时改用 event list/get",
|
||
"只改已有日程字段时改用 event update;改参会人/会议室用 attendee/room 命令",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event create --title \"Q1 复盘会\" --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T15:00:00+08:00\"",
|
||
"dws calendar event create --title \"周会\" --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T15:00:00+08:00\" --attendees userId1,userId2",
|
||
},
|
||
},
|
||
Parameters: append([]contract.ParamDecl{
|
||
{Name: "title", Property: "summary", Required: boolPtr(true)},
|
||
{Name: "start", Property: "startDateTime", Required: boolPtr(true)},
|
||
{Name: "end", Property: "endDateTime", Required: boolPtr(true)},
|
||
{Name: "desc", Property: "description"},
|
||
{Name: "open-dingtalk-ids", Property: "openDingTalkIds"},
|
||
{Name: "remind-minutes", Property: "reminders"},
|
||
{Name: "rich-text-desc", Property: "richTextDescription"},
|
||
{Name: "rooms", Property: "roomIds"},
|
||
{Name: "timezone", Property: "timeZone"},
|
||
}, calendarRecurrenceParamDecls()...),
|
||
},
|
||
})
|
||
|
||
eventUpdateCmd := &cobra.Command{
|
||
Use: "update",
|
||
Short: "修改日程",
|
||
Long: `支持修改标题、描述、时间、地点、忙碌状态等。如需修改会议室,请使用 dws calendar room [add|delete];如需修改参会人,请使用 dws calendar attendee [add|delete]。
|
||
|
||
修改周期日程的循环规则时,--recurrence-* 系列必须整体传入:只传其中一个(比如只改 --recurrence-count)会把循环规则覆盖成不完整的状态。
|
||
至少包含 --recurrence-type、--recurrence-interval(>0) 与 --recurrence-range-type,否则命令会被拒绝执行。
|
||
如果只想微调已有周期日程的某一个循环字段,请先通过 dws calendar event get --id <ID> 读取现有 recurrence,然后在命令中重新提供完整的 pattern + range 字段集合。`,
|
||
Example: ` dws calendar event update --id EVENT_ID --title "新标题" # 查询 eventId: dws calendar event list
|
||
dws calendar event update --id EVENT_ID --desc "新描述" --timezone Asia/Tokyo
|
||
dws calendar event update --id EVENT_ID --recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 5`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"eventId": eventID}
|
||
if v := flagOrFallback(cmd, "title", "summary"); v != "" {
|
||
toolArgs["summary"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "start", "start-time", "startTime", "start_time", "start-date", "startDate"); v != "" {
|
||
toolArgs["startDateTime"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "end", "end-time", "endTime", "end_time", "end-date", "endDate"); v != "" {
|
||
toolArgs["endDateTime"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "desc", "description"); v != "" {
|
||
toolArgs["description"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("timezone"); v != "" {
|
||
toolArgs["timeZone"] = v
|
||
}
|
||
recurrence, err := buildRecurrence(cmd)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if recurrence != nil {
|
||
toolArgs["recurrence"] = recurrence
|
||
}
|
||
if v, _ := cmd.Flags().GetString("rich-text-desc"); v != "" {
|
||
toolArgs["richTextDescription"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("location"); v != "" {
|
||
toolArgs["location"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("free-busy"); v != "" {
|
||
toolArgs["freeBusy"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("update_calendar_event", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventUpdateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "update_calendar_event",
|
||
CanonicalPath: "calendar.update_calendar_event",
|
||
CLIPath: "calendar event update",
|
||
PrimaryCLIPath: "calendar event update",
|
||
},
|
||
Description: "修改日程",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "update_calendar_event"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "修改日程",
|
||
UseWhen: []string{"已知 eventId 且具备组织者权限,需要修改标题、时间、地点、描述等字段时"},
|
||
AvoidWhen: []string{
|
||
"改参会人请用 attendee add/delete;改会议室请用 room add/delete",
|
||
"目标字段未确认时不要更新",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event update --id <EVENT_ID> --title \"新标题\"",
|
||
"dws calendar event update --id <EVENT_ID> --desc \"新描述\" --timezone Asia/Tokyo",
|
||
},
|
||
},
|
||
Parameters: append([]contract.ParamDecl{
|
||
{Name: "desc", Property: "description"},
|
||
{Name: "end", Property: "endDateTime"},
|
||
{Name: "id", Property: "eventId"},
|
||
{Name: "rich-text-desc", Property: "richTextDescription"},
|
||
{Name: "start", Property: "startDateTime"},
|
||
{Name: "timezone", Property: "timeZone"},
|
||
{Name: "title", Property: "summary"},
|
||
}, calendarRecurrenceParamDecls()...),
|
||
},
|
||
})
|
||
|
||
eventDeleteCmd := &cobra.Command{
|
||
Use: "delete",
|
||
Short: "删除日程",
|
||
Example: ` dws calendar event delete --id EVENT_ID # 查询 eventId: dws calendar event list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"eventId": eventID}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("delete_calendar_event", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventDeleteCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "delete_calendar_event",
|
||
CanonicalPath: "calendar.delete_calendar_event",
|
||
CLIPath: "calendar event delete",
|
||
PrimaryCLIPath: "calendar event delete",
|
||
},
|
||
Description: "删除指定日程",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "delete_calendar_event"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "删除指定日程",
|
||
UseWhen: []string{"用户明确要求删除/取消指定日程,且 eventId 已确认时(组织者删除会通知所有参与者)"},
|
||
AvoidWhen: []string{
|
||
"只需从自己日历移除参与身份且非组织者场景需区分影响范围",
|
||
"目标 eventId 未确认或仍需保留日程时不要删除",
|
||
},
|
||
Examples: []string{"dws calendar event delete --id <id>"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "eventId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
eventSuggestCmd := &cobra.Command{
|
||
Use: "suggest",
|
||
Short: "建议日程时间",
|
||
Long: `对于非明确时间或一段时间范围的约会场景,可基于所有参会人的忙闲状态,推荐多个可用的时间块方案,用于解决会议时间协调问题。`,
|
||
Example: ` dws calendar event suggest --users userId1,userId2 --duration 60
|
||
dws calendar event suggest --start "2026-03-10T09:00:00+08:00" --end "2026-03-10T18:00:00+08:00" --users userId1
|
||
dws calendar event suggest --users userId1 --duration 30 --timezone Asia/Tokyo`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
toolArgs := map[string]any{}
|
||
if v := flagOrFallback(cmd, "start", "time-min", "min-time", "start-time", "startTime", "start_time", "start-date", "startDate"); v != "" {
|
||
toolArgs["start"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "end", "time-max", "max-time", "end-time", "endTime", "end_time", "end-date", "endDate"); v != "" {
|
||
toolArgs["end"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("timezone"); v != "" {
|
||
toolArgs["timeZone"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "users", "user-ids", "userIds", "attendees", "attendee-ids", "attendeeIds", "attendee", "user", "participants", "participant-ids", "participantIds", "members"); v != "" {
|
||
toolArgs["attendeeUserIds"] = parseCSVValues(v)
|
||
}
|
||
if v, _ := cmd.Flags().GetString("duration"); v != "" {
|
||
toolArgs["durationMinutes"] = v
|
||
}
|
||
return callMCPTool("list_suggested_event_times", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventSuggestCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_suggested_event_times",
|
||
CanonicalPath: "calendar.list_suggested_event_times",
|
||
CLIPath: "calendar event suggest",
|
||
PrimaryCLIPath: "calendar event suggest",
|
||
},
|
||
Description: "建议日程时间",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_suggested_event_times"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "建议日程时间",
|
||
UseWhen: []string{"时间未定,需要基于参会人闲忙推荐可用时间块时"},
|
||
AvoidWhen: []string{
|
||
"时间已确定只需创建日程时改用 event create",
|
||
"只查闲忙片段时改用 busy search",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event suggest --users userId1,userId2 --duration 60",
|
||
"dws calendar event suggest --start \"2026-03-10T09:00:00+08:00\" --end \"2026-03-10T18:00:00+08:00\" --users userId1",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "duration", Property: "durationMinutes"},
|
||
{Name: "timezone", Property: "timeZone"},
|
||
{Name: "users", Property: "attendeeUserIds"},
|
||
},
|
||
},
|
||
})
|
||
|
||
eventRespondCmd := &cobra.Command{
|
||
Use: "respond",
|
||
Short: "响应日程(接受/拒绝/暂定)",
|
||
Long: `作为日程参会人,设置自己的响应状态(接受、拒绝、暂定)。注意:订阅日历下的日程无参会人,因此不可响应`,
|
||
Example: ` dws calendar event respond --id EVENT_ID --status accepted
|
||
dws calendar event respond --id EVENT_ID --status declined
|
||
dws calendar event respond --id EVENT_ID --status tentative
|
||
# 查询 eventId: dws calendar event list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateRequiredFlags(cmd, "status"); err != nil {
|
||
return err
|
||
}
|
||
status := mustGetFlag(cmd, "status")
|
||
valid := map[string]bool{"needsAction": true, "accepted": true, "declined": true, "tentative": true}
|
||
if !valid[status] {
|
||
return &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "invalid --status value: " + status,
|
||
Suggestion: "可选值: needsAction(未操作), accepted(接受), declined(拒绝), tentative(暂定)",
|
||
}
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"responseStatus": status,
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("respond", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventRespondCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "respond",
|
||
CanonicalPath: "calendar.respond",
|
||
CLIPath: "calendar event respond",
|
||
PrimaryCLIPath: "calendar event respond",
|
||
},
|
||
Description: "响应日程",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "respond"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "响应日程",
|
||
UseWhen: []string{"作为参会人需要设置自己的响应状态(accepted/declined/tentative)时"},
|
||
AvoidWhen: []string{
|
||
"是组织者要改日程内容时改用 event update",
|
||
"订阅日历日程不可响应",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event respond --id <EVENT_ID> --status accepted",
|
||
"dws calendar event respond --id <EVENT_ID> --status declined",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "eventId"},
|
||
{Name: "status", Property: "responseStatus"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── attendee: 参会人 (曾用名: participant) ─────────────────
|
||
|
||
participantCmd := &cobra.Command{
|
||
Use: "attendee",
|
||
Aliases: []string{"participant"},
|
||
Short: "日程参会人管理",
|
||
Long: "管理日程的参会人。alias:`participant`,仍作为别名保留,历史调用无需改动。",
|
||
RunE: groupRunE,
|
||
}
|
||
|
||
participantListCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "查看参会人",
|
||
Long: `查看日程的参会人列表。注意:订阅日历下的日程无参会人,因此不可查看`,
|
||
Example: ` dws calendar attendee list --event EVENT_ID # 查询 eventId: dws calendar event list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("get_calendar_participants", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(participantListCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "get_calendar_participants",
|
||
CanonicalPath: "calendar.get_calendar_participants",
|
||
CLIPath: "calendar attendee list",
|
||
PrimaryCLIPath: "calendar attendee list",
|
||
},
|
||
Description: "获取日程参与人",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "get_calendar_participants"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "获取日程参与人",
|
||
UseWhen: []string{"已知 eventId,需要查看参会人列表及状态时(订阅日历日程无参会人)"},
|
||
AvoidWhen: []string{"要添加/移除参会人时改用 attendee add/delete"},
|
||
Examples: []string{"dws calendar attendee list --event <EVENT_ID>"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "calendar-id", Property: "calendarId"},
|
||
{Name: "event", Property: "eventId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
participantAddCmd := &cobra.Command{
|
||
Use: "add",
|
||
Short: "添加参会人",
|
||
Long: `添加日程的参会人。注意:订阅日历下的日程无参会人,因此不可添加`,
|
||
Example: ` dws calendar attendee add --event EVENT_ID --attendees userId1,userId2
|
||
dws calendar attendee add --event EVENT_ID --attendees userId1 --optional
|
||
# 查询 eventId: dws calendar event list
|
||
# 查询 userId: dws contact user search --keyword "姓名"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
attendees, err := mustFlagOrFallback(cmd, "attendees", "users", "user-ids", "userIds", "attendee-ids", "attendeeIds", "attendee", "user", "participants", "participant-ids", "participantIds", "members")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"attendeesToAdd": parseCSVValues(attendees),
|
||
}
|
||
if v, _ := cmd.Flags().GetBool("optional"); v {
|
||
toolArgs["optional"] = true
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("add_calendar_participant", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(participantAddCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "add_calendar_participant",
|
||
CanonicalPath: "calendar.add_calendar_participant",
|
||
CLIPath: "calendar attendee add",
|
||
PrimaryCLIPath: "calendar attendee add",
|
||
},
|
||
Description: "添加日程参与人",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "add_calendar_participant"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "添加日程参与人",
|
||
UseWhen: []string{"已知 eventId,需要向已有日程批量添加参会人时(订阅日历日程无参会人)"},
|
||
AvoidWhen: []string{
|
||
"要查看参会人时改用 dws calendar attendee list",
|
||
"要移除参会人时改用 dws calendar attendee delete",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar attendee add --event <EVENT_ID> --attendees <USER_ID_1>,<USER_ID_2>",
|
||
"dws calendar attendee add --event <EVENT_ID> --attendees <USER_ID> --optional",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "attendees", Property: "attendeesToAdd"},
|
||
{Name: "calendar-id", Property: "calendarId"},
|
||
{Name: "event", Property: "eventId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
participantDeleteCmd := &cobra.Command{
|
||
Use: "delete",
|
||
Short: "移除参会人",
|
||
Long: `移除日程的参会人。注意:订阅日历下的日程无参会人,因此不可移除`,
|
||
Example: ` dws calendar attendee delete --event EVENT_ID --attendees userId1
|
||
# 查询 eventId: dws calendar event list
|
||
# 查询 userId: dws contact user search --keyword "姓名"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
attendees, err := mustFlagOrFallback(cmd, "attendees", "users", "user-ids", "userIds", "attendee-ids", "attendeeIds", "attendee", "user", "participants", "participant-ids", "participantIds", "members")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"attendeesToRemove": parseCSVValues(attendees),
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("remove_calendar_participant", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(participantDeleteCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "remove_calendar_participant",
|
||
CanonicalPath: "calendar.remove_calendar_participant",
|
||
CLIPath: "calendar attendee delete",
|
||
PrimaryCLIPath: "calendar attendee delete",
|
||
},
|
||
Description: "删除日程参与人",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "remove_calendar_participant"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "删除日程参与人",
|
||
UseWhen: []string{"已知 eventId,需要从日程批量移除参会人时(订阅日历日程无参会人)"},
|
||
AvoidWhen: []string{
|
||
"要添加参会人时改用 attendee add",
|
||
"要查看参会人时改用 attendee list",
|
||
},
|
||
Examples: []string{"dws calendar attendee delete --attendees <attendees> --event <event>"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "attendees", Property: "attendeesToRemove"},
|
||
{Name: "calendar-id", Property: "calendarId"},
|
||
{Name: "event", Property: "eventId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── room: 会议室 ────────────────────────────────────────────
|
||
|
||
roomCmd := &cobra.Command{Use: "room", Short: "会议室管理", RunE: groupRunE}
|
||
|
||
roomSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "搜索会议室 (按名称搜索或按时间段查可用会议室)",
|
||
Long: `
|
||
**搜索会议室** 支持两种模式:
|
||
|
||
1. **按名称搜索**(仅传 --room-name,不传 --start/--end):
|
||
根据会议室名称模糊搜索,返回所有匹配的会议室,不检查可用性。
|
||
适用于"找到某个会议室"的场景。
|
||
|
||
2. **按时间段搜索可用会议室**(传 --start/--end,或不传任何参数):
|
||
查询指定时间段内所有可用的会议室。不传 --start/--end 时默认查询当前时间起 1 小时内的会议室。
|
||
注意,大部分会议室仅在工作时间可用,如果检索时间不在工作时间可能查不到任何结果。
|
||
时间约束:start 必须是未来的时间(API 限制:start can not less current time)。
|
||
- 若传入的 --start 早于当前时间,CLI 自动修正为 now+1min。
|
||
- 若传入的 --end 早于当前时间,视为无效请求返回错误(无法检索过去的时间段)。
|
||
|
||
使用 --room-name 按会议室名称过滤时,CLI 原样透传给服务端做模糊匹配。调用方(模型/脚本)在传入前应主动精简名称,剔除用户口语里常带的通用后缀(如「会议室」「大会议室」「小会议室」),只保留核心专名,避免因后缀导致匹配不到。例如用户说「永澄亭会议室」时,应传 --room-name 永澄亭。
|
||
如果知道roomId,想查该会议室的预订记录,直接用dws calendar busy search 指令。
|
||
此指令搜索到的会议室结果中,有两个值需要注意:
|
||
- customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。
|
||
- supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。
|
||
`,
|
||
Example: ` # 按名称搜索(不检查可用性,返回所有匹配的会议室)
|
||
dws calendar room search --room-name 永澄亭 # 用户说「永澄亭会议室」时,调用方需先精简为「永澄亭」
|
||
|
||
# 按时间段搜索可用会议室
|
||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --group-id GROUP_ID
|
||
dws calendar room search # 不传 --start/--end 时默认当前时间起 1 小时
|
||
|
||
# 名称 + 时间段:搜索指定名称的可用会议室
|
||
dws calendar room search --room-name 永澄亭 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||
|
||
# 分页
|
||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --limit 20 --page 0`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
// Resolve room name (common to both modes)
|
||
roomName := strings.TrimSpace(flagOrFallback(cmd, "room-name", "roomName", "name", "query"))
|
||
|
||
// Resolve time flags to determine routing
|
||
startStr := flagOrFallback(cmd, "start", "time-min", "min-time", "start-time", "startTime", "start_time", "start-date", "startDate")
|
||
endStr := flagOrFallback(cmd, "end", "time-max", "max-time", "end-time", "endTime", "end_time", "end-date", "endDate")
|
||
|
||
// ── Mode 1: Name-only search ──
|
||
// Triggered when --room-name is provided AND neither --start nor --end is provided.
|
||
// Calls search_rooms MCP tool: fuzzy name search, no availability check, no time constraint.
|
||
if roomName != "" && startStr == "" && endStr == "" {
|
||
toolArgs := map[string]any{
|
||
"roomName": roomName,
|
||
}
|
||
return callSearchRoomsByName(cmd, toolArgs)
|
||
}
|
||
|
||
// ── Mode 2: Availability search ──
|
||
// Triggered when --start/--end is provided, or when no --room-name is given.
|
||
// Calls query_available_meeting_room MCP tool: returns only rooms free in the time range.
|
||
now := time.Now()
|
||
startProvided := startStr != ""
|
||
endProvided := endStr != ""
|
||
startCorrected := false
|
||
|
||
// --- 时间约束:API 要求 startTime 必须是未来时间 ---
|
||
// 若用户传了 --end 且 end <= now,直接报错(无法检索过去的时间段)
|
||
if endStr != "" {
|
||
if endT, e := time.Parse(time.RFC3339, endStr); e == nil && !endT.After(now) {
|
||
return &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "--end 不能早于当前时间,无法检索过去的时间段",
|
||
Suggestion: "请传入一个未来的结束时间",
|
||
}
|
||
}
|
||
}
|
||
|
||
if startStr == "" {
|
||
// 留 1 分钟缓冲,避免请求到达服务端时 start < now 被拒 (filterStartTime can not less current time)
|
||
startStr = now.Add(1 * time.Minute).Format(time.RFC3339)
|
||
} else {
|
||
// 用户显式传了 --start 但已过期 → 自动修正为 now+1min
|
||
if startT, e := time.Parse(time.RFC3339, startStr); e == nil && !startT.After(now) {
|
||
startStr = now.Add(1 * time.Minute).Format(time.RFC3339)
|
||
startCorrected = true
|
||
}
|
||
}
|
||
if endStr == "" {
|
||
endStr = now.Add(1 * time.Hour).Format(time.RFC3339)
|
||
}
|
||
|
||
startTime, err := parseISOTimeToMillis("start", startStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
endTime, err := parseISOTimeToMillis("end", endStr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateTimeRange(startTime, endTime); err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"startTime": startTime,
|
||
"endTime": endTime,
|
||
}
|
||
// mcp里并无此参数
|
||
if v, _ := cmd.Flags().GetBool("available"); v {
|
||
toolArgs["needAvailable"] = true
|
||
}
|
||
if v, _ := cmd.Flags().GetString("group-id"); v != "" {
|
||
toolArgs["groupId"] = v
|
||
}
|
||
if roomName != "" {
|
||
toolArgs["roomName"] = roomName
|
||
}
|
||
if v := flagOrFallback(cmd, "limit", "page-size", "size", "count"); v != "" {
|
||
toolArgs["pageSize"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "page", "page-index", "page-num", "page-no"); v != "" {
|
||
toolArgs["pageIndex"] = v
|
||
}
|
||
return callMeetingRoomSearchResult(cmd, toolArgs, startProvided, endProvided, startCorrected)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(roomSearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "query_available_meeting_room",
|
||
CanonicalPath: "calendar.query_available_meeting_room",
|
||
CLIPath: "calendar room search",
|
||
PrimaryCLIPath: "calendar room search",
|
||
},
|
||
Description: "查询空闲或按名搜索会议室",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "query_available_meeting_room"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询空闲或按名搜索会议室",
|
||
UseWhen: []string{"需要按名称模糊搜会议室,或按时间段查询全程空闲且可预订的会议室时"},
|
||
AvoidWhen: []string{
|
||
"要完成预订时在取得 roomId 后改用 room add",
|
||
"只要会议室分组时改用 room list-groups",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar room search --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T15:00:00+08:00\"",
|
||
"dws calendar room search --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T15:00:00+08:00\" --group-id <GROUP_ID>",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "end", Property: "endTime"},
|
||
{Name: "limit", Property: "pageSize"},
|
||
{Name: "page", Property: "pageIndex"},
|
||
{Name: "start", Property: "startTime"},
|
||
},
|
||
},
|
||
})
|
||
|
||
roomAddCmd := &cobra.Command{
|
||
Use: "add",
|
||
Short: "预定会议室",
|
||
Long: `room是会议室,用于线下开会场景。将room加入到日程完成预订`,
|
||
Example: ` dws calendar room add --event EVENT_ID --rooms roomId1,roomId2
|
||
# 查询 eventId: dws calendar event list
|
||
# 查询会议室: dws calendar room list-groups`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rooms, err := mustFlagOrFallback(cmd, "rooms", "room-ids", "roomIds")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"roomIds": parseCSVValues(rooms),
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("add_meeting_room", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(roomAddCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "add_meeting_room",
|
||
CanonicalPath: "calendar.add_meeting_room",
|
||
CLIPath: "calendar room add",
|
||
PrimaryCLIPath: "calendar room add",
|
||
},
|
||
Description: "为日程预订会议室",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "add_meeting_room"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "为日程预订会议室",
|
||
UseWhen: []string{"已知 eventId 与 roomId,需要为已有日程预订线下会议室时"},
|
||
AvoidWhen: []string{
|
||
"要搜索空闲会议室时先用 dws calendar room search",
|
||
"要取消会议室预订时改用 dws calendar room delete",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID>",
|
||
"dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID> --format json",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "event", Property: "eventId"},
|
||
{Name: "rooms", Property: "roomIds"},
|
||
},
|
||
},
|
||
})
|
||
|
||
roomDeleteCmd := &cobra.Command{
|
||
Use: "delete",
|
||
Short: "移除会议室",
|
||
Example: ` dws calendar room delete --event EVENT_ID --rooms roomId1
|
||
# 查询 eventId: dws calendar event list
|
||
# 查询会议室: dws calendar room list-groups`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rooms, err := mustFlagOrFallback(cmd, "rooms", "room-ids", "roomIds")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"roomIds": parseCSVValues(rooms),
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("delete_meeting_room", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(roomDeleteCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "delete_meeting_room",
|
||
CanonicalPath: "calendar.delete_meeting_room",
|
||
CLIPath: "calendar room delete",
|
||
PrimaryCLIPath: "calendar room delete",
|
||
},
|
||
Description: "移除日程中的会议室",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "delete_meeting_room"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "移除日程中的会议室",
|
||
UseWhen: []string{"已知 eventId 与 roomId,需要取消日程会议室预订并释放占用时"},
|
||
AvoidWhen: []string{
|
||
"要新增预订时改用 dws calendar room add",
|
||
"eventId/roomId 未确认时不要移除",
|
||
},
|
||
Examples: []string{"dws calendar room delete --event <event> --rooms <rooms>"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "event", Property: "eventId"},
|
||
{Name: "rooms", Property: "roomIds"},
|
||
},
|
||
},
|
||
})
|
||
|
||
roomListGroupsCmd := &cobra.Command{
|
||
Use: "list-groups",
|
||
Short: "会议室分组列表",
|
||
Example: ` dws calendar room list-groups
|
||
dws calendar room list-groups --limit 20 --page 0`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
toolArgs := map[string]any{}
|
||
if v := flagOrFallback(cmd, "limit", "page-size", "size", "count"); v != "" {
|
||
toolArgs["pageSize"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "page", "page-index", "page-num", "page-no"); v != "" {
|
||
toolArgs["pageIndex"] = v
|
||
}
|
||
if len(toolArgs) == 0 {
|
||
return callMeetingRoomMCPTool("list_meeting_room_groups", nil)
|
||
}
|
||
return callMeetingRoomMCPTool("list_meeting_room_groups", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(roomListGroupsCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_meeting_room_groups",
|
||
CanonicalPath: "calendar.list_meeting_room_groups",
|
||
CLIPath: "calendar room list-groups",
|
||
PrimaryCLIPath: "calendar room list-groups",
|
||
},
|
||
Description: "查询会议室分组列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_meeting_room_groups"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询会议室分组列表",
|
||
UseWhen: []string{"需要分页查看企业会议室分组(groupId/parentId)时"},
|
||
AvoidWhen: []string{"要搜空闲会议室时改用 dws calendar room search"},
|
||
Examples: []string{
|
||
"dws calendar room list-groups",
|
||
"dws calendar room list-groups --limit 20 --page 0",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "limit", Property: "pageSize"},
|
||
{Name: "page", Property: "pageIndex"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── busy: 闲忙 ──────────────────────────────────────────────
|
||
|
||
busyCmd := &cobra.Command{Use: "busy", Short: "闲忙查询", RunE: groupRunE}
|
||
|
||
busySearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "查询用户 / 会议室闲忙状态",
|
||
Long: `查询指定用户或会议室在给定时间范围内的闲忙状态。--users 与 --rooms 至少指定其一,也可以同时指定(查询用户+会议室)。`,
|
||
Example: ` dws calendar busy search --users userId1,userId2 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||
dws calendar busy search --rooms roomId1,roomId2 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||
dws calendar busy search --users userId1 --rooms roomId1 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||
# 查询 userId: dws contact user search --keyword "姓名"
|
||
# 查询 roomId: dws calendar room search 或 dws calendar room list-groups`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
users := flagOrFallback(cmd, "users", "user-ids", "userIds", "attendees", "attendee-ids", "attendeeIds", "attendee", "user", "participants", "participant-ids", "participantIds", "members")
|
||
rooms := flagOrFallback(cmd, "rooms", "room-ids", "roomIds")
|
||
if strings.TrimSpace(users) == "" && strings.TrimSpace(rooms) == "" {
|
||
return &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "--users 与 --rooms 至少指定其一",
|
||
Suggestion: "查询用户闲忙用 --users userId1,userId2;查询会议室闲忙用 --rooms roomId1,roomId2;两者可同时指定",
|
||
}
|
||
}
|
||
startRaw, err := mustFlagOrFallback(cmd, "start", "time-min", "min-time", "start-time", "startTime", "start_time", "start-date", "startDate")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
endRaw, err := mustFlagOrFallback(cmd, "end", "time-max", "max-time", "end-time", "endTime", "end_time", "end-date", "endDate")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
startTime, err := parseISOTimeToMillis("start", startRaw)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
endTime, err := parseISOTimeToMillis("end", endRaw)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateTimeRange(startTime, endTime); err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{
|
||
"startTime": startTime,
|
||
"endTime": endTime,
|
||
}
|
||
if strings.TrimSpace(users) != "" {
|
||
toolArgs["userIds"] = parseCSVValues(users)
|
||
}
|
||
if strings.TrimSpace(rooms) != "" {
|
||
toolArgs["roomIds"] = parseCSVValues(rooms)
|
||
}
|
||
return callFilteredBusyStatus(cmd, toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(busySearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "query_busy_status",
|
||
CanonicalPath: "calendar.query_busy_status",
|
||
CLIPath: "calendar busy search",
|
||
PrimaryCLIPath: "calendar busy search",
|
||
},
|
||
Description: "查询用户或会议室闲忙",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "query_busy_status"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询用户或会议室闲忙",
|
||
UseWhen: []string{"需要查询指定用户和/或会议室在时间范围内的已占用时段时(users 与 rooms 至少其一)"},
|
||
AvoidWhen: []string{"需要创建或改日程时不要使用;本命令只返回闲忙时间片段"},
|
||
Examples: []string{
|
||
"dws calendar busy search --users <USER_ID_1>,<USER_ID_2> --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T18:00:00+08:00\"",
|
||
"dws calendar busy search --rooms <ROOM_ID_1>,<ROOM_ID_2> --start \"2026-03-10T14:00:00+08:00\" --end \"2026-03-10T18:00:00+08:00\"",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "end", Property: "endTime"},
|
||
{Name: "rooms", Property: "roomIds"},
|
||
{Name: "start", Property: "startTime"},
|
||
{Name: "users", Property: "userIds"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── attachment: 附件 ────────────────────────────────────────
|
||
|
||
attachmentCmd := &cobra.Command{Use: "attachment", Short: "日程附件管理", RunE: groupRunE}
|
||
|
||
attachmentAddCmd := &cobra.Command{
|
||
Use: "add",
|
||
Short: "添加日程附件",
|
||
Long: `为指定日程添加附件。需先用钉盘上传文件得到 fileId,再以 <fileId>:<name> 形式逗号分隔传入。注意:订阅日历下的日程不支持添加附件`,
|
||
Example: ` dws calendar attachment add --event EVENT_ID --files fileId1:report.pdf,fileId2:slides.pptx
|
||
# 查询 eventId: dws calendar event list
|
||
# 上传文件并获取 fileId: dws drive 相关命令`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "event", "id", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateRequiredFlags(cmd, "files"); err != nil {
|
||
return err
|
||
}
|
||
raw := mustGetFlag(cmd, "files")
|
||
parts := parseCSVValues(raw)
|
||
attachments := make([]map[string]any, 0, len(parts))
|
||
for _, p := range parts {
|
||
idx := strings.Index(p, ":")
|
||
if idx <= 0 || idx >= len(p)-1 {
|
||
return &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "invalid --files entry: " + p,
|
||
Suggestion: "每个附件必须以 <fileId>:<name> 形式提供,多个用逗号分隔,例如 " +
|
||
"--files fileId1:report.pdf,fileId2:slides.pptx",
|
||
}
|
||
}
|
||
attachments = append(attachments, map[string]any{
|
||
"id": strings.TrimSpace(p[:idx]),
|
||
"name": strings.TrimSpace(p[idx+1:]),
|
||
})
|
||
}
|
||
toolArgs := map[string]any{
|
||
"eventId": eventID,
|
||
"attachments": attachments,
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
return callMCPTool("add_attachments", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(attachmentAddCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "add_attachments",
|
||
CanonicalPath: "calendar.add_attachments",
|
||
CLIPath: "calendar attachment add",
|
||
PrimaryCLIPath: "calendar attachment add",
|
||
},
|
||
Description: "为日程添加附件",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "add_attachments"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "为日程添加附件",
|
||
UseWhen: []string{"已知 eventId,已有钉盘 fileId,需要为日程添加附件时(订阅日历日程不支持)"},
|
||
AvoidWhen: []string{
|
||
"尚未上传钉盘文件取得 fileId 时不要调用",
|
||
"只需查询日程时改用 dws calendar event get",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar attachment add --event <EVENT_ID> --files <FILE_ID>:report.pdf,<FILE_ID2>:slides.pptx",
|
||
"dws calendar attachment add --event <EVENT_ID> --files <FILE_ID>:report.pdf --format json",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "event", Property: "eventId"},
|
||
{Name: "files", Property: "attachments"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── acl: 日历访问权限 ─────────────────────────────────────────
|
||
|
||
aclCmd := &cobra.Command{Use: "acl", Short: "管理我的日历访问权限(共享给他人)", RunE: groupRunE}
|
||
|
||
aclListCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "查询我的日历共享给了谁",
|
||
Long: `查询当前用户主日历的访问控制列表——即"我的日历共享给了哪些人、各自什么权限"。注意:这不是查日历本列表(那是 book list)。`,
|
||
Example: ` dws calendar acl list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return callMCPTool("list_acls", nil)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(aclListCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_acls",
|
||
CanonicalPath: "calendar.list_acls",
|
||
CLIPath: "calendar acl list",
|
||
PrimaryCLIPath: "calendar acl list",
|
||
},
|
||
Description: "查询主日历访问控制列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_acls"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询主日历访问控制列表",
|
||
UseWhen: []string{"需要查看主日历共享给了谁、各自权限时(不是日历本列表)"},
|
||
AvoidWhen: []string{"要列日历本时改用 dws calendar book list"},
|
||
Examples: []string{"dws calendar acl list"},
|
||
},
|
||
},
|
||
})
|
||
|
||
aclAddCmd := &cobra.Command{
|
||
Use: "add",
|
||
Short: "把我的日历共享给某人",
|
||
Long: `将当前用户的日历共享给指定用户,授予对方查看忙闲/标题/详情或编辑权限。`,
|
||
Example: ` dws calendar acl add --user USER_ID --privilege reader
|
||
dws calendar acl add --user USER_ID --privilege writer --no-notification`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
userID, err := mustFlagOrFallback(cmd, "user", "user-id", "userId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateRequiredFlags(cmd, "privilege"); err != nil {
|
||
return err
|
||
}
|
||
privilege := mustGetFlag(cmd, "privilege")
|
||
valid := map[string]bool{"free_busy_reader": true, "title_reader": true, "reader": true, "writer": true}
|
||
if !valid[privilege] {
|
||
return &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "invalid --privilege value: " + privilege,
|
||
Suggestion: "可选值: free_busy_reader(查看忙闲), title_reader(查看标题), reader(查看详情), writer(创建和编辑)",
|
||
}
|
||
}
|
||
toolArgs := map[string]any{
|
||
"userId": userID,
|
||
"privilege": privilege,
|
||
}
|
||
if v, _ := cmd.Flags().GetBool("no-notification"); v {
|
||
toolArgs["sendNotification"] = false
|
||
}
|
||
return callMCPTool("add_acl", toolArgs)
|
||
},
|
||
}
|
||
|
||
aclDeleteCmd := &cobra.Command{
|
||
Use: "delete",
|
||
Short: "删除日历访问权限",
|
||
Long: `删除已经授予的日历访问控制权限。`,
|
||
Example: ` dws calendar acl delete --acl-id ACL_ID
|
||
# 查询 aclId: dws calendar acl list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
aclID, err := mustFlagOrFallback(cmd, "acl-id", "aclId", "id")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("delete_acl", map[string]any{"aclId": aclID})
|
||
},
|
||
}
|
||
|
||
// ── book: 日历本 ────────────────────────────────────────────
|
||
|
||
bookCmd := &cobra.Command{Use: "book", Short: "日历本管理(我能看哪些日历)", RunE: groupRunE}
|
||
|
||
bookListCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "查询用户的日历列表",
|
||
Long: `查询当前用户的所有日历,结果范围:用户自己的日历、已订阅的公共/团队日历、他人共享的日历。主日历 id 固定为 "primary"。
|
||
共享日历本中有来自 xxx 的,且权限大于reader,那么通过 event list --calendar-id <xxx的日历本id> 可查到xxx完整的日程安排
|
||
`,
|
||
Example: ` dws calendar book list`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return callMCPTool("list_calendars", nil)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(bookListCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_calendars",
|
||
CanonicalPath: "calendar.list_calendars",
|
||
CLIPath: "calendar book list",
|
||
PrimaryCLIPath: "calendar book list",
|
||
},
|
||
Description: "查询用户日历列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_calendars"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询用户日历列表",
|
||
UseWhen: []string{"需要列出自己的、已订阅的、他人共享的日历本(主日历 id=primary)时"},
|
||
AvoidWhen: []string{
|
||
"已知日历 id 要详情时改用 dws calendar book get",
|
||
"要按名称模糊搜索时改用 dws calendar book search",
|
||
},
|
||
Examples: []string{"dws calendar book list"},
|
||
},
|
||
},
|
||
})
|
||
|
||
bookGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "查询指定日历本",
|
||
Long: `根据日历id查询指定日历的信息。用户主日历本id固定为 "primary"。`,
|
||
Example: ` dws calendar book get --id primary
|
||
dws calendar book get --id CALENDAR_ID`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
calendarID, err := mustFlagOrFallback(cmd, "id", "calendar-id", "calendarId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("get_calendar", map[string]any{"calendarId": calendarID})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(bookGetCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "get_calendar",
|
||
CanonicalPath: "calendar.get_calendar",
|
||
CLIPath: "calendar book get",
|
||
PrimaryCLIPath: "calendar book get",
|
||
},
|
||
Description: "查询指定日历信息",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "get_calendar"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询指定日历信息",
|
||
UseWhen: []string{"已知日历 id(主日历固定 primary),需要查询该日历本信息时"},
|
||
AvoidWhen: []string{
|
||
"要列全部日历时改用 dws calendar book list",
|
||
"要按名称搜索日历本时改用 dws calendar book search",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar book get --id primary",
|
||
"dws calendar book get --id CALENDAR_ID",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "calendarId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
bookSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "搜索日历本",
|
||
Long: `搜索当前用户拥有的日历本,支持按日历本名模糊搜索。获取全部日历请使用 list。`,
|
||
Example: ` dws calendar book search --query "项目"
|
||
dws calendar book search --query "团队周报"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
query, err := mustFlagOrFallback(cmd, "query", "keyword", "keywords", "search")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("search_calendar", map[string]any{"query": query})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(bookSearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "search_calendar",
|
||
CanonicalPath: "calendar.search_calendar",
|
||
CLIPath: "calendar book search",
|
||
PrimaryCLIPath: "calendar book search",
|
||
},
|
||
Description: "搜索日历本列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "search_calendar"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "搜索日历本列表",
|
||
UseWhen: []string{"需要按日历本名模糊搜索当前用户拥有的日历本时"},
|
||
AvoidWhen: []string{
|
||
"要获取全部日历时改用 dws calendar book list",
|
||
"已知 id 要详情时改用 book get",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar book search --query \"项目\"",
|
||
"dws calendar book search --query \"团队周报\"",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "query", Property: "query"},
|
||
},
|
||
},
|
||
})
|
||
|
||
bookUpdateCmd := &cobra.Command{
|
||
Use: "update",
|
||
Short: "更新指定日历本",
|
||
Long: `更新日历信息,最低权限要求:privilege == "owner"。注意:用户主日历本 以及 他人共享的日历本 不支持更新。`,
|
||
Example: ` dws calendar book update --id CALENDAR_ID --summary "新日历名"
|
||
dws calendar book update --id CALENDAR_ID --desc "日历描述"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
calendarID, err := mustFlagOrFallback(cmd, "id", "calendar-id", "calendarId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"calendarId": calendarID}
|
||
if v := flagOrFallback(cmd, "summary", "title"); v != "" {
|
||
toolArgs["summary"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "desc", "description"); v != "" {
|
||
toolArgs["description"] = v
|
||
}
|
||
return callMCPTool("update_calendar", toolArgs)
|
||
},
|
||
}
|
||
|
||
// ListEvent flags
|
||
eventListCmd.Flags().String("start", "", "开始时间 ISO-8601 (例如 2026-03-10T14:00:00+08:00)")
|
||
eventListCmd.Flags().String("end", "", "结束时间 ISO-8601 (例如 2026-03-10T18:00:00+08:00)")
|
||
eventListCmd.Flags().String("time-min", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("time-min")
|
||
eventListCmd.Flags().String("time-max", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("time-max")
|
||
eventListCmd.Flags().String("min-time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("min-time")
|
||
eventListCmd.Flags().String("max-time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("max-time")
|
||
eventListCmd.Flags().String("start-time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("start-time")
|
||
eventListCmd.Flags().String("end-time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("end-time")
|
||
eventListCmd.Flags().String("startTime", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("startTime")
|
||
eventListCmd.Flags().String("endTime", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("endTime")
|
||
eventListCmd.Flags().String("start_time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("start_time")
|
||
eventListCmd.Flags().String("end_time", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("end_time")
|
||
eventListCmd.Flags().String("start-date", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("start-date")
|
||
eventListCmd.Flags().String("end-date", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("end-date")
|
||
eventListCmd.Flags().String("startDate", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("startDate")
|
||
eventListCmd.Flags().String("endDate", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("endDate")
|
||
eventListCmd.Flags().String("calendar-id", "", "日历 ID (默认 primary 主日历,仅在查询其他日历本时填写)")
|
||
eventListCmd.Flags().String("calendarId", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("calendarId")
|
||
eventListCmd.Flags().String("calendar", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("calendar")
|
||
eventListCmd.Flags().String("cursor", "", "分页游标 (首次查询无需传入,仅翻页时传入上一次返回的 nextCursor)")
|
||
eventListCmd.Flags().String("next-cursor", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("next-cursor")
|
||
eventListCmd.Flags().String("nextCursor", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("nextCursor")
|
||
eventListCmd.Flags().String("page-token", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("page-token")
|
||
eventListCmd.Flags().String("pageToken", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("pageToken")
|
||
eventListCmd.Flags().String("next-token", "", "")
|
||
_ = eventListCmd.Flags().MarkHidden("next-token")
|
||
|
||
eventListCmd.Flags().Int("limit", 0, "每页返回条数 (默认 100,最大 100)")
|
||
eventListCmd.Flags().Int("max-results", 0, "")
|
||
_ = eventListCmd.Flags().MarkHidden("max-results")
|
||
eventListCmd.Flags().Int("maxResults", 0, "")
|
||
_ = eventListCmd.Flags().MarkHidden("maxResults")
|
||
eventListCmd.Flags().Int("page-size", 0, "")
|
||
_ = eventListCmd.Flags().MarkHidden("page-size")
|
||
eventListCmd.Flags().Int("size", 0, "")
|
||
_ = eventListCmd.Flags().MarkHidden("size")
|
||
eventListCmd.Flags().Int("count", 0, "")
|
||
_ = eventListCmd.Flags().MarkHidden("count")
|
||
// GetEvent flags
|
||
eventGetCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventGetCmd.Flags().String("event", "", "")
|
||
_ = eventGetCmd.Flags().MarkHidden("event")
|
||
eventGetCmd.Flags().String("event-id", "", "")
|
||
_ = eventGetCmd.Flags().MarkHidden("event-id")
|
||
eventGetCmd.Flags().String("eventId", "", "")
|
||
_ = eventGetCmd.Flags().MarkHidden("eventId")
|
||
eventGetCmd.Flags().String("calendar-id", "", "日历 ID (默认 primary 主日历)")
|
||
eventGetCmd.Flags().String("calendarId", "", "")
|
||
_ = eventGetCmd.Flags().MarkHidden("calendarId")
|
||
eventGetCmd.Flags().String("calendar", "", "")
|
||
_ = eventGetCmd.Flags().MarkHidden("calendar")
|
||
|
||
// CreateEvent flags
|
||
eventCreateCmd.Flags().String("title", "", "日程标题 (必填,最大2048字符)")
|
||
eventCreateCmd.Flags().String("summary", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("summary")
|
||
eventCreateCmd.Flags().String("start", "", "开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00)")
|
||
eventCreateCmd.Flags().String("end", "", "结束时间 ISO-8601 (必填,例如 2026-03-10T15:00:00+08:00)")
|
||
eventCreateCmd.Flags().String("start-time", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("start-time")
|
||
eventCreateCmd.Flags().String("end-time", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("end-time")
|
||
eventCreateCmd.Flags().String("startTime", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("startTime")
|
||
eventCreateCmd.Flags().String("endTime", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("endTime")
|
||
eventCreateCmd.Flags().String("start_time", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("start_time")
|
||
eventCreateCmd.Flags().String("end_time", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("end_time")
|
||
eventCreateCmd.Flags().String("start-date", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("start-date")
|
||
eventCreateCmd.Flags().String("end-date", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("end-date")
|
||
eventCreateCmd.Flags().String("startDate", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("startDate")
|
||
eventCreateCmd.Flags().String("endDate", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("endDate")
|
||
eventCreateCmd.Flags().String("timezone", "", "时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai)")
|
||
eventCreateCmd.Flags().String("desc", "", "日程描述 (最大5000字符)")
|
||
eventCreateCmd.Flags().String("description", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("description")
|
||
eventCreateCmd.Flags().String("attendees", "", "参会人 userId 列表,逗号分隔 (最多500人),日程组织人自动放入参会人列表,无需传入userId")
|
||
eventCreateCmd.Flags().String("users", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("users")
|
||
eventCreateCmd.Flags().String("user-ids", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("user-ids")
|
||
eventCreateCmd.Flags().String("userIds", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("userIds")
|
||
eventCreateCmd.Flags().String("user", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("user")
|
||
eventCreateCmd.Flags().String("attendee", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("attendee")
|
||
eventCreateCmd.Flags().String("attendee-ids", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("attendee-ids")
|
||
eventCreateCmd.Flags().String("attendeeIds", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("attendeeIds")
|
||
eventCreateCmd.Flags().String("participants", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("participants")
|
||
eventCreateCmd.Flags().String("participant-ids", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("participant-ids")
|
||
eventCreateCmd.Flags().String("participantIds", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("participantIds")
|
||
eventCreateCmd.Flags().String("members", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("members")
|
||
eventCreateCmd.Flags().String("open-dingtalk-ids", "", "openDingTalkId 列表,逗号分隔 (与 --attendees 至少传一个)")
|
||
eventCreateCmd.Flags().String("rooms", "", "会议室 roomId 列表,逗号分隔 (创建时直接预定,roomId 必须来自“room search”返回,若是循环会议,必须设置recurrence-end-date,避免长期预订)")
|
||
eventCreateCmd.Flags().String("room-ids", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("room-ids")
|
||
eventCreateCmd.Flags().String("roomIds", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("roomIds")
|
||
eventCreateCmd.Flags().String("recurrence-type", "", "[recurrence整体必填] 循环类型: daily|weekly|absoluteMonthly|relativeMonthly|absoluteYearly;一旦使用 --recurrence-* 任一 flag,必须同时提供完整的 pattern+range 字段集合")
|
||
eventCreateCmd.Flags().Int("recurrence-interval", 0, "[recurrence整体必填] 循环间隔 (>0;如 daily 时表示每N天,weekly 时表示每N周,以此类推)")
|
||
eventCreateCmd.Flags().String("recurrence-days-of-week", "", "[recurrence.pattern] 周几: sunday,monday,...,saturday (weekly/relativeMonthly 时必填)")
|
||
eventCreateCmd.Flags().Int("recurrence-day-of-month", 0, "[recurrence.pattern] 每月第几天 (absoluteMonthly/absoluteYearly 时必填)")
|
||
eventCreateCmd.Flags().String("recurrence-index", "", "[recurrence.pattern] 每月第几周: first|second|third|fourth|last (relativeMonthly 时必填)")
|
||
eventCreateCmd.Flags().String("recurrence-first-day-of-week", "", "[recurrence.pattern] 一周起始日,默认 sunday")
|
||
eventCreateCmd.Flags().String("recurrence-range-type", "", "[recurrence整体必填] 循环范围: noEnd|endDate|numbered;与 --recurrence-type 必须成对出现")
|
||
eventCreateCmd.Flags().String("recurrence-end-date", "", "[recurrence.range] 循环结束时间 ISO-8601 (range-type=endDate 时必填)")
|
||
eventCreateCmd.Flags().Int("recurrence-count", 0, "[recurrence.range] 循环次数 (range-type=numbered 时必填)")
|
||
eventCreateCmd.Flags().String("rich-text-desc", "", "html格式的富文本类型日程描述,用于复杂内容的展示")
|
||
eventCreateCmd.Flags().String("location", "", "地点信息(纯文本备注,如‘3号楼A区’;不等于预订会议室)")
|
||
eventCreateCmd.Flags().String("free-busy", "", "此日程的忙碌状态,默认值为busy。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲")
|
||
eventCreateCmd.Flags().String("freeBusy", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("freeBusy")
|
||
eventCreateCmd.Flags().String("freebusy", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("freebusy")
|
||
eventCreateCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定共享/订阅日历本时填写,可通过 book list 获取)")
|
||
eventCreateCmd.Flags().String("calendarId", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("calendarId")
|
||
eventCreateCmd.Flags().String("calendar", "", "")
|
||
_ = eventCreateCmd.Flags().MarkHidden("calendar")
|
||
eventCreateCmd.Flags().String("remind-minutes", "", "日程开始前提醒,逗号分隔分钟数 (可选,例如 5,10,15 表示开始前5/10/15分钟提醒;不传则默认15分钟提醒)")
|
||
|
||
// UpdateEvent flags
|
||
eventUpdateCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventUpdateCmd.Flags().String("event", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("event")
|
||
eventUpdateCmd.Flags().String("event-id", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("event-id")
|
||
eventUpdateCmd.Flags().String("eventId", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("eventId")
|
||
eventUpdateCmd.Flags().String("title", "", "新标题")
|
||
eventUpdateCmd.Flags().String("summary", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("summary")
|
||
eventUpdateCmd.Flags().String("start", "", "新开始时间 ISO-8601")
|
||
eventUpdateCmd.Flags().String("end", "", "新结束时间 ISO-8601")
|
||
eventUpdateCmd.Flags().String("start-time", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("start-time")
|
||
eventUpdateCmd.Flags().String("end-time", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("end-time")
|
||
eventUpdateCmd.Flags().String("startTime", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("startTime")
|
||
eventUpdateCmd.Flags().String("endTime", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("endTime")
|
||
eventUpdateCmd.Flags().String("start_time", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("start_time")
|
||
eventUpdateCmd.Flags().String("end_time", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("end_time")
|
||
eventUpdateCmd.Flags().String("start-date", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("start-date")
|
||
eventUpdateCmd.Flags().String("end-date", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("end-date")
|
||
eventUpdateCmd.Flags().String("startDate", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("startDate")
|
||
eventUpdateCmd.Flags().String("endDate", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("endDate")
|
||
eventUpdateCmd.Flags().String("desc", "", "新描述 (最大5000字符)")
|
||
eventUpdateCmd.Flags().String("description", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("description")
|
||
eventUpdateCmd.Flags().String("timezone", "", "时区 IANA 格式 (例如 Asia/Shanghai)")
|
||
eventUpdateCmd.Flags().String("recurrence-type", "", "[recurrence整体必填] 循环类型: daily|weekly|absoluteMonthly|relativeMonthly|absoluteYearly;MCP 不合并部分字段,修改任一循环字段都要重传完整 pattern+range")
|
||
eventUpdateCmd.Flags().Int("recurrence-interval", 0, "[recurrence整体必填] 循环间隔 (>0;如 daily 时表示每N天,weekly 时表示每N周)")
|
||
eventUpdateCmd.Flags().String("recurrence-days-of-week", "", "[recurrence.pattern] 周几: sunday,monday,...,saturday (weekly/relativeMonthly 时必填)")
|
||
eventUpdateCmd.Flags().Int("recurrence-day-of-month", 0, "[recurrence.pattern] 每月第几天 (absoluteMonthly/absoluteYearly 时必填)")
|
||
eventUpdateCmd.Flags().String("recurrence-index", "", "[recurrence.pattern] 每月第几周: first|second|third|fourth|last (relativeMonthly 时必填)")
|
||
eventUpdateCmd.Flags().String("recurrence-first-day-of-week", "", "[recurrence.pattern] 一周起始日,默认 sunday")
|
||
eventUpdateCmd.Flags().String("recurrence-range-type", "", "[recurrence整体必填] 循环范围: noEnd|endDate|numbered;与 --recurrence-type 必须成对出现")
|
||
eventUpdateCmd.Flags().String("recurrence-end-date", "", "[recurrence.range] 循环结束时间 ISO-8601 (range-type=endDate 时必填)")
|
||
eventUpdateCmd.Flags().Int("recurrence-count", 0, "[recurrence.range] 循环次数 (range-type=numbered 时必填)")
|
||
eventUpdateCmd.Flags().String("rich-text-desc", "", "html格式的富文本类型日程描述,用于复杂内容的展示")
|
||
eventUpdateCmd.Flags().String("location", "", "地点信息(纯文本备注,如‘3号楼A区’;不等于预订会议室)")
|
||
eventUpdateCmd.Flags().String("free-busy", "", "修改此日程的忙碌状态,无需修改则不传。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲")
|
||
eventUpdateCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)")
|
||
eventUpdateCmd.Flags().String("calendarId", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("calendarId")
|
||
eventUpdateCmd.Flags().String("calendar", "", "")
|
||
_ = eventUpdateCmd.Flags().MarkHidden("calendar")
|
||
|
||
// DeleteEvent flags
|
||
eventDeleteCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventDeleteCmd.Flags().String("event", "", "")
|
||
_ = eventDeleteCmd.Flags().MarkHidden("event")
|
||
eventDeleteCmd.Flags().String("event-id", "", "")
|
||
_ = eventDeleteCmd.Flags().MarkHidden("event-id")
|
||
eventDeleteCmd.Flags().String("eventId", "", "")
|
||
_ = eventDeleteCmd.Flags().MarkHidden("eventId")
|
||
eventDeleteCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)")
|
||
eventDeleteCmd.Flags().String("calendarId", "", "")
|
||
_ = eventDeleteCmd.Flags().MarkHidden("calendarId")
|
||
eventDeleteCmd.Flags().String("calendar", "", "")
|
||
_ = eventDeleteCmd.Flags().MarkHidden("calendar")
|
||
|
||
// RespondEvent flags
|
||
eventRespondCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventRespondCmd.Flags().String("event", "", "")
|
||
_ = eventRespondCmd.Flags().MarkHidden("event")
|
||
eventRespondCmd.Flags().String("event-id", "", "")
|
||
_ = eventRespondCmd.Flags().MarkHidden("event-id")
|
||
eventRespondCmd.Flags().String("eventId", "", "")
|
||
_ = eventRespondCmd.Flags().MarkHidden("eventId")
|
||
eventRespondCmd.Flags().String("status", "", "响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填)")
|
||
eventRespondCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取),注意:订阅日历下的日程无参会人,因此不可响应")
|
||
eventRespondCmd.Flags().String("calendarId", "", "")
|
||
_ = eventRespondCmd.Flags().MarkHidden("calendarId")
|
||
eventRespondCmd.Flags().String("calendar", "", "")
|
||
_ = eventRespondCmd.Flags().MarkHidden("calendar")
|
||
|
||
// SuggestEvent flags
|
||
eventSuggestCmd.Flags().String("start", "", "推荐时间范围开始 ISO-8601 (默认当前时间)")
|
||
eventSuggestCmd.Flags().String("end", "", "推荐时间范围结束 ISO-8601 (默认次日18点)")
|
||
eventSuggestCmd.Flags().String("time-min", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("time-min")
|
||
eventSuggestCmd.Flags().String("time-max", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("time-max")
|
||
eventSuggestCmd.Flags().String("min-time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("min-time")
|
||
eventSuggestCmd.Flags().String("max-time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("max-time")
|
||
eventSuggestCmd.Flags().String("start-time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("start-time")
|
||
eventSuggestCmd.Flags().String("end-time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("end-time")
|
||
eventSuggestCmd.Flags().String("startTime", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("startTime")
|
||
eventSuggestCmd.Flags().String("endTime", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("endTime")
|
||
eventSuggestCmd.Flags().String("start_time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("start_time")
|
||
eventSuggestCmd.Flags().String("end_time", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("end_time")
|
||
eventSuggestCmd.Flags().String("start-date", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("start-date")
|
||
eventSuggestCmd.Flags().String("end-date", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("end-date")
|
||
eventSuggestCmd.Flags().String("startDate", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("startDate")
|
||
eventSuggestCmd.Flags().String("endDate", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("endDate")
|
||
eventSuggestCmd.Flags().String("timezone", "", "时区 IANA 格式 (默认 Asia/Shanghai)")
|
||
eventSuggestCmd.Flags().String("users", "", "参会人 userId 列表,逗号分隔")
|
||
eventSuggestCmd.Flags().String("user-ids", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("user-ids")
|
||
eventSuggestCmd.Flags().String("userIds", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("userIds")
|
||
eventSuggestCmd.Flags().String("attendees", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("attendees")
|
||
eventSuggestCmd.Flags().String("attendee-ids", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("attendee-ids")
|
||
eventSuggestCmd.Flags().String("attendeeIds", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("attendeeIds")
|
||
eventSuggestCmd.Flags().String("attendee", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("attendee")
|
||
eventSuggestCmd.Flags().String("user", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("user")
|
||
eventSuggestCmd.Flags().String("participants", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("participants")
|
||
eventSuggestCmd.Flags().String("participant-ids", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("participant-ids")
|
||
eventSuggestCmd.Flags().String("participantIds", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("participantIds")
|
||
eventSuggestCmd.Flags().String("members", "", "")
|
||
_ = eventSuggestCmd.Flags().MarkHidden("members")
|
||
eventSuggestCmd.Flags().String("duration", "", "日程持续时间 (分钟,默认30)")
|
||
|
||
eventInstancesCmd := &cobra.Command{
|
||
Use: "instances",
|
||
Short: "查询循环日程的实例列表",
|
||
Long: `查询指定重复性日程(SeriesMaster)在指定时间范围内的所有实例。
|
||
**注意**:此接口只能查询重复性日程的实例;若传入的是普通非循环日程,将查不到任何实例信息。
|
||
必须传入 --id 指定重复性日程的 eventId(即 SeriesMaster 的 eventId,可通过 event list 获取)。
|
||
不传 --start/--end 时,默认查询今天(00:00:00 ~ 23:59:59)的实例。`,
|
||
Example: ` dws calendar event instances --id EVENT_ID
|
||
dws calendar event instances --id EVENT_ID --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00"
|
||
dws calendar event instances --id EVENT_ID --limit 50
|
||
dws calendar event instances --id EVENT_ID --cursor "<nextCursor>"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"eventId": eventID}
|
||
var startTime, endTime int64
|
||
var now time.Time
|
||
if v := flagOrFallback(cmd, "start", "time-min", "min-time", "start-time", "startTime", "start_time", "start-date", "startDate"); v != "" {
|
||
startTime, err = parseISOTimeToMillis("start", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["startTime"] = startTime
|
||
} else {
|
||
now = time.Now()
|
||
startTime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).UnixMilli()
|
||
toolArgs["startTime"] = startTime
|
||
}
|
||
if v := flagOrFallback(cmd, "end", "time-max", "max-time", "end-time", "endTime", "end_time", "end-date", "endDate"); v != "" {
|
||
endTime, err = parseISOTimeToMillis("end", v)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["endTime"] = endTime
|
||
} else {
|
||
if now.IsZero() {
|
||
now = time.Now()
|
||
}
|
||
endTime = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).UnixMilli()
|
||
toolArgs["endTime"] = endTime
|
||
}
|
||
if err := validateTimeRange(startTime, endTime); err != nil {
|
||
return err
|
||
}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "cursor", "next-cursor", "nextCursor", "page-token", "pageToken", "next-token"); v != "" {
|
||
toolArgs["cursor"] = v
|
||
}
|
||
if lim, _ := cmd.Flags().GetInt("limit"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("max-results"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("maxResults"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("page-size"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("size"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
} else if lim, _ := cmd.Flags().GetInt("count"); lim > 0 {
|
||
toolArgs["limit"] = lim
|
||
}
|
||
return callSortedCalendarEvents(cmd, "list_event_instances", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventInstancesCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "list_event_instances",
|
||
CanonicalPath: "calendar.list_event_instances",
|
||
CLIPath: "calendar event instances",
|
||
PrimaryCLIPath: "calendar event instances",
|
||
},
|
||
Description: "查询循环日程的实例列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "list_event_instances"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询循环日程在时间范围内展开的实例",
|
||
UseWhen: []string{"已知循环日程 eventId(SeriesMaster),需要列出某时间窗内的实例时"},
|
||
AvoidWhen: []string{
|
||
"普通非循环日程请用 dws calendar event get / list",
|
||
"未知 eventId 时先 dws calendar event list",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event instances --id <EVENT_ID>",
|
||
"dws calendar event instances --id <EVENT_ID> --start \"2026-03-10T00:00:00+08:00\" --end \"2026-03-31T23:59:59+08:00\"",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "eventId", Required: boolPtr(true)},
|
||
{Name: "start", Property: "startTime"},
|
||
{Name: "end", Property: "endTime"},
|
||
{Name: "calendar-id", Property: "calendarId"},
|
||
{Name: "cursor", Property: "cursor"},
|
||
{Name: "limit", Property: "limit", InterfaceType: "integer"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// InstancesEvent flags (aligned with event list aliases)
|
||
eventInstancesCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventInstancesCmd.Flags().String("event", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("event")
|
||
eventInstancesCmd.Flags().String("event-id", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("event-id")
|
||
eventInstancesCmd.Flags().String("eventId", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("eventId")
|
||
eventInstancesCmd.Flags().String("start", "", "开始时间 ISO-8601 (例如 2026-03-10T00:00:00+08:00)")
|
||
eventInstancesCmd.Flags().String("time-min", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("time-min")
|
||
eventInstancesCmd.Flags().String("min-time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("min-time")
|
||
eventInstancesCmd.Flags().String("start-time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("start-time")
|
||
eventInstancesCmd.Flags().String("startTime", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("startTime")
|
||
eventInstancesCmd.Flags().String("start_time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("start_time")
|
||
eventInstancesCmd.Flags().String("start-date", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("start-date")
|
||
eventInstancesCmd.Flags().String("startDate", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("startDate")
|
||
eventInstancesCmd.Flags().String("end", "", "结束时间 ISO-8601 (例如 2026-03-31T23:59:59+08:00)")
|
||
eventInstancesCmd.Flags().String("time-max", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("time-max")
|
||
eventInstancesCmd.Flags().String("max-time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("max-time")
|
||
eventInstancesCmd.Flags().String("end-time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("end-time")
|
||
eventInstancesCmd.Flags().String("endTime", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("endTime")
|
||
eventInstancesCmd.Flags().String("end_time", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("end_time")
|
||
eventInstancesCmd.Flags().String("end-date", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("end-date")
|
||
eventInstancesCmd.Flags().String("endDate", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("endDate")
|
||
eventInstancesCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)")
|
||
eventInstancesCmd.Flags().String("calendarId", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("calendarId")
|
||
eventInstancesCmd.Flags().String("calendar", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("calendar")
|
||
eventInstancesCmd.Flags().String("cursor", "", "分页游标 (首次查询无需传入,仅翻页时传入上一次返回的 nextCursor)")
|
||
eventInstancesCmd.Flags().String("next-cursor", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("next-cursor")
|
||
eventInstancesCmd.Flags().String("nextCursor", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("nextCursor")
|
||
eventInstancesCmd.Flags().String("page-token", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("page-token")
|
||
eventInstancesCmd.Flags().String("pageToken", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("pageToken")
|
||
eventInstancesCmd.Flags().String("next-token", "", "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("next-token")
|
||
eventInstancesCmd.Flags().Int("limit", 0, "每页返回条数 (默认 100,最大 100)")
|
||
eventInstancesCmd.Flags().Int("max-results", 0, "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("max-results")
|
||
eventInstancesCmd.Flags().Int("maxResults", 0, "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("maxResults")
|
||
eventInstancesCmd.Flags().Int("page-size", 0, "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("page-size")
|
||
eventInstancesCmd.Flags().Int("size", 0, "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("size")
|
||
eventInstancesCmd.Flags().Int("count", 0, "")
|
||
_ = eventInstancesCmd.Flags().MarkHidden("count")
|
||
|
||
eventShareInfoCmd := &cobra.Command{
|
||
Use: "share-info",
|
||
Aliases: []string{"share_info"},
|
||
Short: "获取日程的分享信息",
|
||
Long: `根据日程 ID 获取日程的分享信息,展示日程主题、组织人、地点、入会信息等,用于向他人分享日程。eventId 可通过 dws calendar event list 获取。`,
|
||
Example: ` dws calendar event share-info --id EVENT_ID
|
||
dws calendar event share-info --id EVENT_ID --language zh-CN
|
||
dws calendar event share-info --id EVENT_ID --calendar-id primary`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
eventID, err := mustFlagOrFallback(cmd, "id", "event", "event-id", "eventId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs := map[string]any{"eventId": eventID}
|
||
if v := flagOrFallback(cmd, "calendar-id", "calendarId", "calendar"); v != "" {
|
||
toolArgs["calendarId"] = v
|
||
}
|
||
if v := flagOrFallback(cmd, "language", "lang"); v != "" {
|
||
toolArgs["language"] = v
|
||
}
|
||
return callMCPTool("get_event_share_info", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(eventShareInfoCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "calendar",
|
||
Name: "get_event_share_info",
|
||
CanonicalPath: "calendar.get_event_share_info",
|
||
CLIPath: "calendar event share-info",
|
||
PrimaryCLIPath: "calendar event share-info",
|
||
Aliases: []string{"calendar event share_info"},
|
||
},
|
||
Description: "获取日程的分享信息",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "calendar", RPCName: "get_event_share_info"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "获取日程的分享信息(主题、组织人、地点、入会信息等)",
|
||
UseWhen: []string{"已知 eventId,需要获取日程分享信息或入会信息用于分享给他人时"},
|
||
AvoidWhen: []string{
|
||
"要查看日程详情改用 dws calendar event get",
|
||
"未知 eventId 时先 dws calendar event list",
|
||
},
|
||
Examples: []string{
|
||
"dws calendar event share-info --id <EVENT_ID>",
|
||
"dws calendar event share-info --id <EVENT_ID> --language zh-CN",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "eventId", Required: boolPtr(true)},
|
||
{Name: "calendar-id", Property: "calendarId"},
|
||
{Name: "language", Property: "language"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// EventShareInfo flags
|
||
eventShareInfoCmd.Flags().String("id", "", "日程 ID (必填)")
|
||
eventShareInfoCmd.Flags().String("event", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("event")
|
||
eventShareInfoCmd.Flags().String("event-id", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("event-id")
|
||
eventShareInfoCmd.Flags().String("eventId", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("eventId")
|
||
eventShareInfoCmd.Flags().String("calendar-id", "", "日历 ID (默认 primary 主日历)")
|
||
eventShareInfoCmd.Flags().String("calendarId", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("calendarId")
|
||
eventShareInfoCmd.Flags().String("calendar", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("calendar")
|
||
eventShareInfoCmd.Flags().String("language", "", "语言代码 (可选,如 zh-CN)")
|
||
eventShareInfoCmd.Flags().String("lang", "", "")
|
||
_ = eventShareInfoCmd.Flags().MarkHidden("lang")
|
||
|
||
eventCmd.AddCommand(eventListCmd, eventGetCmd, eventCreateCmd, eventUpdateCmd, eventDeleteCmd, eventSuggestCmd, eventRespondCmd, eventInstancesCmd, eventShareInfoCmd)
|
||
|
||
// participant
|
||
participantCmd.PersistentFlags().String("event", "", "日程 ID (必填)")
|
||
participantCmd.PersistentFlags().String("id", "", "")
|
||
_ = participantCmd.PersistentFlags().MarkHidden("id")
|
||
participantCmd.PersistentFlags().String("event-id", "", "")
|
||
_ = participantCmd.PersistentFlags().MarkHidden("event-id")
|
||
participantCmd.PersistentFlags().String("eventId", "", "")
|
||
_ = participantCmd.PersistentFlags().MarkHidden("eventId")
|
||
participantCmd.PersistentFlags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)。注意:订阅日历下的日程无参会人")
|
||
participantCmd.PersistentFlags().String("calendarId", "", "")
|
||
_ = participantCmd.PersistentFlags().MarkHidden("calendarId")
|
||
participantCmd.PersistentFlags().String("calendar", "", "")
|
||
_ = participantCmd.PersistentFlags().MarkHidden("calendar")
|
||
|
||
// AddAttendee flags
|
||
participantAddCmd.Flags().String("attendees", "", "参会人 userId 列表,逗号分隔 (必填,最多500人)")
|
||
participantAddCmd.Flags().String("users", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("users")
|
||
participantAddCmd.Flags().String("user-ids", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("user-ids")
|
||
participantAddCmd.Flags().String("userIds", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("userIds")
|
||
participantAddCmd.Flags().String("attendee-ids", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("attendee-ids")
|
||
participantAddCmd.Flags().String("attendeeIds", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("attendeeIds")
|
||
participantAddCmd.Flags().String("attendee", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("attendee")
|
||
participantAddCmd.Flags().String("user", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("user")
|
||
participantAddCmd.Flags().String("participants", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("participants")
|
||
participantAddCmd.Flags().String("participant-ids", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("participant-ids")
|
||
participantAddCmd.Flags().String("participantIds", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("participantIds")
|
||
participantAddCmd.Flags().String("members", "", "")
|
||
_ = participantAddCmd.Flags().MarkHidden("members")
|
||
participantAddCmd.Flags().Bool("optional", false, "参会人可选 (默认必选参会人)")
|
||
|
||
// DeleteAttendee flags
|
||
participantDeleteCmd.Flags().String("attendees", "", "参会人 userId 列表,逗号分隔 (必填)")
|
||
participantDeleteCmd.Flags().String("users", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("users")
|
||
participantDeleteCmd.Flags().String("user-ids", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("user-ids")
|
||
participantDeleteCmd.Flags().String("userIds", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("userIds")
|
||
participantDeleteCmd.Flags().String("attendee-ids", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("attendee-ids")
|
||
participantDeleteCmd.Flags().String("attendeeIds", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("attendeeIds")
|
||
participantDeleteCmd.Flags().String("attendee", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("attendee")
|
||
participantDeleteCmd.Flags().String("user", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("user")
|
||
participantDeleteCmd.Flags().String("participants", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("participants")
|
||
participantDeleteCmd.Flags().String("participant-ids", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("participant-ids")
|
||
participantDeleteCmd.Flags().String("participantIds", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("participantIds")
|
||
participantDeleteCmd.Flags().String("members", "", "")
|
||
_ = participantDeleteCmd.Flags().MarkHidden("members")
|
||
participantCmd.AddCommand(participantListCmd, participantAddCmd, participantDeleteCmd)
|
||
|
||
// room
|
||
// SearchRoom flags
|
||
roomSearchCmd.Flags().String("start", "", "开始时间 ISO-8601 (可选,不传则默认当前时间+1分钟,例如 2026-03-10T14:00:00+08:00)")
|
||
roomSearchCmd.Flags().String("end", "", "结束时间 ISO-8601 (可选,不传则默认当前时间+1小时,例如 2026-03-10T15:00:00+08:00)")
|
||
roomSearchCmd.Flags().String("time-min", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("time-min")
|
||
roomSearchCmd.Flags().String("time-max", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("time-max")
|
||
roomSearchCmd.Flags().String("min-time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("min-time")
|
||
roomSearchCmd.Flags().String("max-time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("max-time")
|
||
roomSearchCmd.Flags().String("start-time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("start-time")
|
||
roomSearchCmd.Flags().String("end-time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("end-time")
|
||
roomSearchCmd.Flags().String("startTime", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("startTime")
|
||
roomSearchCmd.Flags().String("endTime", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("endTime")
|
||
roomSearchCmd.Flags().String("start_time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("start_time")
|
||
roomSearchCmd.Flags().String("end_time", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("end_time")
|
||
roomSearchCmd.Flags().String("start-date", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("start-date")
|
||
roomSearchCmd.Flags().String("end-date", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("end-date")
|
||
roomSearchCmd.Flags().String("startDate", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("startDate")
|
||
roomSearchCmd.Flags().String("endDate", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("endDate")
|
||
// mcp里无此参数
|
||
roomSearchCmd.Flags().Bool("available", false, "仅查空闲会议室")
|
||
_ = roomSearchCmd.Flags().MarkHidden("available")
|
||
roomSearchCmd.Flags().String("group-id", "", "会议室分组ID(可选,留空查根目录;会议室超100条时先用 list-groups 获取分组再按分组查询)")
|
||
roomSearchCmd.Flags().String("room-name", "", "按会议室名称过滤(可选,服务端模糊匹配。调用方需先剔除用户口语后缀如「会议室/大会议室/小会议室」,仅传核心专名以提升命中率,例如用户说「永澄亭会议室」应传「永澄亭」)")
|
||
roomSearchCmd.Flags().String("name", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("name")
|
||
roomSearchCmd.Flags().String("roomName", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("roomName")
|
||
roomSearchCmd.Flags().String("query", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("query")
|
||
roomSearchCmd.Flags().String("limit", "", "页大小 (可选,不填默认 100,超过 100 按 100 处理)")
|
||
roomSearchCmd.Flags().String("page-size", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("page-size")
|
||
roomSearchCmd.Flags().String("size", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("size")
|
||
roomSearchCmd.Flags().String("count", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("count")
|
||
roomSearchCmd.Flags().String("page", "", "分页起始位置 (可选,不填默认 0)")
|
||
roomSearchCmd.Flags().String("page-index", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("page-index")
|
||
roomSearchCmd.Flags().String("page-num", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("page-num")
|
||
roomSearchCmd.Flags().String("page-no", "", "")
|
||
_ = roomSearchCmd.Flags().MarkHidden("page-no")
|
||
|
||
// AddRoom flags
|
||
roomAddCmd.Flags().String("event", "", "日程 ID (必填)")
|
||
roomAddCmd.Flags().String("id", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("id")
|
||
roomAddCmd.Flags().String("event-id", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("event-id")
|
||
roomAddCmd.Flags().String("eventId", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("eventId")
|
||
roomAddCmd.Flags().String("rooms", "", "会议室 ID 列表 (必填)")
|
||
roomAddCmd.Flags().String("room-ids", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("room-ids")
|
||
roomAddCmd.Flags().String("roomIds", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("roomIds")
|
||
roomAddCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取),注意:订阅日历下的日程不支持添加会议室")
|
||
roomAddCmd.Flags().String("calendarId", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("calendarId")
|
||
roomAddCmd.Flags().String("calendar", "", "")
|
||
_ = roomAddCmd.Flags().MarkHidden("calendar")
|
||
|
||
// DeleteRoom flags
|
||
roomDeleteCmd.Flags().String("event", "", "日程 ID (必填)")
|
||
roomDeleteCmd.Flags().String("id", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("id")
|
||
roomDeleteCmd.Flags().String("event-id", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("event-id")
|
||
roomDeleteCmd.Flags().String("eventId", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("eventId")
|
||
roomDeleteCmd.Flags().String("rooms", "", "会议室 ID 列表 (必填)")
|
||
roomDeleteCmd.Flags().String("room-ids", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("room-ids")
|
||
roomDeleteCmd.Flags().String("roomIds", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("roomIds")
|
||
roomDeleteCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)。注意:订阅日历下的日程无会议室")
|
||
roomDeleteCmd.Flags().String("calendarId", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("calendarId")
|
||
roomDeleteCmd.Flags().String("calendar", "", "")
|
||
_ = roomDeleteCmd.Flags().MarkHidden("calendar")
|
||
|
||
// ListRoomGroups flags
|
||
roomListGroupsCmd.Flags().String("limit", "", "页大小 (可选,不填默认 100,超过 100 按 100 处理)")
|
||
roomListGroupsCmd.Flags().String("page-size", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("page-size")
|
||
roomListGroupsCmd.Flags().String("size", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("size")
|
||
roomListGroupsCmd.Flags().String("count", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("count")
|
||
roomListGroupsCmd.Flags().String("page", "", "分页起始位置 (可选,不填默认 0)")
|
||
roomListGroupsCmd.Flags().String("page-index", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("page-index")
|
||
roomListGroupsCmd.Flags().String("page-num", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("page-num")
|
||
roomListGroupsCmd.Flags().String("page-no", "", "")
|
||
_ = roomListGroupsCmd.Flags().MarkHidden("page-no")
|
||
|
||
roomCmd.AddCommand(roomSearchCmd, roomAddCmd, roomDeleteCmd, roomListGroupsCmd)
|
||
|
||
// busy
|
||
|
||
// SearchBusy flags
|
||
busySearchCmd.Flags().String("users", "", "用户 ID 列表,逗号分隔 (与 --rooms 至少指定其一)")
|
||
busySearchCmd.Flags().String("rooms", "", "会议室 ID 列表,逗号分隔 (与 --users 至少指定其一,用于查询会议室闲忙)")
|
||
busySearchCmd.Flags().String("user-ids", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("user-ids")
|
||
busySearchCmd.Flags().String("userIds", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("userIds")
|
||
busySearchCmd.Flags().String("attendees", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("attendees")
|
||
busySearchCmd.Flags().String("attendee-ids", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("attendee-ids")
|
||
busySearchCmd.Flags().String("attendeeIds", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("attendeeIds")
|
||
busySearchCmd.Flags().String("attendee", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("attendee")
|
||
busySearchCmd.Flags().String("user", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("user")
|
||
busySearchCmd.Flags().String("participants", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("participants")
|
||
busySearchCmd.Flags().String("participant-ids", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("participant-ids")
|
||
busySearchCmd.Flags().String("participantIds", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("participantIds")
|
||
busySearchCmd.Flags().String("members", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("members")
|
||
busySearchCmd.Flags().String("room-ids", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("room-ids")
|
||
busySearchCmd.Flags().String("roomIds", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("roomIds")
|
||
busySearchCmd.Flags().String("start", "", "开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00)")
|
||
busySearchCmd.Flags().String("end", "", "结束时间 ISO-8601 (必填,例如 2026-03-10T18:00:00+08:00)")
|
||
busySearchCmd.Flags().String("time-min", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("time-min")
|
||
busySearchCmd.Flags().String("time-max", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("time-max")
|
||
busySearchCmd.Flags().String("min-time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("min-time")
|
||
busySearchCmd.Flags().String("max-time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("max-time")
|
||
busySearchCmd.Flags().String("start-time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("start-time")
|
||
busySearchCmd.Flags().String("end-time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("end-time")
|
||
busySearchCmd.Flags().String("startTime", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("startTime")
|
||
busySearchCmd.Flags().String("endTime", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("endTime")
|
||
busySearchCmd.Flags().String("start_time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("start_time")
|
||
busySearchCmd.Flags().String("end_time", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("end_time")
|
||
busySearchCmd.Flags().String("start-date", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("start-date")
|
||
busySearchCmd.Flags().String("end-date", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("end-date")
|
||
busySearchCmd.Flags().String("startDate", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("startDate")
|
||
busySearchCmd.Flags().String("endDate", "", "")
|
||
_ = busySearchCmd.Flags().MarkHidden("endDate")
|
||
|
||
busyCmd.AddCommand(busySearchCmd)
|
||
|
||
// attachment
|
||
|
||
// AddAttachment flags
|
||
attachmentAddCmd.Flags().String("event", "", "日程 ID (必填)")
|
||
attachmentAddCmd.Flags().String("id", "", "")
|
||
_ = attachmentAddCmd.Flags().MarkHidden("id")
|
||
attachmentAddCmd.Flags().String("event-id", "", "")
|
||
_ = attachmentAddCmd.Flags().MarkHidden("event-id")
|
||
attachmentAddCmd.Flags().String("eventId", "", "")
|
||
_ = attachmentAddCmd.Flags().MarkHidden("eventId")
|
||
attachmentAddCmd.Flags().String("files", "", "附件列表,格式 <fileId>:<name>,多项逗号分隔 (必填)")
|
||
attachmentAddCmd.Flags().String("calendar-id", "", "日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 book list 获取)。注意:订阅日历下的日程不支持添加附件")
|
||
attachmentAddCmd.Flags().String("calendarId", "", "")
|
||
_ = attachmentAddCmd.Flags().MarkHidden("calendarId")
|
||
attachmentAddCmd.Flags().String("calendar", "", "")
|
||
_ = attachmentAddCmd.Flags().MarkHidden("calendar")
|
||
|
||
attachmentCmd.AddCommand(attachmentAddCmd)
|
||
|
||
// book
|
||
// GetBook flags
|
||
bookGetCmd.Flags().String("id", "", "日历 ID (必填,主日历固定为 primary)")
|
||
bookGetCmd.Flags().String("calendar-id", "", "")
|
||
_ = bookGetCmd.Flags().MarkHidden("calendar-id")
|
||
bookGetCmd.Flags().String("calendarId", "", "")
|
||
_ = bookGetCmd.Flags().MarkHidden("calendarId")
|
||
|
||
// SearchBook flags
|
||
bookSearchCmd.Flags().String("query", "", "按日历本名称模糊检索 (必填)")
|
||
bookSearchCmd.Flags().String("keyword", "", "")
|
||
_ = bookSearchCmd.Flags().MarkHidden("keyword")
|
||
bookSearchCmd.Flags().String("keywords", "", "")
|
||
_ = bookSearchCmd.Flags().MarkHidden("keywords")
|
||
bookSearchCmd.Flags().String("search", "", "")
|
||
_ = bookSearchCmd.Flags().MarkHidden("search")
|
||
|
||
// UpdateBook flags
|
||
bookUpdateCmd.Flags().String("id", "", "日历 ID (必填)")
|
||
bookUpdateCmd.Flags().String("calendar-id", "", "")
|
||
_ = bookUpdateCmd.Flags().MarkHidden("calendar-id")
|
||
bookUpdateCmd.Flags().String("calendarId", "", "")
|
||
_ = bookUpdateCmd.Flags().MarkHidden("calendarId")
|
||
bookUpdateCmd.Flags().String("summary", "", "日历标题")
|
||
bookUpdateCmd.Flags().String("title", "", "")
|
||
_ = bookUpdateCmd.Flags().MarkHidden("title")
|
||
bookUpdateCmd.Flags().String("desc", "", "日历描述")
|
||
bookUpdateCmd.Flags().String("description", "", "")
|
||
_ = bookUpdateCmd.Flags().MarkHidden("description")
|
||
|
||
bookCmd.AddCommand(bookListCmd, bookGetCmd, bookSearchCmd, bookUpdateCmd)
|
||
|
||
// acl
|
||
// AddAcl flags
|
||
aclAddCmd.Flags().String("user", "", "授予权限的目标用户 ID (必填)")
|
||
aclAddCmd.Flags().String("user-id", "", "")
|
||
_ = aclAddCmd.Flags().MarkHidden("user-id")
|
||
aclAddCmd.Flags().String("userId", "", "")
|
||
_ = aclAddCmd.Flags().MarkHidden("userId")
|
||
aclAddCmd.Flags().String("privilege", "", "授予的日历权限 (必填): free_busy_reader|title_reader|reader|writer")
|
||
aclAddCmd.Flags().Bool("no-notification", false, "不向被授权用户发送提醒 (默认发送)")
|
||
|
||
// DeleteAcl flags
|
||
aclDeleteCmd.Flags().String("acl-id", "", "已授予权限的 ID (必填,可通过 acl list 查询)")
|
||
aclDeleteCmd.Flags().String("aclId", "", "")
|
||
_ = aclDeleteCmd.Flags().MarkHidden("aclId")
|
||
aclDeleteCmd.Flags().String("id", "", "")
|
||
_ = aclDeleteCmd.Flags().MarkHidden("id")
|
||
|
||
aclCmd.AddCommand(aclListCmd, aclAddCmd, aclDeleteCmd)
|
||
|
||
root.AddCommand(eventCmd, participantCmd, roomCmd, busyCmd, attachmentCmd, bookCmd, aclCmd)
|
||
|
||
// Install the unknown-verb fallback on every group command. This covers
|
||
// arbitrary typos like `dws calendar room query --min-duration 30` that
|
||
// the per-verb calendarInfoHintSubCmd registrations below can't anticipate.
|
||
for _, g := range []*cobra.Command{root, eventCmd, participantCmd, roomCmd, busyCmd, attachmentCmd, bookCmd, aclCmd} {
|
||
installUnknownVerbFallback(g)
|
||
}
|
||
// Hint subcommands must swallow any extra flags/args the caller passes,
|
||
// otherwise `dws calendar list` prints the nice "ambiguous command" hint
|
||
// but `dws calendar list --start ...` fails earlier with cobra's
|
||
// `unknown flag: --start`. Disabling flag parsing keeps the message
|
||
// consistent regardless of what the user typed after the wrong verb.
|
||
root.AddCommand(
|
||
calendarInfoHintSubCmd("list", "use: 'dws calendar event list' for list events (or 'dws calendar book list' for list calendars)"),
|
||
calendarInfoHintSubCmd("today", "use: 'dws calendar event list' (defaults to today's schedule)"),
|
||
calendarInfoHintSubCmd("get", "did you mean: 'dws calendar event get'"),
|
||
calendarInfoHintSubCmd("create", "did you mean: 'dws calendar event create'"),
|
||
calendarInfoHintSubCmd("update", "did you mean: 'dws calendar event update'"),
|
||
calendarInfoHintSubCmd("delete", "did you mean: 'dws calendar event delete'"),
|
||
calendarInfoHintSubCmd("suggest", "did you mean: 'dws calendar event suggest'"),
|
||
calendarInfoHintSubCmd("respond", "did you mean: 'dws calendar event respond'"),
|
||
calendarInfoHintSubCmd("add", "use: 'dws calendar attendee add' for attendees (or 'dws calendar room add' for add rooms to event, 'dws calendar attachment add' for add attachments to event)"),
|
||
calendarInfoHintSubCmd("search", "use: 'dws calendar room search' for rooms (or 'dws calendar busy search' for user/room busy status)"),
|
||
calendarInfoHintSubCmd("list-groups", "did you mean: 'dws calendar room list-groups'"),
|
||
)
|
||
|
||
return root
|
||
}
|
||
|
||
// meetingRoomDisabledHint is the user-facing hint surfaced when the upstream
|
||
// MCP returns errorCode 400056 ("所选组织不支持预定钉钉会议室") on any
|
||
// meeting-room related calendar call. Kept calendar-local on purpose so the
|
||
// shared business-error mapper in errors.go stays product-agnostic.
|
||
const meetingRoomDisabledHint = "当前企业使用的企业自建会议室系统,无法使用dws会议室相关能力(如 dws calendar room search / list-groups )。请前往客户端中手动完成预订"
|
||
|
||
// isMeetingRoomDisabledError reports whether the given message is the upstream
|
||
// "meeting room not enabled" business error (errorCode 400056).
|
||
func isMeetingRoomDisabledError(msg string) bool {
|
||
return strings.Contains(msg, "所选组织不支持预定钉钉会议室") ||
|
||
strings.Contains(msg, `"errorCode": "400056"`) ||
|
||
strings.Contains(msg, `"errorCode":"400056"`)
|
||
}
|
||
|
||
// withMeetingRoomDisabledHint replaces the Suggestion on a meeting-room
|
||
// business error with a calendar-specific actionable hint. Non-matching
|
||
// errors and non-CLIError types are returned unchanged.
|
||
func withMeetingRoomDisabledHint(err error) error {
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
cliErr, ok := err.(*CLIError)
|
||
if !ok {
|
||
return err
|
||
}
|
||
if !isMeetingRoomDisabledError(cliErr.Message) {
|
||
return err
|
||
}
|
||
cliErr.Suggestion = meetingRoomDisabledHint
|
||
return cliErr
|
||
}
|
||
|
||
// callMeetingRoomMCPTool wraps callMCPTool for meeting-room MCP calls whose
|
||
// CLI surface is currently `dws calendar room *`. On the upstream
|
||
// "meeting room not enabled" business error (errorCode 400056) the generic
|
||
// suggestion is replaced with a calendar-specific actionable hint.
|
||
func callMeetingRoomMCPTool(toolName string, args map[string]any) error {
|
||
return withMeetingRoomDisabledHint(callMCPTool(toolName, args))
|
||
}
|
||
|
||
// callSearchRoomsByName calls the search_rooms MCP tool (name-only fuzzy search,
|
||
// no availability check) and injects a `searchMode` hint so callers know the
|
||
// results are not filtered by time availability.
|
||
func callSearchRoomsByName(cmd *cobra.Command, toolArgs map[string]any) error {
|
||
raw, err := callMCPToolReturnText(cmd.Context(), "search_rooms", toolArgs)
|
||
if err != nil {
|
||
return withMeetingRoomDisabledHint(err)
|
||
}
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
|
||
var parsed map[string]any
|
||
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// Inject unified hint so callers know this is name-only search (no availability check)
|
||
hint := map[string]any{
|
||
"searchMode": "name-only",
|
||
"hint": "本次为按名称搜索,未检查会议室可用性。如需查询指定时间段内的可用会议室,请添加 --start/--end 参数。",
|
||
}
|
||
if result, ok := parsed["result"].(map[string]any); ok {
|
||
result["hint"] = hint
|
||
} else {
|
||
parsed["hint"] = hint
|
||
}
|
||
|
||
switch deps.Caller.Format() {
|
||
case "json":
|
||
return deps.Out.PrintJSON(parsed)
|
||
case "raw", "table":
|
||
b, err := json.MarshalIndent(parsed, "", " ")
|
||
if err == nil {
|
||
deps.Out.PrintRaw(string(b))
|
||
return nil
|
||
}
|
||
}
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// callMeetingRoomSearchResult calls query_available_meeting_room and injects a
|
||
// `searchRange` hint into the response so callers can see the actual queried
|
||
// time window. The room-search CLI silently defaults to now+1min~now+1h when
|
||
// --start/--end are omitted (and also auto-corrects a past --start to now+1min),
|
||
// which is otherwise invisible to the caller.
|
||
func callMeetingRoomSearchResult(cmd *cobra.Command, toolArgs map[string]any, startProvided, endProvided, startCorrected bool) error {
|
||
raw, err := callMCPToolReturnText(cmd.Context(), "query_available_meeting_room", toolArgs)
|
||
if err != nil {
|
||
return withMeetingRoomDisabledHint(err)
|
||
}
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
|
||
var parsed map[string]any
|
||
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// Inject unified hint so callers know this is availability search (time-range based)
|
||
hint := map[string]any{
|
||
"searchMode": "availability",
|
||
"hint": "本次为按时间段搜索可用会议室,仅返回指定时间范围内可预定的会议室。",
|
||
}
|
||
attachRoomSearchRange(hint, toolArgs, startProvided, endProvided, startCorrected)
|
||
if result, ok := parsed["result"].(map[string]any); ok {
|
||
result["hint"] = hint
|
||
} else {
|
||
parsed["hint"] = hint
|
||
}
|
||
|
||
switch deps.Caller.Format() {
|
||
case "json":
|
||
return deps.Out.PrintJSON(parsed)
|
||
case "raw", "table":
|
||
b, err := json.MarshalIndent(parsed, "", " ")
|
||
if err == nil {
|
||
deps.Out.PrintRaw(string(b))
|
||
return nil
|
||
}
|
||
}
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// attachRoomSearchRange injects the actual queried [startTime, endTime] window
|
||
// plus a human-readable hint into the provided hint map, indicating whether
|
||
// --start/--end were user-set, defaulted (now+1min ~ now+1h), or auto-corrected
|
||
// from a past --start.
|
||
func attachRoomSearchRange(hint map[string]any, toolArgs map[string]any, startProvided, endProvided, startCorrected bool) {
|
||
startMs, sOK := toolArgs["startTime"].(int64)
|
||
endMs, eOK := toolArgs["endTime"].(int64)
|
||
if !sOK || !eOK {
|
||
return
|
||
}
|
||
loc := time.Now().Location()
|
||
|
||
var rangeHint string
|
||
switch {
|
||
case !startProvided && !endProvided:
|
||
rangeHint = "未显式指定 --start/--end,已默认查询 当前时间+1分钟 起 1 小时内的会议室(CLI 本地时区)。如需其它区间请显式传入 --start 和 --end。"
|
||
case !startProvided:
|
||
rangeHint = "未显式指定 --start,已默认使用 当前时间+1分钟(CLI 本地时区)。"
|
||
case !endProvided:
|
||
rangeHint = "未显式指定 --end,已默认使用 当前时间+1小时(CLI 本地时区)。"
|
||
default:
|
||
rangeHint = "本次查询范围由调用方显式指定。"
|
||
}
|
||
if startCorrected {
|
||
rangeHint += "传入的 --start 早于当前时间,已自动修正为 当前时间+1分钟(API 限制:startTime 必须是未来时间)。"
|
||
}
|
||
|
||
hint["searchRange"] = map[string]any{
|
||
"startTime": time.UnixMilli(startMs).In(loc).Format(time.RFC3339),
|
||
"endTime": time.UnixMilli(endMs).In(loc).Format(time.RFC3339),
|
||
"hint": rangeHint,
|
||
}
|
||
}
|
||
|
||
func callFilteredBusyStatus(cmd *cobra.Command, toolArgs map[string]any) error {
|
||
raw, err := callMCPToolReturnText(cmd.Context(), "query_busy_status", toolArgs)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
|
||
var parsed map[string]any
|
||
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// Filter out invalid scheduleItems (a valid item must have a non-null status)
|
||
if results, ok := parsed["result"].([]any); ok {
|
||
for _, entry := range results {
|
||
if m, ok := entry.(map[string]any); ok {
|
||
if items, ok := m["scheduleItems"].([]any); ok {
|
||
filtered := make([]any, 0, len(items))
|
||
for _, item := range items {
|
||
if si, ok := item.(map[string]any); ok {
|
||
if status, exists := si["status"]; exists && status != nil && status != "" {
|
||
filtered = append(filtered, item)
|
||
}
|
||
}
|
||
}
|
||
m["scheduleItems"] = filtered
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
switch deps.Caller.Format() {
|
||
case "json":
|
||
return deps.Out.PrintJSON(parsed)
|
||
case "raw", "table":
|
||
b, err := json.MarshalIndent(parsed, "", " ")
|
||
if err == nil {
|
||
deps.Out.PrintRaw(string(b))
|
||
return nil
|
||
}
|
||
}
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
func callSortedCalendarEvents(cmd *cobra.Command, toolName string, toolArgs map[string]any) error {
|
||
if deps.Caller.DryRun() {
|
||
return callMCPTool(toolName, toolArgs)
|
||
}
|
||
|
||
raw, err := callMCPToolReturnText(cmd.Context(), toolName, toolArgs)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
|
||
var parsed map[string]any
|
||
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// Attach searchRange hint so callers know the actual queried time window.
|
||
// `event list` defaults to today's 00:00:00~23:59:59 when --start/--end
|
||
// are omitted, which is easy for callers to miss.
|
||
attachCalendarSearchRange(parsed, cmd, toolArgs)
|
||
|
||
result, ok := parsed["result"].(map[string]any)
|
||
if ok {
|
||
if events, ok := result["events"].([]any); ok {
|
||
// Filter out invalid events (a valid event must have an id)
|
||
filtered := make([]any, 0, len(events))
|
||
for _, e := range events {
|
||
if m, ok := e.(map[string]any); ok {
|
||
if id, exists := m["id"]; exists && id != nil && id != "" {
|
||
filtered = append(filtered, e)
|
||
}
|
||
}
|
||
}
|
||
sort.SliceStable(filtered, func(i, j int) bool {
|
||
return calendarEventSortKey(filtered[i]) < calendarEventSortKey(filtered[j])
|
||
})
|
||
result["events"] = filtered
|
||
}
|
||
// Surface pagination hint when nextCursor is present
|
||
if nc, ok := result["nextCursor"].(string); ok && nc != "" {
|
||
result["paginationHint"] = "还有更多日程,使用 --cursor \"" + nc + "\" 获取下一页"
|
||
}
|
||
}
|
||
|
||
switch deps.Caller.Format() {
|
||
case "json":
|
||
return deps.Out.PrintJSON(parsed)
|
||
case "raw", "table":
|
||
b, err := json.MarshalIndent(parsed, "", " ")
|
||
if err == nil {
|
||
deps.Out.PrintRaw(string(b))
|
||
return nil
|
||
}
|
||
}
|
||
deps.Out.PrintRaw(raw)
|
||
return nil
|
||
}
|
||
|
||
// attachCalendarSearchRange injects a `searchRange` object describing the
|
||
// time window that was actually sent to MCP, plus a human-readable hint
|
||
// indicating whether --start/--end were user-specified or defaulted.
|
||
func attachCalendarSearchRange(parsed map[string]any, cmd *cobra.Command, toolArgs map[string]any) {
|
||
startMs, sOK := toolArgs["startTime"].(int64)
|
||
endMs, eOK := toolArgs["endTime"].(int64)
|
||
if !sOK || !eOK {
|
||
return
|
||
}
|
||
loc := time.Now().Location()
|
||
startUserSet := cmd != nil && (cmd.Flags().Changed("start") || cmd.Flags().Changed("time-min") || cmd.Flags().Changed("min-time") || cmd.Flags().Changed("start-time") || cmd.Flags().Changed("startTime") || cmd.Flags().Changed("start_time") || cmd.Flags().Changed("start-date") || cmd.Flags().Changed("startDate"))
|
||
endUserSet := cmd != nil && (cmd.Flags().Changed("end") || cmd.Flags().Changed("time-max") || cmd.Flags().Changed("max-time") || cmd.Flags().Changed("end-time") || cmd.Flags().Changed("endTime") || cmd.Flags().Changed("end_time") || cmd.Flags().Changed("end-date") || cmd.Flags().Changed("endDate"))
|
||
|
||
var hint string
|
||
switch {
|
||
case !startUserSet && !endUserSet:
|
||
hint = "未显式指定 --start/--end,已默认查询当天 00:00:00 ~ 23:59:59(CLI 本地时区)。如需其它区间请显式传入 --start 和 --end。"
|
||
case !startUserSet:
|
||
hint = "未显式指定 --start,已默认使用当天 00:00:00(CLI 本地时区)。"
|
||
case !endUserSet:
|
||
hint = "未显式指定 --end,已默认使用当天 23:59:59(CLI 本地时区)。"
|
||
default:
|
||
hint = "本次查询范围由调用方显式指定。"
|
||
}
|
||
|
||
searchRange := map[string]any{
|
||
"startTimeISO": time.UnixMilli(startMs).In(loc).Format(time.RFC3339),
|
||
"endTimeISO": time.UnixMilli(endMs).In(loc).Format(time.RFC3339),
|
||
"hint": hint,
|
||
}
|
||
if result, ok := parsed["result"].(map[string]any); ok {
|
||
result["searchRange"] = searchRange
|
||
return
|
||
}
|
||
parsed["searchRange"] = searchRange
|
||
}
|
||
|
||
func calendarEventSortKey(event any) int64 {
|
||
m, ok := event.(map[string]any)
|
||
if !ok {
|
||
return 0
|
||
}
|
||
if start, ok := m["start"].(map[string]any); ok {
|
||
if dt, ok := start["dateTime"].(string); ok && dt != "" {
|
||
if t, err := time.Parse(time.RFC3339, dt); err == nil {
|
||
return t.UnixMilli()
|
||
}
|
||
}
|
||
}
|
||
if created, ok := m["created"].(float64); ok {
|
||
return int64(created)
|
||
}
|
||
if updated, ok := m["updated"].(float64); ok {
|
||
return int64(updated)
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// recurrenceFlagNames lists all flattened --recurrence-* CLI flags that together
|
||
// compose the single nested `recurrence` MCP parameter ({pattern, range}).
|
||
var recurrenceFlagNames = []string{
|
||
"recurrence-type", "recurrence-interval", "recurrence-days-of-week",
|
||
"recurrence-day-of-month", "recurrence-index", "recurrence-first-day-of-week",
|
||
"recurrence-range-type", "recurrence-end-date", "recurrence-count",
|
||
}
|
||
|
||
// calendarRecurrenceParamDecls declares recurrence required/required_when pins
|
||
// for create/update event leaves (native_annotation).
|
||
func calendarRecurrenceParamDecls() []contract.ParamDecl {
|
||
return []contract.ParamDecl{
|
||
{Name: "recurrence-day-of-month", Property: "recurrence.pattern.dayOfMonth", Required: boolPtr(false), RequiredWhen: "recurrence-type is absoluteMonthly or absoluteYearly"},
|
||
{Name: "recurrence-days-of-week", Property: "recurrence.pattern.daysOfWeek", Required: boolPtr(false), RequiredWhen: "recurrence-type is weekly or relativeMonthly"},
|
||
{Name: "recurrence-index", Property: "recurrence.pattern.index", Required: boolPtr(false), RequiredWhen: "recurrence-type is relativeMonthly"},
|
||
{Name: "recurrence-interval", Property: "recurrence.pattern.interval", Required: boolPtr(false), RequiredWhen: "any recurrence-* flag is provided"},
|
||
{Name: "recurrence-type", Property: "recurrence.pattern.type", Required: boolPtr(false), RequiredWhen: "any recurrence-* flag is provided"},
|
||
{Name: "recurrence-end-date", Property: "recurrence.range.endDate", Required: boolPtr(false), RequiredWhen: "recurrence-range-type is endDate"},
|
||
{Name: "recurrence-count", Property: "recurrence.range.numberOfOccurrences", Required: boolPtr(false), RequiredWhen: "recurrence-range-type is numbered"},
|
||
{Name: "recurrence-range-type", Property: "recurrence.range.type", Required: boolPtr(false), RequiredWhen: "any recurrence-* flag is provided"},
|
||
{Name: "recurrence-first-day-of-week", Property: "recurrence.pattern.firstDayOfWeek"},
|
||
}
|
||
}
|
||
|
||
// recurrenceIncompleteSuggestion is the canonical reminder appended to any
|
||
// recurrence validation error. It explains that the CLI flattens a nested
|
||
// MCP object and that partial updates are NOT supported by the backend.
|
||
const recurrenceIncompleteSuggestion = "CLI 的 --recurrence-* 系列不是彼此独立的参数。\n" +
|
||
"- 创建/修改周期日程时,必须一次性提供**完整**的循环规则(pattern + range),至少包含 --recurrence-type、--recurrence-interval(>0) 与 --recurrence-range-type;\n" +
|
||
"- 修改已有周期日程的任意一个循环字段时,也必须重新提供全部循环字段,否则规则会被覆盖成不完整状态;\n" +
|
||
"- 完整示例:--recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 10"
|
||
|
||
// buildReminders parses a comma-separated minutes string (e.g. "5,10,15")
|
||
// and constructs the MCP `reminders` array: [{"minutes":5},{"minutes":10},{"minutes":15}].
|
||
func buildReminders(raw string) []map[string]any {
|
||
parts := parseCSVValues(raw)
|
||
result := make([]map[string]any, 0, len(parts))
|
||
for _, p := range parts {
|
||
minutes, err := strconv.Atoi(strings.TrimSpace(p))
|
||
if err != nil || minutes <= 0 {
|
||
continue
|
||
}
|
||
result = append(result, map[string]any{"minutes": minutes})
|
||
}
|
||
return result
|
||
}
|
||
|
||
// buildRecurrence constructs the MCP `recurrence` nested object from the
|
||
// flattened --recurrence-* flags. When ANY recurrence flag is set, the FULL
|
||
// structure must be provided — the CLI refuses to send a partial recurrence
|
||
// because MCP does not merge partial values on update.
|
||
//
|
||
// Returns (nil, nil) when no recurrence flag is set at all.
|
||
func buildRecurrence(cmd *cobra.Command) (map[string]any, error) {
|
||
anySet := false
|
||
for _, f := range recurrenceFlagNames {
|
||
if cmd.Flags().Changed(f) {
|
||
anySet = true
|
||
break
|
||
}
|
||
}
|
||
if !anySet {
|
||
return nil, nil
|
||
}
|
||
|
||
patternType, _ := cmd.Flags().GetString("recurrence-type")
|
||
rangeType, _ := cmd.Flags().GetString("recurrence-range-type")
|
||
interval, _ := cmd.Flags().GetInt("recurrence-interval")
|
||
if patternType == "" || rangeType == "" || interval <= 0 {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 结构不完整:--recurrence-type、--recurrence-interval(>0) 与 --recurrence-range-type 必须同时提供",
|
||
Suggestion: recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
|
||
validPatternTypes := map[string]bool{
|
||
"daily": true, "weekly": true, "absoluteMonthly": true,
|
||
"relativeMonthly": true, "absoluteYearly": true,
|
||
}
|
||
if !validPatternTypes[patternType] {
|
||
return nil, &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "invalid --recurrence-type: " + patternType,
|
||
Suggestion: "可选值: daily | weekly | absoluteMonthly | relativeMonthly | absoluteYearly",
|
||
}
|
||
}
|
||
validRangeTypes := map[string]bool{"noEnd": true, "endDate": true, "numbered": true}
|
||
if !validRangeTypes[rangeType] {
|
||
return nil, &CLIError{
|
||
Code: CodeMCPToolError,
|
||
Message: "invalid --recurrence-range-type: " + rangeType,
|
||
Suggestion: "可选值: noEnd(永不结束) | endDate(指定结束时间) | numbered(指定次数)",
|
||
}
|
||
}
|
||
|
||
pattern := map[string]any{"type": patternType, "interval": interval}
|
||
daysOfWeek, _ := cmd.Flags().GetString("recurrence-days-of-week")
|
||
if daysOfWeek != "" {
|
||
pattern["daysOfWeek"] = daysOfWeek
|
||
}
|
||
dayOfMonth, _ := cmd.Flags().GetInt("recurrence-day-of-month")
|
||
if dayOfMonth > 0 {
|
||
pattern["dayOfMonth"] = dayOfMonth
|
||
}
|
||
indexVal, _ := cmd.Flags().GetString("recurrence-index")
|
||
if indexVal != "" {
|
||
pattern["index"] = indexVal
|
||
}
|
||
if v, _ := cmd.Flags().GetString("recurrence-first-day-of-week"); v != "" {
|
||
pattern["firstDayOfWeek"] = v
|
||
}
|
||
|
||
// Pattern-type-specific required fields
|
||
switch patternType {
|
||
case "weekly", "relativeMonthly":
|
||
if daysOfWeek == "" {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 不完整:--recurrence-type=" + patternType + " 时必须提供 --recurrence-days-of-week",
|
||
Suggestion: "示例: --recurrence-days-of-week monday,wednesday,friday\n" + recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
}
|
||
switch patternType {
|
||
case "absoluteMonthly", "absoluteYearly":
|
||
if dayOfMonth <= 0 {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 不完整:--recurrence-type=" + patternType + " 时必须提供 --recurrence-day-of-month",
|
||
Suggestion: "示例: --recurrence-day-of-month 15\n" + recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
}
|
||
if patternType == "relativeMonthly" && indexVal == "" {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 不完整:--recurrence-type=relativeMonthly 时必须提供 --recurrence-index",
|
||
Suggestion: "可选值: first | second | third | fourth | last\n" + recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
|
||
r := map[string]any{"type": rangeType}
|
||
switch rangeType {
|
||
case "endDate":
|
||
v, _ := cmd.Flags().GetString("recurrence-end-date")
|
||
if v == "" {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 不完整:--recurrence-range-type=endDate 时必须提供 --recurrence-end-date",
|
||
Suggestion: "示例: --recurrence-end-date 2026-12-31T23:59:59+08:00\n" + recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
ms, err := parseISOTimeToMillis("recurrence-end-date", v)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
r["endDate"] = ms
|
||
case "numbered":
|
||
n, _ := cmd.Flags().GetInt("recurrence-count")
|
||
if n <= 0 {
|
||
return nil, &CLIError{
|
||
Code: CodeMissingParam,
|
||
Message: "recurrence 不完整:--recurrence-range-type=numbered 时必须提供 --recurrence-count (>0)",
|
||
Suggestion: "示例: --recurrence-count 10\n" + recurrenceIncompleteSuggestion,
|
||
}
|
||
}
|
||
r["numberOfOccurrences"] = n
|
||
case "noEnd":
|
||
// no additional fields required
|
||
}
|
||
|
||
return map[string]any{"pattern": pattern, "range": r}, nil
|
||
}
|