Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Documentation/arc/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# arc commands list

Lists all registered command endpoints in the connected Arc application.

## Usage

```bash
cratis arc commands list [--url <URL>] [-o <FORMAT>]
```

## Options

| Option | Description |
|---|---|
| `--url <URL>` | Base URL of the Arc application (default: `https://localhost:5001`). |
| `-o, --output <FORMAT>` | Output format: `table`, `plain`, `json`, `json-compact`. |

## Output

Each row contains:

| Column | Description |
|---|---|
| Name | The simple type name of the command (e.g. `OpenDebitAccount`). |
| Namespace | The dot-separated namespace path of the command. |
| Route | The HTTP POST route registered for this command (e.g. `/api/accounts/open-debit-account`). |
| Summary | The XML documentation summary of the command type, if available. |

## Examples

```bash
# List commands using the default URL
cratis arc commands list

# List commands from a specific URL
cratis arc commands list --url https://myapp.local:5001

# Output as compact JSON
cratis arc commands list -o json-compact
```

## Notes

The introspection endpoint `GET /.cratis/commands` must be registered in the Arc application. This happens automatically when the application calls `MapIntrospectionEndpoints()` during startup.
36 changes: 36 additions & 0 deletions Documentation/arc/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Arc

The `cratis arc` command group provides commands for introspecting a running Cratis Arc application. It connects to the Arc application's built-in introspection HTTP endpoints to discover registered commands and queries.

## Connection Flags

All `cratis arc` commands accept the following connection flag:

| Flag | Description |
|---|---|
| `--url <URL>` | Base URL of the Arc application. Overrides the `ARC_URL` environment variable. Defaults to `https://localhost:5001`. |

## Connection Resolution Order

The CLI resolves the Arc application URL in this order:

1. `--url` flag
2. `ARC_URL` environment variable
3. Default: `https://localhost:5001`

## Sub-Commands

| Sub-command | Description |
|---|---|
| `commands` | List registered command endpoints in the Arc application. |
| `queries` | List registered query endpoints in the Arc application. |

## Global Flags

All `cratis arc` commands also inherit the top-level global flags:

| Flag | Description |
|---|---|
| `-o, --output <FORMAT>` | Output format: `table`, `plain`, `json`, `json-compact`. |
| `-q, --quiet` | Output only key identifiers, one per line. |
| `--debug` | Print debug information to stderr. |
45 changes: 45 additions & 0 deletions Documentation/arc/queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# arc queries list

Lists all registered query endpoints in the connected Arc application.

## Usage

```bash
cratis arc queries list [--url <URL>] [-o <FORMAT>]
```

## Options

| Option | Description |
|---|---|
| `--url <URL>` | Base URL of the Arc application (default: `https://localhost:5001`). |
| `-o, --output <FORMAT>` | Output format: `table`, `plain`, `json`, `json-compact`. |

## Output

Each row contains:

| Column | Description |
|---|---|
| Name | The simple name of the query method (e.g. `AllAccounts`). |
| Namespace | The dot-separated namespace path of the query. |
| Route | The HTTP GET route registered for this query (e.g. `/api/accounts/all-accounts`). |
| Type | The runtime type name of the query (e.g. `ClientObservable`, `IEnumerable`). |
| Summary | The XML documentation summary of the query type, if available. |

## Examples

```bash
# List queries using the default URL
cratis arc queries list

# List queries from a specific URL
cratis arc queries list --url https://myapp.local:5001

# Output as compact JSON
cratis arc queries list -o json-compact
```

## Notes

The introspection endpoint `GET /.cratis/queries` must be registered in the Arc application. This happens automatically when the application calls `MapIntrospectionEndpoints()` during startup.
6 changes: 6 additions & 0 deletions Documentation/arc/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: Arc
href: index.md
- name: Commands
href: commands.md
- name: Queries
href: queries.md
2 changes: 2 additions & 0 deletions Documentation/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
href: context/toc.yml
- name: Chronicle
href: chronicle/toc.yml
- name: Arc
href: arc/toc.yml
- name: Reference
href: reference/toc.yml
61 changes: 61 additions & 0 deletions Source/Cli/Commands/Arc/ArcCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Net.Sockets;

namespace Cratis.Cli.Commands.Arc;

