Skip to content

jeremychan/ha-agent-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ha-agent-cli

A Home Assistant CLI built for people, scripts, and coding agents. Inspect state, call services, and safely manage automations, scripts, and scenes from one consistent interface.

CI License: MIT Python

Quick Start

Ask your coding agent:

Read this repository and install the CLI.

Or choose one of the two installation paths below. Both require Python 3.11 or newer.

1. Install script

curl -LsSf https://raw.githubusercontent.com/jeremychan/ha-agent-cli/main/install.sh | sh
hash -r
ha-agent --version

The installer installs the current main branch into an isolated environment under ~/.local/share/ha-agent-cli and links ha-agent into ~/.local/bin. It does not use sudo, modify the system Python, or require a published PyPI package.

hash -r clears any older ha-agent location cached by the current shell.

To inspect the script before running it:

curl -LsSf https://raw.githubusercontent.com/jeremychan/ha-agent-cli/main/install.sh -o install.sh
less install.sh
sh install.sh

Run the curl command again to upgrade to the latest main. To install a specific source archive instead, set HA_AGENT_CLI_SOURCE:

curl -LsSf https://raw.githubusercontent.com/jeremychan/ha-agent-cli/main/install.sh |
  HA_AGENT_CLI_SOURCE=https://github.com/jeremychan/ha-agent-cli/archive/refs/tags/v0.1.0.zip sh

To uninstall:

rm -rf ~/.local/share/ha-agent-cli
rm ~/.local/bin/ha-agent

2. Build from source

Clone the repository and install it into a virtual environment:

git clone https://github.com/jeremychan/ha-agent-cli.git
cd ha-agent-cli
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .
ha-agent info

The virtual environment keeps the CLI and its dependencies isolated. Activate it again with . .venv/bin/activate whenever you want to use ha-agent, or follow the development setup below if you plan to contribute.

Features

  • Safe configuration changes: create, update, delete, and export automations, scripts, and scenes
  • Dry-run by default: every mutation needs --confirm before it touches Home Assistant
  • Conflict protection: configuration hashes stop one agent or shell session from overwriting another
  • Verified writes and backups: updates are read back after saving, with the previous config stored locally
  • Consistent output: every command returns an {ok, command, data, meta} envelope in JSON or TOON
  • Focused queries: fuzzy search, field selection, and result limits keep large instances manageable
  • Schema introspection: inspect service fields over WebSocket before constructing a call
  • Agent integration: install the bundled skill file into Claude, Codex, Cursor, or OpenCode

Authentication

ha-agent-cli uses Long-Lived Access Tokens to talk to your Home Assistant instance.

Getting a token

  1. Open your Home Assistant instance in a browser
  2. Go to your Profile (click your avatar in the bottom-left corner)
  3. Scroll to Long-Lived Access Tokens at the bottom
  4. Click Create Token, give it a name (e.g., "ha-agent-cli")
  5. Copy the token. It's shown once and cannot be retrieved later.

Configuring the token

# Option 1: Save to config file (~/.config/ha-agent-cli/config.toml)
ha-agent setup --url http://homeassistant.local:8123 --token YOUR_TOKEN

# Option 2: Environment variables (best for CI/agents)
export HOME_ASSISTANT_URL=http://homeassistant.local:8123
export HOME_ASSISTANT_TOKEN=YOUR_TOKEN

# Option 3: Per-command flags (highest priority)
ha-agent --url http://homeassistant.local:8123 --token YOUR_TOKEN info

Resolution order: CLI flags > environment variables > config file > defaults.

Verify

# Test your connection
ha-agent setup --check

See it in action

The examples below show both the command and its response. Entity IDs will differ on your instance, and timestamps, hashes, and backup paths change on every run. Long state attribute maps are shortened for readability.

Find the right entity without dumping the whole instance

Global output options go before the command. Here, fuzzy search finds kitchen lights, returns only useful fields, and stops after two matches:

$ ha-agent --fields entity_id,state,name --max-results 2 entity search "kitchen light"
{
  "ok": true,
  "command": "entity.search",
  "data": [
    {
      "entity_id": "light.kitchen_ceiling",
      "state": "on",
      "name": "Kitchen ceiling"
    },
    {
      "entity_id": "light.kitchen_cabinets",
      "state": "off",
      "name": "Kitchen cabinets"
    }
  ],
  "meta": {
    "timestamp": "2026-07-18T14:22:31+00:00",
    "duration_ms": 0
  }
}

The same response is smaller in TOON, which is useful when another tool or agent consumes it:

$ ha-agent --format toon --fields entity_id,state,name --max-results 2 entity search "kitchen light"
  ok: true
  command: entity.search
  data:   2[entity_id,state,name]:
    light.kitchen_ceiling,on,Kitchen ceiling
    light.kitchen_cabinets,off,Kitchen cabinets
  meta:     timestamp: 2026-07-18T14:22:31+00:00
    duration_ms: 0

Preview a service call, then run it

Mutating commands are dry-runs unless --confirm is present:

