From ae778ebdab21124e1963a53cc2ef1e361a3103a8 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 19 Jan 2026 19:52:47 -0800 Subject: [PATCH] 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 --- .claude/settings.json | 40 ++++++++ .claude/skills/build.md | 16 ++++ .claude/skills/plan.md | 26 ++++++ .claude/skills/release.md | 28 ++++++ .claude/skills/test.md | 22 +++++ .claude/skills/typecheck.md | 16 ++++ .envrc | 1 + .gitignore | 46 ++++++++++ .vscode/settings.json | 3 + AGENTS.md | 103 +++++++++++++++++++++ CLAUDE.md | 120 ++++++++++++++++++++++++ README.md | 15 +++ bun.lock | 152 +++++++++++++++++++++++++++++++ flake.lock | 61 +++++++++++++ flake.nix | 58 ++++++++++++ index.ts | 130 ++++++++++++++++++++++++++ package.json | 23 +++++ src/config.ts | 130 ++++++++++++++++++++++++++ src/entities.ts | 110 ++++++++++++++++++++++ src/mqtt.ts | 177 ++++++++++++++++++++++++++++++++++++ systant.toml.example | 75 +++++++++++++++ tsconfig.json | 29 ++++++ 22 files changed, 1381 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .claude/skills/build.md create mode 100644 .claude/skills/plan.md create mode 100644 .claude/skills/release.md create mode 100644 .claude/skills/test.md create mode 100644 .claude/skills/typecheck.md create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 bun.lock create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 index.ts create mode 100644 package.json create mode 100644 src/config.ts create mode 100644 src/entities.ts create mode 100644 src/mqtt.ts create mode 100644 systant.toml.example create mode 100644 tsconfig.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..d42b3a2 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://claude.ai/claude-code/settings.schema.json", + "permissions": { + "allow": [ + "Bash(bun test*)", + "Bash(bun run*)", + "Bash(bun build*)", + "Bash(bun install*)", + "Bash(bunx tsc --noEmit*)", + "Bash(git status*)", + "Bash(git diff*)", + "Bash(git log*)", + "Bash(git add*)", + "Bash(git commit*)", + "Bash(ls*)", + "Bash(cat /proc/*)", + "Bash(cat /sys/*)" + ], + "deny": [ + "Bash(rm -rf /)*", + "Bash(sudo *)", + "Bash(*--force*)" + ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "bunx tsc --noEmit --pretty 2>&1 | head -20 || true", + "description": "Type check after file changes", + "timeout": 10000 + } + ] + } + ] + } +} diff --git a/.claude/skills/build.md b/.claude/skills/build.md new file mode 100644 index 0000000..ead50d0 --- /dev/null +++ b/.claude/skills/build.md @@ -0,0 +1,16 @@ +# /build + +Build the systant CLI binary. + +## Instructions + +1. Run type checking first: `bunx tsc --noEmit` +2. If types pass, build the binary: `bun build index.ts --compile --outfile dist/systant` +3. Report the binary size and location +4. If there are errors, show them clearly and suggest fixes + +## Success Criteria + +- No TypeScript errors +- Binary created at `dist/systant` +- Binary is executable diff --git a/.claude/skills/plan.md b/.claude/skills/plan.md new file mode 100644 index 0000000..c31117b --- /dev/null +++ b/.claude/skills/plan.md @@ -0,0 +1,26 @@ +# /plan + +Enter planning mode to design an implementation approach. + +## Instructions + +1. Enter plan mode using EnterPlanMode tool +2. Explore the codebase to understand current state +3. Identify affected files and components +4. Design the implementation approach +5. Present the plan for user approval before coding + +## When to Use + +- New features +- Architectural changes +- Complex bug fixes +- Refactoring tasks + +## Output + +A clear plan including: +- Files to create/modify +- Key implementation steps +- Potential risks or considerations +- Testing approach diff --git a/.claude/skills/release.md b/.claude/skills/release.md new file mode 100644 index 0000000..4c01051 --- /dev/null +++ b/.claude/skills/release.md @@ -0,0 +1,28 @@ +# /release + +Prepare a release of systant. + +## Instructions + +1. Ensure working directory is clean (`git status`) +2. Run tests: `bun test` +3. Type check: `bunx tsc --noEmit` +4. Build binary: `bun build index.ts --compile --outfile dist/systant` +5. Ask user for version bump type (patch/minor/major) +6. Update version in package.json +7. Create git commit with message: "release: v{version}" +8. Create git tag: `v{version}` +9. Report next steps (push, publish, etc.) + +## Prerequisites + +- All tests must pass +- No TypeScript errors +- Clean git working directory (or user confirms to proceed) + +## Success Criteria + +- Binary built successfully +- Version bumped in package.json +- Git commit and tag created +- Clear instructions for next steps diff --git a/.claude/skills/test.md b/.claude/skills/test.md new file mode 100644 index 0000000..f5a1880 --- /dev/null +++ b/.claude/skills/test.md @@ -0,0 +1,22 @@ +# /test + +Run the test suite. + +## Instructions + +1. Run `bun test` to execute all tests +2. If tests fail: + - Analyze the failure messages + - Identify the root cause + - Suggest specific fixes +3. If tests pass, report the summary + +## Options + +- `/test ` - Run tests matching a pattern (e.g., `/test metrics`) +- `/test --watch` - Run in watch mode + +## Success Criteria + +- All tests pass +- Clear reporting of any failures with actionable suggestions diff --git a/.claude/skills/typecheck.md b/.claude/skills/typecheck.md new file mode 100644 index 0000000..c402b21 --- /dev/null +++ b/.claude/skills/typecheck.md @@ -0,0 +1,16 @@ +# /typecheck + +Run TypeScript type checking. + +## Instructions + +1. Run `bunx tsc --noEmit --pretty` +2. If errors found: + - List each error with file, line, and message + - Provide suggested fixes for each +3. If no errors, confirm success + +## Success Criteria + +- Report all type errors clearly +- Suggest actionable fixes diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa61bba --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem +auth_failures.log + +# Nix +.direnv +.direnv/* + +# Local config (use systant.toml.example as template) +systant.toml \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..766db8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "terminal.integrated.fontFamily": "Fira Code" +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1bffd0c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,103 @@ +# Agents + +Specialized sub-agents for systant development tasks. + +## test-runner + +Use this agent after writing or modifying code to run the test suite and verify changes. + +**Responsibilities:** +- Run `bun test` and report results +- Identify failing tests and their root causes +- Suggest fixes for test failures +- Run specific test files when targeted testing is needed + +**Trigger:** After implementing features, fixing bugs, or modifying existing code. + +## code-reviewer + +Use this agent to review code changes before committing. + +**Responsibilities:** +- Check for Bun best practices (no Node.js patterns) +- Verify type safety and explicit return types +- Look for potential bugs or edge cases +- Ensure code follows project conventions +- Flag any security concerns (especially in command execution) + +**Trigger:** Before creating commits or PRs. + +## metrics-specialist + +Use this agent when working on system metric collection. + +**Responsibilities:** +- Understand Linux /proc and /sys interfaces +- Know cross-platform metric collection strategies +- Ensure metrics are properly typed and documented +- Validate metric units and normalization + +**Context:** Systant collects CPU, memory, disk, and network metrics. Metrics should be normalized (percentages 0-100, bytes for sizes) and include metadata for Home Assistant discovery. + +## mqtt-specialist + +Use this agent when working on MQTT publishing or Home Assistant integration. + +**Responsibilities:** +- Understand MQTT topic conventions +- Know Home Assistant discovery protocol +- Ensure proper QoS and retain flag usage +- Handle connection lifecycle (connect, reconnect, disconnect) +- Design topic hierarchies for commands and events + +**Context:** Systant publishes to MQTT with Home Assistant auto-discovery. Topics follow the pattern `systant/{hostname}/{metric_type}`. Command topics use `systant/{hostname}/command/{action}`. + +## events-specialist + +Use this agent when working on the event/command system. + +**Responsibilities:** +- Design secure command execution with allowlists +- Implement event handlers and action dispatching +- Ensure proper input validation and sanitization +- Handle timeouts and error reporting +- Consider security implications of remote command execution + +**Context:** Systant listens for MQTT commands and executes configured actions. Security is paramount - all commands must be validated against an allowlist, inputs sanitized, and execution sandboxed where possible. + +## debug-investigator + +Use this agent when troubleshooting issues or unexpected behavior. + +**Responsibilities:** +- Add strategic logging to trace execution +- Isolate the problem to specific components +- Form and test hypotheses +- Propose minimal fixes + +**Trigger:** When something isn't working as expected. + +## architect + +Use this agent for design decisions and architectural questions. + +**Responsibilities:** +- Evaluate trade-offs between approaches +- Consider future extensibility +- Maintain consistency with existing patterns +- Document decisions in code comments or CLAUDE.md + +**Trigger:** When facing design choices or planning new features. + +## security-auditor + +Use this agent when reviewing security-sensitive code. + +**Responsibilities:** +- Review command execution paths for injection vulnerabilities +- Validate input sanitization +- Check allowlist/denylist implementations +- Ensure proper authentication for MQTT commands +- Review file system access patterns + +**Context:** Systant executes commands based on MQTT messages. This is a critical attack surface that requires careful security review. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8101935 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,120 @@ +# 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 ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run