.DEFAULT_GOAL := help VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") LDFLAGS := -X main.version=$(VERSION) \ -X main.commit=$(COMMIT) \ -X main.buildDate=$(BUILD_DATE) LDFLAGS_RELEASE := $(LDFLAGS) -s -w DESKTOP_DIST_DIR := dist/desktop GOLANGCI_LINT_VERSION ?= v2.11.4 # Isolate each checkout from stale sibling-worktree fixes and issue positions. GOLANGCI_LINT_CACHE ?= $(CURDIR)/.golangci-cache export GOLANGCI_LINT_CACHE CUSTOM_GCL := ./custom-gcl PRICING_SNAPSHOT_FILE := internal/pricing/snapshot/litellm_snapshot.json.gz # sqlite-vec's cgo bindings #include "sqlite3.h". Without an override the # compiler falls back to the system header (the macOS SDK one marks # sqlite3_auto_extension deprecated, warning on every build) which can also # drift from the amalgamation mattn/go-sqlite3 statically links. Compile # against the bundled amalgamation's own header instead, mirroring the # Linux release workflow in .github/workflows/release.yml. # Setting CGO_CFLAGS replaces Go's built-in default of "-O2 -g", so restate # it explicitly or the SQLite amalgamation compiles unoptimized (2-3x slower # queries, caught by the bench gate). Caller-provided flags stay last so # they can still override. SQLITE_INCLUDE_DIR := .sqlite-include export CGO_CFLAGS := -O2 -g -I$(CURDIR)/$(SQLITE_INCLUDE_DIR) $(CGO_CFLAGS) GOPATH_FIRST := $(shell go env GOPATH | cut -d: -f1) AIR_BIN := $(shell if command -v air >/dev/null 2>&1; then command -v air; \ elif [ -n "$$(go env GOBIN)" ] && [ -x "$$(go env GOBIN)/air" ]; then printf "%s" "$$(go env GOBIN)/air"; \ elif [ -x "$(GOPATH_FIRST)/bin/air" ]; then printf "%s" "$(GOPATH_FIRST)/bin/air"; \ fi) .PHONY: build build-release install frontend frontend-dev dev check-air air-install desktop-dev desktop-build desktop-macos-app desktop-macos-dmg desktop-windows-installer desktop-linux-appimage desktop-app docs-install docs-build docs-serve docs-check docs-screenshots docs-assets-branch docs-generated-assets-branch docs-deploy-staging docs-deploy test test-short test-evalingest bench-backends bench-gate bench-gate-config test-postgres test-postgres-ci test-s3 postgres-up postgres-down test-ssh test-ssh-ci ssh-up ssh-down e2e e2e-duckdb vet lint lint-ci lint-golangci lint-golangci-ci nilaway nilaway-golangci-build lint-tools tidy clean release release-darwin-arm64 release-darwin-amd64 release-linux-amd64 install-hooks ensure-embed-dir pricing-snapshot sqlite-vec-header dev-snapshot help # Ensure go:embed has at least one file (no-op if frontend is built) ensure-embed-dir: @mkdir -p internal/web/dist @test -f internal/web/dist/.keep \ || printf '%s\n' \ 'keep embed dir for generated frontend assets' \ > internal/web/dist/.keep # Copy the go-sqlite3 amalgamation header where CGO_CFLAGS points. # Chained from pricing-snapshot so every Go-compiling target stages it # without lengthening each prerequisite list. sqlite-vec-header: @go mod download github.com/mattn/go-sqlite3 @mkdir -p $(SQLITE_INCLUDE_DIR) @install -m 0644 \ "$$(go list -m -f '{{.Dir}}' github.com/mattn/go-sqlite3)/sqlite3-binding.h" \ $(SQLITE_INCLUDE_DIR)/sqlite3.h # Restore the generated LiteLLM fallback snapshot from its artifact branch. # Pinned ref, SHA256, and branch are compiled into the snapshot tool. pricing-snapshot: sqlite-vec-header go run ./internal/pricing/cmd/litellm-snapshot -restore # Build the binary (debug, with embedded pricing snapshot and frontend) build: pricing-snapshot frontend CGO_ENABLED=1 go build -tags fts5 -ldflags="$(LDFLAGS)" -o agentsview ./cmd/agentsview @chmod +x agentsview # Build with optimizations (release) build-release: pricing-snapshot frontend CGO_ENABLED=1 go build -tags fts5 -ldflags="$(LDFLAGS_RELEASE)" -trimpath -o agentsview ./cmd/agentsview @chmod +x agentsview # Install to ~/.local/bin, $GOBIN, or $GOPATH/bin. # Copy to a temp file in the destination directory, then rename into place. # Rename is atomic and produces a fresh inode, so overwriting the binary while # an old agentsview is still running does not leave the kernel validating exec # against stale code-signature pages (which SIGKILLs the new process on macOS). install: build-release @if [ -d "$(HOME)/.local/bin" ]; then \ INSTALL_DIR="$(HOME)/.local/bin"; \ else \ INSTALL_DIR="$${GOBIN:-$$(go env GOBIN)}"; \ if [ -z "$$INSTALL_DIR" ]; then \ GOPATH_FIRST="$$(go env GOPATH | cut -d: -f1)"; \ INSTALL_DIR="$$GOPATH_FIRST/bin"; \ fi; \ mkdir -p "$$INSTALL_DIR"; \ fi; \ echo "Installing to $$INSTALL_DIR/agentsview"; \ tmp="$$(mktemp "$$INSTALL_DIR/agentsview.tmp.XXXXXX")" || exit $$?; \ cleanup() { rm -f "$$tmp"; }; \ trap cleanup EXIT HUP INT TERM; \ cp agentsview "$$tmp" && chmod 755 "$$tmp" && mv -f "$$tmp" "$$INSTALL_DIR/agentsview"; \ status=$$?; \ trap - EXIT HUP INT TERM; \ cleanup; \ exit $$status # Build frontend SPA and copy into embed directory frontend: cd frontend && npm ci && npm run build rm -rf internal/web/dist cp -r frontend/dist internal/web/dist printf '%s\n' \ 'keep embed dir for generated frontend assets' \ > internal/web/dist/.keep # Run Vite+ dev server (use alongside `make dev`) frontend-dev: cd frontend && npm run dev # Build and run agentsview against a fresh snapshot of the prod SQLite DB. # Prod DB is never written; sqlite3 .backup is WAL-safe even with prod running. # Prod config.toml is NOT copied, so remote PG push is disabled in the snapshot. # Overrides: # PROD_DATA_DIR - source data dir (default: $$HOME/.agentsview) # SNAPSHOT_DIR - destination dir (default: tmp/prod-snapshot) # RESNAPSHOT=0 - reuse existing snapshot instead of re-cloning PROD_DATA_DIR ?= $(HOME)/.agentsview SNAPSHOT_DIR ?= tmp/prod-snapshot # Resolve SNAPSHOT_DIR so relative and absolute paths both work. SNAPSHOT_ABS := $(abspath $(SNAPSHOT_DIR)) # Sentinel file written into a snapshot directory the first time # we populate it. Subsequent runs require this marker before # touching any contents, so pointing SNAPSHOT_DIR at an # unrelated existing directory cannot accidentally delete # someone's files. The previous version used rm -rf on the # whole directory; that left dangerous edge cases (e.g. # SNAPSHOT_DIR=tmp wiping unrelated tmp/ contents) even with a # denylist, so we now only ever delete a small set of files we # know we wrote. SNAPSHOT_MARKER := .agentsview-snapshot dev-snapshot: build @if [ ! -f "$(PROD_DATA_DIR)/sessions.db" ]; then \ echo "error: prod sessions.db not found at $(PROD_DATA_DIR)/sessions.db" >&2; \ exit 1; \ fi @if [ -z "$(SNAPSHOT_ABS)" ]; then \ echo "error: SNAPSHOT_DIR resolved to empty path" >&2; \ exit 1; \ fi @if [ -d "$(SNAPSHOT_ABS)" ] && [ ! -f "$(SNAPSHOT_ABS)/$(SNAPSHOT_MARKER)" ]; then \ if [ -n "$$(ls -A "$(SNAPSHOT_ABS)" 2>/dev/null)" ]; then \ echo "error: $(SNAPSHOT_ABS) is non-empty and missing the $(SNAPSHOT_MARKER) marker" >&2; \ echo " refusing to touch a directory we did not create" >&2; \ echo " remove the directory manually if you want to reuse this path" >&2; \ exit 1; \ fi; \ fi @mkdir -p "$(SNAPSHOT_ABS)" @touch "$(SNAPSHOT_ABS)/$(SNAPSHOT_MARKER)" @if [ "$${RESNAPSHOT:-1}" = "1" ] || [ ! -f "$(SNAPSHOT_ABS)/sessions.db" ]; then \ echo "Snapshotting $(PROD_DATA_DIR)/sessions.db -> $(SNAPSHOT_ABS)/sessions.db"; \ for f in sessions.db sessions.db-wal sessions.db-shm \ config.toml config.json config.json.bak \ debug.log; do \ rm -f "$(SNAPSHOT_ABS)/$$f"; \ done; \ sqlite3 "$(PROD_DATA_DIR)/sessions.db" \ ".backup $(SNAPSHOT_ABS)/sessions.db"; \ else \ echo "Reusing existing snapshot at $(SNAPSHOT_ABS)/sessions.db"; \ fi AGENTSVIEW_DATA_DIR="$(SNAPSHOT_ABS)" ./agentsview serve --port 0 # Ensure air is installed for backend live reload check-air: @if [ -z "$(AIR_BIN)" ]; then \ echo "air not found. Install with: make air-install" >&2; \ exit 1; \ fi # Install air for backend live reload air-install: go install github.com/air-verse/air@latest # Run Go server in dev mode with live reload (use with frontend-dev). # Edits to .go files trigger a rebuild + restart via air. dev: pricing-snapshot ensure-embed-dir check-air "$(AIR_BIN)" -c .air.toml -- $(ARGS) # Run the Tauri desktop wrapper in development mode desktop-dev: cd desktop && npm ci && npm run tauri:dev # Build desktop app bundles via Tauri desktop-build: cd desktop && npm ci && npm run tauri:build # Build only the macOS .app bundle (skip DMG packaging). # Skips updater artifact signing when TAURI_SIGNING_PRIVATE_KEY # is not set so local builds succeed without release keys. desktop-macos-app: cd desktop && npm ci && npm run tauri:build:macos-app \ $(if $(TAURI_SIGNING_PRIVATE_KEY),,-- --config '{"bundle":{"createUpdaterArtifacts":false}}') mkdir -p $(DESKTOP_DIST_DIR)/macos rm -rf $(DESKTOP_DIST_DIR)/macos/AgentsView.app cp -R desktop/src-tauri/target/release/bundle/macos/AgentsView.app \ $(DESKTOP_DIST_DIR)/macos/AgentsView.app @echo "macOS app bundle copied to $(DESKTOP_DIST_DIR)/macos/AgentsView.app" # Build macOS DMG installer desktop-macos-dmg: cd desktop && npm ci && npm run tauri:build:macos-dmg mkdir -p $(DESKTOP_DIST_DIR)/macos rm -f $(DESKTOP_DIST_DIR)/macos/*.dmg @dmg_count=$$(find desktop/src-tauri/target/release/bundle/dmg \ -maxdepth 1 -type f -name '*.dmg' | wc -l | tr -d ' '); \ if [ "$$dmg_count" -eq 0 ]; then \ echo "error: no DMG installer found in bundle output" >&2; \ exit 1; \ fi; \ find desktop/src-tauri/target/release/bundle/dmg \ -maxdepth 1 -type f -name '*.dmg' \ -exec cp {} $(DESKTOP_DIST_DIR)/macos/ \;; \ echo "Copied $$dmg_count DMG installer(s) to $(DESKTOP_DIST_DIR)/macos/" # Build Windows NSIS installer bundle (.exe) # Run on Windows runner/host. desktop-windows-installer: cd desktop && npm ci && npm run tauri:build:windows mkdir -p $(DESKTOP_DIST_DIR)/windows rm -f $(DESKTOP_DIST_DIR)/windows/*.exe @bundle_dir="desktop/src-tauri/target/release/bundle/nsis"; \ target_triple="$${TAURI_ENV_TARGET_TRIPLE:-$${CARGO_BUILD_TARGET:-}}"; \ if [ -z "$$target_triple" ] && command -v rustc >/dev/null 2>&1; then \ host_triple=$$(rustc -vV | awk '/^host: /{print $$2}'); \ if [ "$$host_triple" = "aarch64-pc-windows-msvc" ]; then \ target_triple="$$host_triple"; \ fi; \ fi; \ if [ -n "$$target_triple" ] && \ [ -d "desktop/src-tauri/target/$$target_triple/release/bundle/nsis" ]; then \ bundle_dir="desktop/src-tauri/target/$$target_triple/release/bundle/nsis"; \ fi; \ if [ ! -d "$$bundle_dir" ]; then \ echo "error: Windows bundle output directory not found: $$bundle_dir" >&2; \ exit 1; \ fi; \ exe_count=$$(find "$$bundle_dir" \ -maxdepth 1 -type f -name '*.exe' | wc -l | tr -d ' '); \ if [ "$$exe_count" -eq 0 ]; then \ echo "error: no Windows installer (.exe) found in $$bundle_dir" >&2; \ exit 1; \ fi; \ find "$$bundle_dir" \ -maxdepth 1 -type f -name '*.exe' \ -exec cp {} $(DESKTOP_DIST_DIR)/windows/ \;; \ echo "Copied $$exe_count Windows installer(s) to $(DESKTOP_DIST_DIR)/windows/" # Build Linux AppImage bundle # Run on a Linux host. desktop-linux-appimage: cd desktop && npm ci && npm run tauri:build:linux \ $(if $(TAURI_SIGNING_PRIVATE_KEY),,-- --config '{"bundle":{"createUpdaterArtifacts":false}}') cd desktop && bash scripts/repair-appimage-diricon.sh \ src-tauri/target/release/bundle/appimage/*.AppImage mkdir -p $(DESKTOP_DIST_DIR)/linux rm -f $(DESKTOP_DIST_DIR)/linux/*.AppImage @ai_count=$$(find desktop/src-tauri/target/release/bundle/appimage \ -maxdepth 1 -type f -name '*.AppImage' | wc -l | tr -d ' '); \ if [ "$$ai_count" -eq 0 ]; then \ echo "error: no AppImage found in bundle output" >&2; \ exit 1; \ fi; \ find desktop/src-tauri/target/release/bundle/appimage \ -maxdepth 1 -type f -name '*.AppImage' \ -exec cp {} $(DESKTOP_DIST_DIR)/linux/ \;; \ echo "Copied $$ai_count AppImage(s) to $(DESKTOP_DIST_DIR)/linux/" # Backward-compatible alias (macOS .app) desktop-app: desktop-macos-app # Run tests test: pricing-snapshot ensure-embed-dir go test -tags "fts5" ./... -v -count=1 # Run fast tests only test-short: pricing-snapshot ensure-embed-dir go test -tags "fts5" ./... -short -count=1 # Run the quarantined eval-ingest endpoint tests under their build tag. test-evalingest: pricing-snapshot ensure-embed-dir CGO_ENABLED=1 go test -tags "fts5,evalingest" \ ./internal/db ./internal/server -v -count=1 # Compare db.Store read-query performance across SQLite, DuckDB, and PostgreSQL. # Requires Docker because the PostgreSQL backend is started with testcontainers. BENCH_BACKENDS_FLAGS ?= -bench . -run '^$$' -benchmem BENCH_BACKENDS_SESSIONS ?= 1000 BENCH_BACKENDS_MESSAGES_PER_SESSION ?= 64 bench-backends: pricing-snapshot ensure-embed-dir AGENTSVIEW_BENCH_SESSIONS=$(BENCH_BACKENDS_SESSIONS) \ AGENTSVIEW_BENCH_MESSAGES_PER_SESSION=$(BENCH_BACKENDS_MESSAGES_PER_SESSION) \ CGO_ENABLED=1 go test -tags "fts5,benchdb" ./internal/backendbench $(BENCH_BACKENDS_FLAGS) # Hot-path benchmark gate. Runs every benchmark in the gated packages # (sync engine warm/cold/append, message write paths, usage # aggregation, secret scanning). This target is the single source of # truth for the gate configuration: CI's bench.yml runs it on both # the PR head and the merge base, then compares the outputs with # `go run ./cmd/benchgate -old old.txt -new new.txt`. Run it before # and after touching a sync or DB hot path. BENCH_GATE_PACKAGES ?= ./internal/sync ./internal/db ./internal/secrets # Count must stay >= 5: benchgate's time gate needs at least 5 # candidate samples for its significance test. BENCH_GATE_COUNT ?= 6 # Fixed iterations, not a duration: some gated benchmarks grow their # fixture as they iterate, so baseline and candidate must run the # same iteration count to measure identical workloads. BENCH_GATE_TIME ?= 20x bench-gate: pricing-snapshot ensure-embed-dir CGO_ENABLED=1 go test -tags "fts5" -run '^$$' \ -bench . -benchmem \ -count $(BENCH_GATE_COUNT) -benchtime $(BENCH_GATE_TIME) \ -timeout 25m $(BENCH_GATE_PACKAGES) # Prints the gate's sample/iteration configuration in shell-evalable # form. CI evaluates this on the PR head and passes the values into # the merge-base `make bench-gate` invocation, so both sides measure # identical workloads even when a PR changes the defaults above (the # package list intentionally stays per-side). bench-gate-config: @echo "BENCH_GATE_COUNT=$(BENCH_GATE_COUNT) BENCH_GATE_TIME=$(BENCH_GATE_TIME)" # Start test PostgreSQL container postgres-up: docker compose -f docker-compose.test.yml up -d --wait # Stop test PostgreSQL container postgres-down: docker compose -f docker-compose.test.yml down # Run PostgreSQL integration tests (starts postgres automatically) test-postgres: pricing-snapshot ensure-embed-dir postgres-up @echo "Waiting for postgres to be ready..." @sleep 2 TEST_PG_URL="postgres://agentsview_test:agentsview_test_password@localhost:5433/agentsview_test?sslmode=disable" \ CGO_ENABLED=1 go test -tags "fts5,pgtest" -v ./internal/postgres/... ./internal/activity/... -count=1 # PostgreSQL integration tests for CI (postgres already running as service) test-postgres-ci: pricing-snapshot ensure-embed-dir CGO_ENABLED=1 go test -tags "fts5,pgtest" -v ./internal/postgres/... ./internal/activity/... -count=1 # S3 discovery integration tests. testcontainers starts and tears down a # rustfs (S3-compatible) container automatically, so only a working Docker # daemon is required. test-s3: pricing-snapshot ensure-embed-dir CGO_ENABLED=1 go test -tags "fts5,s3test" -v ./internal/sync/... -run TestS3 -count=1 # Start test SSH container ssh-up: docker compose -f docker-compose.test.yml up -d --build --wait sshd docker cp "$$(docker compose -f docker-compose.test.yml ps -q sshd)":/tmp/test_ssh_key testdata/ssh/test_key chmod 600 testdata/ssh/test_key # Stop test SSH container ssh-down: docker compose -f docker-compose.test.yml down sshd # Run SSH integration tests (starts sshd automatically) test-ssh: pricing-snapshot ensure-embed-dir ssh-up TEST_SSH_HOST=localhost TEST_SSH_PORT=2222 TEST_SSH_USER=testuser \ TEST_SSH_KEY=$(CURDIR)/testdata/ssh/test_key \ CGO_ENABLED=1 go test -tags "fts5,sshtest" -v ./internal/ssh/... -count=1 # SSH integration tests for CI (sshd already running) test-ssh-ci: pricing-snapshot ensure-embed-dir CGO_ENABLED=1 go test -tags "fts5,sshtest" -v ./internal/ssh/... -count=1 # Run Playwright E2E tests e2e: cd frontend && npx playwright test # Run focused Playwright smoke tests against duckdb serve. e2e-duckdb: cd frontend && AGENTSVIEW_E2E_BACKEND=duckdb npx playwright test \ e2e/duckdb-backend.spec.ts e2e/session-list.spec.ts --project=chromium # Vet vet: pricing-snapshot ensure-embed-dir go vet -tags fts5 ./... # Lint Go code and auto-fix where possible (local development) lint: lint-golangci nilaway # Run golangci-lint with auto-fixes for local development. lint-golangci: pricing-snapshot ensure-embed-dir @if ! command -v golangci-lint >/dev/null 2>&1; then \ echo "golangci-lint not found. Install with: make lint-tools" >&2; \ exit 1; \ fi golangci-lint run --fix ./... # Lint Go code without fixing (for CI) lint-ci: lint-golangci-ci nilaway # Run golangci-lint without auto-fixes for CI. lint-golangci-ci: pricing-snapshot ensure-embed-dir @if ! command -v golangci-lint >/dev/null 2>&1; then \ echo "golangci-lint not found. Install with: make lint-tools" >&2; \ exit 1; \ fi golangci-lint run ./... # Build a custom golangci-lint binary with the NilAway module plugin. # Strip every repo-local Git env var (GIT_DIR, GIT_INDEX_FILE, # GIT_CONFIG_PARAMETERS, etc.) and disable VCS stamping so the inner # `git clone` and `go build` don't inherit the parent repo's state. # When `make nilaway` runs from a pre-commit hook, git exports those # vars pointing at the parent repo, which makes the clone and the # VCS-stamped build fail with exit 128. The list comes from # `git rev-parse --local-env-vars` so it tracks whatever Git considers # repo-local at runtime. nilaway-golangci-build: @if ! command -v golangci-lint >/dev/null 2>&1; then \ echo "golangci-lint not found. Install with: make lint-tools" >&2; \ exit 1; \ fi @unset_args=$$(git rev-parse --local-env-vars 2>/dev/null | sed 's/^/-u /' | tr '\n' ' '); \ env $$unset_args GOFLAGS=-buildvcs=false \ golangci-lint custom --version "$(GOLANGCI_LINT_VERSION)" --name custom-gcl # Run NilAway through the custom golangci-lint module plugin. nilaway: pricing-snapshot ensure-embed-dir nilaway-golangci-build @set -e; \ root=$$(pwd); \ dirs=$$(go list -f '{{.Dir}}' ./...); \ for dir in $$dirs; do \ if [ "$$dir" = "$$root" ]; then \ pkg="."; \ else \ pkg="./$${dir#$$root/}"; \ fi; \ echo "$(CUSTOM_GCL) run --config .golangci.nilaway.yml $$pkg"; \ GOMAXPROCS=$${GOMAXPROCS:-1} GOGC=$${GOGC:-10} GOMEMLIMIT=$${GOMEMLIMIT:-512MiB} \ $(CUSTOM_GCL) run --config .golangci.nilaway.yml "$$pkg"; \ done # Install pinned local lint tools. lint-tools: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) # Tidy dependencies tidy: pricing-snapshot go mod tidy # Clean build artifacts clean: rm -f agentsview agentsv rm -f $(PRICING_SNAPSHOT_FILE) rm -rf internal/web/dist dist/ tmp/ $(SQLITE_INCLUDE_DIR) mkdir -p internal/web/dist printf '%s\n' \ 'keep embed dir for generated frontend assets' \ > internal/web/dist/.keep docs-install: cd docs && uv sync --frozen --no-dev docs-build: cd docs && uv run --frozen bash ./vercel-build.sh docs-serve: cd docs && bash assets/hydrate-assets.sh && uv run bash ./zensical-docs.sh serve docs-check: bash scripts/check-docs.sh docs-screenshots: bash docs/screenshots/run.sh docs-assets-branch: bash docs/assets/update-static-assets-branch.sh docs-generated-assets-branch: bash docs/screenshots/update-generated-assets-branch.sh docs-deploy-staging: vercel deploy docs docs-deploy: vercel deploy docs --prod # Build release binary for current platform (CGO required for sqlite3) release: pricing-snapshot frontend mkdir -p dist CGO_ENABLED=1 go build -tags fts5 \ -ldflags="$(LDFLAGS_RELEASE)" -trimpath \ -o dist/agentsview-$$(go env GOOS)-$$(go env GOARCH) ./cmd/agentsview # Cross-compile targets (require CC set to target cross-compiler) release-darwin-arm64: pricing-snapshot frontend mkdir -p dist GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags fts5 \ -ldflags="$(LDFLAGS_RELEASE)" -trimpath \ -o dist/agentsview-darwin-arm64 ./cmd/agentsview release-darwin-amd64: pricing-snapshot frontend mkdir -p dist GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -tags fts5 \ -ldflags="$(LDFLAGS_RELEASE)" -trimpath \ -o dist/agentsview-darwin-amd64 ./cmd/agentsview release-linux-amd64: pricing-snapshot frontend mkdir -p dist GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags fts5 \ -ldflags="$(LDFLAGS_RELEASE)" -trimpath \ -o dist/agentsview-linux-amd64 ./cmd/agentsview # Install pre-commit and pre-push hooks via prek install-hooks: @if ! command -v prek >/dev/null 2>&1; then \ echo "prek not found. Install with: brew install prek" >&2; \ exit 1; \ fi prek install -f # Show help help: @echo "agentsview build targets:" @echo "" @echo " build - Build with embedded frontend" @echo " build-release - Release build (optimized, stripped)" @echo " pricing-snapshot - Restore LiteLLM snapshot from artifact branch" @echo " install - Build and install to ~/.local/bin or GOPATH" @echo "" @echo " dev - Run Go server with live reload via air (use with frontend-dev)" @echo " dev-snapshot - Run agentsview against a fresh snapshot of prod sessions.db" @echo " air-install - Install air for backend live reload" @echo " frontend - Build frontend SPA" @echo " frontend-dev - Run Vite+ dev server" @echo " desktop-dev - Run Tauri desktop wrapper in dev mode" @echo " desktop-build - Build Tauri desktop app bundles" @echo " desktop-macos-app - Build macOS .app bundle only" @echo " desktop-macos-dmg - Build macOS DMG installer" @echo " desktop-windows-installer - Build Windows NSIS installer" @echo " desktop-linux-appimage - Build Linux AppImage" @echo " desktop-app - Alias for desktop-macos-app" @echo "" @echo " test - Run all tests" @echo " test-short - Run fast tests only" @echo " bench-backends - Benchmark SQLite, DuckDB, and PostgreSQL stores" @echo " bench-gate - Run the hot-path benchmarks CI gates PRs on" @echo " test-postgres - Run PostgreSQL integration tests" @echo " test-s3 - Run S3 discovery integration tests (Docker)" @echo " postgres-up - Start test PostgreSQL container" @echo " postgres-down - Stop test PostgreSQL container" @echo " test-ssh - Run SSH integration tests" @echo " ssh-up - Start test SSH container" @echo " ssh-down - Stop test SSH container" @echo " e2e - Run Playwright E2E tests" @echo " e2e-duckdb - Run DuckDB-backed Playwright smoke tests" @echo " vet - Run go vet" @echo " lint - Run golangci-lint and NilAway (auto-fix golangci issues)" @echo " lint-ci - Run golangci-lint and NilAway (no fix, for CI)" @echo " lint-golangci - Run golangci-lint with auto-fix" @echo " nilaway - Run NilAway through custom golangci-lint" @echo " lint-tools - Install pinned lint tools" @echo " tidy - Tidy go.mod" @echo "" @echo " release - Release build for current platform" @echo " clean - Remove build artifacts" @echo " install-hooks - Install pre-commit and pre-push git hooks"