$ ha-agent service call light.turn_on \
    --data '{"entity_id":"light.kitchen_ceiling","brightness_pct":70}'
{
  "ok": true,
  "command": "service.call",
  "data": {
    "dry_run": true,
    "service": "light.turn_on",
    "data": {
      "entity_id": "light.kitchen_ceiling",
      "brightness_pct": 70
    }
  },
  "meta": {
    "timestamp": "2026-07-18T14:25:08+00:00",
    "duration_ms": 0
  }
}

Review the target and payload, then send the same call:

$ ha-agent service call light.turn_on \
    --data '{"entity_id":"light.kitchen_ceiling","brightness_pct":70}' \
    --confirm
{
  "ok": true,
  "command": "service.call",
  "data": [],
  "meta": {
    "timestamp": "2026-07-18T14:25:16+00:00",
    "duration_ms": 0
  }
}

An empty data list is a successful Home Assistant service response. Read the entity back if you need the resulting state:

$ ha-agent --format raw entity get light.kitchen_ceiling
{
  "entity_id": "light.kitchen_ceiling",
  "state": "on",
  "attributes": {
    "brightness": 179,
    "friendly_name": "Kitchen ceiling"
  },
  "last_changed": "2026-07-18T14:25:16.281001+00:00",
  "last_updated": "2026-07-18T14:25:16.281001+00:00"
}

Inspect a service before calling it

Service schemas come from the connected Home Assistant instance, so an agent does not need to guess field names:

$ ha-agent schema homeassistant.check_config
{
  "ok": true,
  "command": "schema",
  "data": {
    "service": "homeassistant.check_config",
    "fields": {},
    "description": "Checks the Home Assistant configuration files for errors."
  },
  "meta": {
    "timestamp": "2026-07-18T14:27:40+00:00",
    "duration_ms": 0
  }
}

Create an automation as data

Save the automation configuration as JSON. This example uses an entity-based trigger and restart mode so each new motion event resets the off timer:

{
  "alias": "Kitchen motion light",
  "description": "Turn on the kitchen light after dark, then turn it off two minutes after the last motion.",
  "triggers": [
    {
      "trigger": "state",
      "entity_id": "binary_sensor.kitchen_motion",
      "to": "on"
    }
  ],
  "conditions": [
    {
      "condition": "sun",
      "after": "sunset"
    }
  ],
  "actions": [
    {
      "action": "light.turn_on",
      "target": {
        "entity_id": "light.kitchen_ceiling"
      },
      "data": {
        "brightness_pct": 70
      }
    },
    {
      "wait_for_trigger": [
        {
          "trigger": "state",
          "entity_id": "binary_sensor.kitchen_motion",
          "to": "off",
          "for": "00:02:00"
        }
      ]
    },
    {
      "action": "light.turn_off",
      "target": {
        "entity_id": "light.kitchen_ceiling"
      }
    }
  ],
  "mode": "restart"
}

Assuming that file is named kitchen-motion.json, first ask for a mutation plan:

$ ha-agent automation create kitchen_motion --file kitchen-motion.json
{
  "ok": true,
  "command": "automation.create",
  "data": {
    "dry_run": true,
    "operation": "create",
    "resource": "automation",
    "config_hash": null,
    "before": null,
    "after": {
      "alias": "Kitchen motion light",
      "description": "Turn on the kitchen light after dark, then turn it off two minutes after the last motion.",
      "triggers": [
        {
          "trigger": "state",
          "entity_id": "binary_sensor.kitchen_motion",
          "to": "on"
        }
      ],
      "conditions": [
        {
          "condition": "sun",
          "after": "sunset"
        }
      ],
      "actions": [
        {
          "action": "light.turn_on",
          "target": {
            "entity_id": "light.kitchen_ceiling"
          },
          "data": {
            "brightness_pct": 70
          }
        },
        {
          "wait_for_trigger": [
            {
              "trigger": "state",
              "entity_id": "binary_sensor.kitchen_motion",
              "to": "off",
              "for": "00:02:00"
            }
          ]
        },
        {
          "action": "light.turn_off",
          "target": {
            "entity_id": "light.kitchen_ceiling"
          }
        }
      ],
      "mode": "restart"
    },
    "diff": [],
    "warnings": [],
    "issues": [],
    "references": []
  },
  "meta": {
    "timestamp": "2026-07-18T14:31:02+00:00",
    "duration_ms": 0
  }
}

Nothing has changed yet. Add --confirm to create it and verify the saved configuration:

$ ha-agent automation create kitchen_motion --file kitchen-motion.json --confirm
{
  "ok": true,
  "command": "automation.create",
  "data": {
    "success": true,
    "operation": "create",
    "resource": "automation",
    "config_hash": "sha256:bb7053c7f5190698f6320ed39f8ee00071c305244ca330c6138def742218ceea",
    "backup_path": null,
    "verified": true,
    "issues": [],
    "meta": {}
  },
  "meta": {
    "timestamp": "2026-07-18T14:31:11+00:00",
    "duration_ms": 0
  }
}

