@qomicex/cli 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,98 +1,98 @@
1
- # 权限目录与最小权限原则
2
-
3
- 权限目录源:`src/plugins/types.ts` 的 `PERMISSION_CATALOG`(启动器)与 `packages/qomicex-cli/src/lib/permissions.ts`(CLI verify,两者一致)。方法→权限映射源:CLI `src/lib/permissions.ts` 的 `METHOD_PERMISSIONS` 表(与 `src/plugins/sandbox.ts` 一致)。
4
-
5
- ## 风险分级
6
-
7
- | 级别 | 含义 | 安装详情弹窗视觉 |
8
- |------|------|------|
9
- | `normal` | 只读 / 低影响 | 蓝 |
10
- | `warning` | 写操作 / 网络 / 进程类 | 黄 |
11
- | `danger` | 系统命令 / 文件写 / 装插件 | 红 |
12
-
13
- **最小权限原则**:只声明真正用到的权限。`qomicex verify` 会扫描源码实际调用的桥方法(按 `METHOD_PERMISSIONS` 表),**声明了未用到的 → 报错;用了没声明的 → 报错**。因此 AI 生成插件时务必按"最终调用了哪些 API"反推权限集合,宁可少而准。
14
-
15
- ## 完整权限目录(39 项)
16
-
17
- | 权限 ID | 风险 | 用途 |
18
- |---------|------|------|
19
- | `instance:read` | normal | 读取实例列表 |
20
- | `instance:write` | warning | 创建/修改/删除实例(含安装整合包) |
21
- | `account:read` | normal | 读取账号列表 |
22
- | `license:read` | normal | 读取许可证信息 |
23
- | `config:read` | normal | 读取启动器配置 / 插件配置 |
24
- | `config:write` | warning | 修改启动器配置 / 插件配置 |
25
- | `cache:access` | normal | 读写插件缓存 |
26
- | `endpoint:discover` | normal | 获取后端 API 端点 |
27
- | `page:list` | normal | 获取页面列表 |
28
- | `network:fetch` | warning | 调用后端 API(callBackend)/ 插件互调 |
29
- | `network:cors_proxy` | warning | CORS 代理请求(proxyFetch / proxyFetchStream) |
30
- | `network:websocket` | warning | WebSocket 连接 |
31
- | `network:proxy` | warning | 修改代理设置 |
32
- | `ui:inject_sidebar` | normal | 注入侧边栏菜单 |
33
- | `ui:inject_settings` | normal | 注入设置页 |
34
- | `ui:picture_in_picture` | warning | 画中画窗口 |
35
- | `ui:sub_window` | warning | 独立子窗口 / 悬浮窗 |
36
- | `ui:context_menu` | normal | 注入右键菜单 |
37
- | `ui:toast` | normal | 应用内 toast 通知 |
38
- | `ui:navigate` | normal | 跳转页面 |
39
- | `system:info` | normal | 读取系统和启动器信息 |
40
- | `system:notification` | normal | 发送系统通知 / 打开外链 |
41
- | `clipboard:read` | warning | 读取剪贴板 |
42
- | `clipboard:write` | warning | 写入剪贴板 |
43
- | `wasm:execute` | warning | 执行 WASM 模块(callWasm) |
44
- | `plugin:install` | danger | 安装/卸载/更新插件 |
45
- | `plugin:list` | normal | 读取已安装插件列表 |
46
- | `resource:read` | normal | 读取游戏资源文件 |
47
- | `resource:write` | warning | 写入游戏资源文件 |
48
- | `java:manage` | warning | 管理 Java 运行时 |
49
- | `download:manage` | warning | 管理下载中心任务 |
50
- | `game:process` | warning | 启停游戏进程 |
51
- | `game:log` | normal | 检测游戏日志 |
52
- | `connector:host` | warning | 启停联机 |
53
- | `connector:scan` | normal | 扫描局域网联机 |
54
- | `shell:execute` | danger | 执行系统命令 |
55
- | `filesystem:read` | warning | 读取文件系统 |
56
- | `filesystem:write` | danger | 写入/删除文件系统 |
57
- | (例外)`addMenuItem` | — | 动态注册侧边栏菜单,**无需声明权限** |
58
-
59
- ## 方法 → 权限映射(生成权限列表时照此反推)
60
-
61
- | API 方法 | 所需权限 |
62
- |----------|----------|
63
- | `getSettings` | `config:read` |
64
- | `setSettings`、`registerMethod` | `config:write` |
65
- | `getCache` / `setCache` | `cache:access` |
66
- | `callBackend`、`callPlugin` | `network:fetch` |
67
- | `proxyFetch` / `proxyFetchStream` | `network:cors_proxy` |
68
- | `uploadPlugin` | `plugin:install` |
69
- | `callWasm` / `listWasmPlugins` | `wasm:execute` |
70
- | `readText` / `readBytes` | `filesystem:read` |
71
- | `writeText` / `writeBytes` / `deleteFile` | `filesystem:write` |
72
- | `execCommand` | `shell:execute` |
73
- | `navigate` | `config:read` |
74
- | `showToast` | `ui:toast` |
75
- | `getSystemInfo` | `system:info` |
76
- | `openUrl` | `system:notification` |
77
- | `listPlugins` | `plugin:list` |
78
- | `overlay.*`(create/show/hide/destroy/setHtml/setPosition) | `ui:sub_window` |
79
- | `download.addTask` / `.progress` / `.cancel` / `.list` | `download:manage` |
80
- | `download.registerInstall` | `instance:write` |
81
- | `modpack.install` | `instance:write` |
82
- | `addMenuItem` | 无需权限 |
83
-
84
- ## 常见权限组合
85
-
86
- - **纯 UI 插件**(模板默认):`config:read` + `ui:toast` + `network:cors_proxy`。
87
- - 需要联网的插件:`network:cors_proxy`(外网请求)或 `network:fetch`(调启动器后端)。
88
- - 需要持久化自己的配置:加 `config:write`(配合 `getSettings`/`setSettings`)。
89
- - 需要文件读写:`filesystem:read`(读)或 `filesystem:write`(写/删,danger)。文件访问是**授权制**——首次访问用户弹窗确认,按路径前缀持久化。
90
-
91
- ## 模板默认 manifest
92
-
93
- ```json
94
- {
95
- "layers": ["l2"],
96
- "permissions": ["config:read", "ui:toast", "network:cors_proxy"]
97
- }
98
- ```
1
+ # 权限目录与最小权限原则
2
+
3
+ 权限目录源:`src/plugins/types.ts` 的 `PERMISSION_CATALOG`(启动器)与 `packages/qomicex-cli/src/lib/permissions.ts`(CLI verify,两者一致)。方法→权限映射源:CLI `src/lib/permissions.ts` 的 `METHOD_PERMISSIONS` 表(与 `src/plugins/sandbox.ts` 一致)。
4
+
5
+ ## 风险分级
6
+
7
+ | 级别 | 含义 | 安装详情弹窗视觉 |
8
+ |------|------|------|
9
+ | `normal` | 只读 / 低影响 | 蓝 |
10
+ | `warning` | 写操作 / 网络 / 进程类 | 黄 |
11
+ | `danger` | 系统命令 / 文件写 / 装插件 | 红 |
12
+
13
+ **最小权限原则**:只声明真正用到的权限。`qomicex verify` 会扫描源码实际调用的桥方法(按 `METHOD_PERMISSIONS` 表),**声明了未用到的 → 报错;用了没声明的 → 报错**。因此 AI 生成插件时务必按"最终调用了哪些 API"反推权限集合,宁可少而准。
14
+
15
+ ## 完整权限目录(39 项)
16
+
17
+ | 权限 ID | 风险 | 用途 |
18
+ |---------|------|------|
19
+ | `instance:read` | normal | 读取实例列表 |
20
+ | `instance:write` | warning | 创建/修改/删除实例(含安装整合包) |
21
+ | `account:read` | normal | 读取账号列表 |
22
+ | `license:read` | normal | 读取许可证信息 |
23
+ | `config:read` | normal | 读取启动器配置 / 插件配置 |
24
+ | `config:write` | warning | 修改启动器配置 / 插件配置 |
25
+ | `cache:access` | normal | 读写插件缓存 |
26
+ | `endpoint:discover` | normal | 获取后端 API 端点 |
27
+ | `page:list` | normal | 获取页面列表 |
28
+ | `network:fetch` | warning | 调用后端 API(callBackend)/ 插件互调 |
29
+ | `network:cors_proxy` | warning | CORS 代理请求(proxyFetch / proxyFetchStream) |
30
+ | `network:websocket` | warning | WebSocket 连接 |
31
+ | `network:proxy` | warning | 修改代理设置 |
32
+ | `ui:inject_sidebar` | normal | 注入侧边栏菜单 |
33
+ | `ui:inject_settings` | normal | 注入设置页 |
34
+ | `ui:picture_in_picture` | warning | 画中画窗口 |
35
+ | `ui:sub_window` | warning | 独立子窗口 / 悬浮窗 |
36
+ | `ui:context_menu` | normal | 注入右键菜单 |
37
+ | `ui:toast` | normal | 应用内 toast 通知 |
38
+ | `ui:navigate` | normal | 跳转页面 |
39
+ | `system:info` | normal | 读取系统和启动器信息 |
40
+ | `system:notification` | normal | 发送系统通知 / 打开外链 |
41
+ | `clipboard:read` | warning | 读取剪贴板 |
42
+ | `clipboard:write` | warning | 写入剪贴板 |
43
+ | `wasm:execute` | warning | 执行 WASM 模块(callWasm) |
44
+ | `plugin:install` | danger | 安装/卸载/更新插件 |
45
+ | `plugin:list` | normal | 读取已安装插件列表 |
46
+ | `resource:read` | normal | 读取游戏资源文件 |
47
+ | `resource:write` | warning | 写入游戏资源文件 |
48
+ | `java:manage` | warning | 管理 Java 运行时 |
49
+ | `download:manage` | warning | 管理下载中心任务 |
50
+ | `game:process` | warning | 启停游戏进程 |
51
+ | `game:log` | normal | 检测游戏日志 |
52
+ | `connector:host` | warning | 启停联机 |
53
+ | `connector:scan` | normal | 扫描局域网联机 |
54
+ | `shell:execute` | danger | 执行系统命令 |
55
+ | `filesystem:read` | warning | 读取文件系统 |
56
+ | `filesystem:write` | danger | 写入/删除文件系统 |
57
+ | (例外)`addMenuItem` | — | 动态注册侧边栏菜单,**无需声明权限** |
58
+
59
+ ## 方法 → 权限映射(生成权限列表时照此反推)
60
+
61
+ | API 方法 | 所需权限 |
62
+ |----------|----------|
63
+ | `getSettings` | `config:read` |
64
+ | `setSettings`、`registerMethod` | `config:write` |
65
+ | `getCache` / `setCache` | `cache:access` |
66
+ | `callBackend`、`callPlugin` | `network:fetch` |
67
+ | `proxyFetch` / `proxyFetchStream` | `network:cors_proxy` |
68
+ | `uploadPlugin` | `plugin:install` |
69
+ | `callWasm` / `listWasmPlugins` | `wasm:execute` |
70
+ | `readText` / `readBytes` | `filesystem:read` |
71
+ | `writeText` / `writeBytes` / `deleteFile` | `filesystem:write` |
72
+ | `execCommand` | `shell:execute` |
73
+ | `navigate` | `config:read` |
74
+ | `showToast` | `ui:toast` |
75
+ | `getSystemInfo` | `system:info` |
76
+ | `openUrl` | `system:notification` |
77
+ | `listPlugins` | `plugin:list` |
78
+ | `overlay.*`(create/show/hide/destroy/setHtml/setPosition) | `ui:sub_window` |
79
+ | `download.addTask` / `.progress` / `.cancel` / `.list` | `download:manage` |
80
+ | `download.registerInstall` | `instance:write` |
81
+ | `modpack.install` | `instance:write` |
82
+ | `addMenuItem` | 无需权限 |
83
+
84
+ ## 常见权限组合
85
+
86
+ - **纯 UI 插件**(模板默认):`config:read` + `ui:toast` + `network:cors_proxy`。
87
+ - 需要联网的插件:`network:cors_proxy`(外网请求)或 `network:fetch`(调启动器后端)。
88
+ - 需要持久化自己的配置:加 `config:write`(配合 `getSettings`/`setSettings`)。
89
+ - 需要文件读写:`filesystem:read`(读)或 `filesystem:write`(写/删,danger)。文件访问是**授权制**——首次访问用户弹窗确认,按路径前缀持久化。
90
+
91
+ ## 模板默认 manifest
92
+
93
+ ```json
94
+ {
95
+ "layers": ["l2"],
96
+ "permissions": ["config:read", "ui:toast", "network:cors_proxy"]
97
+ }
98
+ ```
@@ -1,113 +1,113 @@
1
- # 桥 API 签名速查
2
-
3
- 插件脚本通过全局 `window.__PLUGIN_API__` 与启动器交互。L2 iframe 沙箱 / 内联渲染均可用;独立 `pnpm dev`(浏览器直开)时优雅降级为 `null`。
4
-
5
- 完整文档见 `D:\docs\docs\plugins\plugin-api.md`(启动器公开文档,内容更全含完整示例与错误码)。
6
-
7
- ## 全局变量
8
-
9
- | 变量 | 说明 |
10
- |------|------|
11
- | `window.__PLUGIN_API__` | 桥 API 对象(`null` = 浏览器直开,优雅降级) |
12
- | `window.__PLUGIN_API_BASE__` | L2 沙箱内自动注入,值为 `http://localhost:5000/api/plugins/{id}/files`,用于拼包内资源地址 |
13
- | `window.__PLUGIN_ID__` | 当前插件 id |
14
-
15
- ## 调用方式
16
-
17
- ```ts
18
- const api = window.__PLUGIN_API__
19
-
20
- // ① 通用 call(绝大多数方法)
21
- const data = await api.call('getSettings')
22
-
23
- // ② 3 个专用快捷方式
24
- api.registerMethod('name', fn) // 注册方法
25
- await api.callPlugin('id', 'method', ...args) // 调用其他插件方法
26
- await api.proxyFetchStream(req, { onChunk, onError }) // 流式请求
27
- ```
28
-
29
- ## 方法签名速查
30
-
31
- | 方法 | 签名 | 所需权限 |
32
- |------|------|----------|
33
- | **getSettings** | `call('getSettings') → Record<string, unknown>` | `config:read` |
34
- | **setSettings** | `call('setSettings', key, value)` | `config:write` |
35
- | **setCache** | `call('setCache', key, value, ttlSeconds?)` | `cache:access` |
36
- | **getCache** | `call('getCache', key) → any \| null` | `cache:access` |
37
- | **callBackend** | `call('callBackend', endpoint, data?) → any` | `network:fetch` |
38
- | | 有 data → POST;无 data → GET。data 可传 `_method` 覆盖 HTTP 方法 | |
39
- | **proxyFetch** | `call('proxyFetch', { url, method?, headers?, body?, timeoutMs? }) → { status, headers, body?, bodyBase64? }` | `network:cors_proxy` |
40
- | | SSRF 防护:仅 http/https,禁止内网地址 | |
41
- | **proxyFetchStream** | `proxyFetchStream(req, { onChunk, onError })` | `network:cors_proxy` |
42
- | | 消费 SSE 流,逐块回调。`req.signal` 可传 AbortSignal 中断 | |
43
- | **registerMethod** | `registerMethod(name, fn)` | `config:write` |
44
- | | 注册方法到全局注册表,供其他插件 `callPlugin` 调用。插件停用时自动注销 | |
45
- | **callPlugin** | `callPlugin(pluginId, method, ...args) → any` | `network:fetch` |
46
- | | 目标未安装/未激活/未注册方法 → reject | |
47
- | **callWasm** | `call('callWasm', pluginId, exportName?) → { ok, result }` | `wasm:execute` |
48
- | | 调用 L3 WASM 插件导出函数,缺省 `on_load` | |
49
- | **listWasmPlugins** | `call('listWasmPlugins') → string[]` | `wasm:execute` |
50
- | **readText** | `call('readText', path, options?) → { path, content }` | `filesystem:read` |
51
- | | `options: { start?, length? }`。授权制(首次访问用户弹窗确认) | |
52
- | **readBytes** | `call('readBytes', path, options?) → { path, contentBase64 }` | `filesystem:read` |
53
- | | 同 readText 授权制 | |
54
- | **writeText** | `call('writeText', path, content) → { path }` | `filesystem:write` |
55
- | | 授权制,自动创建父目录 | |
56
- | **writeBytes** | `call('writeBytes', path, bytes: Uint8Array) → { path }` | `filesystem:write` |
57
- | | 授权制,自动创建父目录 | |
58
- | **deleteFile** | `call('deleteFile', path) → { path }` | `filesystem:write` |
59
- | | 仅文件不支持目录;授权制 | |
60
- | **execCommand** | `call('execCommand', command, timeoutMs?) → { exitCode, stdout, stderr }` | `shell:execute` |
61
- | | Windows → powershell,Unix → /bin/sh。默认超时 15s,范围 1-120s | |
62
- | **getSystemInfo** | `call('getSystemInfo') → { Os, Architecture, LauncherVersion, ... }` | `system:info` |
63
- | **openUrl** | `call('openUrl', url)` | `system:notification` |
64
- | | 仅 http/https,沙箱内 window.open 被拦时应使用 | |
65
- | **listPlugins** | `call('listPlugins') → [{ id, name, version, status }]` | `plugin:list` |
66
- | **uploadPlugin** | `call('uploadPlugin', fileData: number[], fileName)` | `plugin:install` |
67
- | | 安装 .qplugin 包 | |
68
- | **navigate** | `call('navigate', path)` | `config:read` |
69
- | | 跳转启动器内部路由,勿用外部 URL | |
70
- | **showToast** | `call('showToast', message, type?)` | `ui:toast` |
71
- | | `type: 'info' \| 'error' \| 'success'`,默认 info | |
72
- | **overlay.create** | `call('overlay.create', { title?, html, x?, y?, width?, height?, minimizable?, resizable? }) → overlayId` | `ui:sub_window` |
73
- | **overlay.show/hide/destroy** | `call('overlay.show/hide/destroy', overlayId)` | `ui:sub_window` |
74
- | **overlay.setHtml** | `call('overlay.setHtml', overlayId, html)` | `ui:sub_window` |
75
- | **overlay.setPosition** | `call('overlay.setPosition', overlayId, x, y)` | `ui:sub_window` |
76
- | **download.addTask** | `call('download.addTask', { url, targetPath \| (instanceId, category, fileName), ... }) → { taskId }` | `download:manage` |
77
- | | 支持实例+类别自动解析隔离目录。`extract: true` 下载后自动解压 zip | |
78
- | **download.progress** | `call('download.progress', taskId) → snapshot \| null` | `download:manage` |
79
- | **download.cancel** | `call('download.cancel', taskId)` | `download:manage` |
80
- | **download.list** | `call('download.list') → snapshot[]` | `download:manage` |
81
- | **download.registerInstall** | `call('download.registerInstall', { instanceId, name, gameVersion, loader, loaderVersion })` | `instance:write` |
82
- | | 仅登记安装任务到下载中心,不创建真实下载 | |
83
- | **modpack.install** | `call('modpack.install', { id, gameDir, path | (type, projectId, fileId), ... }) → { instanceId }` | `instance:write` |
84
- | | 一键安装整合包(本地 zip / mrpack 或在线 Modrinth/CurseForge/FTB) | |
85
- | **addMenuItem** | `addMenuItem(item: PluginMenuItem)` | 无需权限 |
86
- | | 运行时动态注册侧边栏菜单项,停用时自动移除 | |
87
-
88
- ## error 处理
89
-
90
- ```ts
91
- try {
92
- await api.call('getSettings')
93
- } catch (e) {
94
- console.error(e.message)
95
- // 常见错误:
96
- // "Permission denied: requires xxx" —— manifest 权限未包含
97
- // "Backend error: 404" —— callBackend 端点不存在
98
- // "Proxy failed: 400" —— proxyFetch 参数错误(含 SSRF 拦截)
99
- // "插件 xxx 未提供方法 yyy" —— callPlugin 目标不可用
100
- }
101
- ```
102
-
103
- ## 文件拖放事件
104
-
105
- 主窗口广播 `file-drop` 事件(`DragDrop::Drop` 触发),payload = 文件绝对路径数组。沙箱插件需经主界面中转:主界面监听后 `callPlugin(pluginId, method, paths)` 转发。
106
-
107
- ## 模板 api.ts
108
-
109
- 模板项目 `src/api.ts` 提供了 `getApi()` 和 `getPluginId()` 辅助函数(使用 `window.__PLUGIN_API__` / `window.__PLUGIN_ID__`):
110
- ```ts
111
- import { getApi, getPluginId } from './api.ts'
112
- const api = getApi() // null if browser direct
1
+ # 桥 API 签名速查
2
+
3
+ 插件脚本通过全局 `window.__PLUGIN_API__` 与启动器交互。L2 iframe 沙箱 / 内联渲染均可用;独立 `pnpm dev`(浏览器直开)时优雅降级为 `null`。
4
+
5
+ 完整文档见 `D:\docs\docs\plugins\plugin-api.md`(启动器公开文档,内容更全含完整示例与错误码)。
6
+
7
+ ## 全局变量
8
+
9
+ | 变量 | 说明 |
10
+ |------|------|
11
+ | `window.__PLUGIN_API__` | 桥 API 对象(`null` = 浏览器直开,优雅降级) |
12
+ | `window.__PLUGIN_API_BASE__` | L2 沙箱内自动注入,值为 `http://localhost:5000/api/plugins/{id}/files`,用于拼包内资源地址 |
13
+ | `window.__PLUGIN_ID__` | 当前插件 id |
14
+
15
+ ## 调用方式
16
+
17
+ ```ts
18
+ const api = window.__PLUGIN_API__
19
+
20
+ // ① 通用 call(绝大多数方法)
21
+ const data = await api.call('getSettings')
22
+
23
+ // ② 3 个专用快捷方式
24
+ api.registerMethod('name', fn) // 注册方法
25
+ await api.callPlugin('id', 'method', ...args) // 调用其他插件方法
26
+ await api.proxyFetchStream(req, { onChunk, onError }) // 流式请求
27
+ ```
28
+
29
+ ## 方法签名速查
30
+
31
+ | 方法 | 签名 | 所需权限 |
32
+ |------|------|----------|
33
+ | **getSettings** | `call('getSettings') → Record<string, unknown>` | `config:read` |
34
+ | **setSettings** | `call('setSettings', key, value)` | `config:write` |
35
+ | **setCache** | `call('setCache', key, value, ttlSeconds?)` | `cache:access` |
36
+ | **getCache** | `call('getCache', key) → any \| null` | `cache:access` |
37
+ | **callBackend** | `call('callBackend', endpoint, data?) → any` | `network:fetch` |
38
+ | | 有 data → POST;无 data → GET。data 可传 `_method` 覆盖 HTTP 方法 | |
39
+ | **proxyFetch** | `call('proxyFetch', { url, method?, headers?, body?, timeoutMs? }) → { status, headers, body?, bodyBase64? }` | `network:cors_proxy` |
40
+ | | SSRF 防护:仅 http/https,禁止内网地址 | |
41
+ | **proxyFetchStream** | `proxyFetchStream(req, { onChunk, onError })` | `network:cors_proxy` |
42
+ | | 消费 SSE 流,逐块回调。`req.signal` 可传 AbortSignal 中断 | |
43
+ | **registerMethod** | `registerMethod(name, fn)` | `config:write` |
44
+ | | 注册方法到全局注册表,供其他插件 `callPlugin` 调用。插件停用时自动注销 | |
45
+ | **callPlugin** | `callPlugin(pluginId, method, ...args) → any` | `network:fetch` |
46
+ | | 目标未安装/未激活/未注册方法 → reject | |
47
+ | **callWasm** | `call('callWasm', pluginId, exportName?) → { ok, result }` | `wasm:execute` |
48
+ | | 调用 L3 WASM 插件导出函数,缺省 `on_load` | |
49
+ | **listWasmPlugins** | `call('listWasmPlugins') → string[]` | `wasm:execute` |
50
+ | **readText** | `call('readText', path, options?) → { path, content }` | `filesystem:read` |
51
+ | | `options: { start?, length? }`。授权制(首次访问用户弹窗确认) | |
52
+ | **readBytes** | `call('readBytes', path, options?) → { path, contentBase64 }` | `filesystem:read` |
53
+ | | 同 readText 授权制 | |
54
+ | **writeText** | `call('writeText', path, content) → { path }` | `filesystem:write` |
55
+ | | 授权制,自动创建父目录 | |
56
+ | **writeBytes** | `call('writeBytes', path, bytes: Uint8Array) → { path }` | `filesystem:write` |
57
+ | | 授权制,自动创建父目录 | |
58
+ | **deleteFile** | `call('deleteFile', path) → { path }` | `filesystem:write` |
59
+ | | 仅文件不支持目录;授权制 | |
60
+ | **execCommand** | `call('execCommand', command, timeoutMs?) → { exitCode, stdout, stderr }` | `shell:execute` |
61
+ | | Windows → powershell,Unix → /bin/sh。默认超时 15s,范围 1-120s | |
62
+ | **getSystemInfo** | `call('getSystemInfo') → { Os, Architecture, LauncherVersion, ... }` | `system:info` |
63
+ | **openUrl** | `call('openUrl', url)` | `system:notification` |
64
+ | | 仅 http/https,沙箱内 window.open 被拦时应使用 | |
65
+ | **listPlugins** | `call('listPlugins') → [{ id, name, version, status }]` | `plugin:list` |
66
+ | **uploadPlugin** | `call('uploadPlugin', fileData: number[], fileName)` | `plugin:install` |
67
+ | | 安装 .qplugin 包 | |
68
+ | **navigate** | `call('navigate', path)` | `config:read` |
69
+ | | 跳转启动器内部路由,勿用外部 URL | |
70
+ | **showToast** | `call('showToast', message, type?)` | `ui:toast` |
71
+ | | `type: 'info' \| 'error' \| 'success'`,默认 info | |
72
+ | **overlay.create** | `call('overlay.create', { title?, html, x?, y?, width?, height?, minimizable?, resizable? }) → overlayId` | `ui:sub_window` |
73
+ | **overlay.show/hide/destroy** | `call('overlay.show/hide/destroy', overlayId)` | `ui:sub_window` |
74
+ | **overlay.setHtml** | `call('overlay.setHtml', overlayId, html)` | `ui:sub_window` |
75
+ | **overlay.setPosition** | `call('overlay.setPosition', overlayId, x, y)` | `ui:sub_window` |
76
+ | **download.addTask** | `call('download.addTask', { url, targetPath \| (instanceId, category, fileName), ... }) → { taskId }` | `download:manage` |
77
+ | | 支持实例+类别自动解析隔离目录。`extract: true` 下载后自动解压 zip | |
78
+ | **download.progress** | `call('download.progress', taskId) → snapshot \| null` | `download:manage` |
79
+ | **download.cancel** | `call('download.cancel', taskId)` | `download:manage` |
80
+ | **download.list** | `call('download.list') → snapshot[]` | `download:manage` |
81
+ | **download.registerInstall** | `call('download.registerInstall', { instanceId, name, gameVersion, loader, loaderVersion })` | `instance:write` |
82
+ | | 仅登记安装任务到下载中心,不创建真实下载 | |
83
+ | **modpack.install** | `call('modpack.install', { id, gameDir, path | (type, projectId, fileId), ... }) → { instanceId }` | `instance:write` |
84
+ | | 一键安装整合包(本地 zip / mrpack 或在线 Modrinth/CurseForge/FTB) | |
85
+ | **addMenuItem** | `addMenuItem(item: PluginMenuItem)` | 无需权限 |
86
+ | | 运行时动态注册侧边栏菜单项,停用时自动移除 | |
87
+
88
+ ## error 处理
89
+
90
+ ```ts
91
+ try {
92
+ await api.call('getSettings')
93
+ } catch (e) {
94
+ console.error(e.message)
95
+ // 常见错误:
96
+ // "Permission denied: requires xxx" —— manifest 权限未包含
97
+ // "Backend error: 404" —— callBackend 端点不存在
98
+ // "Proxy failed: 400" —— proxyFetch 参数错误(含 SSRF 拦截)
99
+ // "插件 xxx 未提供方法 yyy" —— callPlugin 目标不可用
100
+ }
101
+ ```
102
+
103
+ ## 文件拖放事件
104
+
105
+ 主窗口广播 `file-drop` 事件(`DragDrop::Drop` 触发),payload = 文件绝对路径数组。沙箱插件需经主界面中转:主界面监听后 `callPlugin(pluginId, method, paths)` 转发。
106
+
107
+ ## 模板 api.ts
108
+
109
+ 模板项目 `src/api.ts` 提供了 `getApi()` 和 `getPluginId()` 辅助函数(使用 `window.__PLUGIN_API__` / `window.__PLUGIN_ID__`):
110
+ ```ts
111
+ import { getApi, getPluginId } from './api.ts'
112
+ const api = getApi() // null if browser direct
113
113
  ```
