using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;
using System.IO;
using System.Linq;
using System.Text.Json;
using TableMagic.Cli.Excel;
using TableMagic.Cli.Mcp;
using TableMagic.Cli.Skills;
namespace TableMagic.Cli;
class Program
{
static async Task<int> Main(string[] args)
{
var rootCommand = new RootCommand("TableMagic - Excel Skill CLI & MCP Server");
var basePathOption = new Option<string>(
name: "--base-path",
description: "Excel文件基础路径",
getDefaultValue: () => "./excel_files");
var mcpCommand = new Command("mcp", "启动MCP服务器(stdio模式)");
mcpCommand.AddOption(basePathOption);
mcpCommand.SetHandler(async (string basePath) =>
{
await RunMcpServer(basePath);
}, basePathOption);
var listToolsCommand = new Command("list-tools", "列出所有可用的工具");
listToolsCommand.AddOption(basePathOption);
listToolsCommand.SetHandler((string basePath) =>
{
using var provider = new ClosedXmlExcelProvider(basePath);
var manager = CreateSkillManager(provider);
var tools = manager.GetAllTools();
Console.WriteLine($"共 {tools.Count} 个工具:\n");
foreach (var tool in tools)
{
Console.WriteLine($" {tool.Name}: {tool.Description}");
if (tool.RequiredParameters?.Count > 0)
Console.WriteLine($" 必需参数: {string.Join(", ", tool.RequiredParameters)}");
}
}, basePathOption);
var callCommand = new Command("call", "调用指定工具");
var toolNameArg = new Argument<string>("toolName", "工具名称");
var argsOption = new Option<string>(
name: "--args",
description: "工具参数(JSON格式)",
getDefaultValue: () => "{}");
callCommand.AddArgument(toolNameArg);
callCommand.AddOption(argsOption);
callCommand.AddOption(basePathOption);
callCommand.SetHandler(async (string toolName, string argsJson, string basePath) =>
{
using var provider = new ClosedXmlExcelProvider(basePath);
var manager = CreateSkillManager(provider);
var cleanJson = argsJson.TrimStart('\uFEFF');
var arguments = JsonSerializer.Deserialize<Dictionary<string, object>>(cleanJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new();
var result = await manager.ExecuteToolAsync(toolName, arguments);
if (result.Success)
{
Console.WriteLine(result.Content);
}
else
{
Console.Error.WriteLine($"错误: {result.Error}");
if (result.MissingRequiredParams && result.MissingParams.Count > 0)
{
Console.Error.WriteLine();
Console.Error.WriteLine("缺少必需参数,请补充:");
foreach (var mp in result.MissingParams)
{
Console.Error.WriteLine($" - {mp.Name} ({mp.Type}): {mp.PromptHint}");
}
Console.Error.WriteLine();
Console.Error.WriteLine($"示例: tablemagic call {toolName} --args '{{...}}'");
}
Environment.ExitCode = 1;
}
}, toolNameArg, argsOption, basePathOption);
var batchCommand = new Command("batch", "批量执行工具调用(从JSON文件读取)");
var inputFileArg = new Argument<string>("inputFile", "输入JSON文件路径");
batchCommand.AddArgument(inputFileArg);
batchCommand.AddOption(basePathOption);
batchCommand.SetHandler(async (string inputFile, string basePath) =>
{
using var provider = new ClosedXmlExcelProvider(basePath);
var manager = CreateSkillManager(provider);
var json = await File.ReadAllTextAsync(inputFile);
var cleanJson = json.TrimStart('\uFEFF');
var calls = JsonSerializer.Deserialize<List<BatchCall>>(cleanJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new();
foreach (var call in calls)
{
Console.WriteLine($"执行: {call.Tool}");
var result = await manager.ExecuteToolAsync(call.Tool, call.Arguments ?? new());
Console.WriteLine(result.Success ? result.Content : $"错误: {result.Error}");
Console.WriteLine();
}
}, inputFileArg, basePathOption);
rootCommand.AddCommand(mcpCommand);
rootCommand.AddCommand(listToolsCommand);
rootCommand.AddCommand(callCommand);
rootCommand.AddCommand(batchCommand);
return await new CommandLineBuilder(rootCommand)
.UseVersionOption("--version", "-v")
.UseHelp("--help", "-h", "-?")
.UseParseErrorReporting()
.UseExceptionHandler()
.Build()
.InvokeAsync(args);
}
private static SkillManager CreateSkillManager(ClosedXmlExcelProvider provider)
{
var manager = new SkillManager();
manager.LoadSkill(new ExcelBaseSkill(provider, manager));
manager.LoadSkill(new ExcelWorkbookSkill(provider));
manager.LoadSkill(new ExcelSheetSkill(provider));
manager.LoadSkill(new ExcelCellSkill(provider));
manager.LoadSkill(new ExcelRangeSkill(provider));
manager.LoadSkill(new ExcelFormatSkill(provider));
manager.LoadSkill(new ExcelChartSkill(provider));
manager.LoadSkill(new ExcelPivotSkill(provider));
manager.LoadSkill(new ExcelAnalysisSkill(provider));
manager.LoadSkill(new ExcelFinanceSkill(provider));
manager.LoadSkill(new ExcelDataSkill(provider));
manager.LoadSkill(new ExcelDatabaseSkill(provider));
manager.LoadSkill(new ExcelApiSkill(provider));
manager.LoadSkill(new ExcelChartEnhanceSkill(provider));
manager.LoadSkill(new ExcelMailSkill());
manager.LoadSkill(new ExcelFileSkill());
manager.LoadSkill(new ExcelQRSkill(provider));
manager.LoadSkill(new ExcelInvoiceSkill(provider));
manager.LoadSkill(new ExcelRegexSkill(provider));
manager.LoadSkill(new ExcelTocSkill(provider));
manager.LoadSkill(new ExcelScheduleSkill());
manager.LoadSkill(new DocumentGenerationSkill());
manager.LoadSkill(new ExcelPdfSkill(provider));
return manager;
}
private static async Task RunMcpServer(string basePath)
{
using var provider = new ClosedXmlExcelProvider(basePath);
var manager = CreateSkillManager(provider);
var server = new McpServer(manager);
await server.RunAsync();
}
private class BatchCall
{
public string Tool { get; set; } = "";
public Dictionary<string, object>? Arguments { get; set; }
}
}