# KSword 独立命令行插件规范（v1）

## 目标与边界

插件是 `plugin\<plugin-id>\` 下的独立程序，不是 DLL，也不会被
Ksword 进程加载。Ksword GUI 只读取清单、验证入口仍在插件目录内，然后用
`QProcess` 直接启动该入口程序。它不会经过 `KswordCLI.exe`，也不会把插件代码、
Python、ONNX Runtime 或模型加载进 Ksword 进程。

只安装可信插件。进程隔离避免插件崩溃或依赖冲突影响 Ksword 本体，但任何
本地可执行插件本质上仍能以当前用户权限运行。

## 目录与发现

```text
plugin/
  file-analysis/
    plugin.json
    scanner.exe
    scanner.py
    requirements.txt
    Pefile_General_T1.onnx
    features.json
    LICENSE.txt
    NOTICE
```

宿主按以下优先级查找根目录：

1. 环境变量 `KSWORD_PLUGIN_ROOT`；
2. 当前工作目录下的 `plugin\`；
3. `Ksword5.1.exe` 所在目录及其最多六级父目录下的 `plugin\`。

KSword 发行包**不预装第三方插件载荷**：不要把模型、Python 依赖、插件 EXE 或
`plugin\` 目录复制进 `Release\`、Qt 资源、DLL 或驱动。这样插件模型不会增大
KSword 安装包；插件由商城在用户明确同意许可证后按需安装。

首次一键安装时，宿主会优先使用 `KSWORD_PLUGIN_ROOT`；未设置时会在
`Ksword5.1.exe` 同级创建 `plugin\`，并安装为
`plugin\<install_directory>\`。已存在的插件根目录仍按前述发现顺序使用。

## 清单：`plugin.json`

清单必须是 UTF-8、最大 64 KiB 的 JSON 对象。v1 宿主读取并要求以下顶层字段；
额外字段可以存在，供插件自行使用。

| 字段 | 要求 |
| --- | --- |
| `ksword_plugin_api` | 固定为字符串 `"1"`。 |
| `id` | 与目录名相同；仅小写 ASCII 字母、数字和连字符，最多 64 字符。 |
| `name`、`version`、`description` | 用户可见元数据。 |
| `plugin_type` | 可选；默认 `command`。`tab` 表示进程隔离的原生 Tab 插件；`hybrid` 同时提供命令入口和 Tab。 |
| `runtime` | `python` 或 `executable`。 |
| `entrypoint` | 插件目录内的相对文件路径；不能是绝对路径、不能含 `.`/`..` 路径段。 |
| `default_command` | 宿主传给入口的默认子命令，不能含路径穿越。 |
| `targets` | 命令型插件只能包含 `file`、`process` 和/或 `network`；Tab 型插件必须且只能包含 `tab`；Hybrid 型插件必须包含 `tab` 和至少一个命令目标。 |
| `visualization` | 可选的结构化可视化规范；扫描型插件使用 `scan-table`，见下文。 |
| `tab` | `plugin_type: "tab"` 时必需；定义标题、握手事件与启动超时，见下文。 |

`python` 运行时优先使用 `KSWORD_PLUGIN_PYTHON`，其次查找 `python.exe`，最后
使用 `py.exe -3`。Python 依赖必须随插件以 `requirements.txt` 说明，不能要求
Ksword 本体嵌入 Python。

商城发布版本推荐使用 `runtime: "executable"`，并把可直接运行的 EXE 设为
`entrypoint`；Python 文件可以作为源码一同发布，但不应让普通用户配置 Python
环境。`python` runtime 仅用于开发插件或确实需要外部解释器的插件。

## 启动协议

Ksword 直接启动清单入口。对 `runtime: "python"` 且 `default_command: "scan"` 的插件，实际命令等价于：

```text
python.exe <entrypoint> --ksword-plugin scan -- <plugin-arguments...>
```

对 `runtime: "executable"`，Ksword 直接运行 `<entrypoint>` 并追加相同参数。Python 运行时依次查找 `KSWORD_PLUGIN_PYTHON`、`python.exe`、`py.exe -3`。

`--` 后面的参数由宿主原样传递，包含插件自己的 `--help`。插件必须把
`--ksword-plugin <command> -- ...` 视为非交互模式：不得等待 stdin，不得显示
GUI，也不得把人类横幅混入 stdout。

## Ksword 图形界面入口

GUI 中插件一律按“**插件 → <插件名>**”显示，名称和可用目标直接来自 `plugin.json`，没有针对某个插件的硬编码菜单。

- 文件管理器：选择单个常规文件后，从 **插件 → <支持 `file` 的插件>** 调用。
- 进程详细信息：打开任一进程的 **插件** 页，从 **插件 → <支持 `process` 的插件>** 调用。
- 网络流量监控：从控制栏的 **插件 → <支持 `network` 的插件>** 调用；网络插件在独立进程内自行采集流量，宿主不向插件 stdin 转发报文。
- 顶部功能条：**插件管理** 按钮打开“插件管理”。它可重新扫描、查看只读清单详情、打开插件目录，并从商城一键安装独立插件。

文件目标的 GUI 调用示例：

```text
<entrypoint> --ksword-plugin scan -- --target-kind file --path <selected-file>
```

进程目标使用稳定上下文参数；插件不得把 PID 当作内核地址或命令执行载荷：

```text
<entrypoint> --ksword-plugin <default-command> -- --target-kind process --pid <pid> --path <image-path> --process-name <name>
```

网络目标不附带文件或进程参数：

```text
<entrypoint> --ksword-plugin <default-command> -- --target-kind network
```

因此 GUI 只通过命令行协议调用插件：Ksword 直接读取本地清单并启动入口程序，不会导入 Python、ONNX Runtime 或任何插件代码。查找顺序为 `KSWORD_PLUGIN_ROOT`、当前工作目录、主程序同目录的发行布局及源码树的开发布局。启动失败时 GUI 会给出清单、入口文件或运行时的明确提示。

## 扫描型插件可视化

扫描插件不应要求 KSword 理解某个插件的专有字段。宿主根据清单中的
`visualization` 对标准 JSON Lines 事件进行通用渲染：进度条、结果表、汇总项、
错误状态和诊断页均由规范驱动。

```json
{
  "visualization": {
    "type": "scan-table",
    "title": "文件安全扫描",
    "start_event": "scan_started",
    "result_event": "file_result",
    "complete_event": "scan_complete",
    "total_field": "total_files",
    "columns": [
      {
        "field": "result",
        "label": "判定",
        "format": "badge",
        "values": {
          "safe": { "label": "安全", "tone": "success" },
          "malware": { "label": "恶意", "tone": "danger" },
          "error": { "label": "错误", "tone": "warning" }
        }
      },
      { "field": "probability", "label": "风险概率", "format": "percent" },
      { "field": "path", "label": "路径", "format": "path" },
      { "field": "message", "label": "详情", "format": "text" }
    ],
    "summary": [
      { "field": "total", "label": "总计", "format": "integer" },
      { "field": "safe", "label": "安全", "format": "integer" },
      { "field": "malware", "label": "恶意", "format": "integer" },
      { "field": "errors", "label": "错误", "format": "integer" }
    ]
  }
}
```

### 字段约束

- `type`：v1 仅支持 `scan-table`。
- `title`：结果窗口标题，最多 96 个字符。
- `start_event`：开始事件；宿主从 `total_field` 读取总任务数并初始化进度条。
- `result_event`：每收到一次便新增一行并推进进度。
- `complete_event`：完成事件；宿主从该事件读取 `summary` 声明的字段。
- `columns`：1–8 项；每项包含 `field`、`label`、`format`。
- `summary`：可选，最多 8 项。
- 字段名和事件名必须以 ASCII 字母或下划线开头，只能包含字母、数字、
  下划线、点和连字符，最长 64 个字符。

`format` 支持：

- `text`：普通文本；
- `path`：路径文本，长内容提供完整工具提示；
- `percent`：数值在 `[-1, 1]` 时按比例乘以 100 后显示百分比；
- `integer`：整数；
- `number`：通用数值；
- `badge`：状态标签，必须提供 `values` 映射。

`badge.values` 将插件原始值映射为用户可见的 `label` 和颜色语义 `tone`。
`tone` 仅允许 `success`、`danger`、`warning`、`info`、`muted`。这使宿主无需
硬编码 `safe`、`malware` 等任何插件专有值。

### 运行时行为

每个 stdout 事件仍必须包含正确的 `protocol`、`plugin_id` 和 `event`。宿主会
逐行增量解析，不等待插件退出；非 JSON、协议不匹配、`plugin_id` 不匹配、
`error` 事件或缺失声明的完成事件都会把运行标记为失败并显示在“诊断”页。
stderr 只进入诊断页，不会再与 JSON 原文一起弹出消息框。扫描窗口最多显示
10000 行结果，单条无换行 stdout 缓冲最大 1 MiB，并支持用户取消正在运行的插件。

没有 `visualization` 的插件仍可运行，但只显示通用输出和诊断页；符合
`scan-table` 规范的插件会获得完整的结构化扫描界面。

## Tab 型插件

Tab 型插件用于把独立进程提供的原生 Windows 子窗口嵌入 KSword 顶部独立的“插件”页。
插件代码仍不会被加载进 KSword 进程；宿主只创建一个 HWND 容器、启动入口程序、
校验握手事件，再调整插件子窗口大小。清单示例：

```json
{
  "ksword_plugin_api": "1",
  "id": "example-tab",
  "name": "Example Tab",
  "version": "1.0.0",
  "description": "Example process-isolated tab plugin.",
  "plugin_type": "tab",
  "runtime": "executable",
  "entrypoint": "ExampleTab.exe",
  "default_command": "tab",
  "targets": ["tab"],
  "tab": {
    "title": "Example",
    "ready_event": "tab_ready",
    "startup_timeout_ms": 15000
  }
}
```

需要同时保留文件、进程或网络命令入口时，使用 `plugin_type: "hybrid"`。Hybrid
插件的 `targets` 同时声明 `tab` 和所需命令目标，并可通过 `tab.command` 指定 Tab
入口命令；未提供时沿用 `default_command`：

```json
{
  "plugin_type": "hybrid",
  "default_command": "launch",
  "targets": ["process", "tab"],
  "tab": {
    "title": "Example",
    "command": "tab",
    "ready_event": "tab_ready",
    "startup_timeout_ms": 15000
  }
}
```

宿主启动命令等价于：

```text
<entrypoint> --ksword-plugin <tab.command 或 default_command> -- --parent-hwnd <decimal HWND> --host-pid <PID>
```

插件必须校验父窗口确实属于 `--host-pid`，并用宿主提供的 HWND 作为直接父窗口
创建 `WS_CHILD | WS_VISIBLE` 子窗口。创建完成后，stdout 输出一条 UTF-8 JSON
Lines 握手事件：

```json
{"protocol":"ksword-plugin/1","plugin_id":"example-tab","event":"tab_ready","hwnd":"123456"}
```

`hwnd` 推荐使用十进制字符串，避免 JSON 浮点数在 64 位句柄上损失精度。宿主会
使用 `IsWindow`、`GetWindowThreadProcessId`、`GetParent` 和 `WS_CHILD` 再次验证；
句柄不属于刚启动的插件进程、未直接挂在容器下、协议不匹配、单行 stdout 超过
1 MiB 或超时，都会拒绝嵌入并终止插件。插件退出或崩溃不会使 KSword 退出，
Tab 会显示诊断与“重试”按钮。

Tab 型插件不能同时声明 `scan-table` visualization。`tab.title` 最多 96 字符；
`tab.ready_event` 遵循事件名规则；`tab.startup_timeout_ms` 可选，范围为
1000–60000 毫秒。安装或更新 Tab 插件后，需要重新创建顶部“插件”页或重启 KSword
才能重新发现 Tab。

### 基础样式注入

Tab 插件是独立进程，不能继承 KSword 的 Qt palette 或 QSS。宿主启动插件时会注入
一组启动时主题快照环境变量；插件可选择消费，不支持这些变量也不会影响启动：

| 环境变量 | 含义 |
| --- | --- |
| `KSWORD_PLUGIN_STYLE_API` | 样式协议版本，当前为 `1`。 |
| `KSWORD_PLUGIN_THEME` | `dark` 或 `light`。 |
| `KSWORD_PLUGIN_COLOR_WINDOW` | 顶层背景色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_SURFACE` | 内容背景色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_SURFACE_ALT` | 次级内容背景色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_TEXT_PRIMARY` | 主要文字色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_TEXT_SECONDARY` | 次要文字色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_BORDER` | 边框色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_ACCENT` | 强调色，`#RRGGBB`。 |
| `KSWORD_PLUGIN_COLOR_ON_ACCENT` | 强调色背景上的文字色，`#RRGGBB`。 |

