# whatevertools 文档全文
> 由 website/scripts/prepare.mjs 生成。仓库:https://github.com/mowtwo/whatevertools
---
# 快速开始
## 已经有一个 uTools 插件
不用改代码,先看它在各平台能跑到什么程度,再直接运行:
```bash
npm i -D @whatevertools/cli electron
npx whatevertools check ./dist # 用到的 API 在 Electron / 网页上的支持程度
npx whatevertools run ./dist # 在裸 Electron 启动器里运行,Alt+Shift+Space 打开主面板
```
打包成三种产物:
```bash
npx whatevertools pack ./dist --target utools # 原样输出给 uTools 发布
npx whatevertools pack ./dist --target electron --product "我的工具" # Electron 独立应用
npx whatevertools pack ./dist --target web # 静态网页
```
需要长期维护时,用配置文件和 `build`:
```bash
npx whatevertools init --ci github # 生成 whatevertools.config.ts 和 GitHub Actions
npx whatevertools build # 按配置打包所有目标
npx whatevertools build --target electron --binary # 出 dmg / nsis / AppImage 安装包
```
下一步:[移植 uTools 插件](./porting) · [打包与签名](./packaging)
## 写一个新插件
继续用 uTools 的写法(plugin.json + preload + 网页)就能同时发布到 uTools 和其他平台。只想要类型提示:
```bash
npm i -D @whatevertools/utools # 只有 .d.ts,零运行时,不进打包产物
```
也可以直接用原生 API,名字更中立,换成自定义注入名的宿主也能运行:
```ts
import { getApi } from '@whatevertools/api'
const wt = getApi()
wt.lifecycle.onEnter((action) => { /* … */ })
const files = await wt.dialog.openAsync({ properties: ['openFile'] })
if (wt.supports('screen.captureRegion')) await wt.screen.captureRegion()
```
用 Vite 写界面时,加上 [Vite 插件](./vite) 就能在 `vite dev` 里直接调试插件环境。
## 想给自己的应用加插件
- Electron 应用:[给 Electron 应用加插件](./electron-host)
- 网站:[给网站加插件](./web-host)
---
# 核心概念
## 分层
```
插件代码 ──► 兼容层(@whatevertools/compat-utools:window.utools、plugin.json、入口类型、唤起来源)
│
▼
原生 API(@whatevertools/api:window.whatevertools,按命名空间组织)
│ 只依赖能力接口
▼
能力接口(core)◄── Node / Electron 模块垫片(shim-node、shim-electron)
│
┌─────────┴──────────┐
Electron 平台 网页平台 (Tauri:规划中)
│ main 进程宿主 │ 浏览器运行时 + 适配器包
▼ ▼
宿主核心引擎(core):插件表、生命周期、权限、入口与来源、定时任务、动态功能、快捷键、提供者、身份、同步
```
- **平台无关的核心**:生命周期、权限、入口、调度、存储、同步都在 `@whatevertools/core`,平台只实现适配器接口。
- **兼容层**:uTools 特有的名字只出现在 `compat-utools`。宿主可以改注入名(`window.myhost`)或者不加载兼容层。
- **适配器包**:给某个平台补能力的实现(网页上的 sharp、文件对话框、虚拟文件系统……)都是独立的包,宿主可以替换。
## 插件描述
| 文件 | 谁读 | 作用 |
|---|---|---|
| `plugin.json` | uTools、兼容层 | uTools 插件描述 |
| `whatevertools.json` | 兼容层 | 补充身份、权限用途说明、`targets`、独立应用配置;带 `main` 字段时就是原生格式的插件描述。uTools 不读它 |
```json
{
"id": "quick-union-image",
"name": "快拼图",
"version": "1.2.0",
"targets": ["utools", "electron", "web"],
"permissions": { "fs.read": { "reason": "读取你选择、拖入或粘贴的图片" } },
"standalone": { "productName": "快拼图", "startFeature": "stitch" }
}
```
## targets
插件声明自己能在哪些目标上运行。只在桌面上成立的插件(透明置顶悬浮层、桌面挂件)写 `["utools", "electron"]`,打包到网页会直接报错,网页宿主也会拒绝安装。
## 身份与版本
插件能拿到底座(运行时)版本、宿主名字和版本(任意字符串,插件自行解析)、伪装平台名和版本(旧插件 `getAppName` / `getAppVersion` 看到的值)、插件自己的名字和版本:
```ts
whatevertools.identity
// { runtime: { name, version }, host: { name, version }, mock: { platform, runtime }, plugin: { id, name, version }, env }
```
## 支持程度
每个 API 在每个平台上标为:**原生**(行为一致)、**模拟**(换了实现、语义一致)、**部分**、**占位**(不报错但没效果)、**不支持**(抛 `EWTUNSUPPORTED`,宿主可配成警告)。完整表见 [API 支持程度](./api-support),运行时用 `supports(name)` 查询。
---
# 移植 / 多端兼容
目标:同一份插件代码继续发布到 uTools,同时能打包成 Electron 独立应用和网页。大多数 uTools 插件不用改代码,下面是需要注意的地方。
## 1. 先跑兼容报告
```bash
npx whatevertools check ./dist
```
按平台列出插件用到的每个 API 和支持程度。重点看网页列里的「部分 / 占位 / —」。
## 2. 加 whatevertools.json(不影响 uTools)
放在插件构建产物目录(或上一级),给兼容层看:
```json
{
"id": "quick-union-image",
"name": "快拼图",
"version": "1.2.0",
"targets": ["utools", "electron", "web"],
"permissions": {
"fs.read": { "reason": "读取你选择、拖入或粘贴的图片" },
"mcp": { "reason": "让 AI Agent 调用拼图工具" }
},
"standalone": { "productName": "快拼图", "startFeature": "stitch" }
}
```
- `permissions` 只是用途说明,显示在权限询问里,不会自动授予。权限 id 见 hosts.md。
- 只在桌面上成立的插件(透明置顶悬浮层、桌面挂件、模拟键鼠)把 `targets` 写成 `["utools", "electron"]`。
- Vite 项目可以用 `@whatevertools/vite` 的 `manifest` 选项自动写这个文件。
## 3. 类型
```bash
npm i -D @whatevertools/utools # 只有 .d.ts,零运行时,不进打包产物
```
```jsonc
// tsconfig.json
{ "compilerOptions": { "types": ["@whatevertools/utools"] } }
// 或者 window.utools 声明成可选(在普通浏览器里调界面时):
///
```
## 4. 网页平台的差异和改法
| 情况 | 网页上的行为 | 建议 |
|---|---|---|
| `showOpenDialog`(同步) | 返回 `undefined`,异步弹出选择器,选中的文件默认作为一次 `files` 进入(`onPluginEnter` 收到 `type: 'files'`、`from: 'dialog'`) | 改用 `dialog.openAsync`(见下方代码) |
| `showOpenDialog` 选目录且带 `createDirectory` | 返回 `['/downloads']`,写进去的文件都会下载 | 不用改 |
| `showSaveDialog` | 返回 `/downloads/<文件名>`,写入后浏览器下载 | 不用改 |
| `shellShowItemInFolder` | 不报错、没效果 | 不用改 |
| `utools.sharp` | `@whatevertools/sharp-canvas`(Canvas 实现):resize / extend / extract / rotate / flip / flop / composite / trim / flatten / png / jpeg / webp / metadata / stats 等常用操作 | 不支持 AVIF 编码、ICC、动图(只取第一帧);对结果要求严格时在桌面端处理 |
| SVG 输入 | 自动补 `xmlns` 后用浏览器解码 | 不用改 |
| `require('fs')` 等 | 虚拟文件系统(IndexedDB 持久化);`/drop` `/picked` `/downloads` `/tmp` 不落盘 | 别依赖真实路径;用户文件通过拖入 / 选择获得 |
| `require('child_process')`、原生模块 | 默认不支持;宿主可以接本地 bridge 守护进程 | 用 `supports()` 判断后降级 |
| 窗口置顶、鼠标穿透、全局快捷键、屏幕截图 | 浏览器做不到 | 声明 `targets` 排除 web |
| `createBrowserWindow` | `window.open` 模拟(会被弹窗拦截影响) | 能在主界面完成的就别开新窗口 |
打开文件改成异步、同时保持 uTools 构建不变:
```ts
async function openDialog(options) {
// whatevertools 兼容层(独立应用、网页)提供原生 API;uTools 里没有
const native = globalThis[Symbol.for('whatevertools.api')]
if (native?.dialog?.openAsync) return (await native.dialog.openAsync(options)) ?? []
return window.utools.showOpenDialog(options) ?? []
}
```
## 5. Electron 平台的差异
- 每个插件一个独立的会话分区(localStorage / IndexedDB / cookie 互不可见)。
- preload 里的 `require` 拦截是纵深防御,不是安全边界。
- `pluginSetting.single: false`(多实例)暂时按单实例运行。
- 无界面测试(`WHATEVERTOOLS_HEADLESS=1`)时插件窗口固定在屏幕外。
## 6. 打包
见 packaging.md。最小用法:
```bash
npx whatevertools pack ./dist --target electron --name acme --product "Acme 工具"
npx whatevertools pack ./dist --target web --name acme
npx whatevertools pack ./dist --target utools
```
`--name` 替换注入给插件的全局变量名(`window.acme`)、底座名和包名;uTools 兼容层仍注入 `window.utools`,可以用 `--compat utools:myhost` 改名或 `--compat none` 关闭。
## 7. 常见问题
- **网页上卡住没反应**:多半是权限询问用的 `confirm()` 被挡住了(测试环境),或者调用了需要用户手势的 API(文件选择要在点击事件里同步发起)。
- **`EWTUNSUPPORTED`**:这个 API 在当前平台不支持,查 api-support.md,用 `supports()` 判断后降级。
- **`EWTPERM`**:权限被拒绝;用户可以在宿主的设置页改。
- **`EWTENCRYPTED`**:给的是 `.upxs`,只能由 uTools 安装。
- **打包报「声明只支持 …」**:插件的 `targets` 不包含这个目标。
---
# @whatevertools/vite
[whatevertools](https://github.com/mowtwo/whatevertools) 的 Vite 插件。普通的 Vite 项目(React、Vue、原生都行)加上它:
- `vite build`:把 `whatevertools.json`(身份、权限说明、支持的目标、独立应用配置)写进产物,并按需打包成 uTools 插件、Electron 独立应用或网页;
- `vite dev`:在普通浏览器里模拟插件环境——注入运行时、把 `public/preload.js` 打包成浏览器脚本、`window.utools` 直接可用,不用开 uTools 也能调界面。
```ts
// vite.config.ts
import { defineConfig } from 'vite'
import whatevertools from '@whatevertools/vite'
export default defineConfig({
base: './',
plugins: [
whatevertools({
manifest: { id: 'my-plugin', name: '我的插件', targets: ['utools', 'electron', 'web'] },
// 不写 targets 时读环境变量:WT_TARGET=web,electron vite build
targets: ['utools'],
name: 'acme', // electron / web 目标的注入名和包名
}),
],
})
```
uTools 的 `plugin.json` 和 `preload.js` 照常放在 `public/`。产物在 `release/<目标>/`。
| 选项 | 说明 |
|---|---|
| `targets` | `utools` / `electron` / `web`,可多个;默认读 `WT_TARGET` |
| `manifest` | 写进 `dist/whatevertools.json`,`version` 默认取 package.json |
| `out` | 打包输出目录,默认 `release` |
| `name` / `packageName` / `productName` | 注入名、包名、应用名 |
| `compat` | `utools`(默认)/ `utools:别名` / `none` |
| `devRuntime` | `vite dev` 时是否模拟插件环境,默认 `true` |
MIT
---
# 打包与发布
`whatevertools`(简写 `wt`)把一个插件打成三种产物:
| 目标 | 产物 | 说明 |
|---|---|---|
| `utools` | 原样的 uTools 插件目录 | 不带任何运行时,校验 plugin.json;`whatevertools.json` 不会进入产物 |
| `electron` | 独立应用目录,可以继续出安装包(dmg / zip / nsis / AppImage…) | 多个插件时是一个启动器应用 |
| `web` | 静态站点 | 多个插件时根目录是入口页,每个插件一个子目录 |
插件在 `whatevertools.json` 里写 `"targets": ["utools", "electron"]` 可以声明只支持哪些目标,打包到其他目标会直接报错(比如只在桌面上有意义的悬浮挂件)。
## 配置文件
```bash
npx whatevertools init # 生成 whatevertools.config.ts
npx whatevertools init --ci github # 同时生成 .github/workflows/build-apps.yml
```
```ts
// whatevertools.config.ts(也可以是 .mjs / .cjs / .json)
import { defineConfig } from '@whatevertools/cli'
export default defineConfig({
plugins: './dist', // 插件目录或插件包(.upx / .asar / .zip),可以多个
targets: ['utools', 'electron', 'web'],
out: 'release',
name: 'acme', // electron / web 的注入名(window.acme)和底座名
productName: '快拼图',
electron: {
install: { enabled: false }, // 打包后的应用能不能再装插件包(见 README「插件包」)
binary: {
formats: { mac: ['dmg', 'zip'], win: ['nsis'], linux: ['AppImage'] },
arch: ['arm64', 'x64'], // 默认当前机器架构
icon: 'build/icon.png', // 至少 512×512;不写用第一个插件的 logo
builder: {}, // 原样合并进 electron-builder 配置
},
sign: {
mac: { identity: undefined, notarize: undefined, entitlements: undefined },
win: { mode: 'azure', azure: { endpoint: '…', certificateProfileName: '…', codeSigningAccountName: '…' } },
},
},
})
```
## 命令
```bash
wt build # 按配置打包所有 targets
wt build --target web,electron # 只打这几个(也可以用环境变量 WT_TARGET)
wt build --target electron --binary # 继续出安装包(需要项目里 npm i -D electron-builder)
wt build --binary --platform mac,win # 指定安装包平台(跨平台构建受 electron-builder 限制,建议在 CI 矩阵里各平台分别构建)
wt doctor --target electron --binary # 检查 Node、Electron、electron-builder 和签名凭据,缺什么列出来
wt pack <插件目录或插件包...> --target web # 不用配置文件、直接打包
```
所有命令都是非交互的:
- `--json`:stdout 只输出一行结果 JSON(`{"ok":true,...}` / `{"ok":false,"code":"…","error":"…"}`),日志都在 stderr;
- 退出码:`0` 成功,`1` 失败(包括 `doctor` 有必需项没通过),`2` 用法或配置错误;
- 环境变量:`WT_CONFIG` 指定配置文件,`WT_TARGET` 覆盖目标。
### make
```makefile
build:
npx whatevertools build
apps:
npx whatevertools doctor --target electron --binary
npx whatevertools build --target electron --binary --json > release/result.json
```
## 签名和公证
证书、密码、API Key **只从环境变量读**,不要写进配置文件或提交到仓库。缺任何一项时 `wt build --binary` 直接失败,不会悄悄产出一个没签名的包;明确不想签名时在配置里关掉(`sign.mac.identity: false`、`sign.win.mode: 'none'`)。
### macOS
| 环境变量 | 说明 |
|---|---|
| `CSC_LINK` | Developer ID Application 证书(.p12 文件路径或 base64) |
| `CSC_KEY_PASSWORD` | 证书密码 |
| `APPLE_API_KEY` | App Store Connect API Key(**.p8 文件路径**) |
| `APPLE_API_KEY_ID` / `APPLE_API_ISSUER` | Key ID / Issuer ID |
| 或 `APPLE_ID` + `APPLE_APP_SPECIFIC_PASSWORD` + `APPLE_TEAM_ID` | 用 Apple ID 公证 |
| 或 `APPLE_KEYCHAIN_PROFILE` | `xcrun notarytool store-credentials` 存好的配置名 |
本机钥匙串里有 Developer ID Application 证书时不需要 `CSC_LINK`。开启 hardened runtime,默认 entitlements 是 Electron 需要的最小集合(JIT、可执行内存、加载插件自带的原生模块)。有公证凭据时自动公证。
### Windows
| 方式 | 环境变量 |
|---|---|
| pfx 证书 | `WIN_CSC_LINK`(或 `CSC_LINK`)+ `WIN_CSC_KEY_PASSWORD`(或 `CSC_KEY_PASSWORD`) |
| Azure Trusted Signing | `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` + `AZURE_CLIENT_SECRET`,配置里写 `sign.win.azure`;需要在 Windows 上构建 |
### Linux
AppImage / deb / tar.gz 不需要代码签名。
## CI
`wt init --ci github` 生成的工作流:推送 `v*` 标签时在 macOS、Windows、Linux 上分别构建,先跑 `doctor`,再 `build --binary --json`,产物上传为 artifact 并挂到 GitHub Release。需要在仓库 Settings → Secrets 里配置:
`MAC_CSC_LINK`、`MAC_CSC_KEY_PASSWORD`、`APPLE_API_KEY_CONTENT`(.p8 的文件内容,工作流会写成文件)、`APPLE_API_KEY_ID`、`APPLE_API_ISSUER`、`WIN_CSC_LINK`、`WIN_CSC_KEY_PASSWORD`,或 Azure 的三个变量。没配的平台就不签名(`doctor` 会提示)。
## Vite 项目
普通 Vite 项目可以用 `@whatevertools/vite`,`vite build` 时直接产出各目标,`vite dev` 时在浏览器里模拟插件环境,见 [@whatevertools/vite](https://github.com/mowtwo/whatevertools/tree/main/packages/vite)。
---
# 给 Electron 应用加插件
`@whatevertools/electron` 是一个完整的插件宿主:插件表、生命周期、权限询问、入口与快捷键、窗口、MCP 工具、插件包安装。你的 Electron 应用调用 `runApp`(或者直接用 `WhateverToolsHost` 自己组装界面)就有了插件生态。
```bash
npm i @whatevertools/electron @whatevertools/compat-utools
```
```ts
import { runApp } from '@whatevertools/electron'
runApp({
mode: 'launcher', // 'standalone':单个插件就是整个应用
plugins: ['/path/to/plugin', '/path/to/pack.upx'],
compat: [{ type: 'utools' }], // 现有 uTools 插件直接跑
apiName: 'acme', // 原生 API 注入名:window.acme
identity: {
runtimeName: 'acme',
host: { name: 'Acme Launcher', version: '2026.10-beta' },
mock: { platform: { name: 'uTools', version: '7.0.0' } },
},
install: { enabled: true, max: 20, deny: ['bad-*'] },
policy: { plugins: { 'quick-union-image': { 'fs.read': ['/Users/me/Pictures'] } } },
providers: {
user: { get: () => ({ nickname: 'me', avatar: '', type: 'user' }) },
ai: { models: () => [/* … */], complete: async (ctx, req, io) => { /* 调你的模型;io.callTool 调插件工具 */ } },
},
mcp: { enabled: true },
timeouts: { idleExit: 3 * 60 * 1000 }, // 退到后台 3 分钟后结束插件(有窗口开着的除外)
})
```
## 你得到了什么
- **启动器**:主面板(`Alt+Shift+Space`)、超级面板、指令快捷键、URL scheme、托盘。
- **设置页**:每个插件每项权限单独设置,安装 / 卸载插件包,复制 MCP 配置。
- **插件包安装**:`host.installer.install(file)`,详见 [插件包](./packages)。
- **安全**:每个插件独立的会话分区;远程网页窗口拿不到插件 API;插件页的摄像头、定位等浏览器权限映射成插件权限。
- **特殊接口提供者**:用户信息、AI 模型、付费、设备 id 由你提供,调用经过 `provider:` hook。
## 只要宿主、不要启动器界面
```ts
import { WhateverToolsHost } from '@whatevertools/electron'
const host = new WhateverToolsHost({ compat: [{ type: 'utools' }], install: { enabled: true } })
const plugin = host.loadPlugin('/path/to/plugin')
await host.enter(plugin.id, { code: 'main', type: 'text', payload: '打开', from: 'main' })
```
入口类型、唤起来源、面板行为都可以替换,见 [hook 与权限](./hooks)。
---
# 给网站加插件
两种方式:
1. **固定插件**:`wt pack --target web` 把插件打成静态站点,多个插件打成带入口页的站点,部署到任何静态托管。
2. **动态安装**:网站引入 `@whatevertools/web-host`,用户在浏览器里安装 `.upx` 插件包,每个插件跑在自己的 iframe 里。
## 动态安装
需要三个静态文件,页面必须是 https 或 localhost(Service Worker 的要求):
1. `@whatevertools/web-host/sw.js`:放在能覆盖作用域的位置,比如 `/__wt/sw.js`(作用域默认 `/__wt/`)。
2. 浏览器运行时:把 `@whatevertools/web/full` 打成 iife,全局名 `__whatevertoolsRuntime`:
```bash
npx esbuild node_modules/@whatevertools/web/dist/full.js --bundle --format=iife --global-name=__whatevertoolsRuntime --minify --outfile=public/wt-runtime.js
```
3. 你的页面(用打包工具引入本包)。
## 用法
```ts
import { createWebHost } from '@whatevertools/web-host'
const host = createWebHost({
swUrl: '/__wt/sw.js',
runtimeUrl: '/wt-runtime.js',
container: document.querySelector('#plugin-area')!,
policy: { max: 5, deny: ['evil-*'] }, // 同 core 的 InstallPolicy
prompt: async (c) => myDialog(`安装「${c.name}」${c.version}?`), // 默认浏览器 confirm
apiName: 'acme', // 原生 API 注入名;uTools 兼容层默认照常注入 window.utools
})
await host.ready
input.onchange = async () => {
try {
const rec = await host.install(input.files![0]) // File / Blob / Uint8Array;文件夹传 FileList()
host.open(rec.id, { feature: 'main' })
} catch (e: any) {
alert(e.message) // e.code:EWTINSTALL(策略 / 取消 / 描述文件)、EWTENCRYPTED(upxs)
}
}
host.list() // 已安装的插件
host.close('some-id') // 关掉它的 iframe
await host.uninstall('some-id', { purgeData: true })
```
- `host.hooks` 上可以挂 `plugin:install`(`apply`,`args[0]` 是 `InstallCandidate`,返回 `false` 拒绝),接自己的审核或签名校验。
- 描述文件按其他平台的规则读取(`whatevertools.json` 优先,再 `plugin.json`),插件 id 一致;声明了 `targets` 且不含 `web` 的插件会被拒绝。
- 同一个 id 再装一次就是升级:新版本文件写完后才切换记录,已打开的 iframe 自动重新加载。
- preload 不需要构建:运行时用 `preloadMode: 'cjs'`,在浏览器里按 Node 规则 `require` 插件里的相对文件、JSON 和 `node_modules`。
## 限制
- iframe 和宿主页同源,插件之间、插件和宿主之间不是安全边界(存储按插件 id 分开,但恶意插件能访问 `parent`)。只装可信的插件;需要和主站隔离时,把整个宿主(页面 + 作用域)放到单独的子域名下。
- 插件文件在 IndexedDB 里,受浏览器存储配额限制。
- preload 里用到浏览器端没有的 Node 能力(原生模块、真实子进程)时会报错;需要的话接 `@whatevertools/bridge`(通过 `boot` 选项传启动配置)。
MIT
## 在自己的页面里启动运行时
```ts
import { boot } from '@whatevertools/web/full' // 带 uTools 兼容层和默认适配器
const rt = await boot({
manifestFiles: { 'plugin.json': pluginJson },
files: { 'index.html': 1234, 'preload.js': 567 }, // 插件文件清单(相对路径 → 字节数)
base: '/plugins/my-plugin/',
apiName: 'acme',
compat: [{ type: 'utools' }],
preloadMode: 'cjs', // preload 在浏览器里直接按 CommonJS 执行,不用预先打包
fileDialogs: { openDialog: 'enter' },
})
await rt.start()
```
---
# 插件包(upx)
| 格式 | 是什么 | 支持 |
|---|---|---|
| `.upx` | gzip 压缩的 asar 归档(uTools 的离线插件包) | 读、写、安装、打包 |
| `.asar` | Electron 归档(uTools 本地安装的插件就是这个格式) | 读、写、安装、打包 |
| `.zip` | 普通压缩包(store / deflate) | 读、安装、打包 |
| `.upxs` | uTools 加密并签名的安装包 | **不支持**:只能由 uTools 安装,请向作者索取 upx 或源码 |
> 需要独立加载、调试一个已经打成 `.upxs` 的开发者包?另有一个与核心分开的调试工具 [whatevertools Inspector](https://github.com/mowtwo/whatevertools-inspector-dist/releases)(macOS,版本不稳定,仅针对离线开发者包,不公开解密细节,也不绕过任何付费校验)。whatevertools 核心本身仍不支持 `.upxs`。
## 兼容安装模式
加了 SDK 的平台可以动态安装任意插件包,策略可配:
```ts
install: {
enabled: true,
max: 10, // 最多装几个(升级不算新增)
allow: ['acme-*'], // 只允许这些 id
deny: ['bad-*'], // 禁止这些 id(优先于 allow)
formats: ['upx', 'asar'],
confirm: true, // 安装前让用户确认(显示名字、版本、插件声明的权限用途)
}
```
- **Electron**:`host.installer.install(file)` / `uninstall(id)` / `list()`;装进数据目录,重启自动加载,同 id 再装就是升级。启动器默认允许安装,独立应用默认不允许。
- **网页**:`@whatevertools/web-host` 的 `install(file)`,文件存进 IndexedDB,由 Service Worker 提供,见 [给网站加插件](./web-host)。
- **自定义审核**:`plugin:install` hook 返回 `false` 拒绝安装,可以接签名校验、白名单服务或你自己的确认界面。
## 固定打包模式
一个或几个插件直接打成一个产物,用户拿到就能用:
```bash
# 一个 upx 打成 Electron 独立应用 / 网页
npx whatevertools pack ./translate.upx --target electron
npx whatevertools pack ./translate.upx --target web
# 几个插件打成一个「工具箱」应用(启动器模式)或一个带入口页的网站
npx whatevertools pack ocr.upx translate.upx ./my-plugin --target electron --product 工具箱 --package toolbox
npx whatevertools pack ocr.upx translate.upx --target web --product 工具箱 --package toolbox
# 打包后的应用还允许用户再装插件
npx whatevertools pack a.upx b.upx --target electron --product 工具箱 --allow-install --max-plugins 20
```
## 编程接口
```ts
import { openPackage, createUpx, readAsar, writeAsar } from '@whatevertools/upx' // 浏览器和 Node 都能用
import { extractPackage, packDirectory } from '@whatevertools/upx/node'
const pkg = await openPackage(bytes, { name: 'x.upx' }) // { format, files, manifest }
const upx = await packDirectory('./dist') // 插件目录 → .upx
```
---
# hook 与权限
---
# 网页适配器
网页平台没有 Node 和 Electron。缺的能力由一组**可替换的适配器包**补上:运行时默认接入,宿主可以换成自己的实现,也可以关掉。
| 包 | 补的能力 | 默认 |
|---|---|---|
| `@whatevertools/fs-virtual` | `fs` / `fs/promises`:内存常驻、IndexedDB 持久化的 POSIX 文件系统;`/drop` `/picked` `/downloads` `/tmp` 不落盘 | 是 |
| `@whatevertools/db-indexeddb` | 插件数据库 `utools.db` / `dbStorage` | 是 |
| `@whatevertools/sharp-canvas` | `utools.sharp`:Canvas 实现的常用子集 | full 入口默认 |
| `@whatevertools/web-files` | `showOpenDialog` / `showSaveDialog` / `dialog.openAsync` / `shell.openPath`:浏览器文件选择器和下载 | 是(`fileDialogs: false` 关闭) |
| `@whatevertools/shim-node` / `shim-electron` | `path` `os` `events` `util` `crypto` `buffer`……以及 `ipcRenderer` `clipboard` `nativeImage` | 是 |
| `@whatevertools/bridge` + `bridge-server` | 真实文件系统、子进程、系统剪贴板、shell:通过本地守护进程,守护进程用系统对话框问权限 | 否(配置 `bridge`) |
| `@whatevertools/sync-http` / `sync-folder` | 数据同步 | 否(配置 `sync`) |
## sharp-canvas
Browser implementation of a practical subset of the sharp image API on top of HTML Canvas
在浏览器里用 Canvas 实现 [sharp](https://sharp.pixelplumbing.com/) 的常用子集,Web 运行时把它注入成 `utools.sharp` / `image.sharp`,让用了 sharp 的 uTools 插件在浏览器里照常工作。
这是 [whatevertools](https://github.com/mowtwo/whatevertools) 的一部分,用法和架构见仓库 README。
## 用法
```ts
import { createCanvasSharp } from '@whatevertools/sharp-canvas'
const sharp = createCanvasSharp({
readFile: (p) => fs.readFileSync(p), // 路径输入
writeFile: (p, d) => fs.writeFileSync(p, d), // toFile
Buffer, // 有 Buffer shim 时输出用 Buffer.from 包装
})
const png = await sharp(input).resize(256, 256, { fit: 'contain', background: 'transparent' }).png().toBuffer()
```
有 `document` 时用 `