# ShowenV2 HTTP API

## 基础信息

- Base URL: `http://<device-ip>:8080`
- API 前缀: `/api`
- 编码: `UTF-8`
- 认证: 当前版本局域网无认证，后续版本预留
- 权威实现: `src/plugins/http/routes.rs`

## 响应约定

### 成功响应

写操作接口统一返回：

```json
{
  "status": "ok",
  "message": "开始播放"
}
```

### 错误响应

除文件下载接口外，错误统一返回：

```json
{
  "status": "error",
  "message": "错误描述"
}
```

查询接口直接返回业务 JSON，不包裹 `status` 字段。

### 典型错误码

- `400 Bad Request`: 参数、JSON、文件名或配置内容非法
- `403 Forbidden`: 文件管理路径越界或目标目录不合法
- `404 Not Found`: 文件、目录、插件或配置不存在
- `409 Conflict`: 文件移动目标已存在
- `413 Payload Too Large`: 单文件超过 100 MB
- `500 Internal Server Error`: 内部发送消息失败、文件操作失败
- `502 Bad Gateway`: WiFi 插件返回无效 JSON
- `504 Gateway Timeout`: 等待 WiFi 插件响应超时

## Web UI

### GET /

- Method: `GET`
- Path: `/`
- Request Body: 无
- Response Example:

```html
<!doctype html>
<html lang="zh-CN">
  <head><title>Showen 控制台</title></head>
  <body>...</body>
</html>
```

- Error Response: 标准 HTTP 错误页面，无 JSON 包装

### GET /index.html

- Method: `GET`
- Path: `/index.html`
- Request Body: 无
- Response Example:

```html
<!doctype html>
<html lang="zh-CN">
  <head><title>Showen 控制台</title></head>
  <body>...</body>
</html>
```

- Error Response: 标准 HTTP 错误页面，无 JSON 包装

## WebSocket

### GET /ws

- Method: `GET` (Upgrade to WebSocket)
- Path: `/ws`
- Request Body: 无
- Response Example: 连接建立后服务端会先推送当前快照，再持续推送事件
- Error Response: WebSocket 握手失败时返回标准 HTTP 错误

### 服务端事件

#### `status_update`

```json
{
  "type": "status_update",
  "data": {
    "running": true,
    "paused": false,
    "in_transition": false,
    "current_index": 0,
    "playlist_length": 3,
    "current_video": "intro"
  }
}
```

#### `state_update`

```json
{
  "type": "state_update",
  "data": {
    "old_state": "rest",
    "new_state": "interact"
  }
}
```

#### `config_update`

```json
{
  "type": "config_update",
  "data": {
    "display": {
      "fullscreen": true,
      "window_title": "Hologram Player - Cat",
      "rotation": 0,
      "flip_horizontal": true,
      "flip_vertical": true,
      "offset_x": 0,
      "offset_y": 0,
      "prevent_screen_lock": true,
      "render_width": 1280,
      "render_height": 800,
      "output_width": null,
      "output_height": null,
      "scale_mode": "stretch",
      "allow_upscale": true,
      "perspective_correction": {
        "enabled": false,
        "points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
      },
      "chroma_key": {
        "enabled": false,
        "hsv_min": [0, 0, 200],
        "hsv_max": [180, 30, 255],
        "invert": false,
        "feather": 3
      },
      "brightness_adjust": {
        "enabled": true,
        "subject_boost": 1.5,
        "background_suppress": 0.3,
        "threshold": 30
      }
    },
    "playlist": [
      {
        "id": "anim_0",
        "path": "videos/intro.mp4",
        "duration": null,
        "loop_count": 1,
        "random_loop_range": null
      }
    ],
    "transition": {
      "enabled": true,
      "type": "fade",
      "duration": 0.5
    },
    "playback": {
      "loop_playlist": true,
      "auto_start": true
    },
    "scenes": {
      "rest": [],
      "active": [],
      "sleep": [],
      "interact": [],
      "state_machine": null
    },
    "remote_control": {
      "enabled": true,
      "host": "0.0.0.0",
      "port": 8080
    },
    "ble": {
      "enabled": true,
      "device_name": "Showen"
    },
    "source_path": "/home/showen/Showen/ShowenV2/configs/cat_state_machine.json",
    "source_dir": "/home/showen/Showen/ShowenV2/configs"
  }
}
```