Export, edit, and safely update an automation

Exports contain the portable config and its current hash:

$ ha-agent --format raw automation export kitchen_motion > kitchen-motion.export.json
$ jq '{id, hash, alias: .config.alias}' kitchen-motion.export.json
{
  "id": "kitchen_motion",
  "hash": "sha256:bb7053c7f5190698f6320ed39f8ee00071c305244ca330c6138def742218ceea",
  "alias": "Kitchen motion light"
}

Edit .config in the export, write it to a new file, then use the exported hash as an optimistic lock:

$ jq '.config.actions[0].data.brightness_pct = 55 | .config' \
    kitchen-motion.export.json > kitchen-motion.updated.json
$ HASH=$(jq -r .hash kitchen-motion.export.json)
$ ha-agent automation update kitchen_motion \
    --file kitchen-motion.updated.json \
    --config-hash "$HASH" \
    --confirm
{
  "ok": true,
  "command": "automation.update",
  "data": {
    "success": true,
    "operation": "update",
    "resource": "automation",
    "config_hash": "sha256:96c9bd4294332433e9f51cc14f5399e83393c730a72d293efea7a1de9061083b",
    "backup_path": "/home/me/.local/state/ha-agent-cli/backups/http_homeassistant_local_8123/automation/kitchen_motion/20260718T143405Z.json",
    "verified": true,
    "issues": [],
    "meta": {}
  },
  "meta": {
    "timestamp": "2026-07-18T14:34:05+00:00",
    "duration_ms": 0
  }
}

If the automation changed after the export, the update stops with a hash mismatch instead of overwriting the newer version. Updates and deletes also save the previous configuration under ~/.local/state/ha-agent-cli/backups/.

The same workflow applies to scripts and scenes:

$ ha-agent script export bedtime
$ ha-agent script update bedtime --file bedtime.json --config-hash "$HASH"
$ ha-agent scene create reading --file reading-scene.json

These commands are dry-runs as written. Add --confirm after reviewing the returned plan.

Usage

ha-agent
├── setup                          # Configure connection
├── info                           # HA instance info
├── entity
│   ├── list [--domain <d>]        # List entities
│   ├── get <entity_id>            # Get entity state
│   ├── search <query>             # Fuzzy search entities
│   └── history <entity_id>        # State history
├── service
│   ├── list [domain]              # List services
│   ├── call <d.svc> -d '{...}'    # Call service (dry-run, add --confirm)
│   └── schema <d.svc>             # Service schema (WebSocket)
├── state
│   ├── list [domain]              # Raw state dump
│   ├── get <entity_id>            # Get state
│   └── set <entity_id> <val>      # Set state (dry-run, add --confirm)
├── automation
│   ├── list                       # List automations
│   ├── get <entity_id>            # Get runtime state
│   ├── config <id>                # Get editable configuration
│   ├── create <id>                # Create from JSON (dry-run)
│   ├── update <id>                # Update with a config hash (dry-run)
│   ├── delete <id>                # Delete with a config hash (dry-run)
│   ├── export <id>                # Export config and hash
│   ├── trigger <entity_id>        # Trigger (dry-run)
│   └── enable/disable <id>        # Enable/disable (dry-run)
├── script
│   ├── list/get                   # Inspect scripts
│   └── create/update/delete/export
├── scene
│   ├── list/get                   # Inspect scenes
│   └── create/update/delete/export
├── event
│   └── fire <type> -d '{...}'     # Fire event (dry-run)
├── log                            # Error log
├── schema <d.svc>                 # Schema shortcut
├── skill
│   ├── show                       # Print SKILL.md
│   └── install --target <agent>   # Install into agent context
├── completion <shell>             # Shell completion scripts
└── version                        # CLI + HA version

Output Formats

# JSON envelope (default)
ha-agent entity list

# Token-efficient TOON format (30-60% smaller)
ha-agent --format toon --fields entity_id,state entity list

# Raw HA API response (no envelope)
ha-agent --format raw entity list

# Field filtering + result limiting
ha-agent --fields entity_id,state,name --max-results 10 entity list

Exit Codes

Code Meaning
0 Success
1 General error (API error, invalid input)
2 Usage error (bad flags, missing args)
3 Connection error (can't reach HA)
4 Auth error (invalid/expired token)

Agent Integration

# Show the skill file
ha-agent skill show

# Install for your coding agent
ha-agent skill install --target claude    # → AGENTS.md
ha-agent skill install --target opencode  # → AGENTS.md
ha-agent skill install --target cursor    # → .cursor/rules/
ha-agent skill install --target codex     # → .github/copilot-instructions.md
ha-agent skill install --target generic   # → print to stdout

Development

git clone https://github.com/jeremychan/ha-agent-cli
cd ha-agent-cli
pip install -e ".[dev]"
ruff check .
ruff format --check .
mypy src/
pytest tests/unit/ tests/integration/ -v --tb=short --cov=src

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors