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>
131 lines
3.4 KiB
TypeScript
131 lines
3.4 KiB
TypeScript
import yargs from "yargs";
|
|
import { hideBin } from "yargs/helpers";
|
|
import { loadConfig } from "./src/config";
|
|
import { connect } from "./src/mqtt";
|
|
import { createEntityManager } from "./src/entities";
|
|
|
|
const DEFAULT_CONFIG_PATH = "./systant.toml";
|
|
|
|
yargs(hideBin(process.argv))
|
|
.scriptName("systant")
|
|
.usage("$0 <cmd> [args]")
|
|
|
|
.command(
|
|
"run",
|
|
"Start the systant daemon",
|
|
(yargs) => {
|
|
return yargs.option("config", {
|
|
alias: "c",
|
|
type: "string",
|
|
default: DEFAULT_CONFIG_PATH,
|
|
describe: "Path to config file",
|
|
});
|
|
},
|
|
async (argv) => {
|
|
await run(argv.config);
|
|
}
|
|
)
|
|
|
|
.command(
|
|
"check",
|
|
"Check config and connectivity",
|
|
(yargs) => {
|
|
return yargs.option("config", {
|
|
alias: "c",
|
|
type: "string",
|
|
default: DEFAULT_CONFIG_PATH,
|
|
describe: "Path to config file",
|
|
});
|
|
},
|
|
async (argv) => {
|
|
await check(argv.config);
|
|
}
|
|
)
|
|
|
|
.command(
|
|
"once",
|
|
"Poll all entity states once, then exit",
|
|
(yargs) => {
|
|
return yargs.option("config", {
|
|
alias: "c",
|
|
type: "string",
|
|
default: DEFAULT_CONFIG_PATH,
|
|
describe: "Path to config file",
|
|
});
|
|
},
|
|
async (argv) => {
|
|
await once(argv.config);
|
|
}
|
|
)
|
|
|
|
.demandCommand(1, "\nError: You need to specify a command!")
|
|
.help()
|
|
.parse();
|
|
|
|
async function run(configPath: string): Promise<void> {
|
|
const config = await loadConfig(configPath);
|
|
|
|
console.log(`Starting systant on ${config.systant.hostname}`);
|
|
|
|
const mqtt = await connect(config, config.systant.hostname);
|
|
const entities = createEntityManager(config, mqtt);
|
|
|
|
await entities.start();
|
|
|
|
// Handle shutdown
|
|
const shutdown = async () => {
|
|
console.log("\nShutting down...");
|
|
entities.stop();
|
|
await mqtt.disconnect();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on("SIGINT", shutdown);
|
|
process.on("SIGTERM", shutdown);
|
|
|
|
console.log("Systant running. Press Ctrl+C to stop.");
|
|
}
|
|
|
|
async function check(configPath: string): Promise<void> {
|
|
console.log(`Checking config: ${configPath}`);
|
|
|
|
try {
|
|
const config = await loadConfig(configPath);
|
|
const entityCount = Object.keys(config.entities).length;
|
|
console.log("Config loaded successfully");
|
|
console.log(` MQTT broker: ${config.mqtt.broker}`);
|
|
console.log(` Entities: ${entityCount} configured (default interval: ${config.systant.defaultInterval}s)`);
|
|
console.log(` HA discovery: ${config.homeassistant.discovery}`);
|
|
|
|
console.log("\nTesting MQTT connection...");
|
|
const hostname = config.systant.hostname;
|
|
const mqtt = await connect(config, hostname);
|
|
console.log("MQTT connection successful");
|
|
await mqtt.disconnect();
|
|
|
|
console.log("\nAll checks passed!");
|
|
} catch (err) {
|
|
console.error("Check failed:", err instanceof Error ? err.message : err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function once(configPath: string): Promise<void> {
|
|
const config = await loadConfig(configPath);
|
|
const hostname = config.systant.hostname;
|
|
|
|
const mqtt = await connect(config, hostname);
|
|
const entities = createEntityManager(config, mqtt);
|
|
|
|
// Start will do initial poll of all entities
|
|
await entities.start();
|
|
|
|
// Wait a moment for the initial polls to complete
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
entities.stop();
|
|
await mqtt.disconnect();
|
|
|
|
console.log("Entity states published successfully");
|
|
}
|