AppVeyor CLI is a .NET 10 command-line tool for the AppVeyor CI/CD REST API. It uses Spectre.Console.Cli for structured command parsing, Spectre.Console for rich terminal output, and System.Text.Json source generators for AOT-compatible serialization. Every command supports dual output: rich Spectre tables for humans and clean JSON for AI agents and scripts.
graph TD
Program["Program.cs<br/><i>DI + CommandApp</i>"]
Program --> IConsole["IConsoleProvider"]
Program --> IConfig["IConfigService"]
Program --> IClient["IAppVeyorClient"]
IConsole --> ORF["OutputRendererFactory"]
IConfig --> CS["ConfigService"]
IClient --> AC["AppVeyorClient"]
ORF --> CR["ConsoleRenderer<br/><i>Spectre tables</i>"]
ORF --> JR["JsonRenderer<br/><i>JSON stdout</i>"]
CS --> CF["~/.appveyor/config.json"]
CS --> EV["Environment Variables"]
AC --> API["AppVeyor REST API<br/><i>ci.appveyor.com/api</i>"]
subgraph Commands
direction LR
Config["config<br/>set · get · test"]
Projects["project<br/>list · get · add · delete · settings"]
Builds["build<br/>start · history · cancel · rerun · log"]
Envs["environment<br/>list · get · add · delete"]
Deploys["deployment<br/>get · start · cancel"]
Users["user<br/>list · get · add · delete"]
Collabs["collaborator<br/>list · add · delete"]
Roles["role<br/>list · get · add · delete"]
end
Program --> Commands
sequenceDiagram
actor User
participant CLI as Spectre.Console.Cli
participant Cmd as AsyncCommand
participant Guard as ReadOnlyGuard
participant Client as IAppVeyorClient
participant API as AppVeyor API
participant Renderer as IOutputRenderer
User->>CLI: appveyor project list --json
CLI->>CLI: Parse args, validate settings
CLI->>Cmd: ExecuteAsync(context, settings)
opt Write commands only
Cmd->>Guard: ThrowIfReadOnly(settings)
end
Cmd->>Client: GetProjectsAsync()
Client->>API: GET /api/projects
API-->>Client: JSON response
Client-->>Cmd: Project[]
Cmd->>Renderer: OutputRendererFactory.Create(json, console)
alt --json flag or APPVEYOR_OUTPUT=json
Renderer-->>User: Clean JSON to stdout
else Default
Renderer-->>User: Spectre.Console table
end
| Phase | Name | Status | Summary |
|---|---|---|---|
| 0 | Design | Complete | Scope, tech choices, command tree, architecture |
| 1 | CLI Framework | Complete | .NET 10 project, Spectre.Console.Cli, DI bridge |
| 2 | API Client and Output | Complete | Models, API client, dual output rendering |
| 3 | Build and CI/CD | Complete | Cake SDK, GitHub Actions, install scripts |
| 4 | Commands and Features | Complete | 20 commands, read-only mode |
| 5 | Testing | Complete | 31 tests, MockAppVeyorServer |
| 6 | Team Management | Complete | Users, collaborators, roles (11 commands) |
| ADR | Decision | Date | Related Phase |
|---|---|---|---|
| 001 | Spectre.Console.Cli over System.CommandLine | 2026-03 | Phase 1 |
| 002 | Dual output: rich terminal + JSON | 2026-03 | Phase 2 |
| 003 | System.Text.Json source generators | 2026-03 | Phase 2 |
| 004 | Cake SDK build system | 2026-03 | Phase 3 |
| 005 | Read-only mode for safe exploration | 2026-03 | Phase 4 |
| 006 | IConsoleProvider DI workaround | 2026-03 | Phase 2 |
- Command structure -- Each command is an
AsyncCommand<TSettings>with settings inheriting fromGlobalSettings. Commands are organized in domain folders (Config, Projects, Builds, Environments, Deployments, Users, Collaborators, Roles). - DI bridge --
TypeRegistrar/TypeResolverbridge Spectre.Console.Cli to Microsoft.Extensions.DependencyInjection.IConsoleProviderwrapsIAnsiConsoleto avoid Spectre's DI override. - Output abstraction -- Commands call
OutputRendererFactory.Create(settings.Json, console)and use the returnedIOutputRendererfor all output. The renderer handles both Spectre tables and JSON serialization. - API client -- Single
AppVeyorClientusingHttpClientwith Bearer token auth. All paths are relative to the/api/base URL. Error responses are mapped to typedAppVeyorApiException. - Serialization -- All models are C# records with
[JsonPropertyName]attributes, registered inAppVeyorJsonContextfor source-gen serialization. Reflection is disabled. - Read-only guard -- Write commands call
ReadOnlyGuard.ThrowIfReadOnly(settings)as the first line inExecuteAsync.
See AGENTS.md for AI agent guidelines and step-by-step instructions for adding commands and models.