12 KiB
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
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-openaiclient instance per tier, configured withBaseURLpointing to oMLX (http://localhost:PORT/v1) for Tier 1, andhttps://openrouter.ai/api/v1for 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/serialwith 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*; useserial.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 handlehw_id,condition,quality_gateetc. - 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 — oMLX feature set, Gemma 4 VLM support confirmed (HIGH confidence)
- github.com/netbox-community/go-netbox — Official Go client, v4 confirmed (HIGH confidence)
- go.bug.st/serial pkg.go.dev — Active maintenance confirmed, preferred over tarm/serial (HIGH confidence)
- 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)
Conventions
Conventions not yet established. Will populate as patterns emerge during development.
Architecture
Architecture not yet mapped. Follow existing patterns found in the codebase.
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 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-quickfor small fixes, doc updates, and ad-hoc tasks/gsd-debugfor investigation and bug fixing/gsd-execute-phasefor planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
Developer Profile
Profile not yet configured. Run
/gsd-profile-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.