@@ -1,46 +1,46 @@
1
- # AI 生成插件硬性规则
2
-
3
- AI agent 生成/修改插件时必须逐条遵守。违反任何一条都可能导致 `qomicex verify` 不通过或运行时错误。
4
-
5
- ## manifest 校验
6
-
7
- 1. **id 格式**:`^[a-z0-9]+([.-][a-z0-9]+)*$`,3-128 字符,**必须含至少一个点**(反向域名,如 `com.example.demo`)。大写字母、空格、连续双点 → error。
8
- 2. **version**:严格 semver `数字.数字.数字`(可带 `-预发布` / `+构建号`),如 `0.1.0`、`1.2.0-beta.1`。
9
- 3. **minLauncherVersion**:必填字符串。
10
- 4. **layers**:至少一项,值 ∈ `l0`/`l1`/`l2`/`l3`。声明了 `entry.frontend` 但 layers 无 `l2`/`l3` → UI 无法渲染。
11
- 5. **permissions**:值是权限目录 id。未知权限 → warning。
12
- 6. **entry**:`frontend`/`backend`/`theme` 至少一个。`frontend` 应指向 `.html`(如 `dist/index.html`)。
13
- 7. **render**:默认 `iframe`(沙箱),仅显式 `"inline"` 走内联渲染。对 UI 插件建议不做修改,保持 iframe 默认。
14
-
15
- ## 权限
16
-
17
- 8. **最小权限原则**:只声明真正用到的权限。`qomicex verify` 会扫描源码,声明未用 / 用了未声明**都会报错**。按"最终调用了哪些 API 方法"反推权限集合,从 `permissions.md` 的 `METHOD_PERMISSIONS` 表查对应的权限 id。
18
- 9. **danger 权限**:`shell:execute`、`filesystem:write`、`plugin:install` 安装时红色提示,非必要不声明。
19
- 10. **addMenuItem 无需权限**:`addMenuItem` 不要求 manifest 声明任何权限。
20
-
21
- ## 代码
22
-
23
- 11. **TS import 带扩展名**:Vite 强制要求,`import { foo } from './bar.ts'`,不得省略 `.ts`/`.tsx`。例外:目录 barrel(`index.ts`)可省略。
24
- 12. **vite.config.ts 必须设 `base: './'`**:否则产物 `/assets/...` 被解析为站点根路径,插件白屏。
25
- 13. **不使用 `<a>` 做内部导航**:内部路由用 `<Link>`(React Router),`<a>` 触发整页刷新丢失持久状态。外部链接用 `openUrl` / `callBackend('/system/open-url', { url })`。
26
- 14. **沙箱内不用 `window.open`**:L2 iframe 的 `sandbox` 属性不含 `allow-popups`,`window.open` 被拦截。用 `openUrl` 方法或 `callBackend('/system/open-url', { url })`。
27
- 15. **不用 `fetch` 请求外部 URL**:用 `proxyFetch`(非流式)或 `proxyFetchStream`(流式,SSE),两者自带 SSRF 防护(禁止内网)和 CORS 代理。
28
- 16. **不虚构 API**:权限目录里有但桥 API 没有对应方法的能力(如 `clipboard:read`/`clipboard:write`、`network:websocket` 等当前无对应 `__PLUGIN_API__` 方法)不要臆造调用方式。以 `plugin-api.md` 列出的方法为准,不确定就标注「以代码为准」。
29
-
30
- ## 主题
31
-
32
- 17. **CSS 全用 `var(--*)`**:禁止 `#hex` / `rgb()` / `hsl()` 字面量。插件主题文件(`theme.css`)也只覆盖 `var()` token。
33
- 18. **Tailwind 用语义类名**:`bg-primary`、`text-foreground`、`text-muted-foreground`、`bg-muted`、`border-border` 等(`@qomicex/plugin-ui/tailwind-preset` 已映射)。
34
-
35
- ## 安全
36
-
37
- 19. **禁止硬编码密钥**:插件源码 / manifest / package.json 不得包含 API key、token、密码。用户配置经 `setSettings`/`getSettings` 存取。
38
- 20. **不滥用 `shell:execute`**:系统命令调用有 15s 超时(范围 1-120s),仅用于纯本地工具链,不传用户输入到 shell。
39
- 21. **文件读写经授权制**:`filesystem:read`/`write` 首次访问未授权路径时用户弹窗确认,按路径前缀持久化。插件不能假设用户一定会授权。
40
-
41
- ## 开发流程
42
-
43
- 22. **生成项目用 `qomicex create`**:不要手写 `manifest.json` 和项目结构,从模板起步。
44
- 23. **打包前必须 `qomicex verify`**:0 error 才算通过。warning 可接受但建议消除。
45
- 24. **不触碰启动器核心代码**:插件只修改自己目录内的文件,不动 `src/`、`src-tauri/`、`src-backend/` 等。
1
+ # AI 生成插件硬性规则
2
+
3
+ AI agent 生成/修改插件时必须逐条遵守。违反任何一条都可能导致 `qomicex verify` 不通过或运行时错误。
4
+
5
+ ## manifest 校验
6
+
7
+ 1. **id 格式**:`^[a-z0-9]+([.-][a-z0-9]+)*$`,3-128 字符,**必须含至少一个点**(反向域名,如 `com.example.demo`)。大写字母、空格、连续双点 → error。
8
+ 2. **version**:严格 semver `数字.数字.数字`(可带 `-预发布` / `+构建号`),如 `0.1.0`、`1.2.0-beta.1`。
9
+ 3. **minLauncherVersion**:必填字符串。
10
+ 4. **layers**:至少一项,值 ∈ `l0`/`l1`/`l2`/`l3`。声明了 `entry.frontend` 但 layers 无 `l2`/`l3` → UI 无法渲染。
11
+ 5. **permissions**:值是权限目录 id。未知权限 → warning。
12
+ 6. **entry**:`frontend`/`backend`/`theme` 至少一个。`frontend` 应指向 `.html`(如 `dist/index.html`)。
13
+ 7. **render**:默认 `iframe`(沙箱),仅显式 `"inline"` 走内联渲染。对 UI 插件建议不做修改,保持 iframe 默认。
14
+
15
+ ## 权限
16
+
17
+ 8. **最小权限原则**:只声明真正用到的权限。`qomicex verify` 会扫描源码,声明未用 / 用了未声明**都会报错**。按"最终调用了哪些 API 方法"反推权限集合,从 `permissions.md` 的 `METHOD_PERMISSIONS` 表查对应的权限 id。
18
+ 9. **danger 权限**:`shell:execute`、`filesystem:write`、`plugin:install` 安装时红色提示,非必要不声明。
19
+ 10. **addMenuItem 无需权限**:`addMenuItem` 不要求 manifest 声明任何权限。
20
+
21
+ ## 代码
22
+
23
+ 11. **TS import 带扩展名**:Vite 强制要求,`import { foo } from './bar.ts'`,不得省略 `.ts`/`.tsx`。例外:目录 barrel(`index.ts`)可省略。
24
+ 12. **vite.config.ts 必须设 `base: './'`**:否则产物 `/assets/...` 被解析为站点根路径,插件白屏。
25
+ 13. **不使用 `<a>` 做内部导航**:内部路由用 `<Link>`(React Router),`<a>` 触发整页刷新丢失持久状态。外部链接用 `openUrl` / `callBackend('/system/open-url', { url })`。
26
+ 14. **沙箱内不用 `window.open`**:L2 iframe 的 `sandbox` 属性不含 `allow-popups`,`window.open` 被拦截。用 `openUrl` 方法或 `callBackend('/system/open-url', { url })`。
27
+ 15. **不用 `fetch` 请求外部 URL**:用 `proxyFetch`(非流式)或 `proxyFetchStream`(流式,SSE),两者自带 SSRF 防护(禁止内网)和 CORS 代理。
28
+ 16. **不虚构 API**:权限目录里有但桥 API 没有对应方法的能力(如 `clipboard:read`/`clipboard:write`、`network:websocket` 等当前无对应 `__PLUGIN_API__` 方法)不要臆造调用方式。以 `plugin-api.md` 列出的方法为准,不确定就标注「以代码为准」。
29
+
30
+ ## 主题
31
+
32
+ 17. **CSS 全用 `var(--*)`**:禁止 `#hex` / `rgb()` / `hsl()` 字面量。插件主题文件(`theme.css`)也只覆盖 `var()` token。
33
+ 18. **Tailwind 用语义类名**:`bg-primary`、`text-foreground`、`text-muted-foreground`、`bg-muted`、`border-border` 等(`@qomicex/plugin-ui/tailwind-preset` 已映射)。
34
+
35
+ ## 安全
36
+
37
+ 19. **禁止硬编码密钥**:插件源码 / manifest / package.json 不得包含 API key、token、密码。用户配置经 `setSettings`/`getSettings` 存取。
38
+ 20. **不滥用 `shell:execute`**:系统命令调用有 15s 超时(范围 1-120s),仅用于纯本地工具链,不传用户输入到 shell。
39
+ 21. **文件读写经授权制**:`filesystem:read`/`write` 首次访问未授权路径时用户弹窗确认,按路径前缀持久化。插件不能假设用户一定会授权。
40
+
41
+ ## 开发流程
42
+
43
+ 22. **生成项目用 `qomicex create`**:不要手写 `manifest.json` 和项目结构,从模板起步。
44
+ 23. **打包前必须 `qomicex verify`**:0 error 才算通过。warning 可接受但建议消除。
45
+ 24. **不触碰启动器核心代码**:插件只修改自己目录内的文件,不动 `src/`、`src-tauri/`、`src-backend/` 等。
46
46
  25. **不 git commit**:插件开发阶段不提交改动到仓库。