#### `wifi_update`

```json
{
  "type": "wifi_update",
  "data": {
    "ok": true,
    "networks": [
      {
        "ssid": "OfficeWiFi",
        "signal": 78,
        "security": "WPA2"
      }
    ]
  }
}
```

说明：该事件直接转发 WiFi 插件返回的 JSON，字段随具体命令而变。

#### `ble_update`

```json
{
  "type": "ble_update",
  "data": {
    "ready": true
  }
}
```

### 客户端可发送命令

WebSocket 文本消息需为 JSON；成功响应格式不是 HTTP API 的 `{ "status": "ok" }`，而是 WebSocket 专用格式：

```json
{
  "ok": true,
  "cmd": "play"
}
```

错误示例：

```json
{
  "ok": false,
  "cmd": "goto",
  "error": "missing index"
}
```

已支持命令示例：

```json
{"cmd":"play"}
{"cmd":"pause"}
{"cmd":"next"}
{"cmd":"previous"}
{"cmd":"goto","index":3}
{"cmd":"scene","name":"rest"}
{"cmd":"trigger","name":"voice","value":"name"}
{"cmd":"connect","ssid":"OfficeWiFi","password":"secret"}
{"cmd":"ap_start","ssid":"showen","password":"12345678"}
```

## 播放控制

### GET /api/status

- Method: `GET`
- Path: `/api/status`
- Request Body: 无
- Response Example:

```json
{
  "running": true,
  "paused": false,
  "in_transition": false,
  "current_index": 0,
  "playlist_length": 3,
  "current_video": "intro"
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### POST /api/play

- Method: `POST`
- Path: `/api/play`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "开始播放"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/pause

- Method: `POST`
- Path: `/api/pause`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "已暂停"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/next

- Method: `POST`
- Path: `/api/next`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "切换到下一个视频"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/previous

- Method: `POST`
- Path: `/api/previous`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "切换到上一个视频"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/goto/:index

- Method: `POST`
- Path: `/api/goto/:index`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "跳转到视频 2"
}
```

- Error Response: `{ "status": "error", "message": "无效的视频索引" }` 或 `{ "status": "error", "message": "发送命令失败: ..." }`

### GET /api/playlist

- Method: `GET`
- Path: `/api/playlist`
- Request Body: 无
- Response Example:

```json
{
  "playlist": [
    {
      "id": "anim_0",
      "path": "videos/intro.mp4",
      "duration": null,
      "loop_count": 1,
      "random_loop_range": null
    },
    {
      "id": "anim_1",
      "path": "videos/idle.mp4",
      "duration": 12.5,
      "loop_count": 2,
      "random_loop_range": null
    }
  ],
  "current_index": 0
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### POST /api/scene/:name

- Method: `POST`
- Path: `/api/scene/:name`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "切换到场景: rest"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/trigger/:name

- Method: `POST`
- Path: `/api/trigger/:name`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "触发器 'voice' 已发送"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

### POST /api/trigger/:name/:value

- Method: `POST`
- Path: `/api/trigger/:name/:value`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "触发器 'voice' 已发送，值: name"
}
```

- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`

## 配置管理

### GET /api/config

- Method: `GET`
- Path: `/api/config`
- Request Body: 无
- Response Example:

```json
{
  "display": {
    "fullscreen": true,
    "window_title": "Hologram Player - Cat",
    "rotation": 0,
    "flip_horizontal": true,
    "flip_vertical": true,
    "offset_x": 0,
    "offset_y": 0,
    "prevent_screen_lock": true,
    "render_width": 1280,
    "render_height": 800,
    "output_width": null,
    "output_height": null,
    "scale_mode": "stretch",
    "allow_upscale": true,
    "perspective_correction": {
      "enabled": false,
      "points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
    },
    "chroma_key": {
      "enabled": false,
      "hsv_min": [0, 0, 200],
      "hsv_max": [180, 30, 255],
      "invert": false,
      "feather": 3
    },
    "brightness_adjust": {
      "enabled": true,
      "subject_boost": 1.5,
      "background_suppress": 0.3,
      "threshold": 30
    }
  },
  "playlist": [
    {
      "id": "anim_0",
      "path": "videos/intro.mp4",
      "duration": null,
      "loop_count": 1,
      "random_loop_range": null
    }
  ],
  "transition": {
    "enabled": true,
    "type": "fade",
    "duration": 0.5
  },
  "playback": {
    "loop_playlist": true,
    "auto_start": true
  },
  "scenes": {
    "rest": [],
    "active": [],
    "sleep": [],
    "interact": [],
    "state_machine": null
  },
  "remote_control": {
    "enabled": true,
    "host": "0.0.0.0",
    "port": 8080
  },
  "ble": {
    "enabled": true,
    "device_name": "Showen"
  },
  "source_path": "/home/showen/Showen/ShowenV2/configs/cat_state_machine.json",
  "source_dir": "/home/showen/Showen/ShowenV2/configs"
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### GET /api/config/display

- Method: `GET`
- Path: `/api/config/display`
- Request Body: 无
- Response Example:

```json
{
  "fullscreen": true,
  "window_title": "Hologram Player - Cat",
  "rotation": 0,
  "flip_horizontal": true,
  "flip_vertical": true,
  "offset_x": 0,
  "offset_y": 0,
  "prevent_screen_lock": true,
  "render_width": 1280,
  "render_height": 800,
  "output_width": null,
  "output_height": null,
  "scale_mode": "stretch",
  "allow_upscale": true,
  "perspective_correction": {
    "enabled": false,
    "points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
  },
  "chroma_key": {
    "enabled": false,
    "hsv_min": [0, 0, 200],
    "hsv_max": [180, 30, 255],
    "invert": false,
    "feather": 3
  },
  "brightness_adjust": {
    "enabled": true,
    "subject_boost": 1.5,
    "background_suppress": 0.3,
    "threshold": 30
  }
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### POST /api/config

- Method: `POST`
- Path: `/api/config`
- Request Body Example:

```json
{
  "display": {
    "fullscreen": true,
    "window_title": "Showen",
    "rotation": 0,
    "flip_horizontal": false,
    "flip_vertical": false,
    "offset_x": 0,
    "offset_y": 0,
    "prevent_screen_lock": true,
    "render_width": 1280,
    "render_height": 800,
    "output_width": null,
    "output_height": null,
    "scale_mode": "fit",
    "allow_upscale": true,
    "perspective_correction": {
      "enabled": false,
      "points": []
    },
    "chroma_key": {
      "enabled": false,
      "hsv_min": [0, 0, 200],
      "hsv_max": [180, 30, 255],
      "invert": false,
      "feather": 0
    },
    "brightness_adjust": {
      "enabled": false,
      "subject_boost": 1.5,
      "background_suppress": 0.3,
      "threshold": 30
    }
  },
  "playlist": [
    {
      "id": "intro",
      "path": "videos/intro.mp4",
      "duration": null,
      "loop_count": 1,
      "random_loop_range": null
    }
  ],
  "transition": {
    "enabled": true,
    "type": "fade",
    "duration": 0.5
  },
  "playback": {
    "loop_playlist": true,
    "auto_start": true
  },
  "scenes": {
    "rest": [],
    "active": [],
    "sleep": [],
    "interact": [],
    "state_machine": null
  },
  "remote_control": {
    "enabled": true,
    "host": "0.0.0.0",
    "port": 8080
  },
  "ble": {
    "enabled": true,
    "device_name": "Showen"
  }
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "配置已保存，热重载将自动生效"
}
```

- Error Response: `{ "status": "error", "message": "请求体不是有效的 UTF-8" }`、`{ "status": "error", "message": "配置验证失败: ..." }`、`{ "status": "error", "message": "写入配置文件失败: ..." }`

### GET /api/config/available

- Method: `GET`
- Path: `/api/config/available`
- Request Body: 无
- Response Example:

```json
{
  "configs": [
    "cat_state_machine.json",
    "dog_state_machine.json"
  ],
  "active": "cat_state_machine.json"
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### POST /api/config/switch

- Method: `POST`
- Path: `/api/config/switch`
- Request Body Example:

```json
{
  "filename": "dog_state_machine.json"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "已切换到配置: dog_state_machine.json"
}
```

- Error Response: `{ "status": "error", "message": "文件名不合法" }`、`{ "status": "error", "message": "只支持 .json 配置文件" }`、`{ "status": "error", "message": "配置文件不存在" }`、`{ "status": "error", "message": "配置验证失败: ..." }`

## 视频管理

### GET /api/videos

- Method: `GET`
- Path: `/api/videos`
- Request Body: 无
- Response Example:

```json
[
  {
    "name": "intro.mp4",
    "size": 1048576
  },
  {
    "name": "subdir/idle.mp4",
    "size": 2097152
  }
]
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### POST /api/videos/upload

- Method: `POST`
- Path: `/api/videos/upload`
- Request Body Example: `multipart/form-data`，一个或多个 `file` 字段
- Response Example:

```json
{
  "status": "ok",
  "message": "已上传 2 个文件: intro.mp4, idle.mp4"
}
```

- Error Response: `{ "status": "error", "message": "未找到上传文件" }`、`{ "status": "error", "message": "文件大小超过限制: 单文件最大 100 MB" }`、`{ "status": "error", "message": "保存文件失败: ..." }`

### DELETE /api/videos/:filename

- Method: `DELETE`
- Path: `/api/videos/:filename`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "已删除: intro.mp4"
}
```

- Error Response: `{ "status": "error", "message": "无效的文件名" }`、`{ "status": "error", "message": "文件不存在" }`、`{ "status": "error", "message": "删除失败: ..." }`

## WiFi

### GET /api/wifi/status

- Method: `GET`
- Path: `/api/wifi/status`
- Request Body: 无
- Response Example:

```json
{
  "connected": true,
  "ssid": "OfficeWiFi",
  "ip": "192.168.1.23"
}
```

- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 返回了无效 JSON: ..." }`、`{ "status": "error", "message": "WiFi 操作失败" }`

### GET /api/wifi/scan

- Method: `GET`
- Path: `/api/wifi/scan`
- Request Body: 无
- Response Example:

```json
[
  {
    "ssid": "OfficeWiFi",
    "signal": 78,
    "security": "WPA2"
  },
  {
    "ssid": "Guest",
    "signal": 42,
    "security": "open"
  }
]
```

- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 返回了无效 JSON: ..." }`

### POST /api/wifi/scan

- Method: `POST`
- Path: `/api/wifi/scan`
- Request Body: 无
- Response Example:

```json
[
  {
    "ssid": "OfficeWiFi",
    "signal": 78,
    "security": "WPA2"
  }
]
```

- Error Response: 与 `GET /api/wifi/scan` 相同

### POST /api/wifi/connect

- Method: `POST`
- Path: `/api/wifi/connect`
- Request Body Example:

```json
{
  "ssid": "OfficeWiFi",
  "password": "secret123"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "WiFi 连接成功: OfficeWiFi"
}
```

- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 操作失败" }`

### POST /api/wifi/ap/start

- Method: `POST`
- Path: `/api/wifi/ap/start`
- Request Body Example:

```json
{
  "ssid": "showen",
  "password": "12345678"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "AP 热点已启动: SSID=showen"
}
```

- Error Response: `{ "status": "error", "message": "JSON 格式错误: ..." }`、`{ "status": "error", "message": "等待 WiFi 响应超时" }`

说明：请求体可省略；默认 `ssid=showen`、`password=12345678`。

### POST /api/wifi/hotspot/start

- Method: `POST`
- Path: `/api/wifi/hotspot/start`
- Request Body Example:

```json
{
  "ssid": "showen",
  "password": "12345678"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "AP 热点已启动: SSID=showen"
}
```

- Error Response: 与 `POST /api/wifi/ap/start` 相同

说明：这是 `POST /api/wifi/ap/start` 的兼容别名。

### POST /api/wifi/ap/stop

- Method: `POST`
- Path: `/api/wifi/ap/stop`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "AP 热点已关闭"
}
```

- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 操作失败" }`

### POST /api/wifi/hotspot/stop

- Method: `POST`
- Path: `/api/wifi/hotspot/stop`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "AP 热点已关闭"
}
```

- Error Response: 与 `POST /api/wifi/ap/stop` 相同

说明：这是 `POST /api/wifi/ap/stop` 的兼容别名。

## BLE

### POST /api/ble/start

- Method: `POST`
- Path: `/api/ble/start`
- Request Body Example:

```json
{
  "device_name": "Showen"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "BLE 配网服务已内嵌运行中，设备名: Showen"
}
```

- Error Response: `{ "status": "error", "message": "JSON 格式错误: ..." }`

说明：请求体可省略；未传时使用当前配置中的 `ble.device_name`。

### POST /api/ble/stop

- Method: `POST`
- Path: `/api/ble/stop`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "BLE 配网服务随主进程运行，无需手动停止"
}
```

