Skip to content
Closed
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Client.Tools

This repository contains tools shipped by the NuGet Client team to help developers effectively use the latest NuGet features.

| Tool | Description |
| --- | --- |
| [dotnet-package-skills](dotnet-package-skills/README.md) | Copies agent skills bundled in NuGet packages into a repository's skills directory. |
7 changes: 7 additions & 0 deletions dotnet-package-skills/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bin/
obj/
artifacts/
node_modules/
*.user
.DS_Store
.agents/
424 changes: 424 additions & 0 deletions dotnet-package-skills/CONTRIBUTING.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions dotnet-package-skills/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project>

<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<InvariantGlobalization>true</InvariantGlobalization>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'True'">true</ContinuousIntegrationBuild>
</PropertyGroup>

</Project>
8 changes: 8 additions & 0 deletions dotnet-package-skills/DotnetPackageSkills.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/DotnetPackageSkills.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/DotnetPackageSkills.Tests.csproj" />
</Folder>
</Solution>
7 changes: 7 additions & 0 deletions dotnet-package-skills/NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
</packageSources>
</configuration>
616 changes: 616 additions & 0 deletions dotnet-package-skills/README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions dotnet-package-skills/global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "10.0.400",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}
47 changes: 47 additions & 0 deletions dotnet-package-skills/src/Cli/CommandLineDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.CommandLine;

namespace DotnetPackageSkills.Cli;

/// <summary>Sanitizes framework diagnostics without changing arguments or application console output.</summary>
internal static class CommandLineDiagnostics
{
public static int Invoke(ParseResult result, TextWriter output, TextWriter error)
{
// Suggestions use Output, not Error. Buffer both through the whole invocation so
// split escape sequences, surrogate pairs, and Flush calls cannot bypass sanitizing.
using var capturedOutput = new StringWriter(output.FormatProvider) { NewLine = output.NewLine };
using var capturedError = new StringWriter(error.FormatProvider) { NewLine = error.NewLine };

try
{
return result.Invoke(new InvocationConfiguration
{
Output = capturedOutput,
Error = capturedError,
});
}
finally
{
Write(error, capturedError.ToString());
Write(output, capturedOutput.ToString());
}
}

private static void Write(TextWriter writer, string text)
{
if (text.Length == 0)
{
return;
}

var clean = TerminalText.Sanitize(text, multiline: true, trim: false);

if (text.EndsWith('\n') && !clean.EndsWith('\n'))
{
// An unterminated control string can also consume the framework's final newline.
clean += "\n";
}

writer.Write(clean.Replace("\n", writer.NewLine, StringComparison.Ordinal));
}
}
132 changes: 132 additions & 0 deletions dotnet-package-skills/src/Cli/ConsoleViewport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace DotnetPackageSkills.Cli;

internal static class ConsoleViewport
{
public static (int Width, int Height) Size()
{
if (!OperatingSystem.IsWindows())
{
return (Console.WindowWidth, Console.WindowHeight);
}

var buffer = ReadBuffer();
return (buffer.Window.Right - buffer.Window.Left + 1, buffer.Window.Bottom - buffer.Window.Top + 1);
}

public static void SetCursorPosition(int left, int top)
{
if (!OperatingSystem.IsWindows())
{
Console.SetCursorPosition(left, top);
return;
}

var buffer = ReadBuffer();
var position = new Coordinate
{
X = (short)Math.Clamp(buffer.Window.Left + left, buffer.Window.Left, buffer.Window.Right),
Y = (short)Math.Clamp(buffer.Window.Top + top, buffer.Window.Top, buffer.Window.Bottom),
};
if (!SetConsoleCursorPosition(GetStdHandle(-11), position))
{
throw ConsoleError("Could not position the interactive terminal cursor.");
}
}

public static void Clear()
{
if (OperatingSystem.IsWindows())
{
ClearWindows();
}
else
{
Console.Write("\x1b[2J\x1b[H");
}
}

[SupportedOSPlatform("windows")]
private static void ClearWindows()
{
var output = GetStdHandle(-11);
var buffer = ReadBuffer();

// Clear cells in place. Printing blank lines instead pushes stale picker frames
// into scrollback, where the terminal can rewrap them independently after a resize.
var width = (uint)(buffer.Window.Right - buffer.Window.Left + 1);
for (var row = (int)buffer.Window.Top; row <= buffer.Window.Bottom; row++)
{
var position = new Coordinate { X = buffer.Window.Left, Y = (short)row };
if (!FillConsoleOutputCharacter(output, ' ', width, position, out _) ||
!FillConsoleOutputAttribute(output, buffer.Attributes, width, position, out _))
{
throw ConsoleError("Could not clear the interactive terminal viewport.");
}
}
}

[SupportedOSPlatform("windows")]
private static ScreenBufferInfo ReadBuffer()
{
if (!GetConsoleScreenBufferInfo(GetStdHandle(-11), out var buffer))
{
throw ConsoleError("Could not read the interactive terminal viewport.");
}

return buffer;
}

private static IOException ConsoleError(string message) =>
new(message, new Win32Exception(Marshal.GetLastPInvokeError()));

[StructLayout(LayoutKind.Sequential)]
private struct Coordinate
{
public short X;
public short Y;
}

[StructLayout(LayoutKind.Sequential)]
private struct WindowRectangle
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}

[StructLayout(LayoutKind.Sequential)]
private struct ScreenBufferInfo
{
public Coordinate Size;
public Coordinate Cursor;
public ushort Attributes;
public WindowRectangle Window;
public Coordinate MaximumWindowSize;
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern nint GetStdHandle(int handle);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetConsoleScreenBufferInfo(nint output, out ScreenBufferInfo info);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetConsoleCursorPosition(nint output, Coordinate position);

[DllImport("kernel32.dll", EntryPoint = "FillConsoleOutputCharacterW", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FillConsoleOutputCharacter(
nint output, char character, uint length, Coordinate position, out uint written);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FillConsoleOutputAttribute(
nint output, ushort attributes, uint length, Coordinate position, out uint written);
}
Loading