Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Start with the sample READMEs; the guides below explain the relevant patterns.
| [Packages overview](https://repl.yllibed.org/reference/packages/) | [API Reference](https://repl.yllibed.org/api/index.html) |
| [For coding agents](docs/for-coding-agents.md) | [Agent-Native Development](https://repl.yllibed.org/reference/agent-native/) |
| [Glossary](docs/glossary.md) | [Dependency Injection](https://repl.yllibed.org/reference/dependency-injection/) |
| [Running on .NET 11](docs/dotnet-11.md) | |

## AI-assisted development

Expand Down
128 changes: 128 additions & 0 deletions docs/dotnet-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Running on .NET 11

Repl ships for `net10.0` and `net11.0` from 0.12.1. These notes are for developers who build or run a Repl
application with the .NET 11 SDK. Some of the tools below predate .NET 11 (`dotnet run -e`, `dnx`) or ship
on their own (dotnetup), and are here because .NET 11 developers meet them. Each point was checked with the
.NET SDK 11.0.100; the platform is named where it matters.

## C# 15 and union results

A `net11.0` project compiles C# 15 by default, so it can declare a `union` without setting `LangVersion`;
the same declaration does not compile in a `net10.0` project with its default language version. A handler
can return a union, and Repl processes its active case: see
[Union return values](commands.md#union-return-values-net-11).

## `FORCE_COLOR`

The .NET 11 console logger (`Microsoft.Extensions.Logging.Console`) colours its output when `FORCE_COLOR`
has any non-empty value, even `0`, including on a redirected stream and ahead of `NO_COLOR` (checked on
Linux). Repl applies the same rule to its own output, so one variable colours both. Forcing colour onto a
file or a pipe does not make Repl write terminal control sequences there (screen clearing,
shell-integration marks, progress); the exact rules are in [ANSI Detection](output-system.md#ansi-detection).

Colours an application sets through `Console.ForegroundColor` are not written to a redirected stream,
whatever `FORCE_COLOR` says (checked on Windows and Linux). Repl writes its colours as ANSI sequences and
is not affected.

## `dotnet watch`

`dotnet watch` starts the application with its standard input, output and error attached to the
terminal, not redirected (checked on Windows and Linux), so Repl detects a terminal and starts the
interactive loop. But `dotnet watch` keeps the keyboard for its own keys, and the application does not
receive what is typed: on Linux, none of the keys sent while the application was reading reached it, with
or without `--non-interactive`.

Use `dotnet watch` for one-shot commands, which it runs again on each change, and `dotnet run` for the
interactive loop:

```bash
dotnet watch run -- contact list --json
dotnet run
```

## `dotnet run -e`

`dotnet run -e NAME=value` passes an environment variable to the application for that run, which is a
quick way to try environment-driven behaviour (checked on Windows and Linux):

```bash
dotnet run -e FORCE_COLOR=1 -- contact list > contacts.txt
dotnet run -e NO_COLOR=1
```

## dotnetup installs and agent hosts

[dotnetup](https://learn.microsoft.com/dotnet/core/tools/dotnetup/getting-started) (in public preview)
installs .NET per user, by default in `%LOCALAPPDATA%\dotnetup\dotnet` on Windows and
`~/.local/share/dotnetup/dotnet` on Linux; macOS was not checked. According to the dotnetup documentation,
its access mode decides who finds that installation: `none` changes no environment variable, and `shell`
sets `PATH` and `DOTNET_ROOT` only in the shell profile. The default is `everywhere` on Windows, which
changes the system `PATH` and sets `DOTNET_ROOT` for the user, and `shell` on Linux and macOS when dotnetup
recognises the shell, `none` otherwise. A host that was already running keeps its environment until it
restarts.

With `none` or `shell`, an agent host or IDE that does not start from that shell does not find the dotnetup
`dotnet` or `dnx`: it finds none, or another installation that comes first on its `PATH`. When no other
.NET installation is present, a framework-dependent executable, such as a .NET global tool, also stops
with "You must install .NET to run this application". This was checked on Linux with dotnetup as the only
installation.

The configurations below use the default folders above; if dotnetup installed .NET elsewhere
(`--install-path`), use that folder instead.

For an MCP server registered in such a host, use one of these entries in the host's configuration:

- run `dnx` through the absolute path of the dotnetup `dotnet`, which finds its own runtime. Give the
package version: without one, `dnx` resolves the newest version from the feed when the server starts, so
the server changes whenever a new version is published. To update the server, change the version.

```json
{
"mcpServers": {
"myapp": {
"command": "/home/me/.local/share/dotnetup/dotnet/dotnet",
"args": ["dnx", "-y", "MyApp@1.0.0", "--", "mcp", "serve"]
}
}
}
```

- or keep a framework-dependent executable, such as a global tool, and point it to the installation with
`DOTNET_ROOT`:

```json
{
"mcpServers": {
"myapp": {
"command": "/home/me/.dotnet/tools/myapp",
"args": ["mcp", "serve"],
"env": { "DOTNET_ROOT": "/home/me/.local/share/dotnetup/dotnet" }
}
}
}
```

- or publish the application self-contained, which needs no .NET installation
(`dotnet publish -c Release -r linux-x64 --self-contained`, which also works for a project packed as a
tool), and use the published executable as `command`.

On Windows:

- the `dnx` entry's `command` is `C:\Users\me\AppData\Local\dotnetup\dotnet\dotnet.exe`;
- the global tool entry's `command` is `C:\Users\me\.dotnet\tools\myapp.exe`, and its `DOTNET_ROOT` is
`C:\Users\me\AppData\Local\dotnetup\dotnet`;
- the self-contained build uses `-r win-x64`.

JSON needs every backslash doubled (`C:\\Users\\me\\...`).

A tool built for an older major version than the one installed, such as a `net10.0` tool with only .NET 11
in the dotnetup folder, stops with "You must install or update .NET to run this application", through `dnx`
too. Install that runtime with dotnetup, target both `net10.0` and `net11.0`, or let the tool run on the
newer runtime: put `--allow-roll-forward` before the package name in the `dnx` arguments, or add
`"DOTNET_ROLL_FORWARD": "Major"` to the global tool entry's `env`. The error and both roll-forward options
were checked on Linux with only the .NET 11 runtime installed.

Shell completion does not depend on the folder .NET was installed in; set it up from the application's
own executable, as [Shell Completion](shell-completion.md#user-commands) explains.

See also: [MCP Server Reference](mcp-reference.md#publishing-and-installation) | [Output System](output-system.md) | [Shell Completion](shell-completion.md) | [For coding agents](for-coding-agents.md)
3 changes: 3 additions & 0 deletions docs/for-coding-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ Typical MCP client configuration:
}
```

If .NET was installed per user with dotnetup and the host does not start from the user's shell, `myapp`, `dotnet` and `dnx` may not be found; see [dotnetup installs and agent hosts](dotnet-11.md#dotnetup-installs-and-agent-hosts).

## Agent coding guidelines

When generating Repl Toolkit code:
Expand Down Expand Up @@ -242,6 +244,7 @@ Use these rules:
- [Quick start](../README.md#quick-start)
- [MCP Server Integration](mcp-overview.md)
- [MCP Server Reference](mcp-reference.md)
- [Running on .NET 11](dotnet-11.md) ([dotnetup installs and agent hosts](dotnet-11.md#dotnetup-installs-and-agent-hosts), `dotnet watch`)
- [Output System](output-system.md)
- [Testing Toolkit](testing-toolkit.md)
- [Comparison & Migration](comparison.md)
7 changes: 5 additions & 2 deletions docs/mcp-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,11 @@ dotnet nuget push bin/Release/MyApp.1.0.0.nupkg --source https://api.nuget.org/v
dotnet tool install -g MyApp
myapp mcp serve

# Run without install
dnx -y MyApp -- mcp serve
# Run without install, at a fixed version
dnx -y MyApp@1.0.0 -- mcp serve
```

An agent host that does not inherit the user's shell may find neither `dnx` nor the .NET runtime when
.NET was installed with dotnetup; see [dotnetup installs and agent hosts](dotnet-11.md#dotnetup-installs-and-agent-hosts).

NuGet.org discovery: `nuget.org/packages?packagetype=mcpserver`
4 changes: 3 additions & 1 deletion docs/shell-completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ completion detect-shell
```

These commands are CLI-only (they are not available in interactive mode or hosted session mode).
They also require invoking the app through its own executable command head (the running process must match the app binary).
Run them through the app's own executable (`myapp completion install`), which works wherever .NET is installed.
A setup started as `dotnet myapp.dll` is accepted, but the PowerShell completer it writes is registered for `dotnet` as well as `myapp`, and runs `dotnet completion __complete` without the DLL: `myapp` gets no completions, and completing a `dotnet` command line shows `dotnet` error text instead.
To repair such a setup, run `myapp completion install --shell powershell --force` from the executable, then open a new shell.

Structured output is supported via global output flags:

Expand Down
16 changes: 16 additions & 0 deletions src/Repl.Tests/Given_ShellCompletionHostValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,20 @@ public void When_TheExecutableNameOnlyStartsLikeTheApp_Then_HostIsNotSupported()
.Should()
.BeFalse();
}

[TestMethod]
[Description("A dotnet installed by dotnetup lives in a user folder (%LOCALAPPDATA%\\dotnetup\\dotnet on Windows, ~/.local/share/dotnetup/dotnet on Linux, observed with dotnetup 0.3.0-preview); only executable file names are compared, so that folder changes nothing.")]
public void When_TheManagedHostIsADotnetupInstall_Then_HostIsSupported()
{
var (processPath, commandHead) = OperatingSystem.IsWindows()
? ("C:\\Users\\ada\\AppData\\Local\\dotnetup\\dotnet\\dotnet.exe", "C:\\apps\\CoreBasicsSample.dll")
: ("/home/ada/.local/share/dotnetup/dotnet/dotnet", "/apps/CoreBasicsSample.dll");
ShellCompletionHostValidator.IsSupportedHostProcess(
processPath,
entryAssemblyName: "CoreBasicsSample",
commandHead: commandHead,
parentProcessName: "pwsh")
.Should()
.BeTrue();
}
}
Loading