- Error Response: 无业务级错误 JSON

### GET /api/ble/status

- Method: `GET`
- Path: `/api/ble/status`
- Request Body: 无
- Response Example:

```json
{
  "running": true,
  "embedded": true,
  "device_name": "Showen"
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

## App 下载

### GET /api/app/info

- Method: `GET`
- Path: `/api/app/info`
- Request Body: 无
- Response Example:

```json
{
  "version": "0.1.0",
  "apk_available": true,
  "apk_size": 18432000,
  "download_url": "/download/showen-app.apk"
}
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### GET /download/:filename

- Method: `GET`
- Path: `/download/:filename`
- Request Body: 无
- Response Example: 二进制附件下载，响应头包含：

```text
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="showen-app.apk"
```

- Error Response Example:

```text
HTTP 400/404/500
text/html; charset=utf-8
无效的文件名
```

说明：该接口错误响应不是 JSON。

## 插件管理

### GET /api/plugins

- Method: `GET`
- Path: `/api/plugins`
- Request Body: 无
- Response Example:

```json
[
  {
    "id": "wifi",
    "info": {
      "name": "WiFi Plugin",
      "version": "0.1.0",
      "description": "Manage WiFi via nmcli",
      "platform": "LinuxArm64"
    },
    "is_dynamic": false,
    "error_policy": "auto_rollback",
    "error_count": 0,
    "max_errors": 5,
    "enabled": true,
    "test_results": [
      {
        "capability": "wifi_scan",
        "passed": true,
        "message": "no test defined"
      }
    ],
    "capabilities": ["wifi_scan", "wifi_connect"],
    "needs_rollback": false
  }
]
```

- Error Response: `404/405` 由路由层处理；无业务级错误 JSON

### GET /api/plugins/:id

- Method: `GET`
- Path: `/api/plugins/:id`
- Request Body: 无
- Response Example:

```json
{
  "id": "wifi",
  "info": {
    "name": "WiFi Plugin",
    "version": "0.1.0",
    "description": "Manage WiFi via nmcli",
    "platform": "LinuxArm64"
  },
  "is_dynamic": false,
  "error_policy": "auto_rollback",
  "error_count": 0,
  "max_errors": 5,
  "enabled": true,
  "test_results": [],
  "capabilities": ["wifi_scan", "wifi_connect"],
  "needs_rollback": false
}
```

- Error Response: `{ "status": "error", "message": "plugin 'wifi' not found" }`

### POST /api/plugins/:id/enable

- Method: `POST`
- Path: `/api/plugins/:id/enable`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "plugin_enable 命令已发送"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

### POST /api/plugins/:id/disable

- Method: `POST`
- Path: `/api/plugins/:id/disable`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "plugin_disable 命令已发送"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

### POST /api/plugins/:id/rollback

- Method: `POST`
- Path: `/api/plugins/:id/rollback`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "plugin_rollback 命令已发送"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

### POST /api/plugins/:id/switch

- Method: `POST`
- Path: `/api/plugins/:id/switch`
- Request Body Example:

```json
{
  "version": "1.2.3"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "版本切换请求已发送: wifi -> v1.2.3"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

### POST /api/plugins/install

- Method: `POST`
- Path: `/api/plugins/install`
- Request Body Example:

```json
{
  "id": "weather",
  "version": "0.3.0"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "安装请求已发送: weather"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

说明：`version` 可省略，省略时请求体仍需提供 `id`。

### POST /api/plugins/check-updates

- Method: `POST`
- Path: `/api/plugins/check-updates`
- Request Body: 无
- Response Example:

```json
{
  "status": "ok",
  "message": "plugin_check_updates 命令已发送"
}
```

- Error Response: `{ "status": "error", "message": "发送失败: ..." }`

## 文件管理

目录参数 `:dir` 仅支持：`videos`、`configs`、`plugins`。

### GET /api/files/:dir

- Method: `GET`
- Path: `/api/files/:dir?path=<subdir>`
- Request Body: 无
- Response Example:

```json
[
  {
    "name": "subdir",
    "is_dir": true,
    "size": 0
  },
  {
    "name": "intro.mp4",
    "is_dir": false,
    "size": 1048576
  }
]
```

- Error Response: `{ "status": "error", "message": "不支持的目录: logs（仅支持 videos/configs/plugins）" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "目录不存在" }`

### POST /api/files/:dir/upload

- Method: `POST`
- Path: `/api/files/:dir/upload?path=<subdir>`
- Request Body Example: `multipart/form-data`，一个或多个 `file` 字段
- Response Example:

```json
{
  "status": "ok",
  "message": "已上传 1 个文件: intro.mp4"
}
```

- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "目标目录不合法" }`、`{ "status": "error", "message": "未找到上传文件" }`、`{ "status": "error", "message": "文件大小超过限制: 单文件最大 100 MB" }`

### GET /api/files/:dir/download

- Method: `GET`
- Path: `/api/files/:dir/download?path=<file>`
- Request Body: 无
- Response Example: 二进制附件下载，响应头包含：

```text
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="intro.mp4"
```

- Error Response Example:

```text
HTTP 400/403/404
text/html; charset=utf-8
缺少 path 参数
```

说明：该接口错误响应不是 JSON。

### POST /api/files/:dir/delete

- Method: `POST`
- Path: `/api/files/:dir/delete`
- Request Body Example:

```json
{
  "path": "subdir/intro.mp4"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "已删除: subdir/intro.mp4"
}
```

- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "缺少 path" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "文件不存在" }`

### POST /api/files/move

- Method: `POST`
- Path: `/api/files/move`
- Request Body Example:

```json
{
  "from_dir": "videos",
  "from_path": "intro.mp4",
  "to_dir": "videos",
  "to_path": "archive/intro.mp4"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "已移动: intro.mp4 → archive/intro.mp4"
}
```

- Error Response: `{ "status": "error", "message": "源目录不支持" }`、`{ "status": "error", "message": "目标目录不支持" }`、`{ "status": "error", "message": "源路径不合法" }`、`{ "status": "error", "message": "目标路径已存在" }`

### POST /api/files/:dir/mkdir

- Method: `POST`
- Path: `/api/files/:dir/mkdir`
- Request Body Example:

```json
{
  "path": "archive"
}
```

- Response Example:

```json
{
  "status": "ok",
  "message": "已创建目录: archive"
}
```

- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "缺少 path" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "创建失败: ..." }`

## 已移除的旧文档端点

以下端点当前实现中不存在，不应再使用：

- `/api/stop`
- `/api/wifi/disconnect`