该协议只提供基础颜色角色，不要求插件采用特定 UI 框架。运行中切换主题后，重新打开
或重试插件即可取得新的启动快照。

## 插件商城

插件管理窗口会异步读取公开目录：

```text
https://raw.githubusercontent.com/KSwordDEV/Plugins/main/catalog.json
```

目录根对象必须含有 `ksword_plugin_marketplace_api: "1"`。每个条目必须包含 `id`、`name`、`version`、`description`、`targets`、`install_directory`、`archive_url`、`sha256`、`license_name` 和 `license_url`。下载和许可证 URL 仅接受 `raw.githubusercontent.com` 的 HTTPS 地址；ZIP 必须有 64 位 SHA-256。

商城一键安装流程不可绕过许可证：先下载并展示 `license_url` 的全文，用户勾选“已阅读并同意”后，才允许请求插件 ZIP。ZIP 下载完成后，Ksword 会校验 SHA-256；校验失败不会解压或写入插件目录。校验通过后，宿主在插件根目录中建立暂存区，用 Windows PowerShell `Expand-Archive` 解压，并再次验证解压后的 `plugin.json`、目录名、ID 和入口文件。验证通过才替换同 ID 的旧版本；失败时保留旧版本并清理暂存内容。安装完成后“已安装”页自动重新扫描。

