Compare commits
4 Commits
cade9dcc47
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a7d4f352b7 | |||
| 86500af544 | |||
| daa60c53dd | |||
| cd0f96ca1e |
@@ -11,14 +11,14 @@ Use this guide when accessing the Roslyn Bridge for C# code analysis.
|
||||
|
||||
```
|
||||
┌─────────────┐ REST API ┌────────────────────┐ HTTP ┌─────────────────────┐
|
||||
│ Claude │ ◄─────────────────► │ Web API (:5000) │ ◄────────────► │ VS Plugin (:59123) │
|
||||
│ Claude │ ◄─────────────────► │ Web API (:5001) │ ◄────────────► │ VS Plugin (:59123) │
|
||||
│ AI │ │ Middleware │ │ Roslyn Bridge │
|
||||
└─────────────┘ └────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
## Recommended: Web API (Port 5000)
|
||||
## Recommended: Web API (Port 5001)
|
||||
|
||||
**Base URL**: `http://localhost:5000`
|
||||
**Base URL**: `http://localhost:5001`
|
||||
|
||||
The Web API provides a modern RESTful interface with:
|
||||
- Clean REST endpoints with query parameters
|
||||
@@ -69,34 +69,34 @@ Direct access to the Visual Studio plugin (use only if Web API is unavailable):
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:5000/api/health
|
||||
curl http://localhost:5001/api/health
|
||||
|
||||
# Get all projects (returns full file paths)
|
||||
curl http://localhost:5000/api/roslyn/projects
|
||||
curl http://localhost:5001/api/roslyn/projects
|
||||
|
||||
# Get solution overview
|
||||
curl http://localhost:5000/api/roslyn/solution/overview
|
||||
curl http://localhost:5001/api/roslyn/solution/overview
|
||||
|
||||
# Get diagnostics for entire solution
|
||||
curl http://localhost:5000/api/roslyn/diagnostics
|
||||
curl http://localhost:5001/api/roslyn/diagnostics
|
||||
|
||||
# Get diagnostics for specific file
|
||||
curl "http://localhost:5000/api/roslyn/diagnostics?filePath=C:/path/to/file.cs"
|
||||
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=C:/path/to/file.cs"
|
||||
|
||||
# Get symbol at position (use paths from /projects response)
|
||||
curl "http://localhost:5000/api/roslyn/symbol?filePath=C:/path/to/file.cs&line=10&column=5"
|
||||
curl "http://localhost:5001/api/roslyn/symbol?filePath=C:/path/to/file.cs&line=10&column=5"
|
||||
|
||||
# Find all references
|
||||
curl "http://localhost:5000/api/roslyn/references?filePath=C:/path/to/file.cs&line=18&column=30"
|
||||
curl "http://localhost:5001/api/roslyn/references?filePath=C:/path/to/file.cs&line=18&column=30"
|
||||
|
||||
# Search for symbols
|
||||
curl "http://localhost:5000/api/roslyn/symbol/search?symbolName=MyClass&kind=class"
|
||||
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=MyClass&kind=class"
|
||||
|
||||
# Get recent history
|
||||
curl http://localhost:5000/api/history/recent?count=10
|
||||
curl http://localhost:5001/api/history/recent?count=10
|
||||
|
||||
# Get history statistics
|
||||
curl http://localhost:5000/api/history/stats
|
||||
curl http://localhost:5001/api/history/stats
|
||||
```
|
||||
|
||||
**IMPORTANT curl syntax rules:**
|
||||
@@ -170,50 +170,50 @@ curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d
|
||||
|
||||
```bash
|
||||
# Step 1: Check if services are healthy
|
||||
curl http://localhost:5000/api/health
|
||||
curl http://localhost:5001/api/health
|
||||
|
||||
# Step 2: Get all projects and their files
|
||||
curl http://localhost:5000/api/roslyn/projects
|
||||
curl http://localhost:5001/api/roslyn/projects
|
||||
# Response includes: {"data": [{"documents": ["C:/Full/Path/To/File.cs", ...]}]}
|
||||
|
||||
# Step 3: Use the full paths from Step 2 in subsequent queries
|
||||
FILE="C:/Users/AJ/Desktop/MyProject/Program.cs"
|
||||
|
||||
# Get diagnostics (errors/warnings) for a file
|
||||
curl "http://localhost:5000/api/roslyn/diagnostics?filePath=$FILE"
|
||||
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=$FILE"
|
||||
|
||||
# Get symbol information at a specific location
|
||||
curl "http://localhost:5000/api/roslyn/symbol?filePath=$FILE&line=15&column=10"
|
||||
curl "http://localhost:5001/api/roslyn/symbol?filePath=$FILE&line=15&column=10"
|
||||
|
||||
# Find all references to that symbol
|
||||
curl "http://localhost:5000/api/roslyn/references?filePath=$FILE&line=15&column=10"
|
||||
curl "http://localhost:5001/api/roslyn/references?filePath=$FILE&line=15&column=10"
|
||||
|
||||
# Step 4: View your query history
|
||||
curl http://localhost:5000/api/history/recent?count=5
|
||||
curl http://localhost:5001/api/history/recent?count=5
|
||||
|
||||
# Step 5: Get statistics about your queries
|
||||
curl http://localhost:5000/api/history/stats
|
||||
curl http://localhost:5001/api/history/stats
|
||||
```
|
||||
|
||||
### Advanced Query Examples
|
||||
|
||||
```bash
|
||||
# Search for all classes with "Service" in the name
|
||||
curl "http://localhost:5000/api/roslyn/symbol/search?symbolName=Service&kind=class"
|
||||
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=Service&kind=class"
|
||||
|
||||
# Get solution-wide statistics
|
||||
curl http://localhost:5000/api/roslyn/solution/overview
|
||||
curl http://localhost:5001/api/roslyn/solution/overview
|
||||
|
||||
# For complex queries not available as REST endpoints, use POST /query
|
||||
curl -X POST http://localhost:5000/api/roslyn/query \
|
||||
curl -X POST http://localhost:5001/api/roslyn/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"queryType":"searchcode","symbolName":".*Controller","parameters":{"scope":"classes"}}'
|
||||
|
||||
# Build a project
|
||||
curl -X POST "http://localhost:5000/api/roslyn/project/build?projectName=MyProject"
|
||||
curl -X POST "http://localhost:5001/api/roslyn/project/build?projectName=MyProject"
|
||||
|
||||
# Add NuGet package
|
||||
curl -X POST "http://localhost:5000/api/roslyn/project/package/add?projectName=MyProject&packageName=Newtonsoft.Json&version=13.0.3"
|
||||
curl -X POST "http://localhost:5001/api/roslyn/project/package/add?projectName=MyProject&packageName=Newtonsoft.Json&version=13.0.3"
|
||||
```
|
||||
|
||||
## Response Format
|
||||
@@ -253,7 +253,7 @@ All endpoints return JSON in this format:
|
||||
## Troubleshooting
|
||||
|
||||
**"Failed to connect" error:**
|
||||
1. Check if Web API is running: `curl http://localhost:5000/api/health/ping`
|
||||
1. Check if Web API is running: `curl http://localhost:5001/api/health/ping`
|
||||
2. Check if VS Plugin is running: `curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"`
|
||||
3. Ensure Visual Studio is open with a solution loaded
|
||||
|
||||
|
||||
53
AGENTS.md
53
AGENTS.md
@@ -37,3 +37,56 @@
|
||||
- Server binds to `http://localhost:59123/` and accepts only POST; CORS is permissive for local tooling. Do not expose externally.
|
||||
- Endpoints: `/query` (main), `/health` route exists but still requires POST.
|
||||
- Adjust port/paths in `RoslynBridge/Constants/ServerConstants.cs` if needed.
|
||||
|
||||
## Agent Script Usage: rb.ps1 (for AI agents)
|
||||
|
||||
This section is for the agent only. Use `rb.ps1` to minimize tokens and speed up local Roslyn queries.
|
||||
|
||||
- Location: `rb.ps1` (repo root). Invoke with PowerShell.
|
||||
- Auto-detects endpoints: prefers Web API (`http://localhost:5001`), falls back to VS plugin (`http://localhost:59123`).
|
||||
- Output defaults to compact JSON. Use `-Raw` to emit raw response strings, `-Pretty` only for debugging.
|
||||
- Override detection with `-BaseUrl http://localhost:5001` if needed; `-NoDetect` forces Web API pathing.
|
||||
|
||||
Examples (run from repo root):
|
||||
|
||||
```powershell
|
||||
# Health and discovery
|
||||
powershell -NoProfile -Command .\rb.ps1 health
|
||||
powershell -NoProfile -Command .\rb.ps1 projects -Raw
|
||||
powershell -NoProfile -Command .\rb.ps1 overview
|
||||
|
||||
# File-specific queries (use full paths from `projects`)
|
||||
powershell -NoProfile -Command .\rb.ps1 diagnostics -FilePath C:/full/path/File.cs
|
||||
powershell -NoProfile -Command .\rb.ps1 symbol -FilePath C:/full/path/File.cs -Line 12 -Column 4
|
||||
powershell -NoProfile -Command .\rb.ps1 references -FilePath C:/full/path/File.cs -Line 12 -Column 4 -Raw
|
||||
|
||||
# Symbol search
|
||||
powershell -NoProfile -Command .\rb.ps1 search -SymbolName Service -Kind class
|
||||
|
||||
# Project operations
|
||||
powershell -NoProfile -Command .\rb.ps1 build -ProjectName MyProject
|
||||
powershell -NoProfile -Command .\rb.ps1 package-add -ProjectName MyProject -PackageName Newtonsoft.Json -Version 13.0.3
|
||||
|
||||
# History (Web API only)
|
||||
powershell -NoProfile -Command .\rb.ps1 history-recent -Count 10
|
||||
powershell -NoProfile -Command .\rb.ps1 history-stats
|
||||
|
||||
# Compact solution overview summary (uses API-level summary)
|
||||
powershell -NoProfile -Command .\rb.ps1 solution-overview -Format yaml -Top 5
|
||||
|
||||
```
|
||||
|
||||
Key behaviors and assumptions:
|
||||
- Lines are 1-based; columns 0-based.
|
||||
- Forward slashes `/` are accepted in file paths.
|
||||
- Prefer `-Raw` to minimize token usage when chaining outputs.
|
||||
- If Web API is down, commands transparently fall back to the plugin when possible. History endpoints require Web API.
|
||||
- If auto-detection is ambiguous, set `-BaseUrl` explicitly.
|
||||
|
||||
Policy (agent-only):
|
||||
- Do NOT call `curl`, `Invoke-RestMethod`, or `Invoke-WebRequest` directly to Roslyn Bridge.
|
||||
- Always invoke `rb.ps1` for all Roslyn-related queries and operations.
|
||||
- Prefer `-Raw` output and parse only what’s needed to reduce tokens.
|
||||
- Keep timeouts short unless debugging (`-TimeoutSec` can be raised temporarily).
|
||||
- Avoid pretty output unless explicitly needed (`-Pretty`).
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Linq;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Controllers;
|
||||
@@ -91,6 +94,130 @@ public class RoslynController : ControllerBase
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a compact summary of the solution overview
|
||||
/// </summary>
|
||||
/// <param name="topNProjects">How many projects to list (by file count)</param>
|
||||
/// <param name="includeNamespaces">Include top-level namespaces in summary</param>
|
||||
/// <param name="format">One of: json | text | yaml</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Compact summary as JSON or plain text</returns>
|
||||
[HttpGet("solution/overview/summary")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetSolutionOverviewSummary(
|
||||
[FromQuery] int topNProjects = 5,
|
||||
[FromQuery] bool includeNamespaces = true,
|
||||
[FromQuery] string? format = "json",
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest { QueryType = "getsolutionoverview" };
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
var err = result.Error ?? "Unknown error";
|
||||
return Content($"error: {err}", "text/plain", Encoding.UTF8);
|
||||
}
|
||||
|
||||
// Expect Data as JsonElement; shape per Models.SolutionOverview
|
||||
int projectCount = 0;
|
||||
int documentCount = 0;
|
||||
var projectsSummary = new List<(string Name, int FileCount)>();
|
||||
var topLevelNamespaces = new List<string>();
|
||||
|
||||
if (result.Data is JsonElement dataEl && dataEl.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (dataEl.TryGetProperty("projectCount", out var pc)) projectCount = pc.GetInt32();
|
||||
if (dataEl.TryGetProperty("documentCount", out var dc)) documentCount = dc.GetInt32();
|
||||
if (includeNamespaces && dataEl.TryGetProperty("topLevelNamespaces", out var nsEl) && nsEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var ns in nsEl.EnumerateArray())
|
||||
{
|
||||
if (ns.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
topLevelNamespaces.Add(ns.GetString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataEl.TryGetProperty("projects", out var prEl) && prEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var p in prEl.EnumerateArray())
|
||||
{
|
||||
string name = p.TryGetProperty("name", out var nEl) && nEl.ValueKind == JsonValueKind.String ? (nEl.GetString() ?? string.Empty) : string.Empty;
|
||||
int files = p.TryGetProperty("fileCount", out var fEl) && fEl.ValueKind == JsonValueKind.Number ? fEl.GetInt32() : 0;
|
||||
projectsSummary.Add((name, files));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var topProjects = projectsSummary
|
||||
.OrderByDescending(p => p.FileCount)
|
||||
.ThenBy(p => p.Name)
|
||||
.Take(Math.Max(0, topNProjects))
|
||||
.ToList();
|
||||
|
||||
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var compact = new
|
||||
{
|
||||
projectCount,
|
||||
documentCount,
|
||||
projects = topProjects.Select(p => new { name = p.Name, files = p.FileCount }).ToList(),
|
||||
topLevelNamespaces = includeNamespaces ? topLevelNamespaces : null
|
||||
};
|
||||
|
||||
return Ok(new RoslynQueryResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "Compact solution overview",
|
||||
Data = compact,
|
||||
Error = null
|
||||
});
|
||||
}
|
||||
|
||||
// text or yaml: render a compact string
|
||||
var sb = new StringBuilder();
|
||||
if (string.Equals(format, "yaml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sb.AppendLine($"projectCount: {projectCount}");
|
||||
sb.AppendLine($"documentCount: {documentCount}");
|
||||
if (includeNamespaces)
|
||||
{
|
||||
sb.Append("topLevelNamespaces: [");
|
||||
sb.Append(string.Join(", ", topLevelNamespaces));
|
||||
sb.AppendLine("]");
|
||||
}
|
||||
sb.AppendLine("projects:");
|
||||
foreach (var p in topProjects)
|
||||
{
|
||||
sb.AppendLine($" - name: {p.Name}");
|
||||
sb.AppendLine($" files: {p.FileCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// text
|
||||
sb.AppendLine($"Projects: {projectCount} | Documents: {documentCount}");
|
||||
if (includeNamespaces && topLevelNamespaces.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"TopNamespaces: {string.Join(", ", topLevelNamespaces)}");
|
||||
}
|
||||
sb.AppendLine("Top Projects:");
|
||||
foreach (var p in topProjects)
|
||||
{
|
||||
sb.AppendLine($" - {p.Name} ({p.FileCount})");
|
||||
}
|
||||
}
|
||||
|
||||
return Content(sb.ToString(), "text/plain", Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get diagnostics (errors and warnings)
|
||||
/// </summary>
|
||||
|
||||
Binary file not shown.
@@ -1 +1,5 @@
|
||||
{"Version":1,"ManifestType":"Publish","Endpoints":[]}
|
||||
{
|
||||
"Version": 1,
|
||||
"ManifestType": "Publish",
|
||||
"Endpoints": []
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://localhost:5000"
|
||||
"Url": "http://localhost:5001"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
267
SKILL.md
Normal file
267
SKILL.md
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
name: roslyn-bridge
|
||||
description: Use this for C# code analysis, querying .NET projects, finding symbols, getting diagnostics, or any Roslyn/semantic analysis tasks using the bridge server
|
||||
---
|
||||
|
||||
# Roslyn Bridge API Guide
|
||||
|
||||
Use this guide when accessing the Roslyn Bridge for C# code analysis.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────┐ REST API ┌────────────────────┐ HTTP ┌─────────────────────┐
|
||||
│ Claude │ ◄─────────────────► │ Web API (:5001) │ ◄────────────► │ VS Plugin (:59123) │
|
||||
│ AI │ │ Middleware │ │ Roslyn Bridge │
|
||||
└─────────────┘ └────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
## Recommended: Web API (Port 5001)
|
||||
|
||||
**Base URL**: `http://localhost:5001`
|
||||
|
||||
The Web API provides a modern RESTful interface with:
|
||||
- Clean REST endpoints with query parameters
|
||||
- Full Swagger documentation at `/`
|
||||
- Request/response history tracking at `/api/history`
|
||||
- CORS support for web applications
|
||||
- Better error handling and logging
|
||||
|
||||
### Web API Endpoints
|
||||
|
||||
**Health & Info:**
|
||||
- `GET /api/health` - Check health of both Web API and VS plugin
|
||||
- `GET /api/health/ping` - Simple ping
|
||||
|
||||
**Roslyn Operations:**
|
||||
- `GET /api/roslyn/projects` - Get all projects
|
||||
- `GET /api/roslyn/solution/overview` - Solution statistics
|
||||
- `GET /api/roslyn/diagnostics?filePath={path}` - Get errors/warnings
|
||||
- `GET /api/roslyn/symbol?filePath={path}&line={line}&column={col}` - Get symbol info
|
||||
- `GET /api/roslyn/references?filePath={path}&line={line}&column={col}` - Find references
|
||||
- `GET /api/roslyn/symbol/search?symbolName={name}&kind={kind}` - Search symbols
|
||||
- `POST /api/roslyn/query` - Execute any query (fallback to raw queries)
|
||||
- `POST /api/roslyn/format` - Format document
|
||||
- `POST /api/roslyn/project/package/add` - Add NuGet package
|
||||
- `POST /api/roslyn/project/build` - Build project
|
||||
|
||||
**History:**
|
||||
- `GET /api/history` - All history entries
|
||||
- `GET /api/history/{id}` - Specific entry
|
||||
- `GET /api/history/recent?count=50` - Recent entries
|
||||
- `GET /api/history/stats` - Statistics
|
||||
- `DELETE /api/history` - Clear history
|
||||
|
||||
## Alternative: Direct VS Plugin Access (Port 59123)
|
||||
|
||||
**Base URL**: `http://localhost:59123`
|
||||
|
||||
Direct access to the Visual Studio plugin (use only if Web API is unavailable):
|
||||
- **Endpoints**: `/query`, `/health`
|
||||
- **Method**: POST only
|
||||
- **Content-Type**: application/json
|
||||
|
||||
## ⚠️ CRITICAL: Command Syntax Rules
|
||||
|
||||
**ALWAYS use curl with the Bash tool. NEVER pipe curl output to PowerShell.**
|
||||
|
||||
### ✅ CORRECT: Using Web API (Recommended)
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:5001/api/health
|
||||
|
||||
# Get all projects (returns full file paths)
|
||||
curl http://localhost:5001/api/roslyn/projects
|
||||
|
||||
# Get solution overview
|
||||
curl http://localhost:5001/api/roslyn/solution/overview
|
||||
|
||||
# Get diagnostics for entire solution
|
||||
curl http://localhost:5001/api/roslyn/diagnostics
|
||||
|
||||
# Get diagnostics for specific file
|
||||
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=C:/path/to/file.cs"
|
||||
|
||||
# Get symbol at position (use paths from /projects response)
|
||||
curl "http://localhost:5001/api/roslyn/symbol?filePath=C:/path/to/file.cs&line=10&column=5"
|
||||
|
||||
# Find all references
|
||||
curl "http://localhost:5001/api/roslyn/references?filePath=C:/path/to/file.cs&line=18&column=30"
|
||||
|
||||
# Search for symbols
|
||||
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=MyClass&kind=class"
|
||||
|
||||
# Get recent history
|
||||
curl http://localhost:5001/api/history/recent?count=10
|
||||
|
||||
# Get history statistics
|
||||
curl http://localhost:5001/api/history/stats
|
||||
```
|
||||
|
||||
**IMPORTANT curl syntax rules:**
|
||||
- For GET requests, no `-X GET` needed
|
||||
- Use quotes around URLs with query parameters
|
||||
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
|
||||
- **DO NOT pipe to PowerShell** - the JSON output is already formatted
|
||||
|
||||
### Using Direct VS Plugin Access (Fallback)
|
||||
|
||||
```bash
|
||||
# Health check - verify VS plugin is running
|
||||
curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"
|
||||
|
||||
# Get projects via POST
|
||||
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}"
|
||||
|
||||
# Get symbol at position
|
||||
curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\",\"line\":10,\"column\":5}"
|
||||
```
|
||||
|
||||
**Note:** In POST requests to VS plugin, escape backslashes: `\\\\` becomes `\\` in JSON
|
||||
|
||||
## Quick Reference: All Endpoints
|
||||
|
||||
### Query Endpoints
|
||||
|
||||
| Endpoint | Required Fields | Optional Fields | Description |
|
||||
|----------|----------------|-----------------|-------------|
|
||||
| `getprojects` | - | - | Get all projects in the solution |
|
||||
| `getdocument` | `filePath` | - | Get document information for a specific file |
|
||||
| `getsymbol` | `filePath`, `line`, `column` | - | Get symbol information at a specific position |
|
||||
| `getsemanticmodel` | `filePath` | - | Verify semantic model availability (not serializable) |
|
||||
| `getsyntaxtree` | `filePath` | - | Get the syntax tree (source code) for a file |
|
||||
| `getdiagnostics` | - | `filePath` | Get compilation errors and warnings |
|
||||
| `findreferences` | `filePath`, `line`, `column` | - | Find all references to a symbol |
|
||||
| `findsymbol` | `symbolName` | `parameters.kind` | Find symbols by name (supports filtering by kind) |
|
||||
| `gettypemembers` | `symbolName` | `parameters.includeInherited` | Get all members of a type |
|
||||
| `gettypehierarchy` | `symbolName` | `parameters.direction` | Get base types or derived types |
|
||||
| `findimplementations` | `symbolName` OR `filePath`+`line`+`column` | - | Find implementations of an interface/abstract member |
|
||||
| `getnamespacetypes` | `symbolName` | - | Get all types in a namespace |
|
||||
| `getcallhierarchy` | `filePath`, `line`, `column` | `parameters.direction` | Get callers or callees of a method |
|
||||
| `getsolutionoverview` | - | - | Get high-level solution statistics |
|
||||
| `getsymbolcontext` | `filePath`, `line`, `column` | - | Get contextual information about a symbol's location |
|
||||
| `searchcode` | `symbolName` (regex) | `parameters.scope` | Search for code patterns using regex |
|
||||
|
||||
### Editing Endpoints
|
||||
|
||||
| Endpoint | Required Fields | Optional Fields | Description |
|
||||
|----------|----------------|-----------------|-------------|
|
||||
| `formatdocument` | `filePath` | - | Format a document according to coding style |
|
||||
| `organizeusings` | `filePath` | - | Sort and remove unused using statements |
|
||||
| `renamesymbol` | `filePath`, `line`, `column`, `parameters.newName` | - | Rename a symbol across the solution |
|
||||
| `addmissingusing` | `filePath`, `line`, `column` | - | Add missing using statement for a symbol |
|
||||
| `applycodefix` | `filePath`, `line`, `column` | - | Apply available code fixes at a position |
|
||||
|
||||
### Project Operation Endpoints
|
||||
|
||||
| Endpoint | Required Fields | Optional Fields | Description |
|
||||
|----------|----------------|-----------------|-------------|
|
||||
| `addnugetpackage` | `projectName`, `packageName` | `version` | Add a NuGet package to a project |
|
||||
| `removenugetpackage` | `projectName`, `packageName` | - | Remove a NuGet package from a project |
|
||||
| `buildproject` | `projectName` | `configuration` | Build a project or solution |
|
||||
| `cleanproject` | `projectName` | - | Clean build output |
|
||||
| `restorepackages` | `projectName` | - | Restore NuGet packages |
|
||||
| `createdirectory` | `directoryPath` | - | Create a new directory |
|
||||
|
||||
## Common Workflow Examples
|
||||
|
||||
### Typical Code Analysis Workflow
|
||||
|
||||
```bash
|
||||
# Step 1: Check if services are healthy
|
||||
curl http://localhost:5001/api/health
|
||||
|
||||
# Step 2: Get all projects and their files
|
||||
curl http://localhost:5001/api/roslyn/projects
|
||||
# Response includes: {"data": [{"documents": ["C:/Full/Path/To/File.cs", ...]}]}
|
||||
|
||||
# Step 3: Use the full paths from Step 2 in subsequent queries
|
||||
FILE="C:/Users/AJ/Desktop/MyProject/Program.cs"
|
||||
|
||||
# Get diagnostics (errors/warnings) for a file
|
||||
curl "http://localhost:5001/api/roslyn/diagnostics?filePath=$FILE"
|
||||
|
||||
# Get symbol information at a specific location
|
||||
curl "http://localhost:5001/api/roslyn/symbol?filePath=$FILE&line=15&column=10"
|
||||
|
||||
# Find all references to that symbol
|
||||
curl "http://localhost:5001/api/roslyn/references?filePath=$FILE&line=15&column=10"
|
||||
|
||||
# Step 4: View your query history
|
||||
curl http://localhost:5001/api/history/recent?count=5
|
||||
|
||||
# Step 5: Get statistics about your queries
|
||||
curl http://localhost:5001/api/history/stats
|
||||
```
|
||||
|
||||
### Advanced Query Examples
|
||||
|
||||
```bash
|
||||
# Search for all classes with "Service" in the name
|
||||
curl "http://localhost:5001/api/roslyn/symbol/search?symbolName=Service&kind=class"
|
||||
|
||||
# Get solution-wide statistics
|
||||
curl http://localhost:5001/api/roslyn/solution/overview
|
||||
|
||||
# For complex queries not available as REST endpoints, use POST /query
|
||||
curl -X POST http://localhost:5001/api/roslyn/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"queryType":"searchcode","symbolName":".*Controller","parameters":{"scope":"classes"}}'
|
||||
|
||||
# Build a project
|
||||
curl -X POST "http://localhost:5001/api/roslyn/project/build?projectName=MyProject"
|
||||
|
||||
# Add NuGet package
|
||||
curl -X POST "http://localhost:5001/api/roslyn/project/package/add?projectName=MyProject&packageName=Newtonsoft.Json&version=13.0.3"
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
All endpoints return JSON in this format:
|
||||
|
||||
**Success:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Optional message",
|
||||
"data": { /* Response data varies by endpoint */ },
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
**Error:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": null,
|
||||
"data": null,
|
||||
"error": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Line numbers are **1-based** (first line is 1)
|
||||
- Column numbers are **0-based** (first column is 0)
|
||||
- **Use full paths from `/api/roslyn/projects` response** in other endpoints
|
||||
- Forward slashes `/` work in file paths (Windows accepts both `/` and `\`)
|
||||
- Both the Web API (port 5000) and VS Plugin (port 59123) must be running
|
||||
- All workspace modifications use VS threading model
|
||||
- Request history is automatically tracked in the Web API
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Failed to connect" error:**
|
||||
1. Check if Web API is running: `curl http://localhost:5001/api/health/ping`
|
||||
2. Check if VS Plugin is running: `curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}"`
|
||||
3. Ensure Visual Studio is open with a solution loaded
|
||||
|
||||
**"vsPluginStatus": "Disconnected":**
|
||||
- The VS Plugin (port 59123) is not running
|
||||
- Open Visual Studio and ensure the RoslynBridge extension is loaded
|
||||
|
||||
**Empty or null data:**
|
||||
- Ensure you're using the correct file paths from `/api/roslyn/projects`
|
||||
- Verify line/column numbers are within the file bounds
|
||||
- Check that the file is part of the currently open VS solution
|
||||
372
rb.ps1
Normal file
372
rb.ps1
Normal file
@@ -0,0 +1,372 @@
|
||||
# Roslyn Bridge PowerShell helper
|
||||
#
|
||||
# Purpose: Fast, low‑token wrapper around the Roslyn Bridge Web API (port 5001)
|
||||
# and the legacy VS plugin (port 59123). Auto-detects which is running
|
||||
# and provides concise commands for common operations.
|
||||
#
|
||||
# Usage examples:
|
||||
# .\rb.ps1 health
|
||||
# .\rb.ps1 projects
|
||||
# .\rb.ps1 overview
|
||||
# .\rb.ps1 diagnostics -FilePath C:/path/to/File.cs
|
||||
# .\rb.ps1 symbol -FilePath C:/path/to/File.cs -Line 12 -Column 4
|
||||
# .\rb.ps1 references -FilePath C:/path/to/File.cs -Line 12 -Column 4
|
||||
# .\rb.ps1 search -SymbolName MyService -Kind class
|
||||
# .\rb.ps1 build -ProjectName MyProject
|
||||
# .\rb.ps1 package-add -ProjectName MyProject -PackageName Newtonsoft.Json -Version 13.0.3
|
||||
# .\rb.ps1 history-recent -Count 10
|
||||
# .\rb.ps1 query -BodyJson '{"queryType":"searchcode","symbolName":".*Controller"}'
|
||||
#
|
||||
# Output:
|
||||
# - Compressed JSON by default (minimal tokens)
|
||||
# - Add -Pretty for indented JSON
|
||||
# - Add -Raw to return the raw response string
|
||||
|
||||
[CmdletBinding(PositionalBinding=$true)]
|
||||
param(
|
||||
[Parameter(Position=0, Mandatory=$true)]
|
||||
[ValidateSet(
|
||||
'health','projects','overview','diagnostics','symbol','references','search',
|
||||
'build','package-add','history','history-recent','history-stats',
|
||||
'plugin-health','solution-overview'
|
||||
)]
|
||||
[string]$Command,
|
||||
|
||||
# Common params
|
||||
[string]$BaseUrl, # Overrides auto-detected base URL
|
||||
[switch]$NoDetect, # Skip auto-detection
|
||||
[int]$TimeoutSec = 6, # Short default to stay snappy
|
||||
[switch]$Raw, # Emit raw response string
|
||||
[switch]$Pretty, # Indented JSON output
|
||||
|
||||
# Query params
|
||||
[string]$FilePath,
|
||||
[int]$Line,
|
||||
[int]$Column,
|
||||
[string]$SymbolName,
|
||||
[ValidateSet('class','method','property','field','interface','enum','struct','namespace','event','any')]
|
||||
[string]$Kind = 'any',
|
||||
|
||||
# Project ops
|
||||
[string]$ProjectName,
|
||||
[string]$Configuration,
|
||||
[string]$PackageName,
|
||||
[string]$Version,
|
||||
|
||||
# History
|
||||
[int]$Count = 25,
|
||||
|
||||
# Solution overview summary options
|
||||
[ValidateSet('json','text','yaml')]
|
||||
[string]$Format = 'json',
|
||||
[int]$Top = 5,
|
||||
[bool]$IncludeNamespaces = $true
|
||||
)
|
||||
|
||||
set-strictmode -version latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Err {
|
||||
param([string]$Msg)
|
||||
Write-Error -Message $Msg
|
||||
}
|
||||
|
||||
function Get-JsonOutput {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)] $Data
|
||||
)
|
||||
if ($Raw) {
|
||||
# If we were given an object, compress to a compact string; if it's already a string, emit it.
|
||||
if ($Data -is [string]) { return $Data }
|
||||
return ($Data | ConvertTo-Json -Depth 50 -Compress)
|
||||
}
|
||||
$json = $Data | ConvertTo-Json -Depth 50 -Compress
|
||||
if ($Pretty) { $json = ($Data | ConvertTo-Json -Depth 50) }
|
||||
return $json
|
||||
}
|
||||
|
||||
function Test-Url {
|
||||
param(
|
||||
[string]$Url,
|
||||
[ValidateSet('GET','POST')][string]$Method = 'GET'
|
||||
)
|
||||
try {
|
||||
if ($Method -eq 'GET') {
|
||||
$resp = Invoke-WebRequest -Method GET -Uri $Url -TimeoutSec $TimeoutSec
|
||||
} else {
|
||||
$resp = Invoke-WebRequest -Method POST -Uri $Url -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body '{}'
|
||||
}
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BridgeEndpoints {
|
||||
# Decide which base URL to use.
|
||||
# Priority: explicit -BaseUrl -> Web API (5001) if healthy -> Plugin (59123) if healthy
|
||||
$web = 'http://localhost:5001'
|
||||
$plugin = 'http://localhost:59123'
|
||||
|
||||
if ($BaseUrl) {
|
||||
return @{ BaseUrl = $BaseUrl; Mode = 'explicit' }
|
||||
}
|
||||
|
||||
if ($NoDetect) {
|
||||
return @{ BaseUrl = $web; Mode = 'web' }
|
||||
}
|
||||
|
||||
if (Test-Url -Url "$web/api/health/ping" -Method GET) {
|
||||
return @{ BaseUrl = $web; Mode = 'web' }
|
||||
}
|
||||
|
||||
if (Test-Url -Url "$plugin/health" -Method POST) {
|
||||
return @{ BaseUrl = $plugin; Mode = 'plugin' }
|
||||
}
|
||||
|
||||
# Fallback to web
|
||||
return @{ BaseUrl = $web; Mode = 'web' }
|
||||
}
|
||||
|
||||
function Invoke-WebApi {
|
||||
param(
|
||||
[string]$BaseUrl,
|
||||
[ValidateSet('GET','POST')][string]$Method,
|
||||
[string]$Path,
|
||||
[hashtable]$Query,
|
||||
$Body
|
||||
)
|
||||
$uri = [System.UriBuilder]::new($BaseUrl)
|
||||
if ($Path.StartsWith('/')) { $uri.Path = $Path } else { $uri.Path = "$($uri.Path.TrimEnd('/'))/$Path" }
|
||||
if ($Query) {
|
||||
$qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
|
||||
foreach ($k in $Query.Keys) {
|
||||
if ($null -ne $Query[$k] -and $Query[$k] -ne '') { $qs[$k] = [string]$Query[$k] }
|
||||
}
|
||||
$uri.Query = $qs.ToString()
|
||||
}
|
||||
if ($Raw) {
|
||||
if ($Method -eq 'GET') {
|
||||
return (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
|
||||
} else {
|
||||
$bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
|
||||
return (Invoke-WebRequest -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
|
||||
}
|
||||
}
|
||||
if ($Method -eq 'GET') {
|
||||
return Invoke-RestMethod -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec
|
||||
} else {
|
||||
if ($Body -is [string]) {
|
||||
return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
|
||||
} else {
|
||||
return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-PluginApi {
|
||||
param(
|
||||
[string]$BaseUrl,
|
||||
[string]$Path, # '/query' or '/health'
|
||||
$Body # hashtable or string
|
||||
)
|
||||
$uri = "$BaseUrl$Path"
|
||||
if ($Raw) {
|
||||
$bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
|
||||
return (Invoke-WebRequest -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
|
||||
}
|
||||
if ($Body -is [string]) {
|
||||
return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
|
||||
} else {
|
||||
return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Main dispatch
|
||||
#
|
||||
|
||||
$endpoint = Get-BridgeEndpoints
|
||||
$mode = $endpoint.Mode
|
||||
$base = $endpoint.BaseUrl
|
||||
|
||||
try {
|
||||
switch ($Command) {
|
||||
'health' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/health'
|
||||
$out = Get-JsonOutput -Data $resp
|
||||
Write-Output $out
|
||||
} else {
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/health' -Body '{}'
|
||||
$out = Get-JsonOutput -Data $resp
|
||||
Write-Output $out
|
||||
}
|
||||
}
|
||||
'projects' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/projects'
|
||||
} else {
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getprojects' }
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'overview' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview'
|
||||
} else {
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getsolutionoverview' }
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'diagnostics' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{}
|
||||
if ($FilePath) { $q.filePath = $FilePath }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/diagnostics' -Query $q
|
||||
} else {
|
||||
$body = @{ queryType = 'getdiagnostics' }
|
||||
if ($FilePath) { $body.filePath = $FilePath }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'symbol' {
|
||||
if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
|
||||
Write-Err 'symbol requires -FilePath, -Line, -Column'; exit 2
|
||||
}
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ filePath=$FilePath; line=$Line; column=$Column }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol' -Query $q
|
||||
} else {
|
||||
$body = @{ queryType='getsymbol'; filePath=$FilePath; line=$Line; column=$Column }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'references' {
|
||||
if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
|
||||
Write-Err 'references requires -FilePath, -Line, -Column'; exit 2
|
||||
}
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ filePath=$FilePath; line=$Line; column=$Column }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/references' -Query $q
|
||||
} else {
|
||||
$body = @{ queryType='findreferences'; filePath=$FilePath; line=$Line; column=$Column }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'search' {
|
||||
if (-not $SymbolName) { Write-Err 'search requires -SymbolName'; exit 2 }
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ symbolName=$SymbolName }
|
||||
if ($Kind -and $Kind -ne 'any') { $q.kind = $Kind }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol/search' -Query $q
|
||||
} else {
|
||||
$parameters = @{}
|
||||
if ($Kind -and $Kind -ne 'any') { $parameters.kind = $Kind }
|
||||
$body = @{ queryType='findsymbol'; symbolName=$SymbolName }
|
||||
if ($parameters.Count -gt 0) { $body.parameters = $parameters }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'build' {
|
||||
if (-not $ProjectName) { Write-Err 'build requires -ProjectName'; exit 2 }
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ projectName=$ProjectName }
|
||||
if ($Configuration) { $q.configuration = $Configuration }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/build' -Query $q
|
||||
} else {
|
||||
$body = @{ queryType='buildproject'; projectName=$ProjectName }
|
||||
if ($Configuration) { $body.configuration = $Configuration }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'package-add' {
|
||||
if (-not $ProjectName -or -not $PackageName) { Write-Err 'package-add requires -ProjectName and -PackageName'; exit 2 }
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ projectName=$ProjectName; packageName=$PackageName }
|
||||
if ($Version) { $q.version = $Version }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/package/add' -Query $q
|
||||
} else {
|
||||
$body = @{ queryType='addnugetpackage'; projectName=$ProjectName; packageName=$PackageName }
|
||||
if ($Version) { $body.version = $Version }
|
||||
$resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'history' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history'
|
||||
} else {
|
||||
Write-Err 'history is only available via Web API (port 5001).'; exit 2
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'history-recent' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$q = @{ count = $Count }
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/recent' -Query $q
|
||||
} else {
|
||||
Write-Err 'history-recent is only available via Web API (port 5001).'; exit 2
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'history-stats' {
|
||||
if ($mode -eq 'web' -or $mode -eq 'explicit') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/stats'
|
||||
} else {
|
||||
Write-Err 'history-stats is only available via Web API (port 5001).'; exit 2
|
||||
}
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'plugin-health' {
|
||||
$pluginBase = if ($BaseUrl) { $BaseUrl } else { 'http://localhost:59123' }
|
||||
$resp = Invoke-PluginApi -BaseUrl $pluginBase -Path '/health' -Body '{}'
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
}
|
||||
'solution-overview' {
|
||||
if ($mode -ne 'web' -and $mode -ne 'explicit') {
|
||||
Write-Err 'solution-overview requires the Web API (port 5001).'; exit 2
|
||||
}
|
||||
$q = @{ topNProjects=$Top; includeNamespaces=$IncludeNamespaces; format=$Format }
|
||||
# For text/yaml formats, we want the raw response as-is
|
||||
if ($Format -eq 'json') {
|
||||
$resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview/summary' -Query $q
|
||||
Write-Output (Get-JsonOutput -Data $resp)
|
||||
} else {
|
||||
# Build query string properly
|
||||
$qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
|
||||
$qs['topNProjects'] = [string]$Top
|
||||
$qs['includeNamespaces'] = [string]$IncludeNamespaces
|
||||
$qs['format'] = $Format
|
||||
$uri = [System.UriBuilder]::new("$base/api/roslyn/solution/overview/summary")
|
||||
$uri.Query = $qs.ToString()
|
||||
$content = (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
|
||||
Write-Output $content
|
||||
}
|
||||
}
|
||||
default {
|
||||
Write-Err "Unknown command: $Command"; exit 2
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$msg = $_.Exception.Message
|
||||
$respProp = $_.Exception.PSObject.Properties['Response']
|
||||
if ($respProp -and $respProp.Value) {
|
||||
try {
|
||||
$resp = $respProp.Value
|
||||
$stream = $resp.GetResponseStream()
|
||||
if ($stream) {
|
||||
$reader = New-Object System.IO.StreamReader($stream)
|
||||
$content = $reader.ReadToEnd()
|
||||
if ($content) { $msg = "$msg`n$content" }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
Write-Err $msg
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user