systant/CLAUDE.md
ryan ae778ebdab Initial systant implementation in Bun/TypeScript
A lightweight system monitoring agent that:
- Collects metrics via configurable shell commands
- Publishes to MQTT with Home Assistant auto-discovery
- Supports entity types: sensor, binary_sensor, light, switch, button
- Responds to commands over MQTT for controllable entities

Architecture:
- src/config.ts: TOML config loading and validation
- src/mqtt.ts: MQTT client with HA discovery
- src/entities.ts: Entity state polling and command handling
- index.ts: CLI entry point (run, check, once commands)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 19:52:47 -08:00

3.7 KiB

Systant

A system monitoring agent that collects metrics, monitors services, and reports to MQTT/Home Assistant; and responds to events over MQTT to trigger commands or other behavior.

Project Overview

Systant is a lightweight CLI tool written in Bun/TypeScript that:

  • Collects system metrics (CPU, memory, disk, network)
  • Monitors service health
  • Publishes data to MQTT brokers
  • Supports Home Assistant auto-discovery
  • Listens for MQTT commands to trigger actions (run scripts, restart services, etc.)
  • Responds to events with configurable handlers
  • Runs as a daemon or one-shot command

Architecture

index.ts              # CLI entry point (yargs)
src/
  commands/           # CLI command handlers
  metrics/            # System metric collectors
  mqtt/               # MQTT client and publishing
  events/             # MQTT event listeners and handlers
  actions/            # Executable actions (shell, service, notify)
  ha/                 # Home Assistant discovery
  config/             # Configuration loading

Event/Command System

Systant subscribes to MQTT topics and executes configured actions:

Topic: systant/{hostname}/command/{action}
Payload: { "args": [...], "timeout": 30 }

Topic: systant/{hostname}/event/{event_name}
Payload: { ... event data ... }

Actions are sandboxed and configurable via allowlists in the config file. Security is critical - never execute arbitrary commands without validation.

Key Design Decisions

  • Single binary: Compiles to standalone executable via bun build --compile
  • No external services: Uses Bun built-ins (sqlite, file, etc.)
  • Config-driven: TOML configuration for flexibility
  • Typed throughout: Full TypeScript with strict mode

Tech Stack

  • Runtime: Bun (not Node.js)
  • CLI: yargs
  • Config: TOML
  • MQTT: mqtt.js or Bun-native when available
  • Package: Nix flake for reproducible builds

Bun Conventions

Default to using Bun instead of Node.js.

  • Use bun <file> instead of node <file> or ts-node <file>
  • Use bun test instead of jest or vitest
  • Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild
  • Use bun install instead of npm install or yarn install or pnpm install
  • Use bun run <script> instead of npm run <script>
  • Use bunx <package> <command> instead of npx <package> <command>
  • Bun automatically loads .env, so don't use dotenv.

Bun APIs

  • Bun.serve() for HTTP/WebSocket servers
  • bun:sqlite for SQLite (not better-sqlite3)
  • Bun.file() for file I/O (not node:fs readFile/writeFile)
  • Bun.$\cmd`` for shell commands (not execa)
  • Native WebSocket (not ws)

Testing

import { test, expect, describe, beforeEach } from "bun:test";

describe("MetricCollector", () => {
  test("collects CPU metrics", async () => {
    const metrics = await collectCPU();
    expect(metrics.usage).toBeGreaterThanOrEqual(0);
  });
});

Run tests: bun test Run specific: bun test src/metrics Watch mode: bun test --watch

Code Style

  • Prefer async/await over callbacks
  • Use explicit return types on public functions
  • Prefer interface over type for object shapes
  • Use const by default, let only when reassignment needed
  • No classes unless state encapsulation is genuinely needed
  • Prefer pure functions and composition

Commands

bun run start        # Run in development
bun run dist         # Build standalone binary
bun test             # Run tests
bun test --watch     # Watch mode

Planning Protocol

When implementing features:

  1. Discuss the approach before writing code
  2. Start with types/interfaces
  3. Write tests alongside implementation
  4. Keep PRs focused and small