Skip to content

Commit 29866b2

Browse files
committed
Refactor agent skill installation with location and skill selection prompts
1 parent f338a58 commit 29866b2

30 files changed

+916
-178
lines changed

src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ internal sealed class ClaudeCodeAgentEnvironmentScanner : IAgentEnvironmentScann
1717
private const string ClaudeCodeFolderName = ".claude";
1818
private const string McpConfigFileName = ".mcp.json";
1919
private const string AspireServerName = "aspire";
20-
private static readonly string s_skillFilePath = Path.Combine(".claude", "skills", CommonAgentApplicators.AspireSkillName, "SKILL.md");
2120
private static readonly string s_skillBaseDirectory = Path.Combine(".claude", "skills");
22-
private const string SkillFileDescription = "Create Aspire skill file (.claude/skills/aspire/SKILL.md)";
2321

2422
private readonly IClaudeCodeCliRunner _claudeCodeCliRunner;
2523
private readonly PlaywrightCliInstaller _playwrightCliInstaller;
@@ -76,13 +74,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
7674

7775
// Register Playwright CLI installation applicator
7876
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
79-
80-
// Try to add skill file applicator for Claude Code
81-
CommonAgentApplicators.TryAddSkillFileApplicator(
82-
context,
83-
context.RepositoryRoot,
84-
s_skillFilePath,
85-
SkillFileDescription);
8677
}
8778
else
8879
{
@@ -107,13 +98,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
10798

10899
// Register Playwright CLI installation applicator
109100
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
110-
111-
// Try to add skill file applicator for Claude Code
112-
CommonAgentApplicators.TryAddSkillFileApplicator(
113-
context,
114-
context.RepositoryRoot,
115-
s_skillFilePath,
116-
SkillFileDescription);
117101
}
118102
else
119103
{

src/Aspire.Cli/Agents/CommonAgentApplicators.cs

Lines changed: 209 additions & 16 deletions
Large diffs are not rendered by default.

src/Aspire.Cli/Agents/CopilotCli/CopilotCliAgentEnvironmentScanner.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ internal sealed class CopilotCliAgentEnvironmentScanner : IAgentEnvironmentScann
1717
private const string CopilotFolderName = ".copilot";
1818
private const string McpConfigFileName = "mcp-config.json";
1919
private const string AspireServerName = "aspire";
20-
private static readonly string s_skillFilePath = Path.Combine(".github", "skills", CommonAgentApplicators.AspireSkillName, "SKILL.md");
2120
private static readonly string s_skillBaseDirectory = Path.Combine(".github", "skills");
22-
private const string SkillFileDescription = "Create Aspire skill file (.github/skills/aspire/SKILL.md)";
2321

2422
private readonly ICopilotCliRunner _copilotCliRunner;
2523
private readonly PlaywrightCliInstaller _playwrightCliInstaller;
@@ -75,13 +73,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
7573

7674
// Register Playwright CLI installation applicator
7775
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
78-
79-
// Try to add skill file applicator for GitHub Copilot
80-
CommonAgentApplicators.TryAddSkillFileApplicator(
81-
context,
82-
context.RepositoryRoot,
83-
s_skillFilePath,
84-
SkillFileDescription);
8576
return;
8677
}
8778

@@ -113,13 +104,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
113104

114105
// Register Playwright CLI installation applicator
115106
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
116-
117-
// Try to add skill file applicator for GitHub Copilot
118-
CommonAgentApplicators.TryAddSkillFileApplicator(
119-
context,
120-
context.RepositoryRoot,
121-
s_skillFilePath,
122-
SkillFileDescription);
123107
}
124108

125109
/// <summary>

