246 lines
11 KiB
Go
246 lines
11 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package sheet declares high-fidelity shortcuts for the DingTalk sheet MCP
|
|
// product. Tool names and parameter keys mirror the helper commands under
|
|
// internal/helpers/sheet_*.go verbatim.
|
|
package sheet
|
|
|
|
import (
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
|
)
|
|
|
|
// ── workbook & worksheet ──────────────────────────────────────────────
|
|
|
|
// Create creates a new DingTalk online spreadsheet document.
|
|
// ListSheets lists all worksheets in a spreadsheet document.
|
|
var ListSheets = shortcut.Shortcut{
|
|
Service: "sheet",
|
|
Command: "+list-sheets",
|
|
Product: "sheet",
|
|
Description: "获取表格文档中全部工作表列表",
|
|
Intent: "当你拿到一个表格文档、想先了解它里面有哪些工作表(sheet)以及各自的 sheetId 时使用,通常作为读写具体数据前的第一步;传入表格文档 ID 或 URL,返回工作表清单。",
|
|
Risk: shortcut.RiskRead,
|
|
Safety: contract.SafetySpec{
|
|
Effect: "read", Risk: "low",
|
|
Confirmation: "not_required", Idempotency: "idempotent",
|
|
},
|
|
Contract: corecmd.ContractDecl{
|
|
Identity: contract.ToolIdentitySpec{
|
|
ProductID: "sheet",
|
|
Name: "shortcut_list_sheets",
|
|
CanonicalPath: "sheet.shortcut_list_sheets",
|
|
CLIPath: "sheet +list-sheets",
|
|
PrimaryCLIPath: "sheet +list-sheets",
|
|
},
|
|
Description: "获取表格文档中全部工作表列表",
|
|
Interface: &contract.InterfaceSpec{
|
|
Mode: "composite",
|
|
Availability: "available",
|
|
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
|
},
|
|
Selection: contract.SelectionSpec{
|
|
AgentSummary: "获取表格文档中全部工作表列表",
|
|
UseWhen: []string{"当你拿到一个表格文档、想先了解它里面有哪些工作表(sheet)以及各自的 sheetId 时使用,通常作为读写具体数据前的第一步;传入表格文档 ID 或 URL,返回工作表清单。"},
|
|
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
|
Examples: []string{"dws sheet +list-sheets --node NODE_ID"},
|
|
},
|
|
},
|
|
Flags: []shortcut.Flag{
|
|
{Name: "node", Type: shortcut.FlagString, Desc: "表格文档 ID 或 URL", Required: true},
|
|
},
|
|
Tips: []string{`dws sheet +list-sheets --node NODE_ID`},
|
|
Execute: func(rt *shortcut.RuntimeContext) error {
|
|
data, err := rt.CallMCPData("sheet", "get_all_sheets", map[string]any{"nodeId": rt.Str("node")})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sheets := listSheetsProject(data)
|
|
return rt.Output(map[string]any{"count": len(sheets), "sheets": sheets})
|
|
},
|
|
}
|
|
|
|
// listSheetsProject reshapes the raw get_all_sheets response into a clean
|
|
// worksheet list (sheetId/title/index/visibility/rowCount/columnCount) — the
|
|
// the clean output projection applied to every list command. Both
|
|
// the list container and the per-item field names are probed defensively across
|
|
// candidate keys, so an empty/unknown shape yields an empty list rather than a
|
|
// crash or fabricated data.
|
|
func listSheetsProject(data map[string]any) []map[string]any {
|
|
raw := listSheetsResolveList(data)
|
|
out := make([]map[string]any, 0, len(raw))
|
|
for _, item := range raw {
|
|
m, ok := item.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
row := map[string]any{}
|
|
if v, ok := listSheetsFirst(m, "sheetId", "sheet_id", "id"); ok {
|
|
row["sheetId"] = v
|
|
}
|
|
if v, ok := listSheetsFirst(m, "title", "name", "sheetName", "sheet_name"); ok {
|
|
row["title"] = v
|
|
}
|
|
if v, ok := listSheetsFirst(m, "index", "sheetIndex"); ok {
|
|
row["index"] = v
|
|
}
|
|
if v, ok := listSheetsFirst(m, "visibility", "hidden", "visible"); ok {
|
|
row["visibility"] = v
|
|
}
|
|
if v, ok := listSheetsFirst(m, "rowCount", "row_count", "rows"); ok {
|
|
row["rowCount"] = v
|
|
}
|
|
if v, ok := listSheetsFirst(m, "columnCount", "column_count", "columns", "colCount"); ok {
|
|
row["columnCount"] = v
|
|
}
|
|
if len(row) > 0 {
|
|
out = append(out, row)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// listSheetsResolveList locates the worksheet array inside the response,
|
|
// tolerating a bare top-level array container or nesting one level deeper under
|
|
// a common envelope key.
|
|
func listSheetsResolveList(data map[string]any) []any {
|
|
if data == nil {
|
|
return []any{}
|
|
}
|
|
for _, key := range []string{"sheets", "result", "data", "list", "items", "worksheets"} {
|
|
v, ok := data[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if arr, ok := v.([]any); ok {
|
|
return arr
|
|
}
|
|
if inner, ok := v.(map[string]any); ok {
|
|
for _, ik := range []string{"sheets", "list", "items", "worksheets", "result", "data"} {
|
|
if arr, ok := inner[ik].([]any); ok {
|
|
return arr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return []any{}
|
|
}
|
|
|
|
// listSheetsFirst returns the first present candidate key's value.
|
|
func listSheetsFirst(m map[string]any, keys ...string) (any, bool) {
|
|
for _, k := range keys {
|
|
if v, ok := m[k]; ok {
|
|
return v, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// SheetInfo returns the detail of a single worksheet.
|
|
// SheetCreate adds a new worksheet to a spreadsheet.
|
|
// SheetUpdate updates worksheet properties (name/position/hidden/freeze/tab color).
|
|
// SheetCopy duplicates a worksheet within the same spreadsheet.
|
|
// SheetDelete removes a worksheet (irreversible).
|
|
// ── data read/write ───────────────────────────────────────────────────
|
|
|
|
// Read reads structured per-cell data from a worksheet range.
|
|
var Read = shortcut.Shortcut{
|
|
Service: "sheet",
|
|
Command: "+read",
|
|
Product: "sheet",
|
|
Description: "读取工作表指定范围的结构化单元格数据",
|
|
Intent: "当你需要按单元格逐格获取数据(含类型、公式或格式化值等结构化信息)以便程序处理时使用;传入表格与可选范围(A1 表示法,不传则全部),可指定取格式化值/原始值/公式,返回结构化单元格数组。若只想要纯文本可改用 +csv-get。",
|
|
Risk: shortcut.RiskRead,
|
|
Safety: contract.SafetySpec{
|
|
Effect: "read", Risk: "low",
|
|
Confirmation: "not_required", Idempotency: "idempotent",
|
|
},
|
|
Contract: corecmd.ContractDecl{
|
|
Identity: contract.ToolIdentitySpec{
|
|
ProductID: "sheet",
|
|
Name: "shortcut_read",
|
|
CanonicalPath: "sheet.shortcut_read",
|
|
CLIPath: "sheet +read",
|
|
PrimaryCLIPath: "sheet +read",
|
|
},
|
|
Description: "读取工作表指定范围的结构化单元格数据",
|
|
Interface: &contract.InterfaceSpec{
|
|
Mode: "composite",
|
|
Availability: "available",
|
|
Reason: "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
|
|
},
|
|
Selection: contract.SelectionSpec{
|
|
AgentSummary: "读取工作表指定范围的结构化单元格数据",
|
|
UseWhen: []string{"当你需要按单元格逐格获取数据(含类型、公式或格式化值等结构化信息)以便程序处理时使用;传入表格与可选范围(A1 表示法,不传则全部),可指定取格式化值/原始值/公式,返回结构化单元格数组。若只想要纯文本可改用 +csv-get。"},
|
|
AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
|
|
Examples: []string{"dws sheet +read --node NODE_ID --sheet-id SHEET_ID --range \"A1:D10\""},
|
|
},
|
|
},
|
|
Flags: []shortcut.Flag{
|
|
{Name: "node", Type: shortcut.FlagString, Desc: "表格文档 ID 或 URL", Required: true},
|
|
{Name: "sheet-id", Type: shortcut.FlagString, Desc: "工作表 ID 或名称 (不传则第一个工作表)"},
|
|
{Name: "range", Type: shortcut.FlagString, Desc: "读取范围,A1 表示法 (不传则全部数据)"},
|
|
{Name: "value-render-option", Type: shortcut.FlagString, Desc: "取值模式", Enum: []string{"formatted_value", "raw_value", "formula"}},
|
|
},
|
|
Tips: []string{`dws sheet +read --node NODE_ID --sheet-id SHEET_ID --range "A1:D10"`},
|
|
Execute: func(rt *shortcut.RuntimeContext) error {
|
|
params := map[string]any{"nodeId": rt.Str("node")}
|
|
if rt.Changed("sheet-id") {
|
|
params["sheetId"] = rt.Str("sheet-id")
|
|
}
|
|
if rt.Changed("range") {
|
|
params["range"] = rt.Str("range")
|
|
}
|
|
if rt.Changed("value-render-option") {
|
|
params["valueRenderOption"] = rt.Str("value-render-option")
|
|
}
|
|
return rt.CallMCP("get_cell_infos", params)
|
|
},
|
|
}
|
|
|
|
// Write updates a worksheet range with a 2D JSON array of cell objects.
|
|
// Append appends rows to the end of a worksheet.
|
|
// CSVGet reads worksheet data as RFC 4180 CSV.
|
|
// CSVPut writes CSV text into a worksheet at a start cell (values only).
|
|
// CellsSearch finds cells matching a text query.
|
|
// CellsReplace finds and replaces text across a worksheet.
|
|
// CellsClear clears content/format of a worksheet range.
|
|
// ── range operations ──────────────────────────────────────────────────
|
|
|
|
// RangeSort sorts a worksheet range by one or more keys.
|
|
// RangeFill auto-fills a target range from a source range.
|
|
// RangeCopy copies a source range to a target location (supports cross-sheet).
|
|
// RangeMove moves a source range to a target location (source is cleared).
|
|
// ── dimensions & merge ────────────────────────────────────────────────
|
|
|
|
// InsertDimension inserts empty rows or columns before a position.
|
|
// AddDimension appends empty rows or columns at the end of a worksheet.
|
|
// MoveDimension moves rows or columns to a destination position.
|
|
// UpdateDimension updates hidden state and/or row height / column width.
|
|
// DeleteDimension deletes rows or columns from a position (irreversible).
|
|
// MergeCells merges a range of cells.
|
|
// UnmergeCells unmerges cells within a range.
|
|
// ── dropdown lists ────────────────────────────────────────────────────
|
|
|
|
// SetDropdown sets a dropdown list on a cell range.
|
|
// GetDropdown reads dropdown list configuration for a range.
|
|
// DeleteDropdown removes dropdown list configuration from a range.
|
|
func init() {
|
|
shortcut.Register(
|
|
ListSheets,
|
|
Read,
|
|
)
|
|
}
|