{"id":50097930,"url":"https://github.com/srcfl/hugin-agent","last_synced_at":"2026-05-23T05:14:03.694Z","repository":{"id":355150022,"uuid":"1226685762","full_name":"srcfl/hugin-agent","owner":"srcfl","description":"Thin local agent for Hugin (https://hugin.sourceful-labs.net) — scans LAN, talks Modbus, runs Lua drivers. Open + auditable.","archived":false,"fork":false,"pushed_at":"2026-05-12T11:56:25.000Z","size":54,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T13:33:58.797Z","etag":null,"topics":["agent","ems","energy","hugin","localhost","lua","modbus"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/srcfl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-01T17:58:42.000Z","updated_at":"2026-05-12T11:47:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/srcfl/hugin-agent","commit_stats":null,"previous_names":["srcfl/hugin-agent"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/srcfl/hugin-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srcfl%2Fhugin-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srcfl%2Fhugin-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srcfl%2Fhugin-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srcfl%2Fhugin-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/srcfl","download_url":"https://codeload.github.com/srcfl/hugin-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srcfl%2Fhugin-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33383681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T04:15:53.637Z","status":"ssl_error","status_checked_at":"2026-05-23T04:15:53.242Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","ems","energy","hugin","localhost","lua","modbus"],"created_at":"2026-05-23T05:14:00.403Z","updated_at":"2026-05-23T05:14:03.682Z","avatar_url":"https://github.com/srcfl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hugin-agent\n\n\u003e Thin local helper for the [Hugin web app](https://hugin.sourceful-labs.net) — scans your LAN, talks Modbus, runs Lua drivers. Open. Stateless. Auditable.\n\n## Why this exists\n\nHugin's web app at `hugin.sourceful-labs.net` lets you write Lua drivers for energy devices with the help of an AI. To actually **test** a draft against the real hardware on your network, the browser needs a way out — it can't speak Modbus TCP, it can't sweep ARP, it can't talk to your inverter.\n\nThis binary is that way out. It listens on `localhost:19090`, exposes five JSON endpoints, and does exactly the things the browser can't:\n\n- Sweep your LAN for devices (TCP probe + ARP)\n- Read Modbus holding-registers from an IP\n- Execute a forty-two-watts-style Lua driver in a sandbox and stream the emissions back\n\nNothing else. No state stored on disk. No telemetry. No auto-update. No hidden background work. The whole binary is auditable in 30 minutes.\n\nIf you already run [forty-two-watts](https://github.com/frahlg/forty-two-watts), you don't need this — 42w can play the agent role itself (same protocol).\n\n## Install\n\n### macOS / Linux — Homebrew\n\n```bash\nbrew install srcfl/tap/hugin-agent\nhugin-agent\n```\n\n(Homebrew strips macOS quarantine automatically — no Gatekeeper warning.)\n\n### Windows — Scoop\n\n```powershell\nscoop bucket add srcfl https://github.com/srcfl/scoop-bucket\nscoop install hugin-agent\nhugin-agent\n```\n\n### Docker\n\nPublished as a multi-arch image on every release tag. Use as a\nsidecar in your existing compose stack (e.g. forty-two-watts):\n\n```yaml\n# docker-compose.hugin.yml\nservices:\n  hugin-agent:\n    image: ghcr.io/srcfl/hugin-agent:latest\n    restart: unless-stopped\n    network_mode: host          # see Modbus devices on the host LAN\n    volumes:\n      - hugin-agent-data:/var/lib/hugin-agent\nvolumes:\n  hugin-agent-data:\n```\n\n```bash\ndocker compose -f docker-compose.hugin.yml up -d\ndocker logs hugin-agent          # the pairing URL is printed on stderr\n```\n\n`/var/lib/hugin-agent/creds.json` persists NATS pairing across\nrestarts. Without `network_mode: host` the agent can still talk to\nthe workbench (publish port 19090) but won't see Modbus devices on\nthe user's LAN — pick whichever fits your security model.\n\n### From source (any platform with Go 1.25+)\n\n```bash\ngo install github.com/srcfl/hugin-agent/cmd/hugin-agent@latest\nhugin-agent\n```\n\n### Pre-built tarball\n\nDirect downloads at [github.com/srcfl/hugin-agent/releases](https://github.com/srcfl/hugin-agent/releases). On macOS, you'll need to run `xattr -d com.apple.quarantine /path/to/hugin-agent` to bypass Gatekeeper for the unsigned binary — this is exactly what Homebrew does for you.\n\n### Audit before you run\n\n```bash\ngit clone https://github.com/srcfl/hugin-agent\ncd hugin-agent\n# read everything in cmd/ and internal/ — it's small\ngo build ./cmd/hugin-agent\n./hugin-agent\n```\n\n## Pair with Hugin\n\nWhen you start the agent it prints something like:\n\n```\n  Hugin agent v0.1.0\n  Listening on http://127.0.0.1:19090\n  Pairing token: a3f9c2d8e7b1f0a4c5d6e8f7a1b2c3d4\n\n  Pair this agent with the web app:\n    1. Open https://hugin.sourceful-labs.net/settings.html\n    2. Paste the URL above + this token\n    3. Click 'Test connection' then 'Save pairing'\n```\n\nOpen the URL, paste both fields, save. The web app stores the pair in your browser's `localStorage` (never sent to Sourceful's servers). After that, the chat page at `hugin.sourceful-labs.net/chat.html` shows a green \"agent\" pill in the header and unlocks \"Scan my LAN\" + \"Test latest driver\" buttons.\n\n## Flags\n\n| Flag        | Default     | Notes                                                      |\n|-------------|-------------|------------------------------------------------------------|\n| `--host`    | `127.0.0.1` | Use `0.0.0.0` only if you're running this on a Pi and want LAN access. |\n| `--port`    | `19090`     |                                                            |\n| `--token`   | (random)    | Persistent token if you want to skip re-pairing on restart |\n\nSame values via env: `HUGIN_AGENT_HOST`, `HUGIN_AGENT_PORT`, `HUGIN_AGENT_TOKEN`.\n\n## Protocol (v1)\n\nAll endpoints return JSON. Auth: every protected endpoint expects `Authorization: Bearer \u003ctoken\u003e` matching the pairing token.\n\n### `GET /v1/info` (no auth)\n\n```json\n{\n  \"name\": \"hugin-agent\",\n  \"version\": \"0.1.0\",\n  \"protocol_version\": 1,\n  \"capabilities\": [\"scan\", \"probe\", \"run-lua\"]\n}\n```\n\n### `GET /v1/health` (no auth)\n\n```json\n{ \"status\": \"ok\", \"ts\": \"2026-05-02T08:30:00Z\" }\n```\n\n### `POST /v1/scan` (auth)\n\nSweeps the local subnet for devices.\n\nRequest:\n\n```json\n{ \"cidr\": \"192.168.1.0/24\", \"ports\": [502, 80, 1883], \"deep_probe\": false }\n```\n\nResponse:\n\n```json\n{\n  \"devices\": [\n    {\n      \"ip\": \"192.168.1.50\",\n      \"mac\": \"AA:BB:CC:DD:EE:FF\",\n      \"vendor_oui\": \"Sungrow\",\n      \"open_ports\": [502, 80],\n      \"modbus\": { \"fc43_make\": \"Sungrow\", \"fc43_model\": \"SH10RT\" },\n      \"http_banner\": \"...\"\n    }\n  ],\n  \"errors\": []\n}\n```\n\nIf `deep_probe: true`, devices on TCP-502 also get a Modbus FC43 device-identification probe.\n\n### `POST /v1/probe` (auth)\n\nReads holding-registers from a specific Modbus device.\n\nRequest:\n\n```json\n{\n  \"ip\": \"192.168.1.50\",\n  \"protocol\": \"modbus\",\n  \"modbus\": {\n    \"port\": 502,\n    \"slave_id\": 1,\n    \"registers\": [\n      { \"addr\": 16384, \"count\": 2, \"kind\": \"i32_be\" },\n      { \"addr\": 16386, \"count\": 1, \"kind\": \"u16\" }\n    ]\n  }\n}\n```\n\n`kind` is one of: `u16`, `i16`, `u32_be`, `u32_le`, `i32_be`, `i32_le`, `f32_be`, `f32_le`.\n\n### `POST /v1/run-lua` (auth)\n\nExecutes a forty-two-watts Lua driver and returns whatever it emits.\n\nRequest:\n\n```json\n{\n  \"lua_source\": \"DRIVER = {...} function driver_init(c) ... end function driver_poll() ... end\",\n  \"config\": { \"host\": \"192.168.1.50\", \"port\": 502, \"slave_id\": 1 },\n  \"actions\": [\"init\", \"poll\"],\n  \"duration_ms\": 30000\n}\n```\n\n`actions` is run in order; defaults to `[\"init\", \"poll\"]`. The Lua VM is sandboxed (no `os`/`io`/`debug`, no `require`/`load`/`dofile`); the only way out is the `host.*` table.\n\nResponse:\n\n```json\n{\n  \"ok\": true,\n  \"emissions\": [\n    { \"ts\": \"...\", \"channel\": \"meter\", \"data\": { \"power_w\": -2500 } }\n  ],\n  \"metrics\": {},\n  \"logs\": [\"[info] driver_init done\"],\n  \"errors\": []\n}\n```\n\n## Security model\n\n**What the agent CAN do** with the token someone has stolen from your machine:\n\n- Read your Modbus / HTTP-discoverable devices on the LAN it's bound to\n- Send Modbus reads (no writes — `/v1/probe` is read-only; Lua can write but only to the host you give it)\n- Execute Lua source you POST it (sandboxed)\n\n**What the agent CAN'T do:**\n\n- Reach the public internet (we never make outbound calls except the ones drivers explicitly issue via `host.http_get` / `host.http_post`)\n- Persist anything to disk\n- Auto-update itself\n- Phone home with telemetry\n- Run arbitrary Go code (only sandboxed Lua)\n\n**Recommendations:**\n\n- Default bind is `127.0.0.1` (only the same machine). Don't change it unless you understand the trade-off.\n- Don't share your pairing token. Generate a new random one with each restart by leaving `--token` empty.\n- The web app at `hugin.sourceful-labs.net` stores the token in browser localStorage — clear it from settings if you stop using Hugin.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\n## Issues, questions\n\n[github.com/srcfl/hugin-agent/issues](https://github.com/srcfl/hugin-agent/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrcfl%2Fhugin-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrcfl%2Fhugin-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrcfl%2Fhugin-agent/lists"}