# grix-connector

A command-line daemon that connects your local AI coding agents to the [Grix](https://grix.im) platform.

## What is Grix?

Grix is an AI Agent scheduling platform. It lets you manage and interact with multiple AI coding agents through a unified chat interface.

> **Reading this as an AI agent doing the install?** Everything here is terminal-only. Account registration included — you collect the user's email and the emailed verification code in chat and call the API yourself ([step 1](#1-register-a-grix-account-and-create-an-agent)). Never send the user off to a website to sign up.

## Get the Client

After installing grix-connector, download the Grix client from [grix.im](https://grix.im) to chat with your agents. Clients are available for iOS, Android, macOS, Windows, and Linux.

## Supported Agents

Set `client_type` in your config to one of the values below. Each `client_type` maps to a built-in adapter and CLI command — you only need the corresponding CLI installed locally.

| `client_type` | Agent | Adapter | Required CLI |
|---|---|---|---|
| `claude` | Claude Code (Anthropic) | claude | `claude` |
| `codex` | Codex (OpenAI) | codex | `codex` |
| `gemini` | Gemini (Google) | acp | `gemini` |
| `qwen` | Qwen (Alibaba) | acp | `qwen` |
| `copilot` | GitHub Copilot | acp | `copilot` or `gh` |
| `kiro` | Kiro | acp | `kiro-cli` |
| `reasonix` | Reasonix | acp | `reasonix` |
| `cursor` | Cursor Agent | cursor | `agent` |
| `codewhale` | CodeWhale | codewhale | `codewhale` |
| `opencode` | OpenCode | opencode | `opencode` |
| `pi` | Pi | pi | `pi` |
| `openhuman` | OpenHuman | openhuman | `openhuman-core` |
| `agy` | Agy (Antigravity) | agy | `agy` |
| `hermes` | [Hermes](https://github.com/askie/grix-hermes-python) | external | `hermes` |

The ACP adapter (Agent Client Protocol over JSON-RPC) backs Gemini, Qwen, Copilot, Kiro and Reasonix. Hermes is an external agent maintained in a separate project — see [grix-hermes-python](https://github.com/askie/grix-hermes-python) for setup.

You need to have the corresponding CLI tool installed locally before connecting an agent.

## Install

```bash
npm install -g grix-connector
```

Requires Node.js >= 18.

On Windows, `grix-connector` uses the built-in Task Scheduler with a hidden WScript launcher (no extra dependency required).

### After the install: you still need a Grix account

Installing the CLI is not enough — the daemon refuses to start without a Grix account and an agent credential. The install is step 0 of four:

1. **Register a Grix account.** This is where you need the user: **ask them for an email address**, because a verification code is mailed to it and they have to read it back to you. → [step 1](#1-register-a-grix-account-and-create-an-agent)
2. Create the agent and collect `agent_id` / `api_key` / `ws_url`. → [step 1.4](#14-create-the-api-agent)
3. Write `~/.grix/config/agents.json`. → [step 2](#2-create-agent-config)
4. `grix-connector start`. → [step 3](#3-start-the-daemon)

So the very first thing to ask the user for, right after the install, is **an email address**.

The registration flow is also packaged as a skill, `grix-register`. In OpenClaw plugin mode it is loaded automatically; after a global npm install any agent can read it directly:

```bash
cat "$(npm root -g)/grix-connector/openclaw-plugin/skills/grix-register/SKILL.md"
```

Running that skill and following step 1 below are the same thing — the same API calls, described once as a skill and once as plain curl. Use whichever your runtime supports.

## Quick Start

### 1. Register a Grix account and create an agent

This whole step is HTTP API calls — no browser, no dashboard. The user only has to give you two things in chat: **their email address**, and **the 6-digit verification code** that lands in their inbox. You do the rest.

**API base:** `https://grix.dhf.pub/v1` — this is the auth/agent API for both regions (`ws.grix.im` serves the WebSocket only, it has no auth routes). A private deployment replaces the host.

**Response envelope:** every endpoint returns `{"code": 0, "msg": "success", "data": {...}}`. A HTTP 200 with a non-zero `code` is still a failure — always branch on `code`, not on the HTTP status.

#### 1.1 Ask for the email, send the verification code

Ask the user for the email address they want the account under, then:

```bash
curl -s -X POST https://grix.dhf.pub/v1/auth/send-code \
  -H 'Content-Type: application/json' \
  -d '{"email":"<user-email>","scene":"register"}'
```

On `code: 0`, tell the user to check their inbox (and the spam folder) and ask them to paste the 6-digit code back to you.

| `code` | Meaning | What to do |
|---|---|---|
| `10003` | Invalid parameter — the email failed validation | Re-check the address with the user |
| `10005` | Rate limited: a code was already sent | Do **not** retry in a loop. Tell the user to wait ~5 minutes, or ask them for the code that was already sent |

#### 1.2 Register

**Generate the password yourself** — do not make the user invent one. Use a random 12+ character password mixing upper case, lower case, digits and symbols. Then:

```bash
curl -s -X POST https://grix.dhf.pub/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "<user-email>",
    "password": "<generated-password>",
    "email_code": "<code the user gave you>",
    "device_id": "cli_<random-uuid>",
    "platform": "cli"
  }'
```

Returns `data.access_token` (plus `refresh_token`, `expires_in`, `user.id`). Keep the token in memory for the next call.

**After a successful registration, show the user the generated password and tell them to save it** — it is their account password and it is not recoverable from anywhere else.

Failures you will actually hit — note that both of them come back as `code: 10001`, so **read the `msg`, not just the code**:

| `msg` | Meaning | What to do |
|---|---|---|
| `邮箱验证码错误或已过期` (code wrong or expired) | The code really is wrong | Ask the user to re-read it, or re-send it (1.1) |
| `注册失败，请检查邮箱验证码后重试` (registration failed, check the code) | **Almost always: the email is already registered.** The message blames the code, but the code already passed validation | Do **not** re-send the code in a loop — go to 1.3 and log in |

#### 1.3 Existing account — log in instead

Only when the user **gives you their existing password**:

```bash
curl -s -X POST https://grix.dhf.pub/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"account":"<user-email>","password":"<password>","device_id":"cli_<random-uuid>","platform":"cli"}'
```

Same `data.access_token` as register. If the user does not remember their password, do not guess and do not pretend to recover it — ask them to reset it in the Grix client, then come back with the password.

#### 1.4 Create the API agent

This is where `agent_id` and `api_key` come from. Ask the user what to name the agent (or reuse a name already agreed in the conversation):

```bash
curl -s -X POST https://grix.dhf.pub/v1/agents/create \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <access_token>' \
  -d '{"agent_name":"<agent-name>","provider_type":3,"is_main":true}'
```

`provider_type: 3` is the Agent API type — the only type this connector can drive. `is_main: true` gives the first agent the full initial permission scope.

Returns in `data`:

| Field | Use |
|---|---|
| `id` | the `agent_id` for your config |
| `api_key` | the `api_key` for your config — **shown exactly once, it is never retrievable again** |
| `api_endpoint` | the `ws_url` for your config |
| `api_key_hint` | last few characters, for later identification |

**If the name is already taken** by a `provider_type: 3` agent, reuse it instead of inventing a new name — list the agents, find the exact-name entry with `provider_type == 3` and `status != 3`, and rotate its key:

```bash
curl -s -H 'Authorization: Bearer <access_token>' https://grix.dhf.pub/v1/agents/list

curl -s -X POST -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  https://grix.dhf.pub/v1/agents/<agent-id>/api/key/rotate -d '{}'
```

Rotation returns a fresh `api_key` and the same `api_endpoint`. The old key stops working immediately, so only rotate an agent the user actually wants to re-point at this machine.

#### 1.5 Map the fields into the config

| From the API | Into `agents.json` |
|---|---|
| `data.id` | `agent_id` |
| `data.api_key` | `api_key` |
| `data.api_endpoint` | `ws_url` — drop any `?agent_id=…` query string, keep only the part before `?` |

If `api_endpoint` comes back empty, fall back to the regional URL in step 2. Write the `api_key` into `~/.grix/config/agents.json` and nowhere else — never into a log, a shell history file, or a repository.

### 2. Create agent config

Create `~/.grix/config/agents.json`. Choose the `ws_url` for your region — the two regions use different WebSocket domains:

- China mainland: `wss://grix.dhf.pub/v1/agent-api/ws`
- Global: `wss://ws.grix.im/v1/agent-api/ws`

China mainland example:

```json
{
  "agents": [
    {
      "name": "my-agent",
      "ws_url": "wss://grix.dhf.pub/v1/agent-api/ws",
      "agent_id": "your-agent-id",
      "api_key": "your-grix-api-key",
      "client_type": "claude"
    }
  ]
}
```

Global example:

```json
{
  "agents": [
    {
      "name": "my-agent",
      "ws_url": "wss://ws.grix.im/v1/agent-api/ws",
      "agent_id": "your-agent-id",
      "api_key": "your-grix-api-key",
      "client_type": "claude"
    }
  ]
}
```

Change `client_type` to match the agent you want to connect (see table above). You can define multiple agents in one file, or use separate files under `~/.grix/config/`.

### Config Reference

Each agent entry uses one flat structure:

| Field | Required | Description |
|---|---|---|
| `name` | yes | Display name for this agent |
| `ws_url` | yes | WebSocket endpoint URL (region-specific). China mainland: `wss://grix.dhf.pub/v1/agent-api/ws`; Global: `wss://ws.grix.im/v1/agent-api/ws` |
| `agent_id` | yes | Agent ID from Grix platform |
| `api_key` | yes | API key for authentication |
| `client_type` | yes | See Supported Agents table above |
| `prompt_timeout_ms` | no | Prompt execution timeout (ms) |
| `pool.maxSize` | no | Max adapter pool size (default 20) |
| `pool.idleTimeoutMs` | no | Idle adapter eviction timeout (default 300000 = 5 min) |

Adapter command/args/options are built in and resolved from `client_type`. To connect a different agent, simply change `client_type` — no other config changes needed.

### Multi-agent Example

China mainland region (swap the `ws_url` domain to `ws.grix.im` for the global region):

```json
{
  "agents": [
    {
      "name": "my-claude",
      "ws_url": "wss://grix.dhf.pub/v1/agent-api/ws",
      "agent_id": "your-agent-id",
      "api_key": "your-grix-api-key",
      "client_type": "claude"
    },
    {
      "name": "my-gemini",
      "ws_url": "wss://grix.dhf.pub/v1/agent-api/ws",
      "agent_id": "another-agent-id",
      "api_key": "your-grix-api-key",
      "client_type": "gemini"
    }
  ]
}
```

### 3. Start the daemon

```bash
grix-connector start
```

The daemon connects to Grix via WebSocket and starts routing chat messages to your agents.

> The daemon refuses to start when the config directory holds no valid agent config, so on a first-time setup the config file must be written **before** `start`.

### Adding an agent to an existing setup

If `~/.grix/config/agents.json` already exists and other agents are running, **do not overwrite the file** — merge the new entry into it.

**1. Merge the entry.** Read the file as JSON and look through the `agents` array for an entry whose `agent_id` matches the one you are adding:

- found → replace that entry
- not found → append the new entry
- every other entry must be left exactly as it was

Edit it with a script (`node`, `python3`), not by hand — a truncated or re-indented file silently drops the agents you did not intend to touch. If the file does not exist yet, create it with a single-element `agents` array.

**2. Apply the change.** Check whether the daemon is already running:

```bash
grix-connector status
```

- not running (or it reports `daemon is not running`) → `grix-connector start`
- already running → `grix-connector reload`

`reload` hot-loads the new agent and leaves every already-running agent untouched — their live sessions are not interrupted. Do not use `restart` to add an agent: it reconnects everything and drops all in-flight conversations.

**3. Verify.** `grix-connector status` reports the daemon only — it does not list agents. To confirm the new agent is connected, query the local admin API (give it a few seconds after `start`/`reload`):

```bash
curl -s http://127.0.0.1:19580/api/agents
```

The entry for your agent should report `"alive": true`. `19580` is the default admin port; if it was overridden (`--admin-port` or `GRIX_ADMIN_PORT`), the port actually in use is written to `~/.grix/data/admin-port`.

If it never connects, read the newest log under `~/.grix/log/`. In practice it is almost always one of three things:

- the CLI for that `client_type` is not on `PATH`
- the CLI is installed but not logged in
- the `api_key` was truncated when it was copied

**Credentials.** `api_key` is a one-time secret: write it into `~/.grix/config/agents.json` and nowhere else. Do not echo it into logs or commit it to a repository.

### Commands

```bash
grix-connector start     # Start as system service (auto-installs on first run)
grix-connector stop      # Stop the service
grix-connector restart   # Restart the service
grix-connector reload    # Hot-reload agent configs without restarting the daemon
grix-connector status    # Check service status
```

### Reloading config (`reload`)

`reload` applies changes to your config files **without restarting the daemon**. It re-reads `~/.grix/config/*.json`, diffs against what's currently running, and acts per agent:

| Change in config | What happens |
|---|---|
| Agent added | Started and connected |
| Agent removed | Stopped and disconnected |
| Agent config changed | That agent restarts (stop old, start new) |
| Agent unchanged | **Left running untouched** — its sessions are not interrupted |

**When to use it.** You run a multi-agent setup and want to add, remove, or re-key one agent — or tweak one agent's settings — without dropping the live sessions of the *other* agents. A full `restart` would reconnect every agent and interrupt all in-flight conversations; `reload` only touches the agents whose config actually changed.

**Safety.** If any config file fails to parse (e.g. you're mid-edit and the JSON is broken), or no valid agent config is found, the reload aborts and the running agents are left exactly as they are — nothing is torn down on a bad config.

Three equivalent ways to trigger it:

```bash
grix-connector reload                          # CLI (sends SIGHUP to the daemon)
kill -HUP "$(cat ~/.grix/grix-acp.pid)"        # raw signal (Unix)
curl -XPOST http://127.0.0.1:19580/api/reload  # Admin API — returns the per-agent result as JSON
```

The CLI/signal form is fire-and-forget; the per-agent result is written to the daemon log. The Admin API form returns the result (added/removed/restarted/unchanged/failed) synchronously.

### Ports

The daemon binds two local loopback ports (127.0.0.1 only):

| Purpose | Default | Override (env) | Override (CLI) |
|---|---|---|---|
| Health check (`/healthz`) | `19579` | `GRIX_HEALTH_PORT` | `--health-port <port>` |
| Admin API (used by the local CLI) | `19580` | `GRIX_ADMIN_PORT` | `--admin-port <port>` |

If a port is already in use, the daemon refuses to start and writes a clear message to `~/.grix/service/daemon.err.log` and the main log, and marks `~/.grix/daemon-status.json` as `state: "failed"` with a `reason` like `port_bind_in_use:health:19579`.

To pick different ports:

```bash
# via environment
GRIX_HEALTH_PORT=29579 GRIX_ADMIN_PORT=29580 grix-connector restart

# or via CLI flags (when running the daemon directly)
grix-connector --health-port 29579 --admin-port 29580
```

To find what is occupying a port:

```bash
# macOS / Linux
lsof -nP -iTCP:19579 -sTCP:LISTEN

# Windows (PowerShell or cmd)
netstat -ano | findstr :19579
```

## OpenClaw Plugin

grix-connector can also be installed as an [OpenClaw](https://openclaw.io) plugin, providing a Grix channel transport with admin tools and operator CLI.

### Install

```bash
openclaw plugin install grix-connector
```

Or manually add to your OpenClaw project:

```bash
npm install grix-connector
```

### Connecting an agent (plugin mode)

Plugin mode does **not** use `~/.grix/config/agents.json`. The credentials live in OpenClaw's own config (`openclaw.json`) under `channels.grix`, and the field names are camelCase — `wsUrl` / `agentId` / `apiKey` — not the snake_case names used by the daemon.

Write them with `openclaw config set`. **Do not hand-edit `openclaw.json`.**

```bash
openclaw config set channels.grix.accounts.<account-id> \
  '{"name":"my-agent","enabled":true,"wsUrl":"wss://grix.dhf.pub/v1/agent-api/ws","agentId":"your-agent-id","apiKey":"your-grix-api-key"}' \
  --strict-json
```

`<account-id>` is a name you choose (lowercase letters, digits, hyphens). Use the `ws.grix.im` domain instead for the global region. If `channels.grix.enabled` was explicitly turned off before, also run `openclaw config set channels.grix.enabled true --strict-json`.

Then check it:

```bash
openclaw config validate
openclaw grix doctor      # the account should report configured: true, enabled: true
```

Finally send a message to the agent from Grix and confirm the reply really comes from it. Only if the config validates but messages do not route should you run `openclaw gateway restart` — once — and then re-send the message.

### Plugin Features

- **Channel**: Grix chat transport — routes messages between OpenClaw and your Grix deployment
- **Tools**: `grix_query`, `grix_group`, `grix_admin`, `grix_egg`, `grix_register`, `grix_update`, `grix_message_send`, `grix_message_unsend`, `openclaw_memory_setup`
- **CLI**: `openclaw grix doctor` — local diagnostics (the only subcommand; configuration goes through `openclaw config set`)
- **Skills**: 9 bundled skills for admin, group, query, registration, update, messaging, memory setup, and egg orchestration

### Requirements

- OpenClaw >= 2026.4.8
- A Grix account with agent ID and API key — get them with the API flow in [step 1](#1-register-a-grix-account-and-create-an-agent); it works the same in plugin mode

Plugin mode and the standalone daemon can coexist on one machine — they use separate configs and separate processes. Do not, however, bring the **same** `agent_id` online in both at once; give each mode its own agent.

## License

MIT
