sorcha-cli

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sorcha CLI Skill

Sorcha CLI 技能指南

The Sorcha CLI (
sorcha
) is a .NET 10 global tool for managing the Sorcha distributed ledger platform. It uses System.CommandLine 2.0.2 for command parsing, Refit for HTTP API clients, and Spectre.Console for rich terminal output.
Sorcha CLI(
sorcha
)是一款用于管理Sorcha分布式账本平台的.NET 10全局工具。它使用System.CommandLine 2.0.2进行命令解析,Refit构建HTTP API客户端,Spectre.Console实现丰富的终端输出。

Project Location

项目位置

src/Apps/Sorcha.Cli/
├── Commands/           # Command implementations
├── Infrastructure/     # Helpers (ConsoleHelper, ExitCodes, HttpClientFactory)
├── Models/             # Request/Response DTOs for APIs
├── Services/           # Refit interfaces and service contracts
└── Program.cs          # Entry point and command registration
src/Apps/Sorcha.Cli/
├── Commands/           # 命令实现
├── Infrastructure/     # 辅助工具(ConsoleHelper、ExitCodes、HttpClientFactory)
├── Models/             # API的请求/响应DTO
├── Services/           # Refit接口和服务契约
└── Program.cs          # 入口点和命令注册

Quick Reference

快速参考

Creating a New Command

创建新命令

csharp
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Sorcha Contributors

using System.CommandLine;
using System.CommandLine.Parsing;
using System.Net;
using System.Text.Json;
using Refit;
using Sorcha.Cli.Infrastructure;
using Sorcha.Cli.Services;

namespace Sorcha.Cli.Commands;

public class MyCommand : Command
{
    private readonly Option<string> _idOption;
    private readonly Option<bool> _verboseOption;

    public MyCommand(
        HttpClientFactory clientFactory,
        IAuthenticationService authService,
        IConfigurationService configService)
        : base("mycommand", "Description of command")
    {
        // IMPORTANT: Option constructor takes (name, description) NOT (alias1, alias2)
        _idOption = new Option<string>("--id", "The resource ID")
        {
            Required = true
        };

        _verboseOption = new Option<bool>("--verbose", "Enable verbose output")
        {
            Required = false
        };

        // Add options to command
        Options.Add(_idOption);
        Options.Add(_verboseOption);

        // Set the action handler
        this.SetAction(async (ParseResult parseResult, CancellationToken ct) =>
        {
            var id = parseResult.GetValue(_idOption)!;
            var verbose = parseResult.GetValue(_verboseOption);

            // Implementation here
            return ExitCodes.Success;
        });
    }
}
csharp
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Sorcha Contributors

using System.CommandLine;
using System.CommandLine.Parsing;
using System.Net;
using System.Text.Json;
using Refit;
using Sorcha.Cli.Infrastructure;
using Sorcha.Cli.Services;

namespace Sorcha.Cli.Commands;

public class MyCommand : Command
{
    private readonly Option<string> _idOption;
    private readonly Option<bool> _verboseOption;

    public MyCommand(
        HttpClientFactory clientFactory,
        IAuthenticationService authService,
        IConfigurationService configService)
        : base("mycommand", "命令描述")
    {
        // 重要提示:Option构造函数参数为(名称,描述)而非(别名1,别名2)
        _idOption = new Option<string>("--id", "资源ID")
        {
            Required = true
        };

        _verboseOption = new Option<bool>("--verbose", "启用详细输出")
        {
            Required = false
        };

        // 为命令添加选项
        Options.Add(_idOption);
        Options.Add(_verboseOption);

        // 设置动作处理程序
        this.SetAction(async (ParseResult parseResult, CancellationToken ct) =>
        {
            var id = parseResult.GetValue(_idOption)!;
            var verbose = parseResult.GetValue(_verboseOption);

            // 此处添加实现逻辑
            return ExitCodes.Success;
        });
    }
}

System.CommandLine 2.0.2 Key Points

System.CommandLine 2.0.2 核心要点

