sorcha-cli
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSorcha CLI Skill
Sorcha CLI 技能指南
The Sorcha CLI () 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.
sorchaSorcha CLI()是一款用于管理Sorcha分布式账本平台的.NET 10全局工具。它使用System.CommandLine 2.0.2进行命令解析,Refit构建HTTP API客户端,Spectre.Console实现丰富的终端输出。
sorchaProject 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 registrationsrc/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 核心要点
| Pattern | Correct | Wrong |
|---|---|---|
| Option constructor | | |
| Add short alias | | Second constructor param |
| Option name property | Returns | Does NOT return |
| Get option value | | |
| Set action | | Override Execute |
| Test find option | | |
Testing Note: The collection does NOT include the option name. Use to find options in tests.
Aliaseso.Name == "--id"| 模式 | 正确写法 | 错误写法 |
|---|---|---|
| Option构造函数 | | |
| 添加短别名 | | 作为构造函数第二个参数 |
| Option名称属性 | 返回 | 不会返回 |
| 获取选项值 | | |
| 设置动作 | | 重写Execute方法 |
| 测试中查找选项 | | |
测试注意事项: 集合不包含选项名称。在测试中请使用查找选项。
Aliaseso.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
依赖项
| Package | Version | Purpose |
|---|---|---|
| System.CommandLine | 2.0.2 | CLI framework |
| Refit | 9.0.2 | HTTP client |
| Refit.HttpClientFactory | 9.0.2 | DI integration |
| Spectre.Console | 0.54.0 | Rich console output |
| System.IdentityModel.Tokens.Jwt | 8.3.0 | JWT token handling |
| 包 | 版本 | 用途 |
|---|---|---|
| System.CommandLine | 2.0.2 | CLI框架 |
| Refit | 9.0.2 | HTTP客户端 |
| Refit.HttpClientFactory | 9.0.2 | 依赖注入集成 |
| Spectre.Console | 0.54.0 | 富控制台输出 |
| System.IdentityModel.Tokens.Jwt | 8.3.0 | JWT令牌处理 |
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
undefinedbash
undefinedCheck 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
undefinedwhich sorcha # Linux/macOS
where sorcha # Windows
undefinedUninstall Global Tool (for local development)
卸载全局工具(用于本地开发)
If a global tool is installed, it may conflict with local development builds:
bash
undefined如果已安装全局工具,可能会与本地开发构建版本冲突:
bash
undefinedUninstall global tool to use local build
卸载全局工具以使用本地构建版本
dotnet tool uninstall --global sorcha.cli
undefineddotnet tool uninstall --global sorcha.cli
undefinedLocal Build Paths
本地构建路径
After building with , the executable is at:
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
使用构建后,可执行文件位于:
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
undefinedPowerShell 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" # 回退到全局工具
}
undefinedVersion Mismatch Symptoms
版本不匹配症状
If you see unexpected command options or missing features:
- Check if global tool version differs from source code
- Rebuild with
dotnet build src/Apps/Sorcha.Cli -c Release - Verify using vs
sorcha --version./Sorcha.Cli.exe --version
如果遇到意外的命令选项或功能缺失:
- 检查全局工具版本是否与源代码版本不一致
- 使用重新构建
dotnet build src/Apps/Sorcha.Cli -c Release - 对比与
sorcha --version的输出./Sorcha.Cli.exe --version
Documentation Resources
文档资源
Fetch latest System.CommandLine documentation with Context7.
Library ID:
/dotnet/command-line-apiRecommended 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"