diff --git a/.gitea/workflows/build-cutlist.yml b/.gitea/workflows/build-cutlist.yml new file mode 100644 index 0000000..61a235e --- /dev/null +++ b/.gitea/workflows/build-cutlist.yml @@ -0,0 +1,28 @@ +name: Build CutList image + +on: + push: + branches: [master] + paths: + - "CutList.Web/**" + - "CutList.Core/**" + - "CutList.Web/Dockerfile" + - ".gitea/workflows/build-cutlist.yml" + workflow_dispatch: {} + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Log in to Gitea container registry + run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.thecozycat.net -u "${{ gitea.actor }}" --password-stdin + + - name: Build image + run: docker build -f CutList.Web/Dockerfile -t git.thecozycat.net/${{ gitea.repository_owner }}/cutlist:latest -t git.thecozycat.net/${{ gitea.repository_owner }}/cutlist:${{ gitea.sha }} . + + - name: Push image + run: | + docker push git.thecozycat.net/${{ gitea.repository_owner }}/cutlist:latest + docker push git.thecozycat.net/${{ gitea.repository_owner }}/cutlist:${{ gitea.sha }} diff --git a/.gitignore b/.gitignore index 3a2238d..74a780c 100644 --- a/.gitignore +++ b/.gitignore @@ -242,4 +242,9 @@ ModelManifest.xml .paket/paket.exe # FAKE - F# Make -.fake/ \ No newline at end of file +.fake/ + +# AlroCatalog scraper output +scripts/AlroCatalog/__pycache__/ +scripts/AlroCatalog/pdfs/ +scripts/AlroCatalog/screenshots/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index b2795ae..88f20ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,15 +8,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co CutList is a 1D bin packing optimization application that helps users optimize material cutting. It calculates efficient bin packing solutions to minimize waste when cutting stock materials into required parts. -The solution contains three projects: +The solution contains four projects: | Project | Framework | Purpose | |---------|-----------|---------| | **CutList** | .NET 8.0 Windows Forms | Original desktop UI (MVP pattern) | | **CutList.Core** | .NET 8.0 Class Library | Domain models and packing algorithms (platform-agnostic) | -| **CutList.Web** | .NET 8.0 Blazor Server | Web-based UI with EF Core + SQL Server | +| **CutList.Web** | .NET 8.0 Blazor Server | Web-based UI + REST API, EF Core + SQL Server | +| **CutList.Mcp** | .NET 10.0 Console (stdio) | MCP server exposing CutList.Web's REST API as tools for Claude | -**Key Dependencies**: Math-Expression-Evaluator (input parsing), Newtonsoft.Json (serialization), Entity Framework Core (data access), Bootstrap 5 + Bootstrap Icons (UI) +**Key Dependencies**: Math-Expression-Evaluator (input parsing), Newtonsoft.Json (serialization), Entity Framework Core (data access), Bootstrap 5 + Bootstrap Icons (UI), ModelContextProtocol SDK (CutList.Mcp) ## Build Commands @@ -28,10 +29,11 @@ dotnet build CutList.sln dotnet build CutList/CutList.csproj dotnet build CutList.Core/CutList.Core.csproj dotnet build CutList.Web/CutList.Web.csproj +dotnet build CutList.Mcp/CutList.Mcp.csproj # Run applications dotnet run --project CutList/CutList.csproj # WinForms -dotnet run --project CutList.Web/CutList.Web.csproj # Blazor +dotnet run --project CutList.Web/CutList.Web.csproj # Blazor + REST API (default http://localhost:5270) # EF Core migrations (always apply immediately after creating) dotnet ef migrations add --project CutList.Web @@ -41,6 +43,18 @@ dotnet ef database update --project CutList.Web dotnet clean CutList.sln ``` +### Deploying CutList.Web as a Windows Service + +```powershell +powershell -ExecutionPolicy Bypass -File scripts/Deploy-CutListWeb.ps1 -ServiceName CutListWeb -InstallDir C:\Services\CutListWeb -Urls "http://*:5270" -OpenFirewall +``` + +Publishes, (re)creates the `CutListWeb` Windows service with auto-restart recovery, and optionally opens the firewall port. See `docs/deploy-script-guide.md` (global docs) for the template this follows. + +### Publishing CutList.Mcp + +CutList.Mcp is an stdio MCP server, not a hosted service — it's published to `~/.claude/mcp/CutList.Mcp/` and registered in `~/.claude/settings.local.json` (see global `CLAUDE.md` MCP Server Publishing table). It talks to CutList.Web's REST API at `http://localhost:5270`, so CutList.Web must be running (dev `dotnet run` or the deployed Windows service) for the MCP tools to work. + ## Architecture ### CutList.Core — Domain & Algorithms @@ -73,11 +87,25 @@ dotnet clean CutList.sln - `CutListService` bridges UI models to core packing algorithms - `DocumentService` handles JSON file persistence -### CutList.Web (Blazor Server) — Web UI +### CutList.Web (Blazor Server) — Web UI + REST API **Database**: SQL Server via Entity Framework Core (connection string: `DefaultConnection`) -**Service Registration** (Program.cs): All services registered as Scoped — MaterialService, SupplierService, StockItemService, JobService, CutListPackingService, ReportService, PurchaseItemService +**Service Registration** (Program.cs): All services registered as Scoped — MaterialService, SupplierService, StockItemService, JobService, CutListPackingService, ReportService, PurchaseItemService, CatalogService. `IDbContextFactory` is used (not a scoped `DbContext` directly) for Blazor Server circuit safety. + +**REST API** (`Controllers/`): `JobsController`, `MaterialsController`, `StockItemsController`, `SuppliersController`, `CuttingToolsController`, `PackingController`, `CatalogController` — Swagger/OpenAPI enabled in Development. This API is the integration surface `CutList.Mcp` calls into; the Blazor UI talks to the services directly and does not go through it. + +**Error handling**: `UseExceptionHandler("/Error", ...)` in non-Development environments routes to `Components/Pages/Error.razor`. + +### CutList.Mcp — MCP Server + +Stdio-transport MCP server (`ModelContextProtocol` SDK) exposing CutList.Web's REST API as tools for Claude Code. Registers tools via `WithToolsFromAssembly`; logging is disabled entirely so it doesn't interfere with the stdio transport. + +- `ApiClient.cs` — typed `HttpClient` wrapper for CutList.Web's REST API (`BaseAddress` hardcoded to `http://localhost:5270`) +- `JobTools.cs` — job CRUD, parts/stock, optimization (`OptimizeJob`), cutting tools +- `InventoryTools.cs` — suppliers, materials, stock items, supplier offerings +- `CutListTools.cs` — static helpers shared across tool classes +- `Models.cs` — shared DTOs distinct from CutList.Web's own DTOs (kept intentionally thin for MCP tool responses) ## CutList.Web Entities @@ -166,6 +194,10 @@ Abstract base with TPC (Table Per Concrete type) mapping — each shape gets its ### ReportService - `FormatLength(inches)`, `GroupItems(items)` for print report formatting +### CatalogService +- `ExportAsync()` — dumps active suppliers, cutting tools, and materials (with dimensions + stock items + supplier offerings) into a shape-grouped `CatalogData` DTO for bulk export/import tooling +- Backs the `CatalogController` REST endpoint and the `scripts/ExportData` / `scripts/AlroCatalog` data-loading workflows + ## CutList.Web Pages | Route | Page | Purpose | @@ -183,6 +215,7 @@ Abstract base with TPC (Table Per Concrete type) mapping — each shape gets its | `/suppliers` | Suppliers/Index | Supplier list with CRUD | | `/suppliers/{Id}` | Suppliers/Edit | Supplier + offerings management | | `/tools` | Tools/Index | Cutting tools CRUD | +| `/Error` | Error | Unhandled exception page (registered via `UseExceptionHandler`) | ## Shared Components @@ -208,6 +241,13 @@ Abstract base with TPC (Table Per Concrete type) mapping — each shape gets its - **Timestamps** — `CreatedAt` defaults to `GETUTCDATE()`; `UpdatedAt` set on modifications - **Collections** — Encapsulated in Core; use `AsReadOnly()`, access via `Add*` methods - **Priority system** — Lower priority bins used first in packing algorithm +- **UI ↔ MCP split** — The Blazor UI calls services directly (in-process); CutList.Mcp and any other external integration go through the REST API in `Controllers/`. Keep both paths in sync when changing service method signatures used by controllers. + +## Supporting Scripts (`scripts/`) + +- `Deploy-CutListWeb.ps1` — publishes and installs CutList.Web as a Windows Service (see Build Commands above) +- `ExportData/` — standalone console project that exercises `CutList.Web`'s data layer to import/export catalog seed data (e.g. `Data/SeedData/oneals-catalog.json`) +- `AlroCatalog/` — Python scraper (`scrape_alro.py`) for pulling material/size/grade data from the Alro Steel SmartGrid site into a JSON catalog for import; see `SCRAPE_PLAN.md` for scraper status and cascading-dropdown navigation notes. Not part of the .NET build. ## Key Files @@ -221,4 +261,6 @@ Abstract base with TPC (Table Per Concrete type) mapping — each shape gets its | `CutList.Web/Services/JobService.cs` | Job orchestration (CRUD, parts, stock, tools, lock/unlock) | | `CutList.Web/Services/CutListPackingService.cs` | Bridges web entities to Core packing engine | | `CutList.Web/Components/Pages/Jobs/Edit.razor` | Job editor (tabbed: Details, Parts, Stock, Results) | +| `CutList.Mcp/ApiClient.cs` | HTTP client the MCP server uses to call CutList.Web's REST API | +| `CutList.Mcp/Program.cs` | MCP server entrypoint (stdio transport, tool registration) | | `CutList/Presenters/MainFormPresenter.cs` | WinForms business logic orchestrator | diff --git a/CutList.Web/Components/Pages/Jobs/Edit.razor b/CutList.Web/Components/Pages/Jobs/Edit.razor index 4dfeb3f..25704bb 100644 --- a/CutList.Web/Components/Pages/Jobs/Edit.razor +++ b/CutList.Web/Components/Pages/Jobs/Edit.razor @@ -1172,7 +1172,7 @@ else @foreach (var item in entry.Bin.Items) { - + @(string.IsNullOrWhiteSpace(item.Name) ? ArchUnits.FormatFromInches(item.Length) : $"{item.Name} ({ArchUnits.FormatFromInches(item.Length)})") } diff --git a/CutList.Web/Dockerfile b/CutList.Web/Dockerfile new file mode 100644 index 0000000..1162ae1 --- /dev/null +++ b/CutList.Web/Dockerfile @@ -0,0 +1,27 @@ +# Build stage +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Copy solution and project files for restore +COPY CutList.sln . +COPY CutList.Web/CutList.Web.csproj CutList.Web/ +COPY CutList.Core/CutList.Core.csproj CutList.Core/ +RUN dotnet restore CutList.Web/CutList.Web.csproj + +# Copy everything else and build +COPY . . +WORKDIR /src/CutList.Web +RUN dotnet publish -c Release -o /app/publish + +# Runtime stage +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app + +COPY --from=build /app/publish . + +EXPOSE 5270 + +ENV ASPNETCORE_URLS=http://+:5270 +ENV ASPNETCORE_ENVIRONMENT=Production + +ENTRYPOINT ["dotnet", "CutList.Web.dll"]