src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ internal sealed class OpenCodeAgentEnvironmentScanner : IAgentEnvironmentScanner
1616
{
1717
private const string OpenCodeConfigFileName = "opencode.jsonc";
1818
private const string AspireServerName = "aspire";
19-
private static readonly string s_skillFilePath = Path.Combine(".opencode", "skill", CommonAgentApplicators.AspireSkillName, "SKILL.md");
2019
private static readonly string s_skillBaseDirectory = Path.Combine(".opencode", "skill");
21-
private const string SkillFileDescription = "Create Aspire skill file (.opencode/skill/aspire/SKILL.md)";
2220

2321
private readonly IOpenCodeCliRunner _openCodeCliRunner;
2422
private readonly PlaywrightCliInstaller _playwrightCliInstaller;
@@ -70,13 +68,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
7068

7169
// Register Playwright CLI installation applicator
7270
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
73-
74-
// Try to add skill file applicator for OpenCode
75-
CommonAgentApplicators.TryAddSkillFileApplicator(
76-
context,
77-
context.RepositoryRoot,
78-
s_skillFilePath,
79-
SkillFileDescription);
8071
}
8172
else
8273
{
@@ -93,13 +84,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
9384

9485
// Register Playwright CLI installation applicator
9586
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
96-
97-
// Try to add skill file applicator for OpenCode
98-
CommonAgentApplicators.TryAddSkillFileApplicator(
99-
context,
100-
context.RepositoryRoot,
101-
s_skillFilePath,
102-
SkillFileDescription);
10387
}
10488
else
10589
{

src/Aspire.Cli/Agents/Playwright/IPlaywrightCliRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal interface IPlaywrightCliRunner
2020
/// <summary>
2121
/// Installs Playwright CLI skill files into the workspace.
2222
/// </summary>
23-
/// <param name="workingDirectory">The directory in which to run the skill installation command.</param>
23+
/// <param name="workingDirectory">The directory to run the command from (skill files are written relative to this).</param>
2424
/// <param name="cancellationToken">A token to cancel the operation.</param>
2525
/// <returns>True if skill installation succeeded, false otherwise.</returns>
2626
Task<bool> InstallSkillsAsync(string workingDirectory, CancellationToken cancellationToken);

src/Aspire.Cli/Agents/Playwright/PlaywrightCliInstaller.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ internal sealed class PlaywrightCliInstaller(
5454

5555
/// <summary>
5656
/// The primary skill base directory where playwright-cli installs skills.
57+
/// This must match the directory that the playwright-cli binary actually writes to.
58+
/// See: https://github.com/microsoft/playwright-cli/issues/294
5759
/// </summary>
5860
internal static readonly string s_primarySkillBaseDirectory = Path.Combine(".claude", "skills");
5961

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Cli.Agents;
5+
6+
/// <summary>
7+
/// Represents a skill that can be installed into a skill location.
8+
/// </summary>
9+
internal sealed class SkillDefinition
10+
{
11+
/// <summary>
12+
/// The Aspire skill for CLI commands and workflows.
13+
/// </summary>
14+
public static readonly SkillDefinition Aspire = new(
15+
CommonAgentApplicators.AspireSkillName,
16+
"Aspire CLI commands and workflows for distributed apps",
17+
CommonAgentApplicators.SkillFileContent,
18+
isDefault: true);
19+
20+
/// <summary>
21+
/// The Playwright CLI skill for browser automation.
22+
/// </summary>
23+
public static readonly SkillDefinition PlaywrightCli = new(
24+
"playwright-cli",
25+
"Browser automation and functional testing",
26+
skillContent: null, // Playwright is installed via PlaywrightCliInstaller, not a static file
27+
isDefault: true);
28+
29+
/// <summary>
30+
/// The dotnet-inspect skill for querying .NET API surfaces.
31+
/// </summary>
32+
public static readonly SkillDefinition DotnetInspect = new(
33+
CommonAgentApplicators.DotnetInspectSkillName,
34+
"Query .NET API surfaces across NuGet packages and platform libraries",
35+
CommonAgentApplicators.DotnetInspectSkillFileContent,
36+
isDefault: true);
37+
38+
private SkillDefinition(string name, string description, string? skillContent, bool isDefault)
39+
{
40+
Name = name;
41+
Description = description;
42+
SkillContent = skillContent;
43+
IsDefault = isDefault;
44+
}
45+
46+
/// <summary>
47+
/// Gets the skill name (used as the folder name under skill locations).
48+
/// </summary>
49+
public string Name { get; }
50+
51+
/// <summary>
52+
/// Gets the description shown in the selection prompt.
53+
/// </summary>
54+
public string Description { get; }
55+
56+
/// <summary>
57+
/// Gets the content for the SKILL.md file, or <c>null</c> if this skill is installed by other means.
58+
/// </summary>
59+
public string? SkillContent { get; }
60+
61+
/// <summary>
62+
/// Gets whether this skill should be selected by default.
63+
/// </summary>
64+
public bool IsDefault { get; }
65+
66+
/// <summary>
67+
/// Gets all available skill definitions.
68+
/// </summary>
69+
public static IReadOnlyList<SkillDefinition> All { get; } = [Aspire, PlaywrightCli, DotnetInspect];
70+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Cli.Agents;
5+
6+
/// <summary>
7+
/// Represents a location where skill files can be installed.
8+
/// </summary>
9+
internal sealed class SkillLocation
10+
{
11+
/// <summary>
12+
/// Standard <c>.agents/skills/</c> location supported by VS Code, GitHub Copilot, and OpenCode.
13+
/// </summary>
14+
public static readonly SkillLocation Standard = new(
15+
"Standard (.agents/skills/)",
16+
"Supported by VS Code, GitHub Copilot, and OpenCode",
17+
Path.Combine(".agents", "skills"),
18+
isDefault: true,
19+
includeUserLevel: true);
20+
21+
/// <summary>
22+
/// Claude Code <c>.claude/skills/</c> location.
23+
/// </summary>
24+
public static readonly SkillLocation ClaudeCode = new(
25+
"Claude Code (.claude/skills/)",
26+
"Required for Claude Code — also used by playwright-cli which currently writes here",
27+
Path.Combine(".claude", "skills"),
28+
isDefault: false,
29+
includeUserLevel: false);
30+
31+
/// <summary>
32+
/// VS Code / GitHub Copilot <c>.github/skills/</c> location.
33+
/// </summary>
34+
public static readonly SkillLocation GitHubSkills = new(
35+
"VS Code / GitHub Copilot (.github/skills/)",
36+
"Legacy location for GitHub Copilot skills",
37+
Path.Combine(".github", "skills"),
38+
isDefault: false,
39+
includeUserLevel: false);
40+
41+
/// <summary>
42+
/// OpenCode <c>.opencode/skill/</c> location.
43+
/// </summary>
44+
public static readonly SkillLocation OpenCode = new(
45+
"OpenCode (.opencode/skill/)",
46+
"Legacy location for OpenCode skills",
47+
Path.Combine(".opencode", "skill"),
48+
isDefault: false,
49+
includeUserLevel: false);
50+
51+
private SkillLocation(string name, string description, string relativeSkillDirectory, bool isDefault, bool includeUserLevel)
52+
{
53+
Name = name;
54+
Description = description;
55+
RelativeSkillDirectory = relativeSkillDirectory;
56+
IsDefault = isDefault;
57+
IncludeUserLevel = includeUserLevel;
58+
}
59+
60+
/// <summary>
61+
/// Gets the display name for this location.
62+
/// </summary>
63+
public string Name { get; }
64+
65+
/// <summary>
66+
/// Gets the description shown alongside the name in prompts.
67+
/// </summary>
68+
public string Description { get; }
69+
70+
/// <summary>
71+
/// Gets the relative skill directory path (e.g., ".agents/skills").
72+
/// </summary>
73+
public string RelativeSkillDirectory { get; }
74+
75+
/// <summary>
76+
/// Gets whether this location should be selected by default.
77+
/// </summary>
78+
public bool IsDefault { get; }
79+
80+
/// <summary>
81+
/// Gets whether this location also installs skill files at the user level (<c>~/</c>).
82+
/// </summary>
83+
public bool IncludeUserLevel { get; }
84+
85+
/// <summary>
86+
/// Gets all available skill locations.
87+
/// </summary>
88+
public static IReadOnlyList<SkillLocation> All { get; } = [Standard, ClaudeCode, GitHubSkills, OpenCode];
89+
}

src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ internal sealed class VsCodeAgentEnvironmentScanner : IAgentEnvironmentScanner
1717
private const string VsCodeFolderName = ".vscode";
1818
private const string McpConfigFileName = "mcp.json";
1919
private const string AspireServerName = "aspire";
20-
private static readonly string s_skillFilePath = Path.Combine(".github", "skills", CommonAgentApplicators.AspireSkillName, "SKILL.md");
2120
private static readonly string s_skillBaseDirectory = Path.Combine(".github", "skills");
22-
private const string SkillFileDescription = "Create Aspire skill file (.github/skills/aspire/SKILL.md)";
2321

2422
private readonly IVsCodeCliRunner _vsCodeCliRunner;
2523
private readonly PlaywrightCliInstaller _playwrightCliInstaller;
@@ -72,13 +70,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
7270

7371
// Register Playwright CLI installation applicator
7472
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
75-
76-
// Try to add skill file applicator for GitHub Copilot
77-
CommonAgentApplicators.TryAddSkillFileApplicator(
78-
context,
79-
context.RepositoryRoot,
80-
s_skillFilePath,
81-
SkillFileDescription);
8273
}
8374
else if (await IsVsCodeAvailableAsync(cancellationToken).ConfigureAwait(false))
8475
{
@@ -91,13 +82,6 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok
9182

9283
// Register Playwright CLI installation applicator
9384
CommonAgentApplicators.AddPlaywrightCliApplicator(context, _playwrightCliInstaller, s_skillBaseDirectory);
94-
95-
// Try to add skill file applicator for GitHub Copilot
96-
CommonAgentApplicators.TryAddSkillFileApplicator(
97-
context,
98-
context.RepositoryRoot,
99-
s_skillFilePath,
100-
SkillFileDescription);
10185
}
10286
else
10387
{

0 commit comments

Comments
 (0)