PatternCorrectWrong
Option constructor
new Option<string>("--id", "Description")
new Option<string>("--id", "-i")
Add short alias
option.Aliases.Add("-i")
Second constructor param
Option name propertyReturns
"--id"
(with dashes)
Does NOT return
"id"
Get option value
parseResult.GetValue(_option)
parseResult.GetValueForOption()
Set action
this.SetAction(async (pr, ct) => {...})
Override Execute
Test find option
o.Name == "--id"
o.Aliases.Contains("--id")
Testing Note: The
Aliases
collection does NOT include the option name. Use
o.Name == "--id"
to find options in tests.
模式正确写法错误写法
Option构造函数
new Option<string>("--id", "Description")
new Option<string>("--id", "-i")
添加短别名
option.Aliases.Add("-i")
作为构造函数第二个参数
Option名称属性返回
"--id"
(带短横线)
不会返回
"id"
获取选项值
parseResult.GetValue(_option)
parseResult.GetValueForOption()
设置动作
this.SetAction(async (pr, ct) => {...})
重写Execute方法
测试中查找选项
o.Name == "--id"
o.Aliases.Contains("--id")
测试注意事项:
Aliases
集合不包含选项名称。在测试中请使用
o.Name == "--id"
查找选项。

Adding Short Aliases

添加短别名

csharp
_idOption = new Option<string>("--id", "The resource ID") { Required = true };
_idOption.Aliases.Add("-i");  // Add short alias separately
Options.Add(_idOption);
csharp
_idOption = new Option<string>("--id", "资源ID") { Required = true };
_idOption.Aliases.Add("-i");  // 单独添加短别名
Options.Add(_idOption);

Parent Command with Subcommands

包含子命令的父命令

csharp
public class ParentCommand : Command
{
    public ParentCommand(
        HttpClientFactory clientFactory,
        IAuthenticationService authService,
        IConfigurationService configService)
        : base("parent", "Parent command description")
    {
        Subcommands.Add(new ChildListCommand(clientFactory, authService, configService));
        Subcommands.Add(new ChildGetCommand(clientFactory, authService, configService));
        Subcommands.Add(new ChildCreateCommand(clientFactory, authService, configService));
    }
}
csharp
public class ParentCommand : Command
{
    public ParentCommand(
        HttpClientFactory clientFactory,
        IAuthenticationService authService,
        IConfigurationService configService)
        : base("parent", "父命令描述")
    {
        Subcommands.Add(new ChildListCommand(clientFactory, authService, configService));
        Subcommands.Add(new ChildGetCommand(clientFactory, authService, configService));
        Subcommands.Add(new ChildCreateCommand(clientFactory, authService, configService));
    }
}

Registering Commands in Program.cs

在Program.cs中注册命令

csharp
// Program.cs
rootCommand.Subcommands.Add(new MyCommand(clientFactory, authService, configService));
csharp
// Program.cs
rootCommand.Subcommands.Add(new MyCommand(clientFactory, authService, configService));

Refit Service Clients

Refit服务客户端

Interface Definition

接口定义

csharp
// Services/IMyServiceClient.cs
using Refit;

public interface IMyServiceClient
{
    [Get("/api/resources")]
    Task<List<Resource>> ListAsync([Header("Authorization")] string authorization);

    [Get("/api/resources/{id}")]
    Task<Resource> GetAsync(string id, [Header("Authorization")] string authorization);

    [Post("/api/resources")]
    Task<Resource> CreateAsync([Body] CreateRequest request, [Header("Authorization")] string authorization);

    [Put("/api/resources/{id}")]
    Task<Resource> UpdateAsync(string id, [Body] UpdateRequest request, [Header("Authorization")] string authorization);

    [Delete("/api/resources/{id}")]
    Task DeleteAsync(string id, [Header("Authorization")] string authorization);

    // Pagination with query parameters
    [Get("/api/resources")]
    Task<List<Resource>> ListAsync(
        [Query] int? page,
        [Query] int? pageSize,
        [Header("Authorization")] string authorization);

    // OData queries
    [Get("/odata/{resource}")]
    Task<HttpResponseMessage> QueryODataAsync(
        string resource,
        [Query("$filter")] string? filter,
        [Query("$orderby")] string? orderby,
        [Query("$top")] int? top,
        [Query("$skip")] int? skip,
        [Header("Authorization")] string authorization);
}
csharp
// Services/IMyServiceClient.cs
using Refit;