/// <summary>
/// Base class for all CLI commands that interact with a Cratis Arc application over HTTP.
/// </summary>
/// <typeparam name="TSettings">The settings type for this command.</typeparam>
public abstract class ArcCommand<TSettings> : AsyncCommand<TSettings>
where TSettings : ArcSettings
{
/// <inheritdoc/>
protected sealed override async Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken)
{
var format = settings.ResolveOutputFormat();

try
{
using var handler = CreateHttpHandler();
using var httpClient = new HttpClient(handler);
return await ExecuteCommandAsync(httpClient, settings, format, cancellationToken);
}
catch (HttpRequestException ex) when (ex.InnerException is SocketException || ex.StatusCode is null)
{
OutputFormatter.WriteError(format, ArcDefaults.CannotConnectMessage, BuildConnectionHint(settings), ExitCodes.ConnectionErrorCode);
return ExitCodes.ConnectionError;
}
catch (HttpRequestException ex)
{
OutputFormatter.WriteError(format, $"HTTP error: {ex.Message}", BuildConnectionHint(settings), ExitCodes.ServerErrorCode);
return ExitCodes.ServerError;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
OutputFormatter.WriteError(format, ex.Message, errorCode: ExitCodes.ServerErrorCode);
return ExitCodes.ServerError;
}
}

/// <summary>
/// Executes the command logic using the provided <see cref="HttpClient"/>.
/// </summary>
/// <param name="httpClient">The HTTP client configured to reach the Arc application.</param>
/// <param name="settings">The command settings.</param>
/// <param name="format">The resolved output format.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The exit code.</returns>
protected abstract Task<int> ExecuteCommandAsync(HttpClient httpClient, TSettings settings, string format, CancellationToken cancellationToken);

#pragma warning disable MA0039 // TLS validation is intentionally skipped for local development Arc applications
static HttpClientHandler CreateHttpHandler() =>
new() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true };
#pragma warning restore MA0039

static string BuildConnectionHint(ArcSettings settings) =>
$"Verify the Arc application is running at {settings.ResolveUrl()}\n" +
"Use --url to specify a different URL, or set the ARC_URL environment variable.";
}
25 changes: 25 additions & 0 deletions Source/Cli/Commands/Arc/ArcDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc;

/// <summary>
/// Default values for Arc CLI commands.
/// </summary>
public static class ArcDefaults
{
/// <summary>
/// The default URL for an Arc application.
/// </summary>
public const string DefaultUrl = "https://localhost:5001";

/// <summary>
/// Environment variable name for the Arc application URL.
/// </summary>
public const string UrlEnvVar = "ARC_URL";

/// <summary>
/// Standard error message when the CLI cannot reach the Arc application.
/// </summary>
public const string CannotConnectMessage = "Cannot connect to Arc application";
}
39 changes: 39 additions & 0 deletions Source/Cli/Commands/Arc/ArcSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc;

/// <summary>
/// Settings shared by all commands that connect to a Cratis Arc application.
/// </summary>
public class ArcSettings : GlobalSettings
{
/// <summary>
/// Gets or sets the base URL of the Arc application.
/// </summary>
[CommandOption("--url <URL>")]
[Description("Base URL of the Arc application (e.g. https://localhost:5001)")]
public string? Url { get; set; }

/// <summary>
/// Resolves the effective base URL by checking the flag, environment variable, then default.
/// </summary>
/// <returns>The resolved base URL string.</returns>
#pragma warning disable CA1055 // URI string return type — string is intentional here for consistency with connection helpers
public string ResolveUrl()
#pragma warning restore CA1055
{
if (!string.IsNullOrWhiteSpace(Url))
{
return Url.TrimEnd('/');
}

var envVar = Environment.GetEnvironmentVariable(ArcDefaults.UrlEnvVar);
if (!string.IsNullOrWhiteSpace(envVar))
{
return envVar.TrimEnd('/');
}

return ArcDefaults.DefaultUrl;
}
}
14 changes: 14 additions & 0 deletions Source/Cli/Commands/Arc/CommandIntrospectionMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc;

/// <summary>
/// Introspection metadata for a registered command endpoint in the Arc application.
/// </summary>
/// <param name="Name">The simple name of the command type.</param>
/// <param name="Namespace">The namespace path of the command.</param>
/// <param name="Route">The HTTP route registered for this command.</param>
/// <param name="Type">The fully qualified type name of the command.</param>
/// <param name="DocumentationSummary">The XML documentation summary of the command type.</param>
public record CommandIntrospectionMetadata(string Name, string Namespace, string Route, string Type, string DocumentationSummary);
46 changes: 46 additions & 0 deletions Source/Cli/Commands/Arc/Commands/ListCommandsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc.Commands;

/// <summary>
/// Lists all registered command endpoints in the connected Arc application.
/// </summary>
[LlmDescription("Lists all registered command endpoints in the Arc application. Returns name, namespace, HTTP route, type, and documentation summary for each command.")]
[CliCommand("list", "List registered command endpoints", Branch = typeof(ArcBranch.Commands))]
[CliExample("arc", "commands", "list")]
[CliExample("arc", "commands", "list", "--url", "https://localhost:5001")]
[CliExample("arc", "commands", "list", "-o", "json")]
[LlmOutputAdvice("plain", "plain is significantly smaller than JSON for large command lists.")]
public class ListCommandsCommand : ArcCommand<ArcSettings>
{
/// <inheritdoc/>
protected override async Task<int> ExecuteCommandAsync(HttpClient httpClient, ArcSettings settings, string format, CancellationToken cancellationToken)
{
var url = settings.ResolveUrl();
var response = await httpClient.GetAsync($"{url}/.cratis/commands", cancellationToken);

if (!response.IsSuccessStatusCode)
{
OutputFormatter.WriteError(format, $"Arc application returned {(int)response.StatusCode}", errorCode: ExitCodes.ServerErrorCode);
return ExitCodes.ServerError;
}

var json = await response.Content.ReadAsStringAsync(cancellationToken);
var commands = JsonSerializer.Deserialize<List<CommandIntrospectionMetadata>>(json, OutputFormatter.JsonSerializerOptions) ?? [];

OutputFormatter.Write(
format,
commands,
["Name", "Namespace", "Route", "Summary"],
cmd =>
[
cmd.Name,
cmd.Namespace,
cmd.Route,
cmd.DocumentationSummary
]);

return ExitCodes.Success;
}
}
47 changes: 47 additions & 0 deletions Source/Cli/Commands/Arc/Queries/ListQueriesCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc.Queries;

/// <summary>
/// Lists all registered query endpoints in the connected Arc application.
/// </summary>
[LlmDescription("Lists all registered query endpoints in the Arc application. Returns name, namespace, HTTP route, fully qualified type, and documentation summary for each query.")]
[CliCommand("list", "List registered query endpoints", Branch = typeof(ArcBranch.Queries))]
[CliExample("arc", "queries", "list")]
[CliExample("arc", "queries", "list", "--url", "https://localhost:5001")]
[CliExample("arc", "queries", "list", "-o", "json")]
[LlmOutputAdvice("plain", "plain is significantly smaller than JSON for large query lists.")]
public class ListQueriesCommand : ArcCommand<ArcSettings>
{
/// <inheritdoc/>
protected override async Task<int> ExecuteCommandAsync(HttpClient httpClient, ArcSettings settings, string format, CancellationToken cancellationToken)
{
var url = settings.ResolveUrl();
var response = await httpClient.GetAsync($"{url}/.cratis/queries", cancellationToken);

if (!response.IsSuccessStatusCode)
{
OutputFormatter.WriteError(format, $"Arc application returned {(int)response.StatusCode}", errorCode: ExitCodes.ServerErrorCode);
return ExitCodes.ServerError;
}

var json = await response.Content.ReadAsStringAsync(cancellationToken);
var queries = JsonSerializer.Deserialize<List<QueryIntrospectionMetadata>>(json, OutputFormatter.JsonSerializerOptions) ?? [];

OutputFormatter.Write(
format,
queries,
["Name", "Namespace", "Route", "Type", "Summary"],
q =>
[
q.Name,
q.Namespace,
q.Route,
q.Type,
q.DocumentationSummary
]);

return ExitCodes.Success;
}
}
15 changes: 15 additions & 0 deletions Source/Cli/Commands/Arc/QueryIntrospectionMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Arc;

/// <summary>
/// Introspection metadata for a registered query endpoint in the Arc application.
/// </summary>
/// <param name="Name">The simple name of the query.</param>
/// <param name="Namespace">The namespace path of the query.</param>
/// <param name="Route">The HTTP route registered for this query.</param>
/// <param name="FullyQualifiedName">The fully qualified type name of the query.</param>
/// <param name="Type">The runtime type name of the query.</param>
/// <param name="DocumentationSummary">The XML documentation summary of the query type.</param>
public record QueryIntrospectionMetadata(string Name, string Namespace, string Route, string FullyQualifiedName, string Type, string DocumentationSummary);
Loading
Loading