Import smart-home into Gitea
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bun
|
||||
// Home Assistant REST API client for the CoyotePack / SW Labs smart-home project.
|
||||
// Runtime: bun (this machine has no node). See README.md.
|
||||
//
|
||||
// Config: baseUrl comes from config.json (falls back to config.example.json).
|
||||
// Token comes from the env var named by config.tokenEnv (default HA_TOKEN) — never hard-coded.
|
||||
//
|
||||
// Usage:
|
||||
// HA_TOKEN=... bun ha.js ping # verify connection + show HA version
|
||||
// HA_TOKEN=... bun ha.js states # dump all entity states -> data/states.json
|
||||
// HA_TOKEN=... bun ha.js states light # only entities in the "light" domain
|
||||
// HA_TOKEN=... bun ha.js get light.kitchen # one entity
|
||||
// HA_TOKEN=... bun ha.js call light turn_on '{"entity_id":"light.kitchen"}'
|
||||
// HA_TOKEN=... bun ha.js services # list available services -> data/services.json
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadConfig() {
|
||||
const path = existsSync(join(ROOT, "config.json"))
|
||||
? join(ROOT, "config.json")
|
||||
: join(ROOT, "config.example.json");
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const BASE = (cfg.baseUrl || "").replace(/\/+$/, "");
|
||||
const TOKEN = process.env[cfg.tokenEnv || "HA_TOKEN"];
|
||||
|
||||
if (!BASE) fail("No baseUrl in config.");
|
||||
if (!TOKEN) fail(`No token. Set the ${cfg.tokenEnv || "HA_TOKEN"} environment variable.`);
|
||||
|
||||
function fail(msg) {
|
||||
console.error(`ERROR: ${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function api(path, { method = "GET", body } = {}) {
|
||||
const res = await fetch(`${BASE}/api${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
fail(`${method} ${path} -> HTTP ${res.status}\n${text}`);
|
||||
}
|
||||
const ct = res.headers.get("content-type") || "";
|
||||
return ct.includes("application/json") ? res.json() : res.text();
|
||||
}
|
||||
|
||||
function save(name, obj) {
|
||||
const p = join(ROOT, "data", name);
|
||||
writeFileSync(p, JSON.stringify(obj, null, 2));
|
||||
console.log(`saved ${p}`);
|
||||
}
|
||||
|
||||
const [cmd, arg1, arg2] = process.argv.slice(2);
|
||||
|
||||
switch (cmd) {
|
||||
case "ping": {
|
||||
const info = await api("/");
|
||||
const cfgInfo = await api("/config");
|
||||
console.log(`Connected to ${BASE}`);
|
||||
console.log(` message: ${info.message}`);
|
||||
console.log(` version: ${cfgInfo.version}`);
|
||||
console.log(` location: ${cfgInfo.location_name}`);
|
||||
break;
|
||||
}
|
||||
case "states": {
|
||||
const all = await api("/states");
|
||||
const filtered = arg1
|
||||
? all.filter((e) => e.entity_id.startsWith(`${arg1}.`))
|
||||
: all;
|
||||
// Summary by domain
|
||||
const byDomain = {};
|
||||
for (const e of all) {
|
||||
const d = e.entity_id.split(".")[0];
|
||||
byDomain[d] = (byDomain[d] || 0) + 1;
|
||||
}
|
||||
console.log(`${all.length} entities across ${Object.keys(byDomain).length} domains:`);
|
||||
for (const [d, n] of Object.entries(byDomain).sort((a, b) => b[1] - a[1])) {
|
||||
console.log(` ${String(n).padStart(4)} ${d}`);
|
||||
}
|
||||
save(arg1 ? `states.${arg1}.json` : "states.json", filtered);
|
||||
break;
|
||||
}
|
||||
case "get": {
|
||||
if (!arg1) fail("usage: get <entity_id>");
|
||||
console.log(JSON.stringify(await api(`/states/${arg1}`), null, 2));
|
||||
break;
|
||||
}
|
||||
case "services": {
|
||||
save("services.json", await api("/services"));
|
||||
break;
|
||||
}
|
||||
case "call": {
|
||||
if (!arg1 || !arg2) fail('usage: call <domain> <service> \'{"entity_id":"..."}\'');
|
||||
const [domain, service] = [arg1, arg2];
|
||||
const data = process.argv[5] ? JSON.parse(process.argv[5]) : {};
|
||||
console.log(JSON.stringify(await api(`/services/${domain}/${service}`, { method: "POST", body: data }), null, 2));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log("commands: ping | states [domain] | get <entity_id> | services | call <domain> <service> <json>");
|
||||
}
|
||||
Reference in New Issue
Block a user