@qomicex/cli 0.1.1 → 0.1.3

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,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**:插件开发阶段不提交改动到仓库。
@@ -1,80 +1,80 @@
1
- # Ed25519 签名流程
2
-
3
- 签名实现源:`packages/qomicex-cli/src/lib/signature.ts`。规范:**ADR-050 三级信任链**(商店根钥 → 开发者证书 → 包体签名)。与 store `src/lib/signature.ts`、launcher `plugin_signature.rs` 字节级一致。
4
-
5
- ## 签名载荷(规范化)
6
-
7
- ```
8
- payload = canonicalJson({
9
- manifest: sha256Hex(manifest.json 原始字节),
10
- files: [{ path, sha256 }...] // 按 path 升序
11
- })
12
- signedHash = SHA-256(payload) // 文本 UTF-8
13
- signature = Ed25519(私钥, payload 的 UTF-8 字节)
14
- ```
15
-
16
- - `canonicalJson`:递归按键排序、无空白 JSON(键序对哈希无影响,保证确定性)。
17
- - 包内 `signature.json` 与 `signature.cert.json` 本身不参与签名。
18
-
19
- ## 产物文件
20
-
21
- | 文件 | 内容 |
22
- |------|------|
23
- | `signature.json` | `{ alg: "Ed25519", signedHash, signerKeyId, signature }` |
24
- | `signature.cert.json` | 商店根钥签发的开发者证书:`{ alg, keyId, developerId, developerName, publicKey, issuedAt, signature }` |
25
-
26
- 验包要求:**两个文件都必须存在**,缺任一 → 未签名(`verify` 警告不拒绝);根钥验证书失败 / 包体验签失败 / 哈希不符 → 拒绝。
27
-
28
- ## 1. 生成密钥对
29
-
30
- ```bash
31
- openssl genpkey -algorithm Ed25519 -out dev-key.pem
32
- # 可选:提取 raw 32 字节 seed base64(publish 也接受 PEM,通常不必)
33
- openssl pkey -in dev-key.pem -outform DER | tail -c 32 | base64
34
- ```
35
-
36
- 私钥支持三种格式:PKCS#8 PEM、PKCS#8 DER base64、raw 32 字节 seed base64。
37
-
38
- > ⚠️ 私钥 = 开发者身份。**禁止**写入插件源码 / manifest / git 仓库 / 提交任何公开位置。使用环境变量 `QOMICEX_SIGN_KEY` 传入。
39
-
40
- ## 2. pack --key(本地签名,仅 signature.json)
41
-
42
- ```bash
43
- qomicex pack --key ./dev-key.pem
44
- ```
45
-
46
- - `keyId = ed25519:{公钥 base64 前 8 字符}`。
47
- - 若项目根存在 `signature.cert.json` 会自动带上(发布过一次后即有)。
48
- - 无证书时 CLI 会警告:商店上传仍需完整证书,建议走 publish。
49
-
50
- ## 3. publish(完整证书链)
51
-
52
- ```bash
53
- export QOMICEX_SIGN_KEY=<私钥 base64/PEM> # 或 --key ./dev-key.pem
54
- qomicex publish
55
- qomicex publish --changelog "修复 X" --yes
56
- qomicex publish --api http://127.0.0.1:8787/api/v1 # 本地商店(wrangler dev)调试
57
- ```
58
-
59
- 流程(RFC 8628 设备流):
60
- 1. `POST /api/v1/auth/device/code` → 打印授权码 + 验证 URL → 轮询 `device/token` 拿访问令牌。
61
- 2. `POST /api/v1/developer/keys` 上传 Ed25519 公钥 → 商店根钥签发开发者证书(返回 `keyId` + 证书内容)。
62
- 3. 用私钥对包体签名,写入 `signature.json`,与证书一起打进 `.qplugin`。
63
- 4. 查找/创建插件记录(`/plugins/mine` → 无则 `POST /plugins`),确认后 `POST /plugins/:id/versions` multipart 上传。
64
- 5. 成功后将签名包存为 `release/<id>-<version>.signed.qplugin` 供复验。
65
-
66
- ## 4. 验包
67
-
68
- ```bash
69
- qomicex verify --package ./release/x.qplugin
70
- ```
71
-
72
- - 用内置商店根公钥验签:`STORE_ROOT_PUBLIC_KEY_B64`(与 launcher `plugin_signature.rs` 的 `ROOT_PUBLIC_KEY_B64` 一致)。
73
- - 未签名 → 提示"未签名"(警告,不拒绝);签名无效 → error 退出。
74
-
75
- ## 商店根公钥
76
-
77
- ```
78
- hNoXOazEkdTRoxBra8ABlCWXhy16S7rM5ZmEDa+GmnE=
79
- ```
80
- (raw base64 Ed25519 公钥;开发 / 自建商店需替换为对应根钥)
1
+ # Ed25519 签名流程
2
+
3
+ 签名实现源:`packages/qomicex-cli/src/lib/signature.ts`。规范:**ADR-050 三级信任链**(商店根钥 → 开发者证书 → 包体签名)。与 store `src/lib/signature.ts`、launcher `plugin_signature.rs` 字节级一致。
4
+
5
+ ## 签名载荷(规范化)
6
+
7
+ ```
8
+ payload = canonicalJson({
9
+ manifest: sha256Hex(manifest.json 原始字节),
10
+ files: [{ path, sha256 }...] // 按 path 升序
11
+ })
12
+ signedHash = SHA-256(payload) // 文本 UTF-8
13
+ signature = Ed25519(私钥, payload 的 UTF-8 字节)
14
+ ```
15
+
16
+ - `canonicalJson`:递归按键排序、无空白 JSON(键序对哈希无影响,保证确定性)。
17
+ - 包内 `signature.json` 与 `signature.cert.json` 本身不参与签名。
18
+
19
+ ## 产物文件
20
+
21
+ | 文件 | 内容 |
22
+ |------|------|
23
+ | `signature.json` | `{ alg: "Ed25519", signedHash, signerKeyId, signature }` |
24
+ | `signature.cert.json` | 商店根钥签发的开发者证书:`{ alg, keyId, developerId, developerName, publicKey, issuedAt, signature }` |
25
+
26
+ 验包要求:**两个文件都必须存在**,缺任一 → 未签名(`verify` 警告不拒绝);根钥验证书失败 / 包体验签失败 / 哈希不符 → 拒绝。
27
+
28
+ ## 1. 生成密钥对
29
+
30
+ ```bash
31
+ openssl genpkey -algorithm Ed25519 -out dev-key.pem
32
+ # 可选:提取 raw 32 字节 seed base64(publish 也接受 PEM,通常不必)
33
+ openssl pkey -in dev-key.pem -outform DER | tail -c 32 | base64
34
+ ```
35
+
36
+ 私钥支持三种格式:PKCS#8 PEM、PKCS#8 DER base64、raw 32 字节 seed base64。
37
+
38
+ > ⚠️ 私钥 = 开发者身份。**禁止**写入插件源码 / manifest / git 仓库 / 提交任何公开位置。使用环境变量 `QOMICEX_SIGN_KEY` 传入。
39
+
40
+ ## 2. pack --key(本地签名,仅 signature.json)
41
+
42
+ ```bash
43
+ qomicex pack --key ./dev-key.pem
44
+ ```
45
+
46
+ - `keyId = ed25519:{公钥 base64 前 8 字符}`。
47
+ - 若项目根存在 `signature.cert.json` 会自动带上(发布过一次后即有)。
48
+ - 无证书时 CLI 会警告:商店上传仍需完整证书,建议走 publish。
49
+
50
+ ## 3. publish(完整证书链)
51
+
52
+ ```bash
53
+ export QOMICEX_SIGN_KEY=<私钥 base64/PEM> # 或 --key ./dev-key.pem
54
+ qomicex publish
55
+ qomicex publish --changelog "修复 X" --yes
56
+ qomicex publish --api http://127.0.0.1:8787/api/v1 # 本地商店(wrangler dev)调试
57
+ ```
58
+
59
+ 流程(RFC 8628 设备流):
60
+ 1. `POST /api/v1/auth/device/code` → 打印授权码 + 验证 URL → 轮询 `device/token` 拿访问令牌。
61
+ 2. `POST /api/v1/developer/keys` 上传 Ed25519 公钥 → 商店根钥签发开发者证书(返回 `keyId` + 证书内容)。
62
+ 3. 用私钥对包体签名,写入 `signature.json`,与证书一起打进 `.qplugin`。
63
+ 4. 查找/创建插件记录(`/plugins/mine` → 无则 `POST /plugins`),确认后 `POST /plugins/:id/versions` multipart 上传。
64
+ 5. 成功后将签名包存为 `release/<id>-<version>.signed.qplugin` 供复验。
65
+
66
+ ## 4. 验包
67
+
68
+ ```bash
69
+ qomicex verify --package ./release/x.qplugin
70
+ ```
71
+
72
+ - 用内置商店根公钥验签:`STORE_ROOT_PUBLIC_KEY_B64`(与 launcher `plugin_signature.rs` 的 `ROOT_PUBLIC_KEY_B64` 一致)。
73
+ - 未签名 → 提示"未签名"(警告,不拒绝);签名无效 → error 退出。
74
+
75
+ ## 商店根公钥
76
+
77
+ ```
78
+ sPKcrc6QR5gcOnQMdq21Jo3yqxN7Mbm61OYxZnKuHE0=
79
+ ```
80
+ (raw base64 Ed25519 公钥;开发 / 自建商店需替换为对应根钥)
@@ -1,58 +1,58 @@
1
- # 主题语义 Token
2
-
3
- 规范源:`docs/junsi-dev-docs/2-架构设计/主题语义Token规范v1.md`(实现:`src/theme/`)。插件 UI 主题的**唯一正确做法**:全量经 `var(--*)` 消费语义 token,禁止内联色值。
4
-
5
- ## 三级语义模型
6
-
7
- ```
8
- primitives(原始色板)→ semantic(语义角色)→ component(CSS 变量,唯一被 var() 读取)
9
- ```
10
-
11
- v1 落点:`.qtheme` 主题直接表达 semantic/component 层(即 `--*` 平铺变量)。插件不写主题,只**消费**这些变量。token 点分命名,`.` 归一化为 `-`(`background.emphasis` → `--background-emphasis`)。
12
-
13
- ## 色板 token(plugin-ui 消费全集)
14
-
15
- | token(theme.json 键) | CSS 变量 | 默认值(dark) | 语义 |
16
- |---|---|---|---|
17
- | background | `--background` | `230 20% 6%` | 页面底色 |
18
- | foreground | `--foreground` | `220 20% 93%` | 主文字 |
19
- | card / card-foreground | `--card` / `--card-foreground` | `228 18% 10%` / `220 20% 93%` | 卡片 |
20
- | popover / popover-foreground | `--popover` / `--popover-foreground` | `228 18% 10%` / `220 20% 93%` | 浮层 |
21
- | primary / primary-foreground | `--primary` / `--primary-foreground` | `142 71% 48%` / `230 20% 6%` | 主强调 |
22
- | secondary / secondary-foreground | `--secondary` / `--secondary-foreground` | `228 18% 14%` / `220 20% 93%` | 次级 |
23
- | muted / muted-foreground | `--muted` / `--muted-foreground` | `228 10% 18%` / `228 8% 55%` | 弱化 |
24
- | accent / accent-foreground | `--accent` / `--accent-foreground` | `228 18% 14%` / `220 20% 93%` | 强调底 |
25
- | destructive / destructive-foreground | `--destructive` / `--destructive-foreground` | `0 84% 60%` / `220 20% 93%` | 危险 |
26
- | border | `--border` | `228 14% 21%` | 边框 |
27
- | input | `--input` | `228 14% 21%` | 输入框 |
28
- | ring | `--ring` | `142 71% 48%` | 焦点环 |
29
-
30
- 扩展语义(可选,emit 为 `--foreground-accent` 等):`foreground.accent`、`foreground.muted`、`foreground.destructive`、`background.elevated`、`background.emphasis`、`background.sunken`、`border.strong`、`border.accent`、`accent.hover`、`accent.active`、`status.success`、`status.warning`、`status.error`。
31
-
32
- ## 非色 token
33
-
34
- | token | CSS 变量 | 默认值 | 说明 |
35
- |---|---|---|---|
36
- | radius | `--radius` | `0.625rem` | 圆角 |
37
- | glass-blur | `--glass-blur` | `18px` | 毛玻璃模糊 |
38
-
39
- ## var() 消费约定
40
-
41
- - **全部用 `var(--*)`,禁止内联色值**(`#hex` / `rgb()` / `hsl()` 字面量)。plugin-ui 组件已全量 var() 消费,换主题即时生效,无需重建 dist。
42
- - 颜色值多为 HSL 三元组(如 `142 71% 48%`),组件库经 `hsl(var(--primary))` 解析。插件自定义 CSS 需要时同样写 `hsl(var(--primary) / <alpha>)` 形式。
43
- - Tailwind 侧直接用语义类名:`bg-primary`、`text-foreground`、`bg-muted`、`text-muted-foreground`、`border-border` 等(`@qomicex/plugin-ui/tailwind-preset` 已映射)。
44
-
45
- ## 主题贡献(entry.theme)
46
-
47
- - manifest `entry.theme` 指向 `dist/theme.css`,激活时注入 `<style data-plugin-theme>`。
48
- - 主题 CSS 只能**覆盖/补充** token,仍须全部 `var()` 引用,禁止内联色值:
49
-
50
- ```css
51
- :root[data-theme] {
52
- /* 可选:覆盖默认 HSL token */
53
- --primary: 142 71% 48%;
54
- }
55
- ```
56
-
57
- - `qomicex pack` 会自动把根目录的 `theme.css` 拷入 `dist/theme.css`(若 manifest 引用 `dist/theme.css`)。
58
- - **不要在插件里定义与启动器冲突的平铺变量**;需要私有样式时走组件 class(Tailwind)或局部作用域。
1
+ # 主题语义 Token
2
+
3
+ 规范源:`docs/junsi-dev-docs/2-架构设计/主题语义Token规范v1.md`(实现:`src/theme/`)。插件 UI 主题的**唯一正确做法**:全量经 `var(--*)` 消费语义 token,禁止内联色值。
4
+
5
+ ## 三级语义模型
6
+
7
+ ```
8
+ primitives(原始色板)→ semantic(语义角色)→ component(CSS 变量,唯一被 var() 读取)
9
+ ```
10
+
11
+ v1 落点:`.qtheme` 主题直接表达 semantic/component 层(即 `--*` 平铺变量)。插件不写主题,只**消费**这些变量。token 点分命名,`.` 归一化为 `-`(`background.emphasis` → `--background-emphasis`)。
12
+
13
+ ## 色板 token(plugin-ui 消费全集)
14
+
15
+ | token(theme.json 键) | CSS 变量 | 默认值(dark) | 语义 |
16
+ |---|---|---|---|
17
+ | background | `--background` | `230 20% 6%` | 页面底色 |
18
+ | foreground | `--foreground` | `220 20% 93%` | 主文字 |
19
+ | card / card-foreground | `--card` / `--card-foreground` | `228 18% 10%` / `220 20% 93%` | 卡片 |
20
+ | popover / popover-foreground | `--popover` / `--popover-foreground` | `228 18% 10%` / `220 20% 93%` | 浮层 |
21
+ | primary / primary-foreground | `--primary` / `--primary-foreground` | `142 71% 48%` / `230 20% 6%` | 主强调 |
22
+ | secondary / secondary-foreground | `--secondary` / `--secondary-foreground` | `228 18% 14%` / `220 20% 93%` | 次级 |
23
+ | muted / muted-foreground | `--muted` / `--muted-foreground` | `228 10% 18%` / `228 8% 55%` | 弱化 |
24
+ | accent / accent-foreground | `--accent` / `--accent-foreground` | `228 18% 14%` / `220 20% 93%` | 强调底 |
25
+ | destructive / destructive-foreground | `--destructive` / `--destructive-foreground` | `0 84% 60%` / `220 20% 93%` | 危险 |
26
+ | border | `--border` | `228 14% 21%` | 边框 |
27
+ | input | `--input` | `228 14% 21%` | 输入框 |
28
+ | ring | `--ring` | `142 71% 48%` | 焦点环 |
29
+
30
+ 扩展语义(可选,emit 为 `--foreground-accent` 等):`foreground.accent`、`foreground.muted`、`foreground.destructive`、`background.elevated`、`background.emphasis`、`background.sunken`、`border.strong`、`border.accent`、`accent.hover`、`accent.active`、`status.success`、`status.warning`、`status.error`。
31
+
32
+ ## 非色 token
33
+
34
+ | token | CSS 变量 | 默认值 | 说明 |
35
+ |---|---|---|---|
36
+ | radius | `--radius` | `0.625rem` | 圆角 |
37
+ | glass-blur | `--glass-blur` | `18px` | 毛玻璃模糊 |
38
+
39
+ ## var() 消费约定
40
+
41
+ - **全部用 `var(--*)`,禁止内联色值**(`#hex` / `rgb()` / `hsl()` 字面量)。plugin-ui 组件已全量 var() 消费,换主题即时生效,无需重建 dist。
42
+ - 颜色值多为 HSL 三元组(如 `142 71% 48%`),组件库经 `hsl(var(--primary))` 解析。插件自定义 CSS 需要时同样写 `hsl(var(--primary) / <alpha>)` 形式。
43
+ - Tailwind 侧直接用语义类名:`bg-primary`、`text-foreground`、`bg-muted`、`text-muted-foreground`、`border-border` 等(`@qomicex/plugin-ui/tailwind-preset` 已映射)。
44
+
45
+ ## 主题贡献(entry.theme)
46
+
47
+ - manifest `entry.theme` 指向 `dist/theme.css`,激活时注入 `<style data-plugin-theme>`。
48
+ - 主题 CSS 只能**覆盖/补充** token,仍须全部 `var()` 引用,禁止内联色值:
49
+
50
+ ```css
51
+ :root[data-theme] {
52
+ /* 可选:覆盖默认 HSL token */
53
+ --primary: 142 71% 48%;
54
+ }
55
+ ```
56
+
57
+ - `qomicex pack` 会自动把根目录的 `theme.css` 拷入 `dist/theme.css`(若 manifest 引用 `dist/theme.css`)。
58
+ - **不要在插件里定义与启动器冲突的平铺变量**;需要私有样式时走组件 class(Tailwind)或局部作用域。