2029 lines
91 KiB
Go
2029 lines
91 KiB
Go
package helpers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
||
"github.com/spf13/cobra"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
||
)
|
||
|
||
// ──────────────────────────────────────────────────────────
|
||
// dws contact — 通讯录
|
||
// ──────────────────────────────────────────────────────────
|
||
|
||
func parseCSVValues(raw string) []string {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(raw, "[") && strings.HasSuffix(raw, "]") {
|
||
var values []string
|
||
if err := json.Unmarshal([]byte(raw), &values); err == nil {
|
||
return cleanStringValues(values)
|
||
}
|
||
raw = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(raw, "["), "]"))
|
||
}
|
||
parts := strings.Split(raw, ",")
|
||
return cleanStringValues(parts)
|
||
}
|
||
|
||
func cleanStringValues(parts []string) []string {
|
||
values := make([]string, 0, len(parts))
|
||
for _, p := range parts {
|
||
v := strings.Trim(strings.TrimSpace(p), `"'`)
|
||
if v != "" {
|
||
values = append(values, v)
|
||
}
|
||
}
|
||
return values
|
||
}
|
||
|
||
// contactUserIDFlagKeys 汇总 contact user get 支持的所有 flag 名(含 camelCase 派生与全小写写法),
|
||
// 在 RunE 中统一引用,避免每个调用点重复维护别名列表。
|
||
// camelCase 版本 --userId / --userIds 由 RegisterCamelCaseAliases 自动派生,--userid 为手写全小写别名。
|
||
var contactUserIDFlagKeys = []string{"ids", "user-id", "user-ids", "userId", "userIds", "userid"}
|
||
|
||
// contactRootDeptLikeTokens 是用户/模型常写错的"根部门占位符"。钉钉根部门 deptId 恒为 1。
|
||
// 在 contactParseInt64WithAliases 与 list-members 的 CSV 解析里命中这类值时,给出就近提示,避免调用方再去猜。
|
||
var contactRootDeptLikeTokens = map[string]struct{}{
|
||
"self": {}, "me": {}, "root": {}, "0": {},
|
||
}
|
||
|
||
// contactFirstSetFlagName 返回 names 中第一个被用户显式传入的 flag 名(Changed=true)。
|
||
// 用于让报错文案显示用户实际输入的 flag 名,而不是主 flag 名,避免
|
||
// 出现 "用户传 --ids me 却被报 flag --id 不合法" 的错位。
|
||
func contactFirstSetFlagName(cmd *cobra.Command, names ...string) string {
|
||
for _, n := range names {
|
||
if f := cmd.Flag(n); f != nil && f.Changed {
|
||
return n
|
||
}
|
||
}
|
||
if len(names) > 0 {
|
||
return names[0]
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func contactAnyFlagChanged(cmd *cobra.Command, names ...string) bool {
|
||
for _, n := range names {
|
||
if f := cmd.Flag(n); f != nil && f.Changed {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func contactGetBoolWithAliases(cmd *cobra.Command, names ...string) (bool, bool) {
|
||
for _, name := range names {
|
||
if flag := cmd.Flag(name); flag != nil && flag.Changed {
|
||
value, err := cmd.Flags().GetBool(name)
|
||
return value, err == nil
|
||
}
|
||
}
|
||
return false, false
|
||
}
|
||
|
||
func contactOptionalString(cmd *cobra.Command, primary string, aliases ...string) (string, bool) {
|
||
names := append([]string{primary}, aliases...)
|
||
if !contactAnyFlagChanged(cmd, names...) {
|
||
return "", false
|
||
}
|
||
return strings.TrimSpace(flagOrFallback(cmd, primary, aliases...)), true
|
||
}
|
||
|
||
func contactOptionalDepartments(cmd *cobra.Command) ([]map[string]any, bool, error) {
|
||
if !cmd.Flags().Changed("depts") {
|
||
return nil, false, nil
|
||
}
|
||
raw := strings.TrimSpace(mustGetFlag(cmd, "depts"))
|
||
if raw == "" {
|
||
return nil, false, nil
|
||
}
|
||
var departments []map[string]any
|
||
if err := json.Unmarshal([]byte(raw), &departments); err != nil {
|
||
return nil, false, fmt.Errorf("--depts JSON 解析失败: %w\n hint: 正确格式: [{\"deptId\":1}]", err)
|
||
}
|
||
return departments, true, nil
|
||
}
|
||
|
||
// contactParseInt64WithAliases 先在主 flag 与全部别名中找出用户实际传入的值(空则报 missing),
|
||
// 再走根部门占位符警告 + int64 解析,避免用户传别名时 RunE 读不到。
|
||
// 报错文案中使用用户实际输入的 flag 名(比如用户传 --ids me,错误里显示 --ids 而不是主 flag --id),
|
||
// 防止用户/LLM 被"我明明没传这个 flag 为啥报它"的错位文案带偏。
|
||
func contactParseInt64WithAliases(cmd *cobra.Command, primary string, aliases ...string) (int64, error) {
|
||
if err := validateRequiredFlagWithAliases(cmd, primary, aliases...); err != nil {
|
||
return 0, err
|
||
}
|
||
raw := strings.TrimSpace(flagOrFallback(cmd, primary, aliases...))
|
||
setName := contactFirstSetFlagName(cmd, append([]string{primary}, aliases...)...)
|
||
if _, ok := contactRootDeptLikeTokens[strings.ToLower(raw)]; ok {
|
||
return 0, fmt.Errorf(
|
||
"flag --%s 必须是整数;钉钉根部门 deptId=1,请使用 --%s 1", setName, setName)
|
||
}
|
||
v, err := strconv.ParseInt(raw, 10, 64)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("flag --%s must be an integer: %w", setName, err)
|
||
}
|
||
return v, nil
|
||
}
|
||
|
||
func newContactDeptCreateCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "create",
|
||
Short: "创建部门",
|
||
Long: `在当前企业下创建部门。--create-dept-group 必须显式传 true 或 false。
|
||
不传 --parent 时使用企业根部门。该写操作执行前需要确认,自动化场景在用户明确授权后传 --yes。`,
|
||
Example: ` dws contact dept create --name "新产品部" --create-dept-group=true
|
||
dws contact dept create --name "研发一组" --parent 12345 --create-dept-group=false`,
|
||
Args: cobra.NoArgs,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "name", "dept-name", "deptName"); err != nil {
|
||
return err
|
||
}
|
||
name := strings.TrimSpace(flagOrFallback(cmd, "name", "dept-name", "deptName"))
|
||
if name == "" {
|
||
return fmt.Errorf("--%s 不能为空", contactFirstSetFlagName(cmd, "name", "dept-name", "deptName"))
|
||
}
|
||
createGroup, supplied := contactGetBoolWithAliases(cmd, "create-dept-group", "createDeptGroup")
|
||
if !supplied {
|
||
return fmt.Errorf("--create-dept-group 是必填参数,请显式指定 true 或 false")
|
||
}
|
||
toolArgs := map[string]any{
|
||
"deptName": name,
|
||
"createDeptGroup": createGroup,
|
||
}
|
||
if contactAnyFlagChanged(cmd, "parent", "super-dept-id", "super-dept", "superDeptId") {
|
||
parentID, err := contactParseInt64WithAliases(cmd, "parent", "super-dept-id", "super-dept", "superDeptId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["superDeptId"] = parentID
|
||
}
|
||
return callMCPTool("department_create", toolArgs)
|
||
},
|
||
}
|
||
cmd.Flags().String("name", "", "部门名称 (必填)")
|
||
cmd.Flags().String("dept-name", "", "--name 的别名")
|
||
_ = cmd.Flags().MarkHidden("dept-name")
|
||
cmd.Flags().String("parent", "", "父部门 ID(可选,不传默认根部门)")
|
||
cmd.Flags().String("super-dept-id", "", "--parent 的别名")
|
||
cmd.Flags().String("super-dept", "", "--parent 的别名")
|
||
_ = cmd.Flags().MarkHidden("super-dept-id")
|
||
_ = cmd.Flags().MarkHidden("super-dept")
|
||
cmd.Flags().Bool("create-dept-group", false, "是否创建部门群 (必填,需显式传 true 或 false)")
|
||
cli.AnnotateRuntimeRequiredFlags(cmd, "name", "create-dept-group")
|
||
return cmd
|
||
}
|
||
|
||
func newContactDeptUpdateCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "update",
|
||
Aliases: []string{"modify", "edit"},
|
||
Short: "更新部门信息",
|
||
Long: "更新部门名称,并可选择调整父部门。该写操作执行前需要确认,自动化场景在用户明确授权后传 --yes。",
|
||
Example: ` dws contact dept update --dept 12345 --name "新部门名"
|
||
dws contact dept update --dept 12345 --name "新名称" --parent 67890`,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
deptID, err := contactParseInt64WithAliases(cmd, "dept", "id", "ids", "dept-id", "dept-ids", "deptId", "deptIds")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := validateRequiredFlagWithAliases(cmd, "name", "dept-name", "deptName"); err != nil {
|
||
return err
|
||
}
|
||
name := strings.TrimSpace(flagOrFallback(cmd, "name", "dept-name", "deptName"))
|
||
if name == "" {
|
||
return fmt.Errorf("--%s 不能为空", contactFirstSetFlagName(cmd, "name", "dept-name", "deptName"))
|
||
}
|
||
toolArgs := map[string]any{"deptId": deptID, "deptName": name}
|
||
if contactAnyFlagChanged(cmd, "parent", "super-dept-id", "super-dept", "superDeptId") {
|
||
parentID, err := contactParseInt64WithAliases(cmd, "parent", "super-dept-id", "super-dept", "superDeptId")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
toolArgs["superDeptId"] = parentID
|
||
}
|
||
return callMCPTool("department_update", toolArgs)
|
||
},
|
||
}
|
||
cmd.Flags().String("dept", "", "部门 ID (必填)")
|
||
cmd.Flags().String("name", "", "新部门名称 (必填)")
|
||
cmd.Flags().String("dept-name", "", "--name 的别名")
|
||
_ = cmd.Flags().MarkHidden("dept-name")
|
||
cmd.Flags().String("parent", "", "新父部门 ID(可选)")
|
||
cmd.Flags().String("super-dept-id", "", "--parent 的别名")
|
||
cmd.Flags().String("super-dept", "", "--parent 的别名")
|
||
_ = cmd.Flags().MarkHidden("super-dept-id")
|
||
_ = cmd.Flags().MarkHidden("super-dept")
|
||
cli.AnnotateRuntimeRequiredFlags(cmd, "dept", "name")
|
||
return cmd
|
||
}
|
||
|
||
func newContactUserUpdateCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "update",
|
||
Aliases: []string{"modify", "edit"},
|
||
Short: "修改员工信息",
|
||
Long: "修改员工的企业内姓名、所属部门或直属主管。至少提供一个修改项,执行前需要确认。",
|
||
Example: ` dws contact user update --user-id user001 --org-user-name "张三三"
|
||
dws contact user update --user-id user001 --depts '[{"deptId":1}]'`,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "user-id", "id", "userid", "userId"); err != nil {
|
||
return err
|
||
}
|
||
userID := strings.TrimSpace(flagOrFallback(cmd, "user-id", "id", "userid", "userId"))
|
||
if userID == "" {
|
||
return fmt.Errorf("--user-id 不能为空")
|
||
}
|
||
toolArgs := map[string]any{"userId": userID}
|
||
changed := false
|
||
if value, supplied := contactOptionalString(cmd, "org-user-name", "orgUserName"); supplied && value != "" {
|
||
toolArgs["orgUserName"] = value
|
||
changed = true
|
||
}
|
||
departments, supplied, err := contactOptionalDepartments(cmd)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if supplied {
|
||
toolArgs["depts"] = departments
|
||
changed = true
|
||
}
|
||
if value, supplied := contactOptionalString(cmd, "master-user-id", "masterUserId"); supplied && value != "" {
|
||
toolArgs["masterUserId"] = value
|
||
changed = true
|
||
}
|
||
if !changed {
|
||
return fmt.Errorf("至少需要一个修改项:--org-user-name、--depts 或 --master-user-id")
|
||
}
|
||
return callMCPTool("employee_update", toolArgs)
|
||
},
|
||
}
|
||
cmd.Flags().String("user-id", "", "要修改的员工 userId (必填)")
|
||
cmd.Flags().String("id", "", "--user-id 的别名")
|
||
cmd.Flags().String("userid", "", "--user-id 的别名")
|
||
_ = cmd.Flags().MarkHidden("id")
|
||
_ = cmd.Flags().MarkHidden("userid")
|
||
cmd.Flags().String("org-user-name", "", "员工在企业内的名称(可选)")
|
||
cmd.Flags().String("depts", "", "员工所属部门列表 JSON 数组(可选),格式: [{\"deptId\":1}]")
|
||
cmd.Flags().String("master-user-id", "", "直属主管 userId(可选)")
|
||
cli.AnnotateRuntimeRequiredFlags(cmd, "user-id")
|
||
cli.AnnotateRuntimeConstraints(cmd, cli.RuntimeSchemaConstraints{
|
||
RequireOneOf: [][]string{{"org-user-name", "depts", "master-user-id"}},
|
||
})
|
||
return cmd
|
||
}
|
||
|
||
func newContactUserUpdateSelfCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "update-self",
|
||
Aliases: []string{"update-me", "update-self-profile", "edit-self", "modify-self"},
|
||
Short: "更新当前用户自己的 profile 信息",
|
||
Long: "更新当前用户的昵称或头像。头像需先上传到钉盘取得 fileId;执行前需要确认。",
|
||
Example: ` dws contact user update-self --nick "新昵称"
|
||
dws contact user update-self --avatar-file-id "file-id"`,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
toolArgs := map[string]any{}
|
||
if value, supplied := contactOptionalString(cmd, "nick"); supplied && value != "" {
|
||
toolArgs["nick"] = value
|
||
}
|
||
if value, supplied := contactOptionalString(cmd, "avatar-file-id", "avatarFileId"); supplied && value != "" {
|
||
toolArgs["avatarFileId"] = value
|
||
}
|
||
if len(toolArgs) == 0 {
|
||
return fmt.Errorf("至少需要一个修改项:--nick 或 --avatar-file-id")
|
||
}
|
||
return callMCPTool("self_user_profile_update", toolArgs)
|
||
},
|
||
}
|
||
cmd.Flags().String("nick", "", "新昵称(可选)")
|
||
cmd.Flags().String("avatar-file-id", "", "新头像在钉盘的 fileId(可选)")
|
||
cli.AnnotateRuntimeConstraints(cmd, cli.RuntimeSchemaConstraints{
|
||
RequireOneOf: [][]string{{"nick", "avatar-file-id"}},
|
||
})
|
||
return cmd
|
||
}
|
||
|
||
func newContactUserUpdateOwnnessCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "update-ownness",
|
||
Aliases: []string{"set-ownness"},
|
||
Short: "更新用户个人状态",
|
||
Long: "更新指定用户的个人状态文本(展示在个人资料与聊天会话中,如「居家办公中」)。执行前需要确认,自动化场景在用户明确授权后传 --yes。",
|
||
Example: ` dws contact user update-ownness --user-id user001 --ownness-text "居家办公中"`,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "user-id", "id", "userid", "userId"); err != nil {
|
||
return err
|
||
}
|
||
userID := strings.TrimSpace(flagOrFallback(cmd, "user-id", "id", "userid", "userId"))
|
||
if userID == "" {
|
||
return fmt.Errorf("--user-id 不能为空")
|
||
}
|
||
if err := validateRequiredFlagWithAliases(cmd, "ownness-text", "ownnessText"); err != nil {
|
||
return err
|
||
}
|
||
ownnessText := strings.TrimSpace(flagOrFallback(cmd, "ownness-text", "ownnessText"))
|
||
if ownnessText == "" {
|
||
return fmt.Errorf("--ownness-text 不能为空")
|
||
}
|
||
return callMCPTool("user_ownness_update", map[string]any{
|
||
"userId": userID,
|
||
"ownnessText": ownnessText,
|
||
})
|
||
},
|
||
}
|
||
cmd.Flags().String("user-id", "", "要更新个人状态的用户 userId (必填)")
|
||
cmd.Flags().String("id", "", "--user-id 的别名")
|
||
cmd.Flags().String("userid", "", "--user-id 的别名")
|
||
_ = cmd.Flags().MarkHidden("id")
|
||
_ = cmd.Flags().MarkHidden("userid")
|
||
cmd.Flags().String("ownness-text", "", "个人状态文本 (必填),如 \"居家办公中\"")
|
||
cli.AnnotateRuntimeRequiredFlags(cmd, "user-id", "ownness-text")
|
||
return cmd
|
||
}
|
||
|
||
func newContactAccountUpdateCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "update",
|
||
Aliases: []string{"modify", "edit"},
|
||
Short: "更新企业账号用户信息",
|
||
Long: "更新企业账号的员工姓名、部门、直属主管、昵称或头像。至少提供一个修改项,执行前需要确认。",
|
||
Example: ` dws contact account update --user-id user001 --org-user-name "张三"
|
||
dws contact account update --user-id user001 --nick "新昵称" --avatar-file-id "file-id"`,
|
||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "user-id", "id", "userid", "userId"); err != nil {
|
||
return err
|
||
}
|
||
userID := strings.TrimSpace(flagOrFallback(cmd, "user-id", "id", "userid", "userId"))
|
||
if userID == "" {
|
||
return fmt.Errorf("--user-id 不能为空")
|
||
}
|
||
toolArgs := map[string]any{"userId": userID}
|
||
if value, supplied := contactOptionalString(cmd, "org-user-name", "orgUserName"); supplied && value != "" {
|
||
toolArgs["orgUserName"] = value
|
||
}
|
||
departments, supplied, err := contactOptionalDepartments(cmd)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if supplied {
|
||
toolArgs["depts"] = departments
|
||
}
|
||
if value, supplied := contactOptionalString(cmd, "master-user-id", "masterUserId"); supplied && value != "" {
|
||
toolArgs["masterUserId"] = value
|
||
}
|
||
if value, supplied := contactOptionalString(cmd, "nick"); supplied && value != "" {
|
||
toolArgs["nick"] = value
|
||
}
|
||
if value, supplied := contactOptionalString(cmd, "avatar-file-id", "avatarFileId"); supplied && value != "" {
|
||
toolArgs["avatarFileId"] = value
|
||
}
|
||
if len(toolArgs) == 1 {
|
||
return fmt.Errorf("至少需要一个修改项:--org-user-name、--depts、--master-user-id、--nick 或 --avatar-file-id")
|
||
}
|
||
return callMCPTool("exclusive_account_user_update", toolArgs)
|
||
},
|
||
}
|
||
cmd.Flags().String("user-id", "", "被修改企业账号的 userId (必填)")
|
||
cmd.Flags().String("id", "", "--user-id 的别名")
|
||
cmd.Flags().String("userid", "", "--user-id 的别名")
|
||
_ = cmd.Flags().MarkHidden("id")
|
||
_ = cmd.Flags().MarkHidden("userid")
|
||
cmd.Flags().String("org-user-name", "", "企业账号在企业内的员工姓名(可选)")
|
||
cmd.Flags().String("depts", "", "部门列表 JSON 数组(可选),格式: [{\"deptId\":1}]")
|
||
cmd.Flags().String("master-user-id", "", "直属主管 userId(可选)")
|
||
cmd.Flags().String("nick", "", "企业账号自身昵称(可选)")
|
||
cmd.Flags().String("avatar-file-id", "", "企业账号头像在钉盘的 fileId(可选)")
|
||
cli.AnnotateRuntimeRequiredFlags(cmd, "user-id")
|
||
cli.AnnotateRuntimeConstraints(cmd, cli.RuntimeSchemaConstraints{
|
||
RequireOneOf: [][]string{{"org-user-name", "depts", "master-user-id", "nick", "avatar-file-id"}},
|
||
})
|
||
return cmd
|
||
}
|
||
|
||
func newContactCommand() *cobra.Command {
|
||
// Product-level Agent routing Decl (migrated from selection/contact.json
|
||
// products.contact). Catalog assembly stamps provenance contract_final.
|
||
contract.RegisterProductDecl(contract.ProductDecl{
|
||
ID: "contact",
|
||
Selection: contract.ProductSelectionDecl{
|
||
AgentSummary: "查询通讯录与花名册,并管理企业、部门、员工及企业账号",
|
||
UseWhen: []string{
|
||
"按姓名/手机号/userId/部门条件做通讯录精确查询,或明确执行企业与员工入企管理",
|
||
},
|
||
AvoidWhen: []string{
|
||
"职责/上级等语义找人优先 aisearch person;不要用 contact 发消息;写操作前确认当前企业和目标信息",
|
||
},
|
||
},
|
||
})
|
||
root := newGroupCommand(&cobra.Command{
|
||
Use: "contact",
|
||
Short: "通讯录 / 用户 / 部门 / 角色 / 人员关系",
|
||
Long: `查询钉钉通讯录:用户搜索、手机号查找、部门搜索、子部门 / 成员列表、人员关系;用户花名册档案信息(学历、家庭、银行卡、合同等)与离职员工信息。
|
||
|
||
通讯录功能:
|
||
- contact user get-self/search/search-mobile/get: 通讯录用户查询
|
||
- contact user invite/update/update-self/update-ownness: 邀请与更新员工
|
||
- contact dept search/get-info/list-children/list-members/create/update: 部门查询与管理
|
||
- contact relation list-my-followings: 特别关注人查询
|
||
|
||
企业管理功能:
|
||
- contact org create: 创建企业
|
||
- contact account create/update: 创建与更新企业专属账号
|
||
|
||
基础人事功能(HR 花名册):
|
||
- contact user profile fields/get: 员工花名册档案查询(学历、家庭、银行卡等)
|
||
- contact user dismission search: 离职员工列表查询`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
userCmd := newGroupCommand(&cobra.Command{
|
||
Use: "user",
|
||
Short: "人员管理",
|
||
Long: `人员管理:通讯录用户查询、修改员工信息、邀请员工加入企业、用户档案(花名册)查询、离职员工查询。
|
||
|
||
【何时用哪个命令】
|
||
- 查询用户的部门、主管、管理员权限 → contact user get
|
||
- 修改员工信息(姓名 / 部门 / 直属主管) → contact user update
|
||
- 更新当前用户自己的 profile(昵称 / 头像) → contact user update-self
|
||
- 更新用户个人状态(如「居家办公中」) → contact user update-ownness
|
||
- 邀请员工加入企业 → contact user invite
|
||
- 查询用户的学历、家庭、银行卡、合同等档案 → contact user profile get
|
||
- 查询离职员工列表 → contact user dismission search`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
contactUserGetSelfCmd := &cobra.Command{
|
||
Use: "get-self",
|
||
Aliases: []string{"self", "me", "whoami", "current"},
|
||
Short: "获取当前用户信息(我是谁 / 本人)",
|
||
Long: "获取当前登录用户的 userId 与基本信息。\n\n触发词:我是谁 / 我的信息 / 我的 userId / 当前用户 / 本人 / self / me / whoami / current。\n别名:self / me / whoami / current 均等价于 get-self。\n无需参数;禁止用 `dws contact user get --ids me/self` 代替(会返回空数据的假成功)。",
|
||
Example: ` dws contact user get-self
|
||
dws contact user self # 别名(等价)
|
||
dws contact user me # 别名(等价)`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return callMCPTool("get_current_user_profile", nil)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserGetSelfCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_current_user_profile",
|
||
CanonicalPath: "contact.get_current_user_profile",
|
||
CLIPath: "contact user get-self",
|
||
PrimaryCLIPath: "contact user get-self",
|
||
},
|
||
Description: "获取当前登录用户资料与 userId",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "get_current_user_profile"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "获取当前登录用户资料与 userId",
|
||
UseWhen: []string{"用户问我是谁/我的 userId/当前账号信息"},
|
||
AvoidWhen: []string{"查他人详情用 contact user get 或先搜索"},
|
||
Examples: []string{"dws contact user get-self --format json"},
|
||
},
|
||
},
|
||
})
|
||
|
||
relationCmd := newGroupCommand(&cobra.Command{Use: "relation",
|
||
Short: "人员关系查询",
|
||
Long: `查询钉钉人员关系:特别关注人。`,
|
||
RunE: groupRunE})
|
||
|
||
contactRelationListMyFollowingsCmd := &cobra.Command{
|
||
Use: "list-my-followings",
|
||
Short: "获取当前用户的特别关注列表",
|
||
Example: ` dws contact relation list-my-followings`,
|
||
Args: cobra.NoArgs,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return callMCPTool("list_my_followings", nil)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactRelationListMyFollowingsCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "list_my_followings",
|
||
CanonicalPath: "contact.list_my_followings",
|
||
CLIPath: "contact relation list-my-followings",
|
||
PrimaryCLIPath: "contact relation list-my-followings",
|
||
},
|
||
Description: "获取当前用户的特别关注列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "list_my_followings"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "获取当前用户的特别关注列表",
|
||
UseWhen: []string{"需要查看当前用户特别关注或星标联系人时"},
|
||
AvoidWhen: []string{"需要搜索企业通讯录或查询任意员工详情时不要使用;这里只返回当前用户的特别关注列表。"},
|
||
Examples: []string{"dws contact relation list-my-followings --format json"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactUserSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "按关键词搜索用户",
|
||
Example: ` dws contact user search --query "张三"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
// 兼容 --query / --keyword / --name 三种写法(--name 为隐藏别名,对齐 dept search)。
|
||
if err := validateRequiredFlagWithAliases(cmd, "query", "keyword", "name"); err != nil {
|
||
return err
|
||
}
|
||
kw := flagOrFallback(cmd, "query", "keyword", "name")
|
||
return callMCPTool("search_contact_by_key_word", map[string]any{
|
||
"keyword": kw,
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserSearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "search_contact_by_key_word",
|
||
CanonicalPath: "contact.search_contact_by_key_word",
|
||
CLIPath: "contact user search",
|
||
PrimaryCLIPath: "contact user search",
|
||
},
|
||
Description: "按关键词搜索好友和同事,提取 userId/openDingTalkId",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "search_contact_by_key_word"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按关键词搜索好友和同事,提取 userId/openDingTalkId",
|
||
UseWhen: []string{"按姓名等关键词在通讯录里精确搜人,并需要 userId 或 openDingTalkId"},
|
||
AvoidWhen: []string{
|
||
"职责/上级/技能等语义找人优先 aisearch person",
|
||
"已有 userId 查详情用 contact user get",
|
||
"查自己用 contact user get-self",
|
||
},
|
||
Examples: []string{"dws contact user search --query \"张三\" --format json"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "query", Property: "keyword"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactUserSearchMobileCmd := &cobra.Command{
|
||
Use: "search-mobile",
|
||
Short: "按手机号搜索用户",
|
||
Example: ` dws contact user search-mobile --mobile 13800138000`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlags(cmd, "mobile"); err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("search_user_by_mobile", map[string]any{
|
||
"mobile": mustGetFlag(cmd, "mobile"),
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserSearchMobileCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "search_user_by_mobile",
|
||
CanonicalPath: "contact.search_user_by_mobile",
|
||
CLIPath: "contact user search-mobile",
|
||
PrimaryCLIPath: "contact user search-mobile",
|
||
},
|
||
Description: "按手机号搜索用户",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "search_user_by_mobile"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按手机号搜索用户",
|
||
UseWhen: []string{"已知手机号,需要精确搜索用户时"},
|
||
AvoidWhen: []string{"已有用户 ID 或需要批量用户详情时改用批量详情命令;该命令仅按手机号定位用户。"},
|
||
Examples: []string{"dws contact user search-mobile --mobile 13800138000 --format json"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactUserGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "批量获取用户详情(组织管理信息)",
|
||
Long: `批量获取用户详情,返回用户的组织管理信息(来自通讯录领域)。
|
||
|
||
返回字段:
|
||
- isAdmin: 是否为管理员
|
||
- orgEmployeeModel.orgUserId / orgUserName: 用户 ID / 姓名
|
||
- orgEmployeeModel.orgName / orgId: 所属组织名称 / ID
|
||
- orgEmployeeModel.orgMasterUserId / orgMasterDisplayName: 直属主管
|
||
- orgEmployeeModel.depts: 所属部门列表(含 deptId、deptName)
|
||
- orgEmployeeModel.labels: 角色列表
|
||
|
||
【适用场景】
|
||
- 想知道某个用户在哪个部门、上级是谁、是不是管理员
|
||
|
||
【不适用场景】
|
||
- 查询学历、家庭、银行卡、合同、紧急联系人等档案信息 → 请用 contact user profile get`,
|
||
Example: ` dws contact user get --ids userId1,userId2 # 查询 userId: dws contact user search --query "姓名"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, contactUserIDFlagKeys[0], contactUserIDFlagKeys[1:]...); err != nil {
|
||
return err
|
||
}
|
||
raw := flagOrFallback(cmd, contactUserIDFlagKeys[0], contactUserIDFlagKeys[1:]...)
|
||
// 拦截“假 userId”:me/self/current/whoami/i/me 代替真实 userId 会得到空数据的假成功。
|
||
for _, part := range parseCSVValues(raw) {
|
||
switch strings.ToLower(strings.TrimSpace(part)) {
|
||
case "me", "self", "current", "whoami", "i":
|
||
return fmt.Errorf("--ids 需要真实的 userId,不接受 %q 这类占位符\n hint: 获取当前用户用: dws contact user get-self", part)
|
||
}
|
||
}
|
||
return callMCPTool("get_user_info_by_user_ids", map[string]any{
|
||
"user_id_list": parseCSVValues(raw),
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserGetCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_user_info_by_user_ids",
|
||
CanonicalPath: "contact.get_user_info_by_user_ids",
|
||
CLIPath: "contact user get",
|
||
PrimaryCLIPath: "contact user get",
|
||
},
|
||
Description: "按 userId 批量获取员工详情(部门/主管等,受可见性限制)",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "get_user_info_by_user_ids"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按 userId 批量获取员工详情(部门/主管等,受可见性限制)",
|
||
UseWhen: []string{"已有一个或多个 userId,需要部门、主管等详情"},
|
||
AvoidWhen: []string{
|
||
"还没有 userId 时先 search / aisearch person",
|
||
"查自己不要传 me/self,用 contact user get-self",
|
||
},
|
||
Examples: []string{"dws contact user get --ids userId1,userId2"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "ids", Property: "user_id_list"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── label 角色 ──────────────────────────────────────────────────
|
||
|
||
contactLabelCmd := newGroupCommand(&cobra.Command{
|
||
Use: "label",
|
||
Aliases: []string{"role"},
|
||
Short: "角色查询",
|
||
Long: `角色查询:获取企业所有角色列表、根据角色名称查询角色ID、根据角色ID查询角色下的成员。
|
||
|
||
【何时用哪个命令】
|
||
- 获取企业所有角色列表 → contact label list
|
||
- 根据角色名称查询角色ID → contact label get
|
||
- 根据角色ID查询角色下的成员 → contact label list-members
|
||
|
||
【典型场景:查询某类角色的人员(如主管、管理员、财务等)】
|
||
1. contact label list → 获取企业全部角色列表
|
||
2. 从返回结果中匹配目标角色名称及 labelId
|
||
3. contact label list-members --id <labelId> → 获取该角色下的成员`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
runContactLabelList := func(cmd *cobra.Command, args []string) error {
|
||
if len(args) > 0 {
|
||
return fmt.Errorf("contact label list 不接受位置参数: %s", strings.Join(args, " "))
|
||
}
|
||
return callMCPTool("get_org_labels", map[string]any{})
|
||
}
|
||
runContactLabelGet := func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "names", "name", "query", "keyword"); err != nil {
|
||
return err
|
||
}
|
||
raw := flagOrFallback(cmd, "names", "name", "query", "keyword")
|
||
return callMCPTool("search_label_by_name", map[string]any{
|
||
"labelNames": parseCSVValues(raw),
|
||
})
|
||
}
|
||
runContactLabelMembers := func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "id", "label-id", "role-id"); err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("get_label_members_by_labelId", map[string]any{
|
||
"labelId": flagOrFallback(cmd, "id", "label-id", "role-id"),
|
||
})
|
||
}
|
||
|
||
contactLabelGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "根据角色名称查询角色",
|
||
Long: `根据角色名称精确匹配查询角色信息(角色ID、名称等)。支持同时查询多个角色名称,逗号分隔。无需分页。
|
||
|
||
注意:精确匹配可能无结果(如用户输入"管理员"但企业只有"主管理员"和"子管理员"),
|
||
此时应降级使用 label list 获取全部角色列表,从中模糊匹配包含关键词的角色。`,
|
||
Example: ` dws contact label get --names "管理员"
|
||
dws contact label get --names "管理员,财务"`,
|
||
RunE: runContactLabelGet,
|
||
}
|
||
|
||
contactLabelListMembersCmd := &cobra.Command{
|
||
Use: "list-members",
|
||
Short: "查询角色下的成员",
|
||
Long: `根据角色ID查询该角色下的成员列表。`,
|
||
Example: ` dws contact label list-members --id 12345 # 查询 labelId: dws contact label get --names "角色名"`,
|
||
RunE: runContactLabelMembers,
|
||
}
|
||
|
||
contactLabelGetCmd.Flags().String("names", "", "角色名称,逗号分隔 (必填)")
|
||
contactLabelGetCmd.Flags().String("name", "", "--names 的别名")
|
||
contactLabelGetCmd.Flags().String("query", "", "--names 的别名")
|
||
contactLabelGetCmd.Flags().String("keyword", "", "--names 的别名")
|
||
_ = contactLabelGetCmd.Flags().MarkHidden("name")
|
||
_ = contactLabelGetCmd.Flags().MarkHidden("query")
|
||
_ = contactLabelGetCmd.Flags().MarkHidden("keyword")
|
||
|
||
contactLabelListMembersCmd.Flags().String("id", "", "角色 ID (必填)")
|
||
contactLabelListMembersCmd.Flags().String("label-id", "", "--id 的别名")
|
||
contactLabelListMembersCmd.Flags().String("role-id", "", "--id 的别名")
|
||
_ = contactLabelListMembersCmd.Flags().MarkHidden("label-id")
|
||
_ = contactLabelListMembersCmd.Flags().MarkHidden("role-id")
|
||
|
||
contactLabelListAllCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "获取企业所有角色列表",
|
||
Long: `获取当前企业的所有角色(标签)列表,返回角色ID、角色名称等信息。无需参数。
|
||
|
||
用于不知道准确角色名称时,先列出全部角色,再根据需要选择目标角色查询成员。
|
||
|
||
【典型场景】
|
||
- 用户说"企业所有主管/查所有管理员/财务人员有哪些"
|
||
→ 先 label list 浏览全部角色,匹配目标角色后 label list-members 获取成员`,
|
||
Example: ` dws contact label list`,
|
||
RunE: runContactLabelList,
|
||
}
|
||
|
||
contactLabelCmd.AddCommand(contactLabelListAllCmd, contactLabelGetCmd, contactLabelListMembersCmd)
|
||
|
||
contactDeptCmd := newGroupCommand(&cobra.Command{Use: "dept", Short: "部门查询", RunE: groupRunE})
|
||
|
||
contactDeptSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "搜索部门",
|
||
Example: ` dws contact dept search --query "技术部"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "query", "keyword", "name"); err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("search_dept_by_keyword", map[string]any{
|
||
"query": flagOrFallback(cmd, "query", "keyword", "name"),
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactDeptSearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "search_dept_by_keyword",
|
||
CanonicalPath: "contact.search_dept_by_keyword",
|
||
CLIPath: "contact dept search",
|
||
PrimaryCLIPath: "contact dept search",
|
||
},
|
||
Description: "按关键词搜索部门",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "search_dept_by_keyword"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按关键词搜索部门",
|
||
UseWhen: []string{"只知道部门名称或关键词,需要定位 deptId 时"},
|
||
AvoidWhen: []string{"已经有准确部门 ID 并需要详情时改用部门详情命令;该命令用于关键词定位部门。"},
|
||
Examples: []string{"dws contact dept search --query \"技术部\" --format json"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactDeptListChildrenCmd := &cobra.Command{
|
||
Use: "list-children",
|
||
Short: "查看子部门",
|
||
Example: ` dws contact dept list-children --dept 12345`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
// list-children 主 flag 为 --dept;接受 --id / --ids / --dept-id / --dept-ids 作为别名。
|
||
deptID, err := contactParseInt64WithAliases(cmd, "dept", "id", "ids", "dept-id", "dept-ids", "deptId", "deptIds")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("get_sub_depts_by_dept_id", map[string]any{
|
||
"deptId": deptID,
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactDeptListChildrenCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_sub_depts_by_dept_id",
|
||
CanonicalPath: "contact.get_sub_depts_by_dept_id",
|
||
CLIPath: "contact dept list-children",
|
||
PrimaryCLIPath: "contact dept list-children",
|
||
},
|
||
Description: "列出指定部门的直属子部门",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "get_sub_depts_by_dept_id"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "列出指定部门的直属子部门",
|
||
UseWhen: []string{"已知父部门 deptId,需要列出直属子部门时"},
|
||
AvoidWhen: []string{"需要父部门自身详情或递归组织树时不要使用;该命令只列直属子部门。"},
|
||
Examples: []string{"dws contact dept list-children --dept 12345 --format json"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "dept", Property: "deptId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactDeptGetInfoCmd := &cobra.Command{
|
||
Use: "get-info",
|
||
Short: "获取部门详情(部门ID、名称、人数)",
|
||
Example: ` dws contact dept get-info --dept 12345 # 查询 deptId: dws contact dept search`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
// get-info 主 flag 为 --dept;接受 --id / --ids / --dept-id / --dept-ids 作为别名。
|
||
deptID, err := contactParseInt64WithAliases(cmd, "dept", "id", "ids", "dept-id", "dept-ids", "deptId", "deptIds")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("get_dept_info_by_dept_id", map[string]any{
|
||
"deptId": deptID,
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactDeptGetInfoCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_dept_info_by_dept_id",
|
||
CanonicalPath: "contact.get_dept_info_by_dept_id",
|
||
CLIPath: "contact dept get-info",
|
||
PrimaryCLIPath: "contact dept get-info",
|
||
},
|
||
Description: "获取指定部门详情",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "get_dept_info_by_dept_id"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "获取指定部门详情",
|
||
UseWhen: []string{"已知 deptId,需要部门名称、人数等详情时"},
|
||
AvoidWhen: []string{"只知道部门名称时应先搜索部门;需要列出直属子部门时改用子部门查询。"},
|
||
Examples: []string{"dws contact dept get-info --dept 12345 --format json"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "dept", Property: "deptId"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactDeptListMembersCmd := &cobra.Command{
|
||
Use: "list-members",
|
||
Short: "查看部门成员(仅本部门,不含下级)",
|
||
Long: "查看指定部门的成员列表。\n\n范围:仅返回传入 deptId 的**本部门**直接成员,**不递归下级部门**。\n跨层级需求:先 'dws contact dept list-children --dept <父deptId>' 枚举子部门,再对子 deptId 分别或合并调用本命令。",
|
||
Example: ` dws contact dept list-members --depts 12345,67890 # 查询 deptId: dws contact dept search 或 dws contact dept list-children`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
// list-members 主 flag 为 --depts;接受 --ids / --id / --dept-id / --dept-ids 作为别名。
|
||
if err := validateRequiredFlagWithAliases(cmd, "depts", "ids", "id", "dept-id", "dept-ids", "deptId", "deptIds"); err != nil {
|
||
return err
|
||
}
|
||
raw := flagOrFallback(cmd, "depts", "ids", "id", "dept-id", "dept-ids", "deptId", "deptIds")
|
||
// 拦截逗号分隔列表中的根部门占位符(self/me/root/0),提示应用 --depts 1。
|
||
// 报错里显示用户实际输入的 flag 名,避免出现 "用户传 --id self 却被报 --depts 不合法" 的错位。
|
||
setName := contactFirstSetFlagName(cmd, "depts", "ids", "id", "dept-id", "dept-ids", "deptId", "deptIds")
|
||
for _, t := range parseCSVValues(raw) {
|
||
if _, ok := contactRootDeptLikeTokens[strings.ToLower(strings.TrimSpace(t))]; ok {
|
||
return fmt.Errorf(
|
||
"flag --%s 包含非法占位符 %q;钉钉根部门 deptId=1,请使用 --%s 1", setName, t, setName)
|
||
}
|
||
}
|
||
return callMCPTool("get_dept_members_by_deptId", map[string]any{
|
||
"deptIds": parseCSVValues(raw),
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactDeptListMembersCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_dept_members_by_deptId",
|
||
CanonicalPath: "contact.get_dept_members_by_deptId",
|
||
CLIPath: "contact dept list-members",
|
||
PrimaryCLIPath: "contact dept list-members",
|
||
},
|
||
Description: "查看部门成员(逗号分隔 deptId)",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "contact", RPCName: "get_dept_members_by_deptId"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查看部门成员(逗号分隔 deptId)",
|
||
UseWhen: []string{"需要按一个或多个 deptId 查看部门成员名单时"},
|
||
AvoidWhen: []string{"只需部门详情或人数时使用 contact dept get-info;需要直属子部门时使用 contact dept list-children"},
|
||
Examples: []string{"dws contact dept list-members --depts 12345,67890 --format json"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "depts", Property: "deptIds"},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── user profile 用户档案(花名册) ────────────────────────────────────
|
||
contactUserProfileCmd := newGroupCommand(&cobra.Command{
|
||
Use: "profile",
|
||
Short: "用户档案(花名册)",
|
||
Long: `用户档案(花名册):查询花名册字段列表、查询员工花名册字段信息。
|
||
|
||
花名册字段包含:试用/转正信息、个人/家庭信息、学历信息、银行卡/合同信息、
|
||
紧急联系人和其他企业自定义信息。
|
||
|
||
【与 contact user get 的区别】
|
||
- contact user get: 组织管理信息(部门、主管、管理员权限)
|
||
- contact user profile get: 个人档案信息(学历、家庭、银行卡等)`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
contactUserProfileFieldsCmd := &cobra.Command{
|
||
Use: "fields",
|
||
Short: "查询花名册有权限的字段列表",
|
||
Long: `查询花名册有权限的字段列表,根据当前用户查询花名册有权限的字段列表。
|
||
|
||
花名册字段包含:试用/转正信息、个人/家庭信息、学历信息、银行卡/合同信息、
|
||
紧急联系人和其他企业自定义信息。
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。
|
||
|
||
【典型用法】
|
||
通常作为 contact user profile get 的前置步骤,用于获取可查询的字段 code 列表。`,
|
||
Example: ` dws contact user profile fields`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return callMCPToolOnServer("hrmregister", "list_authorized_roster_fields", map[string]any{})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserProfileFieldsCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "list_authorized_roster_fields",
|
||
CanonicalPath: "contact.list_authorized_roster_fields",
|
||
CLIPath: "contact user profile fields",
|
||
PrimaryCLIPath: "contact user profile fields",
|
||
},
|
||
Description: "查询当前用户有权查看的花名册字段",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "hrmregister", RPCName: "list_authorized_roster_fields"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询当前用户有权查看的花名册字段",
|
||
UseWhen: []string{"查询花名册前,需要先确认当前用户可见的字段 code 时"},
|
||
AvoidWhen: []string{"需要读取某位员工的具体花名册字段值时不要停在字段目录,应再调用员工花名册查询。"},
|
||
Examples: []string{"dws contact user profile fields --format json"},
|
||
},
|
||
},
|
||
})
|
||
|
||
contactUserProfileGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "查询员工花名册字段信息(个人档案)",
|
||
Long: `查询员工花名册字段信息,根据当前用户指定员工和字段列表,查询相应管理范围内员工的字段值信息。
|
||
|
||
花名册字段包含:试用/转正信息、个人/家庭信息、学历信息、银行卡/合同信息、
|
||
紧急联系人和其他企业自定义信息。
|
||
|
||
返回字段枚举说明:
|
||
- employeeType 员工类型:0 无类型,1 全职,2 兼职,3 实习,4 劳务派遣,5 退休返聘,6 劳务外包
|
||
- employeeStatus 员工状态:-1 无状态,1 待入职,2 试用,3 正式,4 离职,5 待离职,6 试岗,7 已退休
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。
|
||
--staff-id 为查询员工 ID,--fields 为指定字段集合(逗号分隔),可通过
|
||
contact user profile fields 获取可用字段列表。
|
||
|
||
【适用场景】
|
||
- 查询某员工的学历、家庭、银行卡、紧急联系人、合同等档案信息
|
||
|
||
【不适用场景】
|
||
- 查询用户的部门、主管、管理员权限 → 请用 contact user get`,
|
||
Example: ` dws contact user profile get --staff-id STAFF_ID
|
||
dws contact user profile get --staff-id STAFF_ID --fields fieldCode1,fieldCode2`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
params := map[string]any{}
|
||
if v := mustGetFlag(cmd, "staff-id"); v != "" {
|
||
params["staffId"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetString("fields"); v != "" {
|
||
fieldCodes := parseCSVValues(v)
|
||
if len(fieldCodes) > 0 {
|
||
params["fieldCodeList"] = fieldCodes
|
||
}
|
||
}
|
||
return callMCPToolOnServer("hrmregister", "get_authorized_emp_rosterInfo", params)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserProfileGetCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "get_authorized_emp_rosterInfo",
|
||
CanonicalPath: "contact.get_authorized_emp_rosterInfo",
|
||
CLIPath: "contact user profile get",
|
||
PrimaryCLIPath: "contact user profile get",
|
||
},
|
||
Description: "按字段 code 查询指定员工花名册字段值",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "hrmregister", RPCName: "get_authorized_emp_rosterInfo"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按字段 code 查询指定员工花名册字段值",
|
||
UseWhen: []string{"已确认 staffId 与可见字段 code,需要读花名册字段"},
|
||
AvoidWhen: []string{
|
||
"尚不知道可见字段时先用 contact user profile fields",
|
||
"普通通讯录姓名搜索不要走花名册",
|
||
},
|
||
Examples: []string{"dws contact user profile get --staff-id STAFF_ID --fields fieldCode1,fieldCode2"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "fields", Property: "fieldCodeList", Required: boolPtr(false)},
|
||
{Name: "staff-id", Required: boolPtr(false)},
|
||
},
|
||
},
|
||
})
|
||
contactUserProfileGetCmd.Flags().String("staff-id", "", "查询员工 ID(可选)")
|
||
contactUserProfileGetCmd.Flags().String("fields", "", "指定字段集合, 逗号分隔, 可通过 profile fields 获取(可选)")
|
||
|
||
contactUserProfileCmd.AddCommand(contactUserProfileFieldsCmd, contactUserProfileGetCmd)
|
||
|
||
// ── user dismission 离职员工 ───────────────────────────────────────────
|
||
contactUserDismissionCmd := newGroupCommand(&cobra.Command{
|
||
Use: "dismission",
|
||
Short: "离职员工查询",
|
||
Long: `离职员工查询:分页获取离职员工列表,支持按员工姓名、离职时间范围、部门进行过滤。`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
contactUserDismissionSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "分页获取离职员工列表",
|
||
Long: `分页获取离职员工列表,支持按员工姓名、离职时间范围、部门进行过滤。
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。
|
||
--name 员工姓名,模糊搜索(可选)
|
||
--start 离职日期查询范围开始,格式 YYYY-MM-DD(可选)
|
||
--end 离职日期查询范围结束,格式 YYYY-MM-DD(可选)
|
||
--depts 部门 ID 列表,逗号分隔(可选)
|
||
--hide-retirement 是否隐藏退休,默认 true(可选)
|
||
--hide-partner 是否隐藏合作伙伴,默认 false(可选)
|
||
--page 页码,从 1 开始(可选,默认 1)
|
||
--limit 页大小,200 以内(可选,默认 20)
|
||
|
||
注意:--start 和 --end 必须同时设置或同时不设置,不允许只设置其中一个。
|
||
|
||
【适用场景】
|
||
- 查询公司离职员工名单
|
||
- 按时间范围/部门/姓名筛选离职员工
|
||
|
||
【不适用场景】
|
||
- 查询在职员工 → 使用 contact user search`,
|
||
Example: ` dws contact user dismission search
|
||
dws contact user dismission search --name "张三"
|
||
dws contact user dismission search --start 2026-01-01 --end 2026-03-31
|
||
dws contact user dismission search --depts 123456,789012 --page 1 --limit 50`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
startStr, _ := cmd.Flags().GetString("start")
|
||
endStr, _ := cmd.Flags().GetString("end")
|
||
if (startStr == "") != (endStr == "") {
|
||
return fmt.Errorf("--start 和 --end 必须同时设置或同时不设置")
|
||
}
|
||
searchVO := map[string]any{}
|
||
if v, _ := cmd.Flags().GetString("name"); v != "" {
|
||
searchVO["empName"] = v
|
||
}
|
||
if startStr != "" {
|
||
ts, err := parseDateToTimestamp(startStr, "start")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
searchVO["startDate"] = ts
|
||
}
|
||
if endStr != "" {
|
||
ts, err := parseDateToTimestamp(endStr, "end")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
searchVO["endDate"] = ts
|
||
}
|
||
if v, _ := cmd.Flags().GetString("depts"); v != "" {
|
||
searchVO["depts"] = parseCSVInts(v)
|
||
}
|
||
if cmd.Flags().Changed("hide-retirement") {
|
||
v, _ := cmd.Flags().GetBool("hide-retirement")
|
||
searchVO["hideRetirement"] = v
|
||
}
|
||
if cmd.Flags().Changed("hide-partner") {
|
||
v, _ := cmd.Flags().GetBool("hide-partner")
|
||
searchVO["hidePartner"] = v
|
||
}
|
||
params := map[string]any{
|
||
"searchVO": searchVO,
|
||
}
|
||
if v, _ := cmd.Flags().GetInt("page"); v > 0 {
|
||
params["pageNum"] = v
|
||
}
|
||
if v, _ := cmd.Flags().GetInt("limit"); v > 0 {
|
||
params["pageSize"] = v
|
||
}
|
||
return callMCPToolOnServer("hrmregister", "query_dismission_employee_list", params)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserDismissionSearchCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "read", Risk: "low",
|
||
Confirmation: "not_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "query_dismission_employee_list",
|
||
CanonicalPath: "contact.query_dismission_employee_list",
|
||
CLIPath: "contact user dismission search",
|
||
PrimaryCLIPath: "contact user dismission search",
|
||
},
|
||
Description: "查询离职员工列表",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "mcp",
|
||
Availability: "available",
|
||
Ref: &contract.InterfaceRefSpec{ProductID: "hrmregister", RPCName: "query_dismission_employee_list"},
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "查询离职员工列表",
|
||
UseWhen: []string{"需要按姓名、离职时间或部门查询离职员工时"},
|
||
AvoidWhen: []string{"需要查询在职员工或修改离职信息时不要使用;该命令只检索离职员工记录。"},
|
||
Examples: []string{
|
||
"dws contact user dismission search --format json",
|
||
"dws contact user dismission search --name \"张三\" --format json",
|
||
},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "depts", Property: "searchVO.depts"},
|
||
{Name: "end", Property: "searchVO.endDate"},
|
||
{Name: "hide-partner", Property: "searchVO.hidePartner"},
|
||
{Name: "hide-retirement", Property: "searchVO.hideRetirement"},
|
||
{Name: "limit", Property: "pageSize"},
|
||
{Name: "name", Property: "searchVO.empName"},
|
||
{Name: "page", Property: "pageNum"},
|
||
{Name: "start", Property: "searchVO.startDate"},
|
||
},
|
||
},
|
||
})
|
||
contactUserDismissionSearchCmd.Flags().String("name", "", "员工姓名,模糊搜索(可选)")
|
||
contactUserDismissionSearchCmd.Flags().String("start", "", "离职日期查询范围开始,格式 YYYY-MM-DD(可选),与end要么都不填要么都填")
|
||
contactUserDismissionSearchCmd.Flags().String("end", "", "离职日期查询范围结束,格式 YYYY-MM-DD(可选),与start要么都不填要么都填")
|
||
contactUserDismissionSearchCmd.Flags().String("depts", "", "部门 ID 列表,逗号分隔(可选)")
|
||
contactUserDismissionSearchCmd.Flags().Bool("hide-retirement", true, "是否隐藏退休,默认 true(可选)")
|
||
contactUserDismissionSearchCmd.Flags().Bool("hide-partner", false, "是否隐藏合作伙伴,默认 false(可选)")
|
||
contactUserDismissionSearchCmd.Flags().Int("page", 1, "页码,从 1 开始(可选)")
|
||
contactUserDismissionSearchCmd.Flags().Int("limit", 20, "页大小,200 以内(可选)")
|
||
|
||
contactUserDismissionCmd.AddCommand(contactUserDismissionSearchCmd)
|
||
|
||
contactUserInviteCmd := &cobra.Command{
|
||
Use: "invite",
|
||
Short: "邀请员工加入企业",
|
||
Long: `通过手机号邀请单个员工加入当前企业。
|
||
|
||
参数:
|
||
--org-user-name 员工在企业内的名称
|
||
--org-user-mobile 员工手机号
|
||
--depts 员工所属部门列表 JSON 数组,格式: [{"deptId":1}]
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。`,
|
||
Example: ` dws contact user invite --org-user-name "张三" --org-user-mobile "13800138000" --depts '[{"deptId":1}]'`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlags(cmd, "org-user-name", "org-user-mobile"); err != nil {
|
||
return err
|
||
}
|
||
deptsJSON := mustGetFlag(cmd, "depts")
|
||
var depts []map[string]any
|
||
if deptsJSON != "" {
|
||
if err := json.Unmarshal([]byte(deptsJSON), &depts); err != nil {
|
||
return fmt.Errorf("--depts JSON 解析失败: %w\n hint: 正确格式: [{\"deptId\":1}]", err)
|
||
}
|
||
}
|
||
name := strings.TrimSpace(mustGetFlag(cmd, "org-user-name"))
|
||
mobile := strings.TrimSpace(mustGetFlag(cmd, "org-user-mobile"))
|
||
if name == "" || mobile == "" {
|
||
return fmt.Errorf("--org-user-name 和 --org-user-mobile 不能为空")
|
||
}
|
||
return callMCPTool("add_employee", map[string]any{
|
||
"orgUserName": name,
|
||
"orgUserMobile": mobile,
|
||
"depts": depts,
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactUserInviteCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "non_idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "add_employee",
|
||
CanonicalPath: "contact.add_employee",
|
||
CLIPath: "contact user invite",
|
||
PrimaryCLIPath: "contact user invite",
|
||
},
|
||
Description: "按手机号邀请一名员工加入当前企业",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps employee identity and decoded department JSON to contact/add_employee, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "按手机号邀请一名员工加入当前企业",
|
||
UseWhen: []string{"用户明确要求邀请或添加员工到当前企业,且已提供企业内姓名和手机号"},
|
||
AvoidWhen: []string{"需要创建企业专属登录账号时使用 contact account create;需要创建企业组织本身时使用 contact org create"},
|
||
Examples: []string{"dws contact user invite --org-user-name \"张三\" --org-user-mobile \"13800138000\" --depts '[{\"deptId\":1}]'"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "depts", Property: "depts", Required: boolPtr(false), InterfaceType: "array"},
|
||
{Name: "org-user-mobile", Property: "orgUserMobile", Required: boolPtr(true)},
|
||
{Name: "org-user-name", Property: "orgUserName", Required: boolPtr(true)},
|
||
},
|
||
},
|
||
})
|
||
contactUserInviteCmd.Flags().String("org-user-name", "", "员工在企业内的名称 (必填)")
|
||
contactUserInviteCmd.Flags().String("org-user-mobile", "", "员工手机号 (必填)")
|
||
contactUserInviteCmd.Flags().String("depts", "", "员工所属部门列表 JSON 数组(可选),格式: [{\"deptId\":1}]")
|
||
contactUserUpdateCmd := newContactUserUpdateCommand()
|
||
DeclareLeafMetadata(contactUserUpdateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "employee_update",
|
||
CanonicalPath: "contact.employee_update",
|
||
CLIPath: "contact user update",
|
||
PrimaryCLIPath: "contact user update",
|
||
},
|
||
Description: "修改指定员工的企业内姓名、所属部门或直属主管",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps employee update flags to contact/employee_update, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "修改指定员工的企业内姓名、所属部门或直属主管",
|
||
UseWhen: []string{"用户明确要求更新已有员工的组织信息,且已确认目标 userId 和至少一个修改项"},
|
||
AvoidWhen: []string{"修改当前用户自己的昵称或头像应使用 contact user update-self;创建企业专属账号应使用 contact account create"},
|
||
Examples: []string{"dws contact user update --user-id user001 --org-user-name \"张三三\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "depts", Property: "depts", Required: boolPtr(false), InterfaceType: "array"},
|
||
{Name: "id", Property: "userId", Required: boolPtr(false)},
|
||
{Name: "master-user-id", Property: "masterUserId", Required: boolPtr(false)},
|
||
{Name: "org-user-name", Property: "orgUserName", Required: boolPtr(false)},
|
||
{Name: "user-id", Property: "userId", Required: boolPtr(true)},
|
||
{Name: "userid", Property: "userId", Required: boolPtr(false)},
|
||
},
|
||
},
|
||
})
|
||
contactUserUpdateSelfCmd := newContactUserUpdateSelfCommand()
|
||
DeclareLeafMetadata(contactUserUpdateSelfCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "self_user_profile_update",
|
||
CanonicalPath: "contact.self_user_profile_update",
|
||
CLIPath: "contact user update-self",
|
||
PrimaryCLIPath: "contact user update-self",
|
||
},
|
||
Description: "更新当前登录用户自己的昵称或头像",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps self-profile update flags to contact/self_user_profile_update, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "更新当前登录用户自己的昵称或头像",
|
||
UseWhen: []string{"用户明确要求修改自己的 profile 昵称或头像 fileId"},
|
||
AvoidWhen: []string{"修改其他员工的组织信息应使用 contact user update;修改企业专属账号应使用 contact account update"},
|
||
Examples: []string{"dws contact user update-self --nick \"新昵称\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "avatar-file-id", Property: "avatarFileId", Required: boolPtr(false)},
|
||
{Name: "nick", Property: "nick", Required: boolPtr(false)},
|
||
},
|
||
},
|
||
})
|
||
contactUserUpdateOwnnessCmd := newContactUserUpdateOwnnessCommand()
|
||
DeclareLeafMetadata(contactUserUpdateOwnnessCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "user_ownness_update",
|
||
CanonicalPath: "contact.user_ownness_update",
|
||
CLIPath: "contact user update-ownness",
|
||
PrimaryCLIPath: "contact user update-ownness",
|
||
},
|
||
Description: "更新指定用户的个人状态文本(展示在个人资料与聊天会话中,如「居家办公中」)",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps personal-status update flags to contact/user_ownness_update, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "更新指定用户的个人状态文本(如「居家办公中」)",
|
||
UseWhen: []string{"用户明确要求设置或修改自己/指定用户的个人状态文本,且已确认目标 userId 和状态内容"},
|
||
AvoidWhen: []string{"修改员工组织信息(姓名 / 部门 / 主管)应使用 contact user update;修改当前用户昵称或头像应使用 contact user update-self"},
|
||
Examples: []string{"dws contact user update-ownness --user-id user001 --ownness-text \"居家办公中\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "id", Property: "userId", Required: boolPtr(false)},
|
||
{Name: "ownness-text", Property: "ownnessText", Required: boolPtr(true)},
|
||
{Name: "user-id", Property: "userId", Required: boolPtr(true)},
|
||
{Name: "userid", Property: "userId", Required: boolPtr(false)},
|
||
},
|
||
},
|
||
})
|
||
|
||
// ── flags 注册 ───────────────────────────────────────────────
|
||
contactUserSearchCmd.Flags().String("query", "", "搜索关键词 (必填)")
|
||
contactUserSearchCmd.Flags().String("keyword", "", "--query 的别名")
|
||
contactUserSearchCmd.Flags().String("name", "", "--query 的别名")
|
||
_ = contactUserSearchCmd.Flags().MarkHidden("keyword")
|
||
_ = contactUserSearchCmd.Flags().MarkHidden("name")
|
||
contactUserSearchMobileCmd.Flags().String("mobile", "", "手机号 (必填)")
|
||
contactUserGetCmd.Flags().String("ids", "", "用户 ID 列表 (必填)")
|
||
contactUserGetCmd.Flags().String("user-id", "", "--ids 的别名")
|
||
contactUserGetCmd.Flags().String("user-ids", "", "--ids 的别名")
|
||
contactUserGetCmd.Flags().String("userid", "", "--ids 的别名(全小写)")
|
||
_ = contactUserGetCmd.Flags().MarkHidden("user-id")
|
||
_ = contactUserGetCmd.Flags().MarkHidden("user-ids")
|
||
_ = contactUserGetCmd.Flags().MarkHidden("userid")
|
||
userCmd.AddCommand(
|
||
contactUserGetSelfCmd, contactUserSearchCmd, contactUserSearchMobileCmd, contactUserGetCmd,
|
||
contactUserInviteCmd, // 邀请员工加入企业
|
||
contactUserUpdateCmd, // 修改员工信息
|
||
contactUserUpdateSelfCmd, // 更新当前用户自己的 profile 信息
|
||
contactUserUpdateOwnnessCmd, // 更新用户个人状态
|
||
contactUserProfileCmd, // 花名册档案
|
||
contactUserDismissionCmd, // 离职员工
|
||
)
|
||
|
||
contactDeptSearchCmd.Flags().String("query", "", "搜索关键词 (必填)")
|
||
contactDeptSearchCmd.Flags().String("keyword", "", "--query 的别名")
|
||
contactDeptSearchCmd.Flags().String("name", "", "--query 的别名")
|
||
_ = contactDeptSearchCmd.Flags().MarkHidden("keyword")
|
||
_ = contactDeptSearchCmd.Flags().MarkHidden("name")
|
||
// 主 flag 与 RunE 读取保持一致:get-info / list-children 用 --dept,list-members 用 --depts。
|
||
// 历史上主 flag 曾误注册为 --id/--ids,导致 RunE 读的 --dept/--depts 未注册、命令行传入报 unknown flag。
|
||
contactDeptGetInfoCmd.Flags().String("dept", "", "部门 ID (必填)")
|
||
contactDeptListChildrenCmd.Flags().String("dept", "", "部门 ID (必填)")
|
||
contactDeptListMembersCmd.Flags().String("depts", "", "部门 ID 列表 (必填)")
|
||
|
||
// dept 系列命令统一接受 --id / --ids / --dept-id / --dept-ids 别名(集中注册避免逐命令重复写)。
|
||
// camelCase --deptId / --deptIds 由 RegisterCamelCaseAliases 自动派生,无需手写。
|
||
type deptIDAliasSpec struct {
|
||
cmd *cobra.Command
|
||
aliases []string
|
||
}
|
||
contactDeptCreateCmd := newContactDeptCreateCommand()
|
||
DeclareLeafMetadata(contactDeptCreateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "non_idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "department_create",
|
||
CanonicalPath: "contact.department_create",
|
||
CLIPath: "contact dept create",
|
||
PrimaryCLIPath: "contact dept create",
|
||
},
|
||
Description: "在当前企业的根部门或指定父部门下创建部门",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps department creation flags to contact/department_create, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "在当前企业的根部门或指定父部门下创建部门",
|
||
UseWhen: []string{"用户明确要求新建部门,且已确认部门名称、父部门及是否同步创建部门群"},
|
||
AvoidWhen: []string{"修改已有部门名称或父级应使用 contact dept update;仅查找部门应使用 contact dept search"},
|
||
Examples: []string{"dws contact dept create --name \"新产品部\" --create-dept-group=true"},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "create-dept-group", Property: "createDeptGroup", Required: boolPtr(true), InterfaceType: "boolean"},
|
||
{Name: "dept-name", Property: "deptName", Required: boolPtr(false)},
|
||
{Name: "name", Property: "deptName", Required: boolPtr(true)},
|
||
{Name: "parent", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "super-dept", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "super-dept-id", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
},
|
||
},
|
||
})
|
||
contactDeptUpdateCmd := newContactDeptUpdateCommand()
|
||
DeclareLeafMetadata(contactDeptUpdateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "department_update",
|
||
CanonicalPath: "contact.department_update",
|
||
CLIPath: "contact dept update",
|
||
PrimaryCLIPath: "contact dept update",
|
||
},
|
||
Description: "更新指定部门的名称,并可调整父部门",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps department update flags to contact/department_update, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "更新指定部门的名称,并可调整父部门",
|
||
UseWhen: []string{"用户明确要求修改已有部门名称或迁移父部门,且已确认目标 deptId"},
|
||
AvoidWhen: []string{"创建新部门应使用 contact dept create;仅查看部门信息应使用 contact dept get-info"},
|
||
Examples: []string{"dws contact dept update --dept 12345 --name \"研发中心\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "dept", Property: "deptId", Required: boolPtr(true), InterfaceType: "integer"},
|
||
{Name: "dept-id", Property: "deptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "dept-ids", Property: "deptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "dept-name", Property: "deptName", Required: boolPtr(false)},
|
||
{Name: "id", Property: "deptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "ids", Property: "deptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "name", Property: "deptName", Required: boolPtr(true)},
|
||
{Name: "parent", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "super-dept", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
{Name: "super-dept-id", Property: "superDeptId", Required: boolPtr(false), InterfaceType: "integer"},
|
||
},
|
||
},
|
||
})
|
||
for _, s := range []deptIDAliasSpec{
|
||
{contactDeptGetInfoCmd, []string{"id", "dept-id", "ids", "dept-ids"}},
|
||
{contactDeptListChildrenCmd, []string{"id", "ids", "dept-id", "dept-ids"}},
|
||
{contactDeptListMembersCmd, []string{"ids", "id", "dept-id", "dept-ids"}},
|
||
{contactDeptUpdateCmd, []string{"id", "ids", "dept-id", "dept-ids"}},
|
||
} {
|
||
for _, name := range s.aliases {
|
||
s.cmd.Flags().String(name, "", "部门 ID 别名(等价于当前命令的主 flag)")
|
||
_ = s.cmd.Flags().MarkHidden(name)
|
||
}
|
||
}
|
||
contactDeptCmd.AddCommand(
|
||
contactDeptSearchCmd,
|
||
contactDeptGetInfoCmd,
|
||
contactDeptListChildrenCmd,
|
||
contactDeptListMembersCmd,
|
||
contactDeptCreateCmd,
|
||
contactDeptUpdateCmd,
|
||
)
|
||
|
||
// ── org 企业管理 ──────────────────────────────────────────────────
|
||
|
||
contactOrgCmd := newGroupCommand(&cobra.Command{
|
||
Use: "org",
|
||
Short: "企业管理",
|
||
Long: `企业管理:创建企业。
|
||
|
||
【何时用哪个命令】
|
||
- 创建新企业 → contact org create
|
||
- 创建企业专属账号 → contact account create
|
||
- 邀请员工加入企业 → contact user invite`,
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
contactOrgCreateCmd := &cobra.Command{
|
||
Use: "create",
|
||
Short: "创建企业",
|
||
Long: `创建一个新的钉钉企业。需提供企业名称和当前用户在企业内的名称(作为创建者)。
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。`,
|
||
Example: ` dws contact org create --org-name "我的企业" --creator-username "张三"`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlags(cmd, "org-name", "creator-username"); err != nil {
|
||
return err
|
||
}
|
||
orgName := strings.TrimSpace(mustGetFlag(cmd, "org-name"))
|
||
creatorUsername := strings.TrimSpace(mustGetFlag(cmd, "creator-username"))
|
||
if orgName == "" || creatorUsername == "" {
|
||
return fmt.Errorf("--org-name 和 --creator-username 不能为空")
|
||
}
|
||
return callMCPTool("org_create", map[string]any{
|
||
"orgName": orgName,
|
||
"creatorUsername": creatorUsername,
|
||
})
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactOrgCreateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "non_idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "org_create",
|
||
CanonicalPath: "contact.org_create",
|
||
CLIPath: "contact org create",
|
||
PrimaryCLIPath: "contact org create",
|
||
},
|
||
Description: "创建一个新的钉钉企业组织",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps organization and creator names to contact/org_create, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "创建一个新的钉钉企业组织",
|
||
UseWhen: []string{"用户明确要求创建、新建、开通或初始化企业组织,并已提供企业名称和创建者企业内名称"},
|
||
AvoidWhen: []string{"请求中包含企业账号、专属账号或登录账号时改用 contact account create;邀请员工时改用 contact user invite"},
|
||
Examples: []string{"dws contact org create --org-name \"我的企业\" --creator-username \"张三\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "creator-username", Property: "creatorUsername", Required: boolPtr(true)},
|
||
{Name: "org-name", Property: "orgName", Required: boolPtr(true)},
|
||
},
|
||
},
|
||
})
|
||
contactOrgCreateCmd.Flags().String("org-name", "", "企业名称 (必填)")
|
||
contactOrgCreateCmd.Flags().String("creator-username", "", "创建者在企业内的名称,对应 creatorUsername (必填)")
|
||
contactOrgCmd.AddCommand(contactOrgCreateCmd)
|
||
|
||
// ── account 企业账号管理 ──────────────────────────────────────────
|
||
|
||
contactAccountCmd := newGroupCommand(&cobra.Command{
|
||
Use: "account",
|
||
Short: "企业账号管理",
|
||
Long: "企业账号管理:创建或更新企业专属账号。",
|
||
RunE: groupRunE,
|
||
})
|
||
|
||
contactAccountCreateCmd := &cobra.Command{
|
||
Use: "create",
|
||
Short: "创建企业专属账号",
|
||
Long: `为当前企业创建一个专属登录账号。
|
||
|
||
必填:--org-user-name、--login-id
|
||
可选:--org-user-mobile、--email、--dept-ids、--send-pwd-via-sms
|
||
|
||
注意:
|
||
- 登录号(--login-id)请勿包含手机号等联系方式,否则可能被运营商拦截短信
|
||
- --send-pwd-via-sms 控制是否通过手机短信/邮件发送登录邀请
|
||
|
||
认证信息(corpId、optUserId)由系统自动注入,无需手动传入。`,
|
||
Example: ` dws contact account create --org-user-name "张三" --login-id "zhangsan001" --org-user-mobile "13800138000" --email "zhangsan@example.com" --dept-ids "1,2,3" --send-pwd-via-sms`,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlags(cmd, "org-user-name", "login-id"); err != nil {
|
||
return err
|
||
}
|
||
orgUserName := strings.TrimSpace(mustGetFlag(cmd, "org-user-name"))
|
||
loginID := strings.TrimSpace(mustGetFlag(cmd, "login-id"))
|
||
if orgUserName == "" || loginID == "" {
|
||
return fmt.Errorf("--org-user-name 和 --login-id 不能为空")
|
||
}
|
||
toolArgs := map[string]any{
|
||
"orgUserName": orgUserName,
|
||
"loginId": loginID,
|
||
}
|
||
if cmd.Flags().Changed("send-pwd-via-sms") {
|
||
sendPwdViaSMS, _ := cmd.Flags().GetBool("send-pwd-via-sms")
|
||
toolArgs["sendPwdViaSms"] = sendPwdViaSMS
|
||
}
|
||
if mobile := strings.TrimSpace(mustGetFlag(cmd, "org-user-mobile")); mobile != "" {
|
||
toolArgs["orgUserMobile"] = mobile
|
||
}
|
||
if email := strings.TrimSpace(mustGetFlag(cmd, "email")); email != "" {
|
||
toolArgs["email"] = email
|
||
}
|
||
if cmd.Flags().Changed("dept-ids") {
|
||
ids, err := parseCSVIntsStrict(mustGetFlag(cmd, "dept-ids"))
|
||
if err != nil {
|
||
return fmt.Errorf("--dept-ids 解析失败: %w", err)
|
||
}
|
||
toolArgs["deptIds"] = ids
|
||
}
|
||
return callMCPTool("exclusive_account_create", toolArgs)
|
||
},
|
||
}
|
||
DeclareLeafMetadata(contactAccountCreateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "not_required", Idempotency: "non_idempotent",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "exclusive_account_create",
|
||
CanonicalPath: "contact.exclusive_account_create",
|
||
CLIPath: "contact account create",
|
||
PrimaryCLIPath: "contact account create",
|
||
},
|
||
Description: "为当前企业创建专属登录账号",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps enterprise-account flags to contact/exclusive_account_create, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "为当前企业创建专属登录账号",
|
||
UseWhen: []string{"用户明确要求创建企业账号、专属账号或企业登录账号,并已提供员工名称和登录号"},
|
||
AvoidWhen: []string{"创建企业组织本身应使用 contact org create;仅邀请已有手机号员工入企应使用 contact user invite"},
|
||
Examples: []string{"dws contact account create --org-user-name \"张三\" --login-id \"zhangsan001\" --org-user-mobile \"13800138000\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "dept-ids", Property: "deptIds", Required: boolPtr(false), InterfaceType: "array"},
|
||
{Name: "email", Property: "email", Required: boolPtr(false)},
|
||
{Name: "login-id", Property: "loginId", Required: boolPtr(true)},
|
||
{Name: "org-user-mobile", Property: "orgUserMobile", Required: boolPtr(false)},
|
||
{Name: "org-user-name", Property: "orgUserName", Required: boolPtr(true)},
|
||
{Name: "send-pwd-via-sms", Property: "sendPwdViaSms", Required: boolPtr(false), InterfaceType: "boolean"},
|
||
},
|
||
},
|
||
})
|
||
contactAccountCreateCmd.Flags().String("org-user-name", "", "员工在企业内的名称 (必填)")
|
||
contactAccountCreateCmd.Flags().String("login-id", "", "登录号 (必填),请勿包含手机号")
|
||
contactAccountCreateCmd.Flags().String("org-user-mobile", "", "员工手机号(可选)")
|
||
contactAccountCreateCmd.Flags().String("email", "", "邮箱(可选)")
|
||
contactAccountCreateCmd.Flags().String("dept-ids", "", "要加入的部门 ID 列表,逗号分隔(可选)")
|
||
contactAccountCreateCmd.Flags().Bool("send-pwd-via-sms", false, "是否通过手机短信/邮件发送登录邀请(可选)")
|
||
contactAccountUpdateCmd := newContactAccountUpdateCommand()
|
||
DeclareLeafMetadata(contactAccountUpdateCmd, LeafSpec{
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write", Risk: "medium",
|
||
Confirmation: "user_required", Idempotency: "unknown",
|
||
},
|
||
Contract: LeafContract{
|
||
Identity: contract.ToolIdentitySpec{
|
||
ProductID: "contact",
|
||
Name: "exclusive_account_user_update",
|
||
CanonicalPath: "contact.exclusive_account_user_update",
|
||
CLIPath: "contact account update",
|
||
PrimaryCLIPath: "contact account update",
|
||
},
|
||
Description: "更新企业专属账号的组织信息或个人资料",
|
||
Interface: &contract.InterfaceSpec{
|
||
Mode: "composite",
|
||
Availability: "available",
|
||
Reason: "Reviewed unpinned remote adapter: the executable CLI maps enterprise-account update flags to contact/exclusive_account_user_update, which is absent from the pinned MCP metadata snapshot.",
|
||
},
|
||
Selection: contract.SelectionSpec{
|
||
AgentSummary: "更新企业专属账号的组织信息或个人资料",
|
||
UseWhen: []string{"用户明确要求修改已有企业专属账号的姓名、部门、主管、昵称或头像"},
|
||
AvoidWhen: []string{"创建新企业专属账号应使用 contact account create;修改普通员工组织信息应使用 contact user update"},
|
||
Examples: []string{"dws contact account update --user-id user001 --nick \"新昵称\""},
|
||
},
|
||
Parameters: []contract.ParamDecl{
|
||
{Name: "avatar-file-id", Property: "avatarFileId", Required: boolPtr(false)},
|
||
{Name: "depts", Property: "depts", Required: boolPtr(false), InterfaceType: "array"},
|
||
{Name: "id", Property: "userId", Required: boolPtr(false)},
|
||
{Name: "master-user-id", Property: "masterUserId", Required: boolPtr(false)},
|
||
{Name: "nick", Property: "nick", Required: boolPtr(false)},
|
||
{Name: "org-user-name", Property: "orgUserName", Required: boolPtr(false)},
|
||
{Name: "user-id", Property: "userId", Required: boolPtr(true)},
|
||
{Name: "userid", Property: "userId", Required: boolPtr(false)},
|
||
},
|
||
},
|
||
})
|
||
contactAccountCmd.AddCommand(contactAccountCreateCmd, contactAccountUpdateCmd)
|
||
|
||
relationCmd.AddCommand(contactRelationListMyFollowingsCmd)
|
||
root.AddCommand(userCmd, contactDeptCmd, contactLabelCmd, relationCmd, contactOrgCmd, contactAccountCmd)
|
||
|
||
addQueryFlags := func(cmd *cobra.Command) {
|
||
cmd.Flags().String("query", "", "搜索关键词 (必填)")
|
||
cmd.Flags().String("keyword", "", "--query 的别名")
|
||
cmd.Flags().String("name", "", "--query 的别名")
|
||
_ = cmd.Flags().MarkHidden("keyword")
|
||
_ = cmd.Flags().MarkHidden("name")
|
||
}
|
||
addUserIDFlags := func(cmd *cobra.Command) {
|
||
cmd.Flags().String("ids", "", "用户 ID 列表")
|
||
cmd.Flags().String("user-id", "", "--ids 的别名")
|
||
cmd.Flags().String("user-ids", "", "--ids 的别名")
|
||
cmd.Flags().String("userid", "", "--ids 的别名(全小写)")
|
||
_ = cmd.Flags().MarkHidden("user-id")
|
||
_ = cmd.Flags().MarkHidden("user-ids")
|
||
_ = cmd.Flags().MarkHidden("userid")
|
||
}
|
||
addLabelNameFlags := func(cmd *cobra.Command) {
|
||
cmd.Flags().String("names", "", "角色名称,逗号分隔")
|
||
cmd.Flags().String("name", "", "--names 的别名")
|
||
cmd.Flags().String("query", "", "--names 的别名")
|
||
cmd.Flags().String("keyword", "", "--names 的别名")
|
||
_ = cmd.Flags().MarkHidden("name")
|
||
_ = cmd.Flags().MarkHidden("query")
|
||
_ = cmd.Flags().MarkHidden("keyword")
|
||
}
|
||
addLabelIDFlags := func(cmd *cobra.Command) {
|
||
cmd.Flags().String("id", "", "角色 ID")
|
||
cmd.Flags().String("label-id", "", "--id 的别名")
|
||
cmd.Flags().String("role-id", "", "--id 的别名")
|
||
_ = cmd.Flags().MarkHidden("label-id")
|
||
_ = cmd.Flags().MarkHidden("role-id")
|
||
}
|
||
addDeptIDFlags := func(cmd *cobra.Command) {
|
||
cmd.Flags().String("dept", "", "部门 ID")
|
||
cmd.Flags().String("id", "", "--dept 的别名")
|
||
cmd.Flags().String("dept-id", "", "--dept 的别名")
|
||
cmd.Flags().String("dept-ids", "", "--dept 的别名")
|
||
_ = cmd.Flags().MarkHidden("id")
|
||
_ = cmd.Flags().MarkHidden("dept-id")
|
||
_ = cmd.Flags().MarkHidden("dept-ids")
|
||
}
|
||
runContactUserGet := func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, contactUserIDFlagKeys[0], contactUserIDFlagKeys[1:]...); err != nil {
|
||
return err
|
||
}
|
||
raw := flagOrFallback(cmd, contactUserIDFlagKeys[0], contactUserIDFlagKeys[1:]...)
|
||
for _, part := range parseCSVValues(raw) {
|
||
switch strings.ToLower(strings.TrimSpace(part)) {
|
||
case "me", "self", "current", "whoami", "i":
|
||
return fmt.Errorf("--ids 需要真实的 userId,不接受 %q 这类占位符\n hint: 获取当前用户用: dws contact user get-self", part)
|
||
}
|
||
}
|
||
return callMCPTool("get_user_info_by_user_ids", map[string]any{
|
||
"user_id_list": parseCSVValues(raw),
|
||
})
|
||
}
|
||
runContactUserSearch := func(cmd *cobra.Command, args []string) error {
|
||
if err := validateRequiredFlagWithAliases(cmd, "query", "keyword", "name"); err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("search_contact_by_key_word", map[string]any{
|
||
"keyword": flagOrFallback(cmd, "query", "keyword", "name"),
|
||
})
|
||
}
|
||
|
||
contactRootSearchCmd := &cobra.Command{
|
||
Use: "search",
|
||
Short: "按关键词搜索用户(兼容入口)",
|
||
Hidden: true,
|
||
RunE: runContactUserSearch,
|
||
}
|
||
contactRootFindCmd := &cobra.Command{
|
||
Use: "find",
|
||
Short: "按关键词搜索用户(兼容入口)",
|
||
Hidden: true,
|
||
RunE: runContactUserSearch,
|
||
}
|
||
addQueryFlags(contactRootSearchCmd)
|
||
addQueryFlags(contactRootFindCmd)
|
||
|
||
contactRootGetCmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "获取用户/部门/角色详情(兼容入口)",
|
||
Hidden: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
switch {
|
||
case contactAnyFlagChanged(cmd, contactUserIDFlagKeys...):
|
||
return runContactUserGet(cmd, args)
|
||
case contactAnyFlagChanged(cmd, "dept", "id", "dept-id", "dept-ids"):
|
||
deptID, err := contactParseInt64WithAliases(cmd, "dept", "id", "dept-id", "dept-ids")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return callMCPTool("get_dept_info_by_dept_id", map[string]any{"deptId": deptID})
|
||
case contactAnyFlagChanged(cmd, "names", "name", "query", "keyword"):
|
||
return runContactLabelGet(cmd, args)
|
||
case contactAnyFlagChanged(cmd, "label-id", "role-id"):
|
||
return runContactLabelMembers(cmd, args)
|
||
default:
|
||
return fmt.Errorf("contact get 需要指定 --ids <userId>、--dept <deptId>、--names <角色名> 或 --label-id <角色ID>")
|
||
}
|
||
},
|
||
}
|
||
addUserIDFlags(contactRootGetCmd)
|
||
addDeptIDFlags(contactRootGetCmd)
|
||
addLabelNameFlags(contactRootGetCmd)
|
||
contactRootGetCmd.Flags().String("label-id", "", "角色 ID")
|
||
contactRootGetCmd.Flags().String("role-id", "", "--label-id 的别名")
|
||
_ = contactRootGetCmd.Flags().MarkHidden("role-id")
|
||
|
||
contactRootListCmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "列出角色/部门成员/用户详情(兼容入口)",
|
||
Hidden: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
switch {
|
||
case contactAnyFlagChanged(cmd, "depts", "id", "dept-id", "dept-ids"):
|
||
if err := validateRequiredFlagWithAliases(cmd, "depts", "id", "dept-id", "dept-ids"); err != nil {
|
||
return err
|
||
}
|
||
raw := flagOrFallback(cmd, "depts", "id", "dept-id", "dept-ids")
|
||
setName := contactFirstSetFlagName(cmd, "depts", "id", "dept-id", "dept-ids")
|
||
for _, t := range parseCSVValues(raw) {
|
||
if _, ok := contactRootDeptLikeTokens[strings.ToLower(strings.TrimSpace(t))]; ok {
|
||
return fmt.Errorf("flag --%s 包含非法占位符 %q;钉钉根部门 deptId=1,请使用 --%s 1", setName, t, setName)
|
||
}
|
||
}
|
||
return callMCPTool("get_dept_members_by_deptId", map[string]any{"deptIds": parseCSVValues(raw)})
|
||
case contactAnyFlagChanged(cmd, contactUserIDFlagKeys...):
|
||
return runContactUserGet(cmd, args)
|
||
case contactAnyFlagChanged(cmd, "names", "name", "query", "keyword"):
|
||
return runContactLabelGet(cmd, args)
|
||
default:
|
||
return runContactLabelList(cmd, args)
|
||
}
|
||
},
|
||
}
|
||
contactRootListCmd.Flags().String("depts", "", "部门 ID 列表")
|
||
contactRootListCmd.Flags().String("id", "", "--depts 的别名")
|
||
contactRootListCmd.Flags().String("dept-id", "", "--depts 的别名")
|
||
contactRootListCmd.Flags().String("dept-ids", "", "--depts 的别名")
|
||
_ = contactRootListCmd.Flags().MarkHidden("id")
|
||
_ = contactRootListCmd.Flags().MarkHidden("dept-id")
|
||
_ = contactRootListCmd.Flags().MarkHidden("dept-ids")
|
||
addUserIDFlags(contactRootListCmd)
|
||
addLabelNameFlags(contactRootListCmd)
|
||
|
||
for _, use := range []string{"self", "me", "whoami", "get-self", "user-self", "current-user"} {
|
||
root.AddCommand(&cobra.Command{
|
||
Use: use,
|
||
Short: "获取当前用户信息(兼容入口)",
|
||
Hidden: true,
|
||
RunE: contactUserGetSelfCmd.RunE,
|
||
})
|
||
}
|
||
root.AddCommand(contactRootSearchCmd, contactRootFindCmd, contactRootGetCmd, contactRootListCmd)
|
||
|
||
contactHintSubCmd := func(use, suggestion string) *cobra.Command {
|
||
c := hintSubCmd(use, suggestion)
|
||
runHint := c.RunE
|
||
c.DisableFlagParsing = true
|
||
c.RunE = func(cmd *cobra.Command, args []string) error {
|
||
for _, arg := range args {
|
||
if arg == "--help" || arg == "-h" {
|
||
return cmd.Help()
|
||
}
|
||
}
|
||
return runHint(cmd, args)
|
||
}
|
||
return c
|
||
}
|
||
|
||
root.AddCommand(contactHintSubCmd("department", "use: dws contact dept [search|list-members|list-children|get-info]"))
|
||
|
||
// hint: dws contact user find/list/info/detail → 指向 user search / user get
|
||
userCmd.AddCommand(contactHintSubCmd("find", "use: dws contact user search --query <关键词>"))
|
||
userCmd.AddCommand(contactHintSubCmd("list", "use: dws contact user search --query <关键词>"))
|
||
// 针对 LLM 常混淆的 REST 风格子命名:user info / user detail / user get-info
|
||
userCmd.AddCommand(contactHintSubCmd("info", "use: dws contact user get --ids <用户ID> or dws contact user get-self"))
|
||
userCmd.AddCommand(contactHintSubCmd("detail", "use: dws contact user get --ids <用户ID>"))
|
||
userCmd.AddCommand(contactHintSubCmd("get-info", "use: dws contact user get --ids <用户ID>"))
|
||
// 注:me / whoami / current 现已是 get-self 的真别名(Aliases),不再注册 hintSubCmd(会与真别名冲突)。
|
||
|
||
// hint: dws contact dept list / dept info / dept detail → 指向 list-members / list-children / get-info
|
||
contactDeptCmd.AddCommand(contactHintSubCmd("list", "use: dws contact dept list-members --depts <部门ID> or dws contact dept list-children --dept <父部门ID>"))
|
||
contactDeptCmd.AddCommand(contactHintSubCmd("info", "use: dws contact dept get-info --dept <部门ID>"))
|
||
contactDeptCmd.AddCommand(contactHintSubCmd("detail", "use: dws contact dept get-info --dept <部门ID>"))
|
||
|
||
// dws contact label find/search/info/detail/list-all → 真实兼容入口。
|
||
// 注:list 已是真命令(label list),不再注册 hintSubCmd(会与真命令冲突)。
|
||
for _, use := range []string{"find", "search", "info"} {
|
||
cmd := &cobra.Command{Use: use, Hidden: true, RunE: runContactLabelGet}
|
||
addLabelNameFlags(cmd)
|
||
contactLabelCmd.AddCommand(cmd)
|
||
}
|
||
contactLabelDetailCmd := &cobra.Command{Use: "detail", Hidden: true, RunE: runContactLabelMembers}
|
||
addLabelIDFlags(contactLabelDetailCmd)
|
||
contactLabelCmd.AddCommand(contactLabelDetailCmd)
|
||
contactLabelCmd.AddCommand(&cobra.Command{Use: "list-all", Hidden: true, RunE: runContactLabelList})
|
||
|
||
// contact 子树统一错误兜底:任何 flag 解析失败均在尾部追加 "See '<CommandPath> --help' for usage."
|
||
// 与 docker / kubectl / gh 的 UX 一致。unknown subcommand 由 cobra 自带 Did-You-Mean 处理。
|
||
var attachContactHelpHint func(c *cobra.Command)
|
||
attachContactHelpHint = func(c *cobra.Command) {
|
||
c.SetFlagErrorFunc(func(cc *cobra.Command, err error) error {
|
||
// 与 root 级 flagErrorWithSuggestions 保持同款尾部 hint 格式(句号结尾为全树 UX 约定)。
|
||
msg := fmt.Sprintf("%s\nSee '%s --help' for usage.", err.Error(), cc.CommandPath())
|
||
return errors.New(msg)
|
||
})
|
||
for _, sub := range c.Commands() {
|
||
attachContactHelpHint(sub)
|
||
}
|
||
}
|
||
attachContactHelpHint(root)
|
||
|
||
return root
|
||
}
|
||
|
||
// parseCSVInts 解析逗号分隔的整数字符串为 []int64 切片,
|
||
// 去除空白并过滤无法解析的项。
|
||
func parseCSVInts(s string) []int64 {
|
||
parts := strings.Split(s, ",")
|
||
result := make([]int64, 0, len(parts))
|
||
for _, p := range parts {
|
||
if trimmed := strings.TrimSpace(p); trimmed != "" {
|
||
if n, err := strconv.ParseInt(trimmed, 10, 64); err == nil {
|
||
result = append(result, n)
|
||
}
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func parseCSVIntsStrict(s string) ([]int64, error) {
|
||
parts := strings.Split(s, ",")
|
||
result := make([]int64, 0, len(parts))
|
||
for i, p := range parts {
|
||
trimmed := strings.TrimSpace(p)
|
||
if trimmed == "" {
|
||
return nil, fmt.Errorf("第 %d 项为空", i+1)
|
||
}
|
||
n, err := strconv.ParseInt(trimmed, 10, 64)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("第 %d 项 %q 不是整数: %w", i+1, trimmed, err)
|
||
}
|
||
result = append(result, n)
|
||
}
|
||
return result, nil
|
||
}
|