Files
dingtalk-workspace-cli/internal/cli/canonical.go

146 lines
6.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cli
import (
"fmt"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/spf13/cobra"
)
// schemaCommandCatalogError / payloads use deliverySchemaCatalog
// (RegisterSchemaSourceRoot → ResolveSchemaBuild). There is no committed
// Schema Catalog embed fallback.
var schemaCommandCatalogError = deliverySchemaCatalogError
// NewMCPCommand registers the mcp product declaration and returns its root
// command. The app layer attaches reviewed static MCP helpers as
// subcommands; live discovery surfaces are retired.
func NewMCPCommand() *cobra.Command {
// Product-level Agent routing Decl (migrated from selection/mcp.json
// products.mcp). Schema assembly stamps provenance contract_final.
contract.RegisterProductDecl(contract.ProductDecl{
ID: "mcp",
Selection: contract.ProductSelectionDecl{
AgentSummary: "解析和管理当前身份可用的 MCP 服务连接信息",
UseWhen: []string{
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端",
},
AvoidWhen: []string{
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp",
},
},
})
// The legacy dynamic MCP surface has been removed; the app layer adds
// reviewed static MCP helpers as subcommands of this command.
cmd := &cobra.Command{
Use: "mcp",
Short: "管理 MCP 服务连接信息",
Long: "管理经过审核并纳入 Schema 的 MCP 服务连接辅助能力。",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
return cmd
}
// NewSchemaCommand serves the typed Schema contract. Production assembles from
// declarations via ResolveSchemaBuild (factory registered by internal/app).
// A malformed assembly fails closed; the command takes no discovery loader
// because queries never run service discovery.
func NewSchemaCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "schema [path]",
Short: "渐进查看命令 Schema (产品 / 分组 / 工具参数)",
Long: `查看当前可运行命令的 Schema 元数据。
不带参数时列出产品和工具数量;传产品或分组路径逐层展开;传具体工具路径输出扁平参数 Schema(对齐 GWS:parameters 内联 required,键为 CLI flag)。普通 Agent 查询应使用 --compact:它按稳定字段白名单输出选参、约束、安全语义和已评审的返回契约。省略 --compact 的 full leaf 保留参数映射、接口绑定和 provenance,仅用于定向审计;--all 输出全部工具的完整 leaf Schema,用于审计/CI。helper、MCP 与本地 Cobra 命令均须通过 ContractFinal.Identity 声明进入收集的身份集,并从同一声明装配的 ToolSpec 投影;查询不执行服务发现或临时合成第二份 Schema。`,
Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
all, _ := cmd.Flags().GetBool("all")
compact, _ := cmd.Flags().GetBool("compact")
cliPath, _ := cmd.Flags().GetString("cli-path")
cliPath = strings.TrimSpace(cliPath)
if cliPath != "" && len(args) > 0 {
return apperrors.NewValidation("--cli-path and positional argument are mutually exclusive")
}
if len(args) == 1 && strings.EqualFold(strings.TrimSpace(args[0]), "list") {
args = nil
}
if all && (cliPath != "" || len(args) > 0) {
return apperrors.NewValidation("--all cannot be combined with a schema path")
}
if cliPath != "" {
args = []string{cliPath}
}
if err := schemaCommandCatalogError(); err != nil {
return fmt.Errorf("load typed Schema registry: %w", err)
}
var payload map[string]any
var err error
if all {
payload, err = deliverySchemaAllPayload()
} else if len(args) == 0 {
payload, err = deliverySchemaOverviewPayload()
} else {
payload, err = queryDeliverySchemaPayload(args)
}
if err != nil {
return err
}
if compact {
payload = stripSchemaPayloadCompact(payload)
}
return output.WriteFiltered(cmd.OutOrStdout(), output.ResolveFormat(cmd, output.FormatJSON), payload, output.ResolveFields(cmd), output.ResolveJQ(cmd))
},
}
cmd.Flags().Bool("all", false, "输出全部工具的完整 leaf Schema(包括参数和约束,用于审计/CI)")
cmd.Flags().Bool("compact", false, "按稳定字段白名单输出 Agent 选参、约束、安全语义和返回契约")
cmd.Flags().String("cli-path", "", "按 CLI 命令路径查询")
return cmd
}
// splitSchemaPathTokens splits a CLI path on dots, slashes, and
// whitespace, returning only non-empty tokens.
func splitSchemaPathTokens(raw string) []string {
fields := strings.FieldsFunc(raw, func(r rune) bool {
return r == '.' || r == '/' || r == ' ' || r == '\t'
})
out := fields[:0]
for _, f := range fields {
if s := strings.TrimSpace(f); s != "" {
out = append(out, s)
}
}
return out
}
// normalizeSchemaQueryCLIPath accepts the historical query spellings while
// keeping authored Registry CLI paths strict and space-separated. Canonical
// identity lookup still runs before this compatibility normalization.
func normalizeSchemaQueryCLIPath(path string) string {
parts := splitSchemaPathTokens(strings.TrimSpace(path))
if len(parts) > 0 && parts[0] == "dws" {
parts = parts[1:]
}
return strings.Join(parts, " ")
}