商城下载的 ZIP 不写入用户的下载目录，也不会随 KSword 安装包分发。许可证同意不会持久化：每次一键安装或更新均需再次阅读并确认。

## 输出、退出码与兼容性

插件模式的 stdout 是 UTF-8 JSON Lines，每一行必须至少包含：

```json
{"protocol":"ksword-plugin/1","plugin_id":"example","event":"..."}
```

建议依次发出 `ready`、`scan_started`、零或多条 `file_result`、`scan_complete`。
失败用 `event: "error"` 并提供稳定的 `code` 和 `message`。stderr 保留给人工
诊断。退出码 `0` 表示命令完成；`64` 表示参数错误；`2` 表示目标或运行环境不
可用；其它非零值由插件自行定义，并由 Ksword 直接显示。

宿主不会解析或信任插件结果来执行 R0 操作。插件若需要改变系统状态，必须自行
提供独立、明确且受其自身安全策略约束的命令。

## 发布与第三方内容

每个第三方插件必须在根目录保留原始 `LICENSE` 和 `NOTICE`，并在清单中引用。
发布包必须携带其模型、特征文件、依赖说明和许可证。第三方插件发布在
`KSwordDEV/Plugins`，不提交进 KSword 主仓库，也不编入 Ksword 安装包。当前
`ARK/PYAS-Scanner` 是参考实现；它保留原始交互式调用，同时实现上述 JSON Lines
插件入口。
