Metadata-Version: 2.4
Name: liauto
Version: 0.1.0b3
Summary: Unofficial CLI for LiAuto/LiXiang vehicles
Author: licli contributors
License: Proprietary (see LICENSE)
Keywords: lixiang,liauto,car,vehicle
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Go
Classifier: Topic :: Utilities
Requires-Python: >=3.0
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# licli

licli is a CLI for Li Auto (LiXiang) vehicle APIs. It covers auth, real-time status, travel history, control commands, an HTTP API server, and an MCP server.

Run it without installing anything (PyPI via `uvx`, or npm via `npx`):

```bash
alias licli='uvx liauto'
licli help

# or

alias licli='npx -y @liauto/cli'
licli version
```

## Commands

```bash
licli activate          # activate the current device (first activation or re-activation)
licli login             # device login, prints an OAuth URL, then persists the pasted token JSON
licli vehicles          # list cached vehicles
licli status <VIN>      # query vehicle state (--json/--raw)
licli travel <VIN> [YYYY-MM]   # monthly travel history; with YYYY-MM, that month's detail
licli control <VIN> <action>   # lock/unlock/find/wake + ac <on|off|auto|temp|fast-cool|fast-heat> + seat-vent/seat-heat [pos] <1-3|off> + wheel-heat <on|off> + window [pos] <0-99> + trunk <open|close> (action: result <id>)
licli version           # print version
licli serve             # HTTP API server (default 127.0.0.1:18001; --addr/--token/--allow-control)
licli mcp               # MCP server, stdio (default) or --transport http (default 127.0.0.1:18002)
```

Run `licli` with no arguments (or `licli <unknown>`) to print the command list.

Global flags: `--json` (JSON output), `-v` / `--verbose` (verbose logging to stderr).

Exit codes: `0` success, `1` user input error, `2` network failure, `3` HTTP non-200, `4` activation failure, `5` config permission/corrupt, `6` missing/invalid/expired activation code.

## Activation code

Running any licli command except `version` requires a valid activation code, set as an
environment variable:

```bash
export LICLI_CODE=<activation code>
licli login
licli vehicles    # list vehicles
```

A code is issued by the tool's publisher (offline `license-keygen`). It is an
Ed25519-signed token: the binary only verifies it with the built-in public key and
cannot forge codes. Missing/invalid/expired codes exit with code 6.

## HTTP API server (`serve`)

`licli serve` runs a foreground HTTP server (default `127.0.0.1:18001`, override with `--addr`). Bind to a loopback address, set `--token`, or pass `--allow-control` to enable the control endpoints.

Endpoints:

| Method | Path | Description |
| --- | --- | --- |
| GET | `/healthz` | Liveness probe (unauthenticated) |
| GET | `/vehicles` | List vehicles; query `limit` (default 20), `offset`, `refresh=true` |
| GET | `/vehicles/{vin}/status` | Vehicle state (`state`, `platform`, `fetched_at`, `location` when available) |
| GET | `/vehicles/{vin}/travel` | Travel month history (`months`) |
| GET | `/vehicles/{vin}/travel/{ym}` | One month's detail; `{ym}` is `YYYY-MM` |
| POST | `/vehicles/{vin}/control` | Send a control command |
| GET | `/vehicles/{vin}/control/{requestId}` | Query a prior command's result |

All endpoints except `/healthz` require `Authorization: Bearer <token>` when a token is configured. The two `control` endpoints return `403 control_disabled` unless the control gate is open (loopback bind, a configured token, or `--allow-control`).

`POST /control` body (strict JSON, unknown fields rejected):

```json
{ "action": "lock", "temp": 22.5, "pos": "front-left", "level": 2, "open": true }
```

`action` is one of `lock`, `unlock`, `find`, `wake`, `ac_on`, `ac_off`, `ac_auto`, `ac_temp`, `ac_fast_cool`, `ac_fast_heat`, `seat_vent`, `seat_heat`, `wheel_heat`, `window`, `trunk`. The other fields are per-action; unset fields take the action's default:

| Field | Applies to | Values |
| --- | --- | --- |
| `temp` | `ac_temp` | number, 16–32 |
| `pos` | `seat_vent` | `fl` \| `fr` |
| `pos` | `seat_heat` | `fl` \| `fr` \| `rl` \| `rr` |
| `pos` | `window` | `fl` \| `fr` \| `rl` \| `rr` \| `all` |
| `level` | `seat_vent`, `seat_heat` | int, 0–3 (0 = off) |
| `level` | `window` | int, 0–99 |
| `open` | `trunk` | bool |

`pos` is only valid for `seat_vent`/`seat_heat`/`window`; any other action rejects it. Errors use a unified envelope `{"ok": false, "error": {"code", "message", "retryable"}}`.

## MCP server (`mcp`)

`licli mcp` serves the [Model Context Protocol](https://modelcontextprotocol.io) over stdio (default) or Streamable HTTP (`--transport http`, default `127.0.0.1:18002`). It exposes six tools: `vehicles`, `status`, `travel_months`, `travel_month`, `control`, `control_result`. The two `control*` tools are enabled by default under stdio; under `--transport http` they require `--allow-control` or `--token`.

Register it with Claude Code (no install needed):

```bash
claude mcp add licli -- uvx liauto mcp
```

## Configuration

Credentials are stored under the XDG config directory (`~/.config/licli` by default, or `$XDG_CONFIG_HOME/licli`) with `0700` directory and `0600` file permissions:

- `device.json` — activated device + key suite
- `auth.json` — API token, ID token, refresh token, expiry

## Docker Compose deployment

Run the HTTP API server (`serve`) and MCP server (`mcp`) in containers sharing the host config:

```yaml
services:
  licli-api: &licli
    build:
      dockerfile_inline: |
        FROM ghcr.io/astral-sh/uv:python3.13-alpine
        RUN uv tool install liauto
        ENV PATH="/root/.local/bin:$${PATH}"
        ENV XDG_CONFIG_HOME=/config
        ENV LICLI_SERVE_ADDR=0.0.0.0:18001
        ENV LICLI_MCP_ADDR=0.0.0.0:18002
        ENV LICLI_MCP_TRANSPORT=http
        ENTRYPOINT ["liauto"]
    command: ["serve", "--allow-control"]
    ports:
      - "18001:18001"
    environment:
      LICLI_CODE: ${LICLI_CODE:-}
      LICLI_SERVE_TOKEN: ${LICLI_SERVE_TOKEN:-}
    volumes:
      - ${LICLI_CONFIG_DIR:-~/.config/licli}:/config/licli
    restart: unless-stopped

  licli-mcp:
    <<: *licli
    command: ["mcp", "--allow-control"]
    ports:
      - "18002:18002"
```

`licli-api` shares its build, env, volume and restart policy with `licli-mcp` via the `&licli` anchor. Both read the same host credentials (`auth.json` / `device.json` under `~/.config/licli`, or `$LICLI_CONFIG_DIR`). The containers bind non-loopback addresses, so `--allow-control` enables the control endpoints and `LICLI_SERVE_TOKEN` gates them behind a bearer token.