public interface IMyServiceClient
{
    [Get("/api/resources")]
    Task<List<Resource>> ListAsync([Header("Authorization")] string authorization);

    [Get("/api/resources/{id}")]
    Task<Resource> GetAsync(string id, [Header("Authorization")] string authorization);

    [Post("/api/resources")]
    Task<Resource> CreateAsync([Body] CreateRequest request, [Header("Authorization")] string authorization);

    [Put("/api/resources/{id}")]
    Task<Resource> UpdateAsync(string id, [Body] UpdateRequest request, [Header("Authorization")] string authorization);

    [Delete("/api/resources/{id}")]
    Task DeleteAsync(string id, [Header("Authorization")] string authorization);

    // 带查询参数的分页
    [Get("/api/resources")]
    Task<List<Resource>> ListAsync(
        [Query] int? page,
        [Query] int? pageSize,
        [Header("Authorization")] string authorization);

    // OData查询
    [Get("/odata/{resource}")]
    Task<HttpResponseMessage> QueryODataAsync(
        string resource,
        [Query("$filter")] string? filter,
        [Query("$orderby")] string? orderby,
        [Query("$top")] int? top,
        [Query("$skip")] int? skip,
        [Header("Authorization")] string authorization);
}

Using Refit Client in Commands

在命令中使用Refit客户端

csharp
// Get client from factory
var client = await clientFactory.CreateMyServiceClientAsync(profileName);

// Get auth token
var token = await authService.GetAccessTokenAsync(profileName);
if (string.IsNullOrEmpty(token))
{
    ConsoleHelper.WriteError("You must be authenticated.");
    return ExitCodes.AuthenticationError;
}

// Call API with Bearer token
var result = await client.GetAsync(id, $"Bearer {token}");
csharp
// 从工厂获取客户端
var client = await clientFactory.CreateMyServiceClientAsync(profileName);

// 获取认证令牌
var token = await authService.GetAccessTokenAsync(profileName);
if (string.IsNullOrEmpty(token))
{
    ConsoleHelper.WriteError("您必须先完成认证。");
    return ExitCodes.AuthenticationError;
}

// 使用Bearer令牌调用API
var result = await client.GetAsync(id, $"Bearer {token}");

Error Handling Pattern

错误处理模式

csharp
try
{
    var result = await client.GetAsync(id, $"Bearer {token}");
    // Success handling
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    ConsoleHelper.WriteError($"Resource '{id}' not found.");
    return ExitCodes.NotFound;
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
    ConsoleHelper.WriteError("Authentication failed. Your access token may have expired.");
    ConsoleHelper.WriteInfo("Run 'sorcha auth login' to re-authenticate.");
    return ExitCodes.AuthenticationError;
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
    ConsoleHelper.WriteError("You do not have permission to access this resource.");
    return ExitCodes.AuthorizationError;
}
catch (ApiException ex)
{
    ConsoleHelper.WriteError($"API Error: {ex.Message}");
    if (ex.Content != null)
    {
        ConsoleHelper.WriteError($"Details: {ex.Content}");
    }
    return ExitCodes.GeneralError;
}
catch (Exception ex)
{
    ConsoleHelper.WriteError($"Failed: {ex.Message}");
    return ExitCodes.GeneralError;
}
csharp
try
{
    var result = await client.GetAsync(id, $"Bearer {token}");
    // 成功处理逻辑
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    ConsoleHelper.WriteError($"资源'{id}'未找到。");
    return ExitCodes.NotFound;
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
    ConsoleHelper.WriteError("认证失败。您的访问令牌可能已过期。");
    ConsoleHelper.WriteInfo("运行'sorcha auth login'重新认证。");
    return ExitCodes.AuthenticationError;
}
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
    ConsoleHelper.WriteError("您没有访问该资源的权限。");
    return ExitCodes.AuthorizationError;
}
catch (ApiException ex)
{
    ConsoleHelper.WriteError($"API错误: {ex.Message}");
    if (ex.Content != null)
    {
        ConsoleHelper.WriteError($"详情: {ex.Content}");
    }
    return ExitCodes.GeneralError;
}
catch (Exception ex)
{
    ConsoleHelper.WriteError($"执行失败: {ex.Message}");
    return ExitCodes.GeneralError;
}

Console Output Helpers

控制台输出辅助方法

csharp
// Success message (green)
ConsoleHelper.WriteSuccess("Operation completed successfully!");

// Error message (red)
ConsoleHelper.WriteError("Something went wrong.");

// Warning message (yellow)
ConsoleHelper.WriteWarning("This action cannot be undone.");

// Info message (cyan)
ConsoleHelper.WriteInfo("Use 'sorcha help' for more information.");
csharp
// 成功消息(绿色)
ConsoleHelper.WriteSuccess("操作已成功完成!");

// 错误消息(红色)
ConsoleHelper.WriteError("发生错误。");

// 警告消息(黄色)
ConsoleHelper.WriteWarning("此操作无法撤销。");

// 信息消息(青色)
ConsoleHelper.WriteInfo("使用'sorcha help'获取更多信息。");

Exit Codes

退出码

csharp
public static class ExitCodes
{
    public const int Success = 0;
    public const int GeneralError = 1;
    public const int AuthenticationError = 2;
    public const int AuthorizationError = 3;
    public const int NotFound = 4;
    public const int ValidationError = 5;
}
csharp
public static class ExitCodes
{
    public const int Success = 0;
    public const int GeneralError = 1;
    public const int AuthenticationError = 2;
    public const int AuthorizationError = 3;
    public const int NotFound = 4;
    public const int ValidationError = 5;
}

JSON Output Support

JSON输出支持

csharp
// Check output format option
var outputFormat = parseResult.GetValue(BaseCommand.OutputOption) ?? "table";
if (outputFormat.Equals("json", StringComparison.OrdinalIgnoreCase))
{
    Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
    return ExitCodes.Success;
}

// Otherwise display as table
Console.WriteLine($"{"ID",-36} {"Name",-30} {"Status",-10}");
Console.WriteLine(new string('-', 80));
foreach (var item in results)
{
    Console.WriteLine($"{item.Id,-36} {item.Name,-30} {item.Status,-10}");
}
csharp
// 检查输出格式选项
var outputFormat = parseResult.GetValue(BaseCommand.OutputOption) ?? "table";
if (outputFormat.Equals("json", StringComparison.OrdinalIgnoreCase))
{
    Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
    return ExitCodes.Success;
}

// 否则以表格形式显示
Console.WriteLine($"{"ID",-36} {"Name",-30} {"Status",-10}");
Console.WriteLine(new string('-', 80));
foreach (var item in results)
{
    Console.WriteLine($"{item.Id,-36} {item.Name,-30} {item.Status,-10}");
}

See Also

另请参阅

  • commands - Complete command reference
  • testing - Unit test patterns and fixes
  • models - DTO and model patterns
  • commands - 完整命令参考
  • testing - 单元测试模式与修复方案
  • models - DTO与模型模式

Related Skills

相关技能

  • dotnet - .NET 10 / C# 13 patterns
  • xunit - Unit testing with xUnit
  • fluent-assertions - FluentAssertions patterns
  • moq - Mocking with Moq
  • dotnet - .NET 10 / C# 13 开发模式
  • xunit - 使用xUnit进行单元测试
  • fluent-assertions - FluentAssertions使用模式
  • moq - 使用Moq进行模拟测试

Dependencies

依赖项

PackageVersionPurpose
System.CommandLine2.0.2CLI framework
Refit9.0.2HTTP client
Refit.HttpClientFactory9.0.2DI integration
Spectre.Console0.54.0Rich console output
System.IdentityModel.Tokens.Jwt8.3.0JWT token handling
版本用途
System.CommandLine2.0.2CLI框架
Refit9.0.2HTTP客户端
Refit.HttpClientFactory9.0.2依赖注入集成
Spectre.Console0.54.0富控制台输出
System.IdentityModel.Tokens.Jwt8.3.0JWT令牌处理

Tool Version Management

工具版本管理

The Sorcha CLI can be installed as a .NET global tool or run from local build. When working on the CLI, check for version mismatches.
Sorcha CLI可以作为.NET全局工具安装,也可以从本地构建运行。开发CLI时,请检查版本是否匹配。

Check for Global Tool Installation

检查全局工具安装情况

bash
undefined
bash
undefined

Check if sorcha is installed as a global tool

检查sorcha是否作为全局工具安装

dotnet tool list --global | grep -i sorcha
dotnet tool list --global | grep -i sorcha

Find where the current 'sorcha' command is located

查找当前'sorcha'命令的位置

which sorcha # Linux/macOS where sorcha # Windows
undefined
which sorcha # Linux/macOS where sorcha # Windows
undefined

Uninstall Global Tool (for local development)

卸载全局工具(用于本地开发)

If a global tool is installed, it may conflict with local development builds:
bash
undefined
如果已安装全局工具,可能会与本地开发构建版本冲突:
bash
undefined

Uninstall global tool to use local build

卸载全局工具以使用本地构建版本

dotnet tool uninstall --global sorcha.cli
undefined
dotnet tool uninstall --global sorcha.cli
undefined

Local Build Paths

本地构建路径

After building with
dotnet build src/Apps/Sorcha.Cli
, the executable is at:
  • Release:
    src/Apps/Sorcha.Cli/bin/Release/net10.0/Sorcha.Cli.exe
  • Debug:
    src/Apps/Sorcha.Cli/bin/Debug/net10.0/Sorcha.Cli.exe
使用
dotnet build src/Apps/Sorcha.Cli
构建后,可执行文件位于:
  • Release版本:
    src/Apps/Sorcha.Cli/bin/Release/net10.0/Sorcha.Cli.exe
  • Debug版本:
    src/Apps/Sorcha.Cli/bin/Debug/net10.0/Sorcha.Cli.exe

Walkthrough Scripts

演练脚本

When writing walkthrough scripts that use the CLI, prefer finding the local build:
powershell
undefined
编写使用CLI的演练脚本时,优先使用本地构建版本:
powershell
undefined

PowerShell pattern for finding CLI

PowerShell中查找CLI的模式

$RepoRoot = (Get-Item $PSScriptRoot).Parent.Parent.FullName $LocalCliPath = Join-Path $RepoRoot "src/Apps/Sorcha.Cli/bin/Release/net10.0/Sorcha.Cli.exe" $DebugCliPath = Join-Path $RepoRoot "src/Apps/Sorcha.Cli/bin/Debug/net10.0/Sorcha.Cli.exe"
if (Test-Path $LocalCliPath) { $SorchaCliPath = $LocalCliPath } elseif (Test-Path $DebugCliPath) { $SorchaCliPath = $DebugCliPath } else { $SorchaCliPath = "sorcha" # Fall back to global tool }
undefined
$RepoRoot = (Get-Item $PSScriptRoot).Parent.Parent.FullName $LocalCliPath = Join-Path $RepoRoot "src/Apps/Sorcha.Cli/bin/Release/net10.0/Sorcha.Cli.exe" $DebugCliPath = Join-Path $RepoRoot "src/Apps/Sorcha.Cli/bin/Debug/net10.0/Sorcha.Cli.exe"
if (Test-Path $LocalCliPath) { $SorchaCliPath = $LocalCliPath } elseif (Test-Path $DebugCliPath) { $SorchaCliPath = $DebugCliPath } else { $SorchaCliPath = "sorcha" # 回退到全局工具 }
undefined

Version Mismatch Symptoms

版本不匹配症状

If you see unexpected command options or missing features:
  1. Check if global tool version differs from source code
  2. Rebuild with
    dotnet build src/Apps/Sorcha.Cli -c Release
  3. Verify using
    sorcha --version
    vs
    ./Sorcha.Cli.exe --version
如果遇到意外的命令选项或功能缺失:
  1. 检查全局工具版本是否与源代码版本不一致
  2. 使用
    dotnet build src/Apps/Sorcha.Cli -c Release
    重新构建
  3. 对比
    sorcha --version
    ./Sorcha.Cli.exe --version
    的输出

Documentation Resources

文档资源

Fetch latest System.CommandLine documentation with Context7.
Library ID:
/dotnet/command-line-api
Recommended Queries:
  • "Option constructor aliases System.CommandLine 2.0"
  • "SetAction handler pattern"
  • "ParseResult GetValue"
使用Context7获取最新的System.CommandLine文档。
库ID:
/dotnet/command-line-api
推荐查询:
  • "Option constructor aliases System.CommandLine 2.0"
  • "SetAction handler pattern"
  • "ParseResult GetValue"