homelabby/CLAUDE.md

157 lines
12 KiB
Markdown

<!-- GSD:project-start source:PROJECT.md -->
## Project
**HWLab**
HWLab is a self-hosted, AI-powered hardware inventory management system for homelab environments. It combines local multimodal AI (Gemma 4 via oMLX) for photo-based intake and categorization, NetBox as the authoritative inventory database, and automated label printing with integrated cable testing workflows. Built with Go + React, running entirely on a Mac Mini M4.
**Core Value:** Any physical item can be cataloged by uploading a photo — AI extracts data, creates a NetBox record, and prints a QR-coded label — with zero manual data entry for 80-90% of items.
### Constraints
- **Hardware**: Mac Mini M4 with 16GB — Gemma 4 model must fit in memory (E4B confirmed, 26B A4B needs testing with TurboQuant)
- **No cloud dependency**: Standard operations must work fully local — OpenRouter only for Tier 2/3 AI
- **NetBox is source of truth**: HWLab stores no inventory data locally (only advisor chat history + config in SQLite)
- **USB protocols**: Printer and tester protocols need reverse-engineering once hardware arrives
- **Tech stack**: Go backend, React TypeScript frontend, Tailwind CSS
<!-- GSD:project-end -->
<!-- GSD:stack-start source:research/STACK.md -->
## Technology Stack
## Recommended Stack
### Core Technologies
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Go | 1.22+ | Backend API, USB/serial control, AI orchestration | Native concurrency for USB polling + HTTP serving; single binary deployment; no runtime dependency; best-in-class serial I/O on macOS |
| React | 18.x | SPA frontend | Mature ecosystem, hooks-based state management, excellent TypeScript support; right level of complexity for a single-operator tool |
| TypeScript | 5.x | Frontend type safety | Catches API contract drift early; NetBox has complex nested types that benefit from strict typing |
| Tailwind CSS | 3.x | Utility-first styling | ClickHouse-inspired design system maps well to utility classes; no CSS file sprawl; dark theme with arbitrary values is trivial |
| SQLite (via mattn/go-sqlite3) | 3.x / v1.14+ | Local-only storage (chat history, config, session state) | Zero-dependency embedded DB; perfectly suited for single-operator append-heavy chat logs; no separate process |
| oMLX | latest | Local LLM inference server on Mac Mini M4 | Native Apple Silicon MLX backend, OpenAI-compatible API endpoint, paged SSD KV caching, continuous batching; supports Gemma 4 VLM natively; drop-in for any OpenAI-SDK client |
### Go Backend Libraries
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| github.com/netbox-community/go-netbox/v4 | v4.x | NetBox REST API client | All CRUD against NetBox — officially maintained, generated from NetBox OpenAPI spec |
| go.bug.st/serial | v1.x | USB serial communication | Printer + cable tester I/O; actively maintained, cross-platform, better API than tarm/serial; supports port enumeration |
| github.com/gin-gonic/gin | v1.9+ | HTTP router and middleware | Fast, well-documented, large middleware ecosystem; handles file uploads for photo intake cleanly |
| github.com/mattn/go-sqlite3 | v1.14+ | SQLite driver (cgo) | Standard Go SQLite driver; use with database/sql for portability |
| github.com/jmoiern/sqlx | v1.3+ | database/sql extension | Named params and struct scanning reduce boilerplate for chat history queries |
| github.com/sashabaranov/go-openai | v1.x | OpenAI-compatible client | Handles both oMLX local endpoint and OpenRouter remote; single client, swap base URL per tier |
| github.com/skip2/go-qrcode | v0.0.0 | QR code generation | Generate HW-XXXXX QR codes server-side before sending to printer |
| github.com/google/uuid | v1.x | UUID generation | HW-XXXXX ID assignment; deterministic or random depending on design choice |
| github.com/spf13/viper | v1.x | Config management | YAML/env config for AI endpoints, NetBox URL/token, USB port paths |
### Frontend Libraries
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| Vite | 5.x | Build tool and dev server | Fastest HMR in class; Go backend runs separately, Vite proxies API calls in dev |
| TanStack Query (React Query) | v5 | Server state management | NetBox data is server state not client state; handles caching, refetch, stale-while-revalidate |
| TanStack Router | v1 | Type-safe routing | Better TypeScript integration than React Router v6 for complex nested routes |
| Zustand | v4 | Client state (UI state, chat) | Lightweight, no boilerplate, perfect for chat message buffer and USB device status |
| shadcn/ui | latest | Component primitives | Radix UI + Tailwind, fully owned code (not a dependency), dark mode first, ClickHouse aesthetic compatible |
| react-dropzone | v14 | Photo drag-and-drop | Standard library for file upload UX; works with multipart form to Go backend |
| react-hot-toast | v2 | Toast notifications | Inventory actions (label printed, test passed) need lightweight non-blocking feedback |
| lucide-react | latest | Icon set | Consistent, tree-shakeable, TypeScript-native, pairs well with shadcn/ui |
### Development Tools
| Tool | Purpose | Notes |
|------|---------|-------|
| Air (cosmtrek/air) | Go hot reload in development | Watch + rebuild backend on save; configure alongside Vite proxy |
| golangci-lint | Go linting | Run in CI; catches serial/concurrent code issues early |
| ESLint + typescript-eslint | Frontend linting | Strict TypeScript rules; catch NetBox API type drift |
| Prettier | Frontend formatting | Single config, no debates |
| Bruno (or httpie) | API testing | Test NetBox integration and Go endpoints locally without Postman account |
## Installation
# Go backend — initialize module
# Core Go dependencies
# Frontend — scaffold
# Frontend core dependencies
# shadcn/ui init (run in frontend dir)
# Dev dependencies
## Alternatives Considered
| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| go.bug.st/serial | tarm/serial | tarm/serial is adequate if you only need basic read/write and already have it; go.bug.st/serial is preferred for new projects due to active maintenance and port listing |
| gin-gonic/gin | chi, echo, fiber | chi if you want stdlib-compatible handlers; echo for similar feature set; fiber if you need extreme HTTP throughput (not relevant here) |
| go-openai (sashabaranov) | Custom HTTP client | Custom client only if you need streaming SSE to browser — go-openai supports streaming too, prefer it |
| TanStack Router | React Router v6 | React Router v6 is fine if team is already familiar; TanStack Router has stronger TypeScript guarantees for route params |
| TanStack Query | SWR | SWR is lighter but TanStack Query has better devtools and mutation handling for NetBox writes |
| mattn/go-sqlite3 (cgo) | modernc.org/sqlite (pure Go) | Use modernc.org/sqlite if you want zero cgo dependency (simpler cross-compilation); slightly slower but avoids gcc requirement |
| oMLX | Ollama | Ollama works but oMLX has better Apple Silicon performance via MLX backend; Gemma 4 VLM continuous batching support is oMLX-specific |
## What NOT to Use
| Avoid | Why | Use Instead |
|-------|-----|-------------|
| GORM | Heavy ORM abstraction for what is essentially two tables (chat logs, config); migrations and reflection overhead not worth it for SQLite local-only store | database/sql + sqlx directly |
| tarm/serial | Last commit 2018, unmaintained, no port enumeration, open issues unaddressed | go.bug.st/serial |
| Redux (React) | Massive boilerplate for a solo-operator SPA; overkill when TanStack Query handles server state | Zustand + TanStack Query |
| axios | Unnecessary HTTP client abstraction; TanStack Query works with native fetch; one fewer dependency | fetch API directly inside TanStack Query |
| Next.js | SSR/SSG overhead not needed — Go handles the API, React is a pure SPA served as static files | Vite + React SPA |
| llama.cpp Go bindings | Not optimized for Apple Silicon; MLX backend (oMLX) is 2-4x faster on M-series; cgo binding complexity | oMLX via OpenAI-compatible HTTP |
| Direct NetBox HTTP calls (no client) | NetBox v4 API has 200+ endpoints; go-netbox generates typed structs from OpenAPI spec — manual HTTP is brittle | go-netbox/v4 |
## Stack Patterns by Variant
- Use a single `go-openai` client instance per tier, configured with `BaseURL` pointing to oMLX (`http://localhost:PORT/v1`) for Tier 1, and `https://openrouter.ai/api/v1` for Tier 2/3
- Switch by returning the appropriate client from a factory based on model config
- Do NOT hardcode tier selection in business logic — make it config-driven from the start
- Use `go.bug.st/serial` with a dedicated goroutine per device, communicating via channels
- Do not share a serial.Port across goroutines — open one port per device, serialize writes in the owning goroutine
- USB device paths on macOS follow `/dev/cu.usbserial-*` or `/dev/cu.usbmodem*`; use `serial.GetPortsList()` to enumerate at startup
- Accept multipart/form-data in Gin; decode image in Go; send as base64 in the OpenAI vision API call to oMLX
- Store the original photo in a local temp directory only until the NetBox record is created; do not persist photos in HWLab itself
- Gemma 4 E4B fits comfortably in 16GB; Gemma 4 26B A4B needs TurboQuant KV offload — benchmark before committing to 26B
- go-netbox v4 represents custom fields as `map[string]interface{}`; define typed wrapper structs in your own code to handle `hw_id`, `condition`, `quality_gate` etc.
- Custom fields must be created in NetBox admin before the Go code can write them — treat this as infrastructure provisioning (Phase 1 task)
## Version Compatibility
| Package | Compatible With | Notes |
|---------|-----------------|-------|
| go-netbox/v4 | NetBox 4.x | Generated from NetBox 4.x OpenAPI; not compatible with NetBox 3.x API responses |
| mattn/go-sqlite3 v1.14+ | Go 1.21+ | Requires cgo; needs gcc or Xcode CLT on macOS |
| shadcn/ui | React 18, Tailwind 3.x | shadcn v2+ requires Tailwind 3.x; not yet tested with Tailwind 4 alpha |
| oMLX | macOS 15+, Apple Silicon M1+ | Won't run on Intel Mac or Linux |
| go-openai v1.x | OpenAI API v1, OpenRouter, oMLX | Any OpenAI-compatible endpoint works; set `BaseURL` in `ClientConfig` |
## Sources
- [github.com/jundot/omlx](https://github.com/jundot/omlx) — oMLX feature set, Gemma 4 VLM support confirmed (HIGH confidence)
- [github.com/netbox-community/go-netbox](https://github.com/netbox-community/go-netbox) — Official Go client, v4 confirmed (HIGH confidence)
- [go.bug.st/serial pkg.go.dev](https://pkg.go.dev/go.bug.st/serial) — Active maintenance confirmed, preferred over tarm/serial (HIGH confidence)
- [omlx.ai](https://omlx.ai/) — macOS 15+ requirement, 16GB minimum RAM, MLX backend (HIGH confidence)
- WebSearch: tarm/serial maintenance status — last substantive commit 2018, multiple forks active (MEDIUM confidence)
- Training data: gin, TanStack Query, Zustand, Vite, shadcn/ui versions — standard 2025 React/Go stack (MEDIUM confidence, verify pinned versions before first install)
<!-- GSD:stack-end -->
<!-- GSD:conventions-start source:CONVENTIONS.md -->
## Conventions
Conventions not yet established. Will populate as patterns emerge during development.
<!-- GSD:conventions-end -->
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
## Architecture
Architecture not yet mapped. Follow existing patterns found in the codebase.
<!-- GSD:architecture-end -->
<!-- GSD:skills-start source:skills/ -->
## Project Skills
No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, or `.github/skills/` with a `SKILL.md` index file.
<!-- GSD:skills-end -->
<!-- GSD:workflow-start source:GSD defaults -->
## GSD Workflow Enforcement
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks
- `/gsd-debug` for investigation and bug fixing
- `/gsd-execute-phase` for planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
<!-- GSD:workflow-end -->
<!-- GSD:profile-start -->
## Developer Profile
> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile.
> This section is managed by `generate-claude-profile` -- do not edit manually.
<!-- GSD:profile-end -->