---
name: remote
description: >-
  Use this when listing or operating Remote devices over HTTPS: exec a command,
  read sensors, trigger agent update, or call OpenWrt ubus. Also when the user
  wants to log in with username/password or an operator API key and talk to
  exchange.ivjn.us (or a self-hosted Exchange) without opening a WebSocket.
---

# Remote — HTTP Agent Skill

Operate an **already-paired online Agent** over HTTPS. Not for pairing a new device. Not for interactive PTY, file transfer, or Android screen/tree/tap (those need WebSocket / Console / CLI).

## URLs

| What | URL |
| --- | --- |
| Production Exchange | `https://exchange.ivjn.us` |
| This skill | `https://remote.ivjn.us/SKILL.md` |
| Full HTTP API | `https://remote.ivjn.us/http-api.md` |
| Self-hosted | operator's HTTPS origin |

JSON `Content-Type: application/json`. Every `/api/*` except public login needs `Authorization: Bearer <token>`.

Set `BASE` to the Exchange origin (production or self-hosted).

## Credentials (critical)

**Never** print passwords, API keys, or Bearer tokens in chat, logs, or visible command lines. Prefer env:

- `REMOTE_TOKEN` — operator primary API key or session token
- `REMOTE_USER` / `REMOTE_PASSWORD` — only if no token; login once, keep token in env, never echo

If credentials are missing, ask the user / secret-input UI. Do not guess.

```bash
export BASE="${BASE:-https://exchange.ivjn.us}"
export TOKEN="$REMOTE_TOKEN"

# Password login only when REMOTE_TOKEN is unset:
# curl -sS -X POST "$BASE/api/auth/login" -H 'Content-Type: application/json' \
#   -d "{\"username\":\"$REMOTE_USER\",\"password\":\"$REMOTE_PASSWORD\"}"
# → {token, expires_in, username, tenant_id}; then: export TOKEN='…'  (do not echo)
```

`POST /api/auth/logout` revokes that session; primary API keys are **not** revoked by logout.

## List devices

```bash
curl -sS "$BASE/api/devices" -H "Authorization: Bearer $TOKEN"
```

Items include `id`, `name`, `online`, `enabled`, `info`, timestamps, `active_sessions`. Resolve a user-facing name to `id`. Ops require `online: true`. If several names match, ask. If none, say so.

## One-shot ops

Readonly operators → `403`; offline → `503`; concurrent session limit → `429`.

### Exec

```bash
curl -sS -X POST "$BASE/api/devices/$DEVICE_ID/exec" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"command":"uname -a","timeout_secs":30,"hold":true}'
# → {code, timed_out, stdout?, stderr?, job_id?}
```

`hold` defaults to **true**. With hold, `timeout_secs` `0`/omitted uses a 3600s soft timeout, hard cap 86400s; optional `hold_timeout_secs` overrides it within that cap. With `hold:false`, `timeout_secs` `0`/omitted uses the Exchange default (`LUCI_EXEC_TIMEOUT_SECS`, typically 60s), cap 600s. stdout/stderr are UTF-8 lossy, 2 MiB each.

**HTTP/operator disconnect detaches held jobs instead of killing them.** A foreground wait timeout must detach, not kill; use the returned `job_id` and the hold endpoints to inspect or explicitly terminate a job:

| Method and path | Success body |
| --- | --- |
| `GET /api/devices/{id}/exec/holds` | `{jobs:[…]}` |
| `POST /api/devices/{id}/exec/holds/{job_id}/kill` | `{job_id, killed}` |
| `GET /api/devices/{id}/exec/holds/{job_id}/cat` | `{job_id, status, stdout?, stderr?, …}` |

The kill endpoint requires explicit user confirmation. Streaming attach/tail and `detach_session` use WebSocket / Console / CLI, not additional HTTP endpoints.

### Sensors

```bash
curl -sS -X POST "$BASE/api/devices/$DEVICE_ID/sensors" \
  -H "Authorization: Bearer $TOKEN"
# → SensorReport JSON
```

### Update

```bash
curl -sS -X POST "$BASE/api/devices/$DEVICE_ID/update" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"check_only":true}'
# apply: {"check_only":false,"source":"https://…"}
# → {phase, message}; terminal: up_to_date | available | applied | error
```

### Ubus (OpenWrt only)

```bash
curl -sS -X POST "$BASE/api/devices/$DEVICE_ID/ubus" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"object":"system","method":"board","params":{}}'
# → {code, data}
```

## Safety

Destructive exec (`rm`, format, reboot, shutdown, overwrite) needs **explicit user confirmation**. Do not rotate device tokens remotely unless the user still has another way onto that machine. Do not publish secrets.

## Errors

| Status | Meaning |
| --- | --- |
| `401` | Missing/invalid token |
| `403` | Readonly / disabled |
| `400` | Empty command / bad source URL |
| `429` | Session limit |
| `503` | Offline |
| `504` | HTTP deadline |
| `502` | Device-side error |

## HTTP vs WebSocket

- **HTTP:** exec / sensors / update / ubus (this skill).
- **WebSocket:** PTY, files, Android screen — `POST /api/ws-ticket` then `GET /ws/browser` with first JSON `{"ticket":"…"}`. Do **not** implement WS here unless asked; tell the user to use Console/CLI.

Full reference: https://remote.ivjn.us/http-api.md
