Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DevToys-app/DevToys/llms.txt

Use this file to discover all available pages before exploring further.

DevToys ships a companion CLI executable (devtoys.cli) that exposes tools as sub-commands. Every ICommandLineTool export in an extension becomes a first-class command available from the terminal, complete with typed options, help text, and proper exit codes. The CLI uses the same extension discovery mechanism as the GUI app, so a single .devtoys package can power both experiences.
1
Create or reuse a class library
2
If you are adding CLI support to an existing extension, skip this step. Otherwise, create a new .NET net8.0 class library and add the DevToys.Api reference exactly as described in the GUI Tool guide.
3
dotnet new classlib -n MyDevToysExtension -f net8.0
cd MyDevToysExtension
dotnet add package DevToys.Api
4
Implement ICommandLineTool
5
Create a class that implements ICommandLineTool. The single required method is:
6
ValueTask<int> InvokeAsync(ILogger logger, CancellationToken cancellationToken);
7
InvokeAsync receives an ILogger for telemetry and a CancellationToken that is cancelled when the user interrupts the process. It must return an integer exit code (0 for success, non-zero for failure).
8
Decorate with [Export] and [CommandName]
9
Apply the MEF export attribute and the [CommandName] metadata attribute:
10
[Export(typeof(ICommandLineTool))]
[Name("MyExtension.Base64CLI")]
[CommandName(
    Name = "base64",
    Alias = "b64",
    DescriptionResourceName = nameof(Strings.Base64Description),
    ResourceManagerBaseName = "MyDevToysExtension.Strings")]
internal sealed class Base64CommandLineTool : ICommandLineTool
{
    public ValueTask<int> InvokeAsync(ILogger logger, CancellationToken cancellationToken)
    {
        // implementation
        return ValueTask.FromResult(0);
    }
}
11
CommandName properties:
12
PropertyRequiredDescriptionNameYesThe primary sub-command name (e.g., base64). Invoked as devtoys.cli base64 ....AliasNoA short alias (e.g., b64). Invoked as devtoys.cli b64 ....DescriptionResourceNameYesResource key for the command’s help description.ResourceManagerBaseNameYesThe base name of the resource manager that contains the description string.
13
Add [CommandLineOption] properties
14
Each option the command accepts is a property decorated with [CommandLineOption]. The property type determines the value type that the CLI parser will coerce — bool, string, FileInfo, int, and other common types are all supported.
15
[CommandLineOption(
    Name = "file",
    Alias = "f",
    IsRequired = true,
    DescriptionResourceName = nameof(Strings.FileOptionDescription))]
internal FileInfo? File { get; set; }

[CommandLineOption(
    Name = "utf8",
    DescriptionResourceName = nameof(Strings.Utf8OptionDescription))]
internal bool Utf8 { get; set; } = true; // default value
16
CommandLineOption properties (in addition to Name, Alias, and DescriptionResourceName inherited from CommandNameAttribute):
17
PropertyTypeDescriptionNamestringLong option name, e.g., "file"--file <value>AliasstringShort alias, e.g., "f"-f <value>IsRequiredboolWhen true, the CLI shows an error if the option is not provided. Default is false.DescriptionResourceNamestringResource key for the option’s help description.
18
Return an exit code from InvokeAsync
19
The integer returned from InvokeAsync becomes the process exit code. Use 0 for success and a non-zero value for any error condition. The CLI framework propagates this value to the shell.
20
public async ValueTask<int> InvokeAsync(ILogger logger, CancellationToken cancellationToken)
{
    if (File is null || !File.Exists)
    {
        Console.Error.WriteLine("File not found.");
        return 1;
    }

    // ... process the file ...
    return 0;
}

Full Working Example

The following is a complete base64 encode/decode CLI tool, mirroring the canonical example from the ICommandLineTool XML documentation:
using System.ComponentModel.Composition;
using System.Text;
using DevToys.Api;
using Microsoft.Extensions.Logging;

[Export(typeof(ICommandLineTool))]
[Name("MyExtension.Base64CLI")]
[CommandName(
    Name = "base64",
    Alias = "b64",
    DescriptionResourceName = nameof(Strings.Base64Description),
    ResourceManagerBaseName = "MyDevToysExtension.Strings")]
internal sealed class Base64CommandLineTool : ICommandLineTool
{
    [CommandLineOption(
        Name = "file",
        Alias = "f",
        IsRequired = true,
        DescriptionResourceName = nameof(Strings.FileOptionDescription))]
    internal FileInfo? File { get; set; }

    [CommandLineOption(
        Name = "utf8",
        DescriptionResourceName = nameof(Strings.Utf8OptionDescription))]
    internal bool Utf8 { get; set; } = true;

    public async ValueTask<int> InvokeAsync(ILogger logger, CancellationToken cancellationToken)
    {
        if (File is null || !File.Exists)
        {
            Console.Error.WriteLine($"File '{File?.FullName}' does not exist.");
            return 1;
        }

        Encoding encoding = Utf8 ? Encoding.UTF8 : Encoding.ASCII;

        // Show a progress bar for potentially large files.
        using var progress = new ConsoleProgressBar();

        byte[] bytes = await System.IO.File.ReadAllBytesAsync(File.FullName, cancellationToken);
        progress.Report(50);

        string encoded = Convert.ToBase64String(bytes);
        progress.Report(100);

        Console.WriteLine(encoded);
        return 0;
    }
}

ConsoleProgressBar

For long-running CLI operations, the ConsoleProgressBar class provides an animated in-place progress indicator. It implements IProgress<double> and IDisposable. Call Report(value) with a percentage between 0 and 100. The bar is automatically suppressed when standard output is redirected to a file.
using var progress = new ConsoleProgressBar();

for (int i = 0; i <= 100; i += 10)
{
    await DoWorkChunkAsync();
    progress.Report(i);
}
// Dispose clears the bar from the console line.

Dual GUI + CLI Extension

A single extension assembly can export both an IGuiTool and an ICommandLineTool for the same feature. This is the recommended pattern — users get a visual tool in the desktop app and a scriptable command in the terminal, without duplicating business logic.
// Shared logic
internal static class Base64Logic
{
    internal static string Encode(byte[] bytes) => Convert.ToBase64String(bytes);
}

// GUI tool uses Base64Logic
[Export(typeof(IGuiTool))]
[Name("MyExtension.Base64GUI")]
// ... ToolDisplayInformation etc ...
internal sealed class Base64GuiTool : IGuiTool { /* ... */ }

// CLI tool uses the same Base64Logic
[Export(typeof(ICommandLineTool))]
[Name("MyExtension.Base64CLI")]
[CommandName(Name = "base64", Alias = "b64", ...)]
internal sealed class Base64CommandLineTool : ICommandLineTool { /* ... */ }

Logging Guidelines

The ILogger passed to InvokeAsync sends telemetry to DevToys. Follow these rules to protect user privacy:
DO log errors, performance timings, and relevant system information to help diagnose issues.
DO NOT log anything the user typed or pasted into the tool. User input may contain passwords, tokens, or other personal information.
// ✅ Good — logs a duration metric
logger.LogInformation("Encoding completed in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);

// ✅ Good — logs a system compatibility note
logger.LogWarning("Large file detected; memory usage may be high on 32-bit systems.");

// ❌ Bad — logs actual user data
logger.LogDebug("Input text: {InputText}", userProvidedText);

Build docs developers (and LLMs) love