mulby-cli 1.1.5

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.
Files changed (59) hide show
  1. package/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  2. package/README.md +852 -0
  3. package/assets/default-icon.png +0 -0
  4. package/dist/commands/ai-session.js +44 -0
  5. package/dist/commands/build.js +111 -0
  6. package/dist/commands/config-ai.js +291 -0
  7. package/dist/commands/config.js +53 -0
  8. package/dist/commands/create/ai-create.js +183 -0
  9. package/dist/commands/create/assets.js +53 -0
  10. package/dist/commands/create/basic.js +72 -0
  11. package/dist/commands/create/index.js +73 -0
  12. package/dist/commands/create/react.js +136 -0
  13. package/dist/commands/create/templates/basic.js +383 -0
  14. package/dist/commands/create/templates/react/backend.js +72 -0
  15. package/dist/commands/create/templates/react/config.js +166 -0
  16. package/dist/commands/create/templates/react/docs.js +78 -0
  17. package/dist/commands/create/templates/react/hooks.js +469 -0
  18. package/dist/commands/create/templates/react/index.js +41 -0
  19. package/dist/commands/create/templates/react/types.js +1228 -0
  20. package/dist/commands/create/templates/react/ui.js +528 -0
  21. package/dist/commands/create/templates/react.js +1888 -0
  22. package/dist/commands/dev.js +141 -0
  23. package/dist/commands/pack.js +160 -0
  24. package/dist/commands/resume.js +97 -0
  25. package/dist/commands/test-ui.js +50 -0
  26. package/dist/index.js +71 -0
  27. package/dist/services/ai/PLUGIN_API.md +1102 -0
  28. package/dist/services/ai/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  29. package/dist/services/ai/context-manager.js +639 -0
  30. package/dist/services/ai/index.js +88 -0
  31. package/dist/services/ai/knowledge.js +52 -0
  32. package/dist/services/ai/prompts.js +114 -0
  33. package/dist/services/ai/providers/base.js +38 -0
  34. package/dist/services/ai/providers/claude.js +284 -0
  35. package/dist/services/ai/providers/deepseek.js +28 -0
  36. package/dist/services/ai/providers/gemini.js +191 -0
  37. package/dist/services/ai/providers/glm.js +31 -0
  38. package/dist/services/ai/providers/minimax.js +27 -0
  39. package/dist/services/ai/providers/openai.js +177 -0
  40. package/dist/services/ai/tools.js +204 -0
  41. package/dist/services/ai-generator.js +968 -0
  42. package/dist/services/config-manager.js +117 -0
  43. package/dist/services/dependency-manager.js +236 -0
  44. package/dist/services/file-writer.js +66 -0
  45. package/dist/services/plan-adapter.js +244 -0
  46. package/dist/services/plan-command-handler.js +172 -0
  47. package/dist/services/plan-manager.js +502 -0
  48. package/dist/services/session-manager.js +113 -0
  49. package/dist/services/task-analyzer.js +136 -0
  50. package/dist/services/tui/index.js +57 -0
  51. package/dist/services/tui/store.js +123 -0
  52. package/dist/types/ai.js +172 -0
  53. package/dist/types/plan.js +2 -0
  54. package/dist/ui/Terminal.js +56 -0
  55. package/dist/ui/components/InputArea.js +176 -0
  56. package/dist/ui/components/LogArea.js +19 -0
  57. package/dist/ui/components/PlanPanel.js +69 -0
  58. package/dist/ui/components/SelectArea.js +13 -0
  59. package/package.json +45 -0
@@ -0,0 +1,1102 @@
1
+ # InTools 插件开发完整指南
2
+
3
+ > 本文档包含 InTools 插件开发所需的全部信息,包括 Manifest 配置规范和 API 参考。
4
+ > - **UI/渲染进程**:`window.intools.{模块名}`
5
+ > - **插件后端**:`context.api.{模块名}`
6
+
7
+ ---
8
+
9
+ # 第一部分:Manifest 配置规范
10
+
11
+ ## manifest.json 基本结构
12
+
13
+ ```json
14
+ {
15
+ "name": "my-plugin",
16
+ "version": "1.0.0",
17
+ "type": "utility",
18
+ "displayName": "我的插件",
19
+ "description": "插件描述",
20
+ "main": "dist/main.js",
21
+ "ui": "ui/index.html",
22
+ "icon": "icon.png",
23
+ "pluginSetting": {
24
+ "single": true,
25
+ "height": 400
26
+ },
27
+ "window": {
28
+ "width": 800,
29
+ "height": 600
30
+ },
31
+ "features": [
32
+ {
33
+ "code": "main",
34
+ "explain": "主功能",
35
+ "cmds": [{ "type": "keyword", "value": "关键词" }]
36
+ }
37
+ ]
38
+ }
39
+ ```
40
+
41
+ ## 顶层字段
42
+
43
+ | 字段 | 类型 | 必需 | 说明 |
44
+ |------|------|------|------|
45
+ | name | string | ✓ | 插件唯一标识 |
46
+ | version | string | ✓ | 版本号 |
47
+ | type | string | | 类型(utility/productivity/developer/system/media/network/ai/entertainment/other) |
48
+ | displayName | string | ✓ | 显示名称 |
49
+ | description | string | | 插件描述 |
50
+ | main | string | ✓ | 后端入口文件 |
51
+ | ui | string | | UI 入口文件 |
52
+ | preload | string | | 自定义 preload 脚本(可使用 Node.js) |
53
+ | icon | string/object | | 插件图标(路径/URL/SVG) |
54
+ | features | array | ✓ | 功能入口列表 |
55
+ | pluginSetting | object | | 插件行为设置 |
56
+ | window | object | | 独立窗口配置 |
57
+
58
+ ## PluginSetting 配置
59
+
60
+ | 字段 | 类型 | 默认值 | 说明 |
61
+ |------|------|--------|------|
62
+ | single | boolean | true | 单例模式(不允许多开) |
63
+ | height | number | - | 初始高度 |
64
+
65
+ ## Window 配置
66
+
67
+ | 字段 | 默认值 | 说明 |
68
+ |------|--------|------|
69
+ | width | 500 | 默认宽度 |
70
+ | height | 400 | 默认高度 |
71
+ | minWidth | 300 | 最小宽度 |
72
+ | minHeight | 200 | 最小高度 |
73
+ | maxWidth | - | 最大宽度 |
74
+ | maxHeight | - | 最大高度 |
75
+
76
+ ## Feature 字段
77
+
78
+ | 字段 | 类型 | 必需 | 说明 |
79
+ |------|------|------|------|
80
+ | code | string | ✓ | 功能代码 |
81
+ | explain | string | ✓ | 功能说明 |
82
+ | cmds | array | ✓ | 触发命令列表 |
83
+ | mode | string | | 模式(ui/silent/detached) |
84
+ | route | string | | UI 路由路径 |
85
+ | icon | string/object | | 功能独立图标 |
86
+ | mainHide | boolean | | 触发时隐藏主窗口 |
87
+ | mainPush | boolean | | 向搜索框推送内容 |
88
+
89
+ ## Cmd 命令类型
90
+
91
+ | type | 触发方式 | 可用字段 |
92
+ |------|----------|----------|
93
+ | keyword | 关键词匹配 | `value`(关键词) |
94
+ | regex | 正则匹配 | `match`(正则), `explain?`, `label?`(指令名称), `minLength?`, `maxLength?` |
95
+ | files | 文件拖入 | `exts?`, `fileType?`(file/directory/any, 默认 any), `match?`(文件名正则, 与 exts 二选一), `minLength?`, `maxLength?` |
96
+ | img | 图片拖入 | `exts?` |
97
+ | over | 选中文本 | `label?`(指令名称), `exclude?`(排除正则), `minLength?`, `maxLength?`(默认 10000) |
98
+
99
+ ### 示例
100
+
101
+ ```json
102
+ {
103
+ "features": [
104
+ {
105
+ "code": "format",
106
+ "explain": "格式化 JSON",
107
+ "cmds": [
108
+ { "type": "keyword", "value": "json" },
109
+ { "type": "regex", "match": "^\\s*[{\\[]", "explain": "检测到 JSON" }
110
+ ]
111
+ },
112
+ {
113
+ "code": "process-pdf",
114
+ "explain": "处理 PDF 文件",
115
+ "cmds": [
116
+ { "type": "files", "exts": [".pdf"], "minLength": 1, "maxLength": 10 }
117
+ ]
118
+ },
119
+ {
120
+ "code": "folder-tool",
121
+ "explain": "文件夹工具",
122
+ "cmds": [
123
+ { "type": "files", "fileType": "directory" }
124
+ ]
125
+ }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ ## Icon 图标配置
131
+
132
+ ```json
133
+ // 字符串简写
134
+ "icon": "icon.png"
135
+ "icon": "https://example.com/icon.png"
136
+ "icon": "<svg>...</svg>"
137
+
138
+ // 对象形式
139
+ "icon": { "type": "file", "value": "assets/logo.png" }
140
+ "icon": { "type": "url", "value": "https://example.com/icon.png" }
141
+ "icon": { "type": "svg", "value": "<svg>...</svg>" }
142
+ ```
143
+
144
+ ## Preload 预加载脚本 ⭐ 核心概念
145
+
146
+ > [!IMPORTANT]
147
+ > **Preload 是 InTools 插件访问 Node.js 能力的核心机制。** 对于需要在渲染进程(UI)中使用 Node.js API、第三方 npm 模块或 Electron 渲染进程 API 的插件来说,Preload 是**必不可少**的。
148
+
149
+ ### 什么是 Preload?
150
+
151
+ Preload 脚本是一个特殊的 JavaScript 文件,在**渲染进程加载之前**执行,具有以下特点:
152
+
153
+ | 特性 | 说明 |
154
+ |------|------|
155
+ | 🔧 **Node.js 完整支持** | 可以使用 `require()` 导入任何 Node.js 原生模块和 npm 包 |
156
+ | 🖥️ **Electron API 访问** | 可以调用 Electron 渲染进程 API |
157
+ | 🌉 **桥接能力** | 通过 `window.xxx` 将原生能力暴露给前端 React/Vue 组件 |
158
+ | ⚡ **同步执行** | 在页面 DOM 加载前执行,确保 API 可用 |
159
+ | 📄 **使用 .cjs 扩展名** | 由于项目使用 `type: module`,preload 必须命名为 `*.cjs` |
160
+
161
+ ### 适用场景
162
+
163
+ 以下场景**需要使用** Preload:
164
+
165
+ - 📂 使用 `pdf-lib`、`sharp`、`ffmpeg` 等需要 Node.js 环境的 npm 包
166
+ - 🔐 调用 Node.js 加密模块 (`crypto`)、子进程 (`child_process`)
167
+ - 📁 需要比 `window.intools.filesystem` 更底层的文件操作
168
+ - 🔗 与本地数据库交互 (SQLite、LevelDB 等)
169
+ - 🎯 任何需要原生能力但又想在前端统一调用的场景
170
+
171
+ ---
172
+
173
+ ### 配置方式
174
+
175
+ 在 `manifest.json` 中添加 `preload` 字段,指定预加载脚本路径:
176
+
177
+ ```json
178
+ {
179
+ "name": "my-plugin",
180
+ "version": "1.0.0",
181
+ "displayName": "我的插件",
182
+ "main": "dist/main.js",
183
+ "ui": "ui/index.html",
184
+ "preload": "preload.cjs", // 👈 使用 .cjs 扩展名,放在根目录
185
+ "features": [...]
186
+ }
187
+ ```
188
+
189
+ > [!WARNING]
190
+ > **必须使用 `.cjs` 扩展名!** 由于模板使用 `"type": "module"`,所有 `.js` 文件会被当作 ES Module 处理。使用 `.cjs` 扩展名可确保文件始终被视为 CommonJS。
191
+
192
+ > [!IMPORTANT]
193
+ > **preload 不需要打包!** 直接使用源码文件,放在项目根目录。这样 `node_modules` 中的依赖可以正常解析。
194
+
195
+ ---
196
+
197
+ ### preload.cjs 编写规范
198
+
199
+ ```javascript
200
+ // preload.cjs - 必须使用 CommonJS 规范和 .cjs 扩展名
201
+ const fs = require('fs')
202
+ const os = require('os')
203
+ const path = require('path')
204
+ const { PDFDocument } = require('pdf-lib') // 可使用 npm 包
205
+
206
+ /**
207
+ * 通过 window 对象暴露 API 给前端
208
+ * 命名建议:window.{插件名}Api 或 window.{功能名}Api
209
+ */
210
+ window.myPluginApi = {
211
+ // 同步方法
212
+ getHomeDir: () => os.homedir(),
213
+ getPlatform: () => process.platform,
214
+
215
+ // 异步方法
216
+ readFile: async (filePath) => {
217
+ return fs.promises.readFile(filePath, 'utf-8')
218
+ },
219
+
220
+ // 复杂功能封装
221
+ mergePDFs: async (pdfPaths, outputPath) => {
222
+ const mergedPdf = await PDFDocument.create()
223
+ for (const pdfPath of pdfPaths) {
224
+ const pdfBytes = fs.readFileSync(pdfPath)
225
+ const pdf = await PDFDocument.load(pdfBytes)
226
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices())
227
+ pages.forEach(page => mergedPdf.addPage(page))
228
+ }
229
+ const bytes = await mergedPdf.save()
230
+ fs.writeFileSync(outputPath, bytes)
231
+ return outputPath
232
+ }
233
+ }
234
+
235
+ console.log('[Preload] API 已挂载到 window.myPluginApi')
236
+ ```
237
+
238
+ ---
239
+
240
+ ### 前端调用示例
241
+
242
+ ```tsx
243
+ // 在 React 组件中调用
244
+ import { useEffect, useState } from 'react'
245
+
246
+ // 类型声明(推荐单独放在 types.d.ts)
247
+ declare global {
248
+ interface Window {
249
+ myPluginApi?: {
250
+ getHomeDir: () => string
251
+ getPlatform: () => string
252
+ readFile: (path: string) => Promise<string>
253
+ mergePDFs: (paths: string[], output: string) => Promise<string>
254
+ }
255
+ }
256
+ }
257
+
258
+ export function MyComponent() {
259
+ const [homeDir, setHomeDir] = useState('')
260
+
261
+ useEffect(() => {
262
+ // 使用可选链确保安全访问
263
+ if (window.myPluginApi) {
264
+ setHomeDir(window.myPluginApi.getHomeDir())
265
+ }
266
+ }, [])
267
+
268
+ const handleMerge = async () => {
269
+ const result = await window.myPluginApi?.mergePDFs(
270
+ ['/path/to/1.pdf', '/path/to/2.pdf'],
271
+ '/path/to/merged.pdf'
272
+ )
273
+ console.log('合并完成:', result)
274
+ }
275
+
276
+ // 核心 API 仍然可用
277
+ const handleCopy = async () => {
278
+ const text = await window.intools.clipboard.readText()
279
+ console.log('剪贴板内容:', text)
280
+ }
281
+
282
+ return <div>Home: {homeDir}</div>
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ### 模块引入方法
289
+
290
+ Preload 脚本支持多种模块引入方式:
291
+
292
+ #### 1. 引入 Node.js 原生模块
293
+
294
+ ```javascript
295
+ // preload.cjs
296
+ const fs = require('fs') // 文件系统
297
+ const os = require('os') // 操作系统信息
298
+ const path = require('path') // 路径操作
299
+ const crypto = require('crypto') // 加密
300
+ const { spawn } = require('child_process') // 子进程
301
+
302
+ window.nodeApi = {
303
+ homeDir: os.homedir(),
304
+ platform: process.platform,
305
+ cpus: os.cpus().length,
306
+ hash: (text) => crypto.createHash('md5').update(text).digest('hex')
307
+ }
308
+ ```
309
+
310
+ #### 2. 引入自编写模块
311
+
312
+ ```javascript
313
+ // preload.cjs
314
+ // 相对于 preload.js 文件的路径
315
+ const utils = require('./lib/utils') // 同级 lib 目录
316
+ const helpers = require('./helpers/format') // 同级 helpers 目录
317
+ const shared = require('../shared/constants') // 上级目录
318
+
319
+ window.myApi = {
320
+ format: utils.formatData,
321
+ constants: shared.APP_NAME
322
+ }
323
+ ```
324
+
325
+ > [!NOTE]
326
+ > 自编写模块也必须使用 CommonJS 格式(`module.exports`),路径相对于 `preload.cjs` 文件位置。
327
+
328
+ #### 3. 引入第三方模块
329
+
330
+ **方式 A:通过 npm 安装**
331
+
332
+ ```bash
333
+ # 在插件目录安装依赖
334
+ cd my-plugin
335
+ npm install pdf-lib lodash dayjs
336
+ ```
337
+
338
+ ```javascript
339
+ // preload.cjs
340
+ const { PDFDocument } = require('pdf-lib')
341
+ const _ = require('lodash')
342
+ const dayjs = require('dayjs')
343
+
344
+ window.pdfApi = {
345
+ mergePDFs: async (paths) => { /* ... */ },
346
+ formatDate: (date) => dayjs(date).format('YYYY-MM-DD')
347
+ }
348
+ ```
349
+
350
+ **方式 B:通过源码引入**
351
+
352
+ 将第三方库源码放入插件目录:
353
+
354
+ ```
355
+ my-plugin/
356
+ ├── preload.cjs
357
+ ├── vendor/
358
+ │ ├── lodash.min.js
359
+ │ └── crypto-js.js
360
+ ```
361
+
362
+ ```javascript
363
+ // preload.cjs
364
+ const _ = require('./vendor/lodash.min.js')
365
+ const CryptoJS = require('./vendor/crypto-js.js')
366
+ ```
367
+
368
+ #### 4. 引入 Electron 渲染进程 API
369
+
370
+ ```javascript
371
+ // preload.cjs
372
+ const {
373
+ ipcRenderer, // 进程通信
374
+ clipboard, // 剪贴板(直接访问,无需 IPC)
375
+ shell, // 打开外部链接/文件
376
+ nativeImage, // 图片处理
377
+ contextBridge // 上下文桥接(自定义 preload 模式下不需要)
378
+ } = require('electron')
379
+
380
+ window.electronApi = {
381
+ // 剪贴板操作
382
+ readClipboard: () => clipboard.readText(),
383
+ writeClipboard: (text) => clipboard.writeText(text),
384
+ readImage: () => clipboard.readImage().toDataURL(),
385
+
386
+ // 打开外部资源
387
+ openExternal: (url) => shell.openExternal(url),
388
+ showInFolder: (path) => shell.showItemInFolder(path),
389
+
390
+ // 自定义 IPC 通信(与主进程交互)
391
+ send: (channel, data) => ipcRenderer.send(channel, data),
392
+ invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
393
+ on: (channel, callback) => {
394
+ ipcRenderer.on(channel, (event, ...args) => callback(...args))
395
+ }
396
+ }
397
+ ```
398
+
399
+ > [!TIP]
400
+ > 虽然可以直接使用 Electron API,但建议优先使用 `window.intools` 封装的 API,它们提供了更好的跨平台兼容性和错误处理。
401
+
402
+ ---
403
+
404
+ ### 与 Main 后端的区别
405
+
406
+ | 对比项 | Preload 脚本 | Main 后端 (main.js) |
407
+ |--------|--------------|---------------------|
408
+ | 执行环境 | 渲染进程(带 Node.js 权限) | 独立 Worker 进程 |
409
+ | 调用方式 | `window.xxxApi.method()` | `window.intools.host.invoke()` |
410
+ | 适合场景 | 同步操作、UI 紧密相关的原生功能 | 后台任务、长时间运行的操作 |
411
+ | 进程通信 | 无需 IPC,直接调用 | 需要 IPC,异步调用 |
412
+ | 生命周期 | 随 UI 窗口创建/销毁 | 独立管理,可持久化 |
413
+
414
+ > [!TIP]
415
+ > **选择建议**:如果功能与 UI 紧密相关且需要快速响应,使用 **Preload**;如果是后台任务或需要在无 UI 时运行,使用 **Main 后端**。
416
+
417
+ ---
418
+
419
+ ### 注意事项
420
+
421
+ | 项目 | 说明 |
422
+ |------|------|
423
+ | 📄 文件扩展名 | **必须使用 `.cjs`** 扩展名,放在项目根目录 |
424
+ | 📝 文件格式 | **必须是 CommonJS** 格式,使用 `require()` 导入模块 |
425
+ | 📦 不需要打包 | 直接使用源码文件,不要用 esbuild/webpack 打包 |
426
+ | 🔍 代码规范 | 必须是清晰可读的源码,**禁止压缩/混淆**(安全审查需要) |
427
+ | 📦 可用模块 | Node.js 原生模块 + 已安装的 npm 包 |
428
+ | 🌐 API 暴露 | 通过 `window.xxx` 暴露,建议使用 `window.{插件名}Api` 命名 |
429
+ | 🔧 核心 API | `window.intools` 核心 API 在 Preload 环境中**仍然可用** |
430
+ | ⚠️ 安全性 | 拥有完整 Node.js 权限,**请谨慎处理用户输入** |
431
+ | 📦 打包 | `intools pack` 会自动包含 preload 及其依赖 |
432
+
433
+ > [!CAUTION]
434
+ > Preload 脚本拥有完整的 Node.js 权限,可以访问文件系统、网络等敏感资源。请确保代码安全,避免执行不可信的用户输入。
435
+
436
+ ---
437
+
438
+ ### Preload vs 前端代码:适用场景区分 ⭐
439
+
440
+ > [!IMPORTANT]
441
+ > **Preload 并非万能!** 并非所有 Node.js 相关功能都应放在 Preload 中。以下是基于 pdf-tools 插件开发实践总结的经验。
442
+
443
+ #### 核心原则
444
+
445
+ | 放在 `preload.cjs` | 放在前端 React/Vue 项目 |
446
+ |-------------------|------------------------|
447
+ | 纯 Node.js 环境操作 | 需要 DOM/浏览器 API 的操作 |
448
+ | 无需渲染的数据处理 | 需要 `document`、`canvas` 等 |
449
+ | 第三方纯 JS 库 | 需要浏览器环境的第三方库 |
450
+
451
+ #### ✅ 适合放在 Preload 的功能
452
+
453
+ ```javascript
454
+ // preload.cjs
455
+ const fs = require('fs');
456
+ const { PDFDocument } = require('pdf-lib'); // 纯 JS 库,不依赖 DOM
457
+
458
+ window.pdfApi = {
459
+ // ✅ 文件 I/O - Node.js 原生能力
460
+ readFile: async (path) => fs.promises.readFile(path),
461
+ saveFile: async (path, data) => fs.promises.writeFile(path, data),
462
+
463
+ // ✅ PDF 字节操作 - pdf-lib 是纯 JS 库,不需要渲染
464
+ getPDFInfo: async (pdfPath) => {
465
+ const bytes = await fs.promises.readFile(pdfPath);
466
+ const pdf = await PDFDocument.load(bytes);
467
+ return { pageCount: pdf.getPageCount() };
468
+ },
469
+
470
+ // ✅ 合并/拆分/水印 - 都是字节级操作,不涉及可视化
471
+ mergePDFs: async (files, output) => { /* ... */ },
472
+ splitPDF: async (file, outputDir) => { /* ... */ },
473
+ addWatermark: async (file, config) => { /* ... */ },
474
+ };
475
+ ```
476
+
477
+ **适用场景**:
478
+ - 📂 文件读写 (`fs`)
479
+ - 🔧 PDF 结构操作(合并、拆分、提取元数据)
480
+ - 📦 纯 JS 第三方库(`pdf-lib`、`docx`、`xlsx`)
481
+ - 🔐 加密、压缩等纯数据处理
482
+
483
+ #### ❌ 不适合放在 Preload 的功能
484
+
485
+ 以下功能**必须放在前端项目**(如 React 组件或 Service 类):
486
+
487
+ ```typescript
488
+ // src/ui/services/PDFService.ts
489
+ import * as pdfjsLib from 'pdfjs-dist';
490
+
491
+ class PDFService {
492
+ // ❌ 需要 canvas 渲染 - 必须在浏览器环境
493
+ async convertPDFToImages(pdfPath: string) {
494
+ const pdf = await pdfjsLib.getDocument(data).promise;
495
+
496
+ for (let i = 1; i <= pdf.numPages; i++) {
497
+ const page = await pdf.getPage(i);
498
+
499
+ // 这些 API 只在浏览器中存在!
500
+ const canvas = document.createElement('canvas'); // ❌ 需要 DOM
501
+ const context = canvas.getContext('2d'); // ❌ 需要 Canvas API
502
+
503
+ await page.render({ canvasContext: context }).promise;
504
+
505
+ const blob = await new Promise(resolve =>
506
+ canvas.toBlob(resolve, 'image/png') // ❌ 需要 Blob API
507
+ );
508
+ }
509
+ }
510
+ }
511
+ ```
512
+
513
+ **不适合的场景**:
514
+ - 🖼️ PDF 页面渲染为图片(需要 `canvas`)
515
+ - 📄 转换为 Word/PPT 时需要渲染图片(扫描件 PDF)
516
+ - 🎨 任何涉及 `document`、`canvas`、`Image` 的操作
517
+
518
+ #### 错误示例与报错
519
+
520
+ 如果在 Preload 中使用浏览器 API:
521
+
522
+ ```javascript
523
+ // preload.cjs - ❌ 错误做法!
524
+ window.pdfApi = {
525
+ renderToImage: async () => {
526
+ const canvas = document.createElement('canvas');
527
+ // 💥 报错: ReferenceError: document is not defined
528
+ }
529
+ };
530
+ ```
531
+
532
+ #### 正确的架构模式
533
+
534
+ ```
535
+ ┌─────────────────────────────────────────────────────────────┐
536
+ │ 前端 React 项目 │
537
+ │ ┌─────────────────────────────────────────────────────┐ │
538
+ │ │ PDFService.ts │ │
539
+ │ │ - convertPDFToImages() ← 使用 pdfjs-dist + canvas │ │
540
+ │ │ - convertToWord() ← 需要渲染扫描件 │ │
541
+ │ │ - compressPDF() ← 需要渲染后压缩 │ │
542
+ │ └──────────────────────────┬──────────────────────────┘ │
543
+ │ │ 调用 │
544
+ │ ▼ │
545
+ │ ┌─────────────────────────────────────────────────────┐ │
546
+ │ │ window.pdfApi (来自 preload.cjs) │ │
547
+ │ │ - readFile() / saveFile() ← 文件 I/O │ │
548
+ │ │ - getPDFInfo() ← 获取元数据 │ │
549
+ │ │ - mergePDFs() ← 字节操作 │ │
550
+ │ │ - splitPDF() ← 字节操作 │ │
551
+ │ └─────────────────────────────────────────────────────┘ │
552
+ └─────────────────────────────────────────────────────────────┘
553
+
554
+ │ require()
555
+
556
+ ┌─────────────────────────────────────────────────────────────┐
557
+ │ preload.cjs │
558
+ │ - fs, path (Node.js 原生) │
559
+ │ - pdf-lib, docx, xlsx (纯 JS 第三方库) │
560
+ └─────────────────────────────────────────────────────────────┘
561
+ ```
562
+
563
+ > [!TIP]
564
+ > **简单判断方法**:如果你的代码中出现了 `document`、`canvas`、`Image`、`Blob` 等关键字,那它就应该放在前端 React 项目中,而不是 Preload。
565
+
566
+ ---
567
+
568
+ ### 开发流程
569
+
570
+ 使用 preload 的插件开发流程与普通插件略有不同:
571
+
572
+ #### 项目结构
573
+
574
+ ```
575
+ my-plugin/
576
+ ├── manifest.json # 配置 preload 字段
577
+ ├── package.json # 依赖列表(含 type: module)
578
+ ├── preload.cjs # 👈 Preload 脚本(根目录,不打包)
579
+ ├── lib/ # 可选:自编写模块
580
+ │ └── utils.cjs
581
+ ├── src/
582
+ │ ├── main.ts # 后端入口
583
+ │ └── ui/ # React UI
584
+ ├── dist/ # 构建输出(main.js)
585
+ ├── ui/ # UI 构建输出
586
+ └── node_modules/ # npm 依赖
587
+ ```
588
+
589
+ #### CLI 命令说明
590
+
591
+ | 命令 | Preload 处理方式 |
592
+ |------|-----------------|
593
+ | `intools create` | 创建项目模板,手动添加 `preload.cjs` |
594
+ | `intools dev` | 无需处理,preload 使用源码直接运行 |
595
+ | `intools build` | 无需处理,preload **不需要打包** |
596
+ | `intools pack` | **自动打包** preload 文件 + node_modules 生产依赖 |
597
+
598
+ #### 开发步骤
599
+
600
+ ```bash
601
+ # 1. 创建插件
602
+ cd plugins
603
+ npx intools create my-plugin --template react
604
+
605
+ # 2. 手动创建 preload.cjs
606
+ touch preload.cjs
607
+
608
+ # 3. 在 manifest.json 中配置
609
+ # "preload": "preload.cjs"
610
+
611
+ # 4. 安装第三方依赖(如需要)
612
+ npm install pdf-lib lodash
613
+
614
+ # 5. 编写 preload.cjs(使用 require)
615
+
616
+ # 6. 开发调试
617
+ npm run dev
618
+
619
+ # 7. 构建
620
+ npm run build
621
+
622
+ # 8. 打包发布
623
+ npm run pack
624
+ ```
625
+
626
+ #### 打包后的结构
627
+
628
+ `intools pack` 会生成包含以下内容的 `.inplugin` 文件:
629
+
630
+ ```
631
+ my-plugin-1.0.0.inplugin
632
+ ├── manifest.json
633
+ ├── main.js # 后端(打包后)
634
+ ├── preload.cjs # Preload 源码
635
+ ├── ui/ # UI 构建产物
636
+ ├── node_modules/ # 👈 自动包含生产依赖
637
+ │ ├── pdf-lib/
638
+ │ └── (依赖的依赖...)
639
+ ├── icon.png
640
+ └── README.md
641
+ ```
642
+
643
+ > [!NOTE]
644
+ > 只有 `package.json` 中 `dependencies`(生产依赖)会被打包。`devDependencies` 不会包含在内。
645
+
646
+ ---
647
+
648
+ # 第二部分:API 参考
649
+
650
+ ## 1. 剪贴板 (clipboard)
651
+
652
+ | 方法 | 环境 | 说明 |
653
+ |------|------|------|
654
+ | `readText()` | R/B | 读取文本 → `string` |
655
+ | `writeText(text)` | R/B | 写入文本 |
656
+ | `readImage()` | R/B | 读取图片 → `Buffer | null` |
657
+ | `writeImage(image)` | R/B | 写入图片(路径/Buffer/DataURL) |
658
+ | `writeFiles(paths)` | R | 写入文件路径 |
659
+ | `readFiles()` | R/B | 读取文件列表 → `ClipboardFileInfo[]` |
660
+ | `getFormat()` | R/B | 获取格式 → `'text' | 'image' | 'files' | 'html' | 'empty'` |
661
+
662
+ ---
663
+
664
+ ## 2. 文件系统 (filesystem)
665
+
666
+ | 方法 | 环境 | 说明 |
667
+ |------|------|------|
668
+ | `readFile(path, encoding?)` | R/B | 读取文件 → `Buffer | string` |
669
+ | `writeFile(path, data, encoding?)` | R/B | 写入文件 (`data`: `string \| Buffer \| ArrayBuffer`) |
670
+ | `exists(path)` | R/B | 检查是否存在 → `boolean` |
671
+ | `unlink(path)` | R/B | 删除文件 |
672
+ | `readdir(path)` | R/B | 读取目录 → `string[]` |
673
+ | `mkdir(path)` | R/B | 创建目录(递归) |
674
+ | `stat(path)` | R/B | 获取文件信息 → `FileStat` |
675
+ | `copy(src, dest)` | R/B | 复制文件 |
676
+ | `move(src, dest)` | R/B | 移动/重命名文件 |
677
+ | `extname(path)` | B | 获取扩展名 |
678
+ | `join(...paths)` | B | 拼接路径 |
679
+ | `dirname(path)` | B | 获取目录名 |
680
+ | `basename(path, ext?)` | B | 获取文件名 |
681
+
682
+ ---
683
+
684
+ ## 3. 存储 (storage)
685
+
686
+ | 方法 | 环境 | 说明 |
687
+ |------|------|------|
688
+ | `get(key, namespace?)` | R/B | 获取数据 |
689
+ | `set(key, value, namespace?)` | R/B | 存储数据 |
690
+ | `remove(key, namespace?)` | R/B | 删除数据 |
691
+ | `clear()` | B | 清空存储 |
692
+ | `keys()` | B | 获取所有键 |
693
+
694
+ ---
695
+
696
+ ## 4. 对话框 (dialog)
697
+
698
+ | 方法 | 环境 | 说明 |
699
+ |------|------|------|
700
+ | `showOpenDialog(options?)` | R/B | 打开文件对话框 → `string[]` |
701
+ | `showSaveDialog(options?)` | R/B | 保存文件对话框 → `string | null` |
702
+ | `showMessageBox(options)` | R/B | 消息框 → `{ response, checkboxChecked }` |
703
+ | `showErrorBox(title, content)` | R/B | 错误框(同步) |
704
+
705
+ **OpenDialogOptions**: `title`, `defaultPath`, `buttonLabel`, `filters`, `properties`
706
+ **properties**: `'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles'`
707
+
708
+ ---
709
+
710
+ ## 5. 通知 (notification)
711
+
712
+ | 方法 | 环境 | 说明 |
713
+ |------|------|------|
714
+ | `show(message, type?)` | R/B | 显示通知(`type='error'` 时不静音) |
715
+
716
+ ---
717
+
718
+ ## 6. Shell
719
+
720
+ | 方法 | 环境 | 说明 |
721
+ |------|------|------|
722
+ | `openPath(path)` | R/B | 用默认应用打开文件 |
723
+ | `openExternal(url)` | R/B | 用浏览器打开 URL |
724
+ | `showItemInFolder(path)` | R/B | 在文件管理器中显示 |
725
+ | `openFolder(path)` | R/B | 打开文件所在目录 |
726
+ | `trashItem(path)` | R/B | 移动到回收站 |
727
+ | `beep()` | R/B | 播放系统提示音 |
728
+
729
+ ---
730
+
731
+ ## 7. HTTP 请求 (http)
732
+
733
+ | 方法 | 环境 | 说明 |
734
+ |------|------|------|
735
+ | `request(options)` | R/B | 发起请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) → `HttpResponse` |
736
+ | `get(url, headers?)` | R/B | GET 请求 |
737
+ | `post(url, body?, headers?)` | R/B | POST 请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) |
738
+ | `put(url, body?, headers?)` | R/B | PUT 请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) |
739
+ | `delete(url, headers?)` | R/B | DELETE 请求 |
740
+
741
+ **HttpRequestOptions**: `url`, `method`, `headers`, `body`, `timeout`
742
+ **HttpResponse**: `{ status, statusText, headers, data }`
743
+
744
+ ---
745
+
746
+ ## 8. 系统 (system)
747
+
748
+ | 方法 | 环境 | 说明 |
749
+ |------|------|------|
750
+ | `getSystemInfo()` | R/B | 获取系统信息 → `SystemInfo` |
751
+ | `getAppInfo()` | R/B | 获取应用信息 → `AppInfo` |
752
+ | `getPath(name)` | R/B | 获取特定路径 |
753
+ | `getEnv(name)` | R/B | 获取环境变量 |
754
+ | `getIdleTime()` | R/B | 获取空闲时间(秒) |
755
+ | `getFileIcon(path)` | R/B | 获取文件图标 → base64 DataURL |
756
+ | `getNativeId()` | R/B | 获取设备唯一标识 |
757
+ | `isDev()` | R/B | 是否开发环境 |
758
+ | `isMacOS() / isWindows() / isLinux()` | R/B | 判断操作系统 |
759
+
760
+ **getPath 支持**: `'home' | 'appData' | 'userData' | 'temp' | 'exe' | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos' | 'logs'`
761
+
762
+ ---
763
+
764
+ ## 9. 屏幕 (screen)
765
+
766
+ | 方法 | 环境 | 说明 |
767
+ |------|------|------|
768
+ | `getAllDisplays()` | R/B | 获取所有显示器 → `DisplayInfo[]` |
769
+ | `getPrimaryDisplay()` | R/B | 获取主显示器 |
770
+ | `getDisplayNearestPoint(point)` | R/B | 获取坐标位置的显示器 |
771
+ | `getCursorScreenPoint()` | R/B | 获取鼠标位置 |
772
+ | `getSources(options?)` | R/B | 获取捕获源列表 |
773
+ | `capture(options?)` | R/B | 截取屏幕 → `Buffer` |
774
+ | `captureRegion(region, options?)` | R/B | 截取指定区域 |
775
+ | `screenCapture()` | R | 交互式区域截图 → DataURL |
776
+ | `colorPick()` | R | 屏幕取色 → `{ hex, rgb, r, g, b }` |
777
+ | `getMediaStreamConstraints(options)` | R/B | 获取录屏约束配置 |
778
+
779
+ ---
780
+
781
+ ## 10. 输入 (input)
782
+
783
+ | 方法 | 环境 | 说明 |
784
+ |------|------|------|
785
+ | `hideMainWindowPasteText(text)` | R/B | 粘贴文本到焦点应用 |
786
+ | `hideMainWindowPasteImage(image)` | R/B | 粘贴图片到焦点应用 (`image`: `Path \| Buffer \| DataURL \| ArrayBuffer`) |
787
+ | `hideMainWindowPasteFile(paths)` | R/B | 粘贴文件到焦点应用 |
788
+ | `hideMainWindowTypeString(text)` | R/B | 模拟键入文本 |
789
+ | `simulateKeyboardTap(key, ...modifiers)` | R/B | 模拟按键 |
790
+ | `simulateMouseMove(x, y)` | R/B | 移动鼠标 |
791
+ | `simulateMouseClick(x, y)` | R/B | 左键单击 |
792
+ | `simulateMouseDoubleClick(x, y)` | R/B | 左键双击 |
793
+ | `simulateMouseRightClick(x, y)` | R/B | 右键点击 |
794
+
795
+ **修饰键**: `'ctrl' | 'alt' | 'shift' | 'command'`
796
+
797
+ ---
798
+
799
+ ## 11. 窗口 (window)
800
+
801
+ | 方法 | 环境 | 说明 |
802
+ |------|------|------|
803
+ | `hide(restorePreWindow?)` | R | 隐藏窗口 |
804
+ | `show()` | R | 显示窗口 |
805
+ | `setSize(width, height)` | R | 设置尺寸 |
806
+ | `setExpendHeight(height)` | R | 调整高度 |
807
+ | `center()` | R | 窗口居中 |
808
+ | `setAlwaysOnTop(flag)` | R | 设置置顶 |
809
+ | `detach()` | R | 分离为独立窗口 |
810
+ | `close()` | R | 关闭窗口 |
811
+ | `reload()` | R | 重新加载 |
812
+ | `getMode()` | R | 获取模式 → `'attached' | 'detached'` |
813
+ | `getWindowType()` | R | 获取类型 → `'main' | 'detach'` |
814
+ | `getState()` | R | 获取状态 |
815
+ | `minimize() / maximize()` | R | 最小化/最大化 |
816
+ | `create(url, options?)` | R | 创建子窗口 → `ChildWindowHandle` |
817
+ | `sendToParent(channel, ...args)` | R | 向父窗口发消息 |
818
+ | `onChildMessage(callback)` | R | 监听子窗口消息 |
819
+ | `findInPage(text, options?)` | R | 页面内查找 |
820
+ | `startDrag(filePath)` | R | 触发文件拖拽 |
821
+
822
+ ### 子输入框 (subInput)
823
+
824
+ | 方法 | 说明 |
825
+ |------|------|
826
+ | `set(placeholder?, isFocus?)` | 显示子输入框 |
827
+ | `remove()` | 移除子输入框 |
828
+ | `setValue(text)` | 设置内容 |
829
+ | `focus() / blur() / select()` | 焦点控制 |
830
+ | `onChange(callback)` | 监听变化 |
831
+
832
+ ---
833
+
834
+ ## 12. 主题 (theme)
835
+
836
+ | 方法 | 环境 | 说明 |
837
+ |------|------|------|
838
+ | `get()` | R | 获取主题信息 → `{ mode, actual }` |
839
+ | `set(mode)` | R | 设置主题 → `'light' | 'dark' | 'system'` |
840
+ | `getActual()` | R | 获取实际主题 → `'light' | 'dark'` |
841
+ | `onThemeChange(callback)` | R | 监听主题变化 |
842
+
843
+ ---
844
+
845
+ ## 13. 插件管理 (plugin)
846
+
847
+ | 方法 | 环境 | 说明 |
848
+ |------|------|------|
849
+ | `getAll()` | R | 获取所有插件 |
850
+ | `search(query)` | R | 搜索插件功能 |
851
+ | `run(name, featureCode, input?)` | R | 执行插件功能 |
852
+ | `install(filePath)` | R | 安装插件 |
853
+ | `enable(name) / disable(name)` | R | 启用/禁用插件 |
854
+ | `uninstall(name)` | R | 卸载插件 |
855
+ | `getReadme(name)` | R | 获取 README |
856
+ | `redirect(label, payload?)` | R | 跳转到其他插件 |
857
+ | `outPlugin(isKill?)` | R | 退出当前插件 |
858
+
859
+ ### 事件
860
+
861
+ | 事件 | 说明 |
862
+ |------|------|
863
+ | `onPluginInit(callback)` | 插件初始化 |
864
+ | `onPluginAttach(callback)` | 插件附着 |
865
+ | `onPluginDetached(callback)` | 插件分离 |
866
+
867
+ ---
868
+
869
+ ## 14. 动态指令 (features)
870
+
871
+ | 方法 | 环境 | 说明 |
872
+ |------|------|------|
873
+ | `getFeatures(codes?)` | B | 获取动态指令 |
874
+ | `setFeature(feature)` | B | 注册动态指令 |
875
+ | `removeFeature(code)` | B | 删除动态指令 |
876
+
877
+ **DynamicFeatureInput**:
878
+ - `code` - 指令代码
879
+ - `explain` - 说明文字
880
+ - `icon` - 图标(路径/SVG/URL)
881
+ - `platform` - 平台限制
882
+ - `mode` - 模式:`'ui' | 'silent' | 'detached'`
883
+ - `route` - 路由路径
884
+ - `mainHide` - 触发时隐藏主窗口
885
+ - `mainPush` - 向搜索框推送内容
886
+ - `cmds` - 命令数组
887
+
888
+ ### cmds 命令类型
889
+
890
+ | 类型 | 字段 | 说明 |
891
+ |------|------|------|
892
+ | `keyword` | `value` | 关键词匹配 |
893
+ | `regex` | `match`, `explain?`, `label?`, `minLength?`, `maxLength?` | 正则匹配 |
894
+ | `files` | `exts?`, `fileType?`, `match?`, `minLength?`, `maxLength?` | 文件匹配 |
895
+ | `img` | `exts?` | 图片匹配 |
896
+ | `over` | `label?`, `exclude?`, `minLength?`, `maxLength?` | 覆盖匹配 |
897
+
898
+ **files 特殊字段**:
899
+ - `fileType`: `'file' | 'directory' | 'any'` - 文件类型过滤 (默认 'any')
900
+ - `match`: 匹配文件名的正则表达式 (与 `exts` 二选一)
901
+ - `minLength` / `maxLength`: 文件数量限制
902
+
903
+ **regex / over 特殊字段**:
904
+ - `minLength` / `maxLength`: 输入文本长度限制 (over 默认为 10000)
905
+ - `exclude`: 排除的正则表达式(仅 over)
906
+
907
+ ---
908
+
909
+ ## 15. 快捷键 (shortcut)
910
+
911
+ | 方法 | 环境 | 说明 |
912
+ |------|------|------|
913
+ | `register(accelerator)` | R/B | 注册全局快捷键 |
914
+ | `unregister(accelerator)` | R/B | 注销快捷键 |
915
+ | `unregisterAll()` | R/B | 注销所有快捷键 |
916
+ | `isRegistered(accelerator)` | R/B | 检查是否已注册 |
917
+ | `onTriggered(callback)` | R | 监听触发事件 |
918
+
919
+ **accelerator 格式**: `CommandOrControl+Shift+X`, `Alt+P`, `F12`
920
+
921
+ ---
922
+
923
+ ## 16. 权限 (permission)
924
+
925
+ | 方法 | 环境 | 说明 |
926
+ |------|------|------|
927
+ | `getStatus(type)` | R/B | 获取权限状态 |
928
+ | `request(type)` | R/B | 请求权限 |
929
+ | `canRequest(type)` | R/B | 是否可请求 |
930
+ | `openSystemSettings(type)` | R/B | 打开系统设置 |
931
+ | `isAccessibilityTrusted()` | R/B | macOS 辅助功能权限 |
932
+
933
+ **type**: `'accessibility' | 'screen' | 'camera' | 'microphone' | 'geolocation' | 'notifications' | 'contacts' | 'calendar'`
934
+
935
+ ---
936
+
937
+ ## 17. 安全存储 (security)
938
+
939
+ | 方法 | 环境 | 说明 |
940
+ |------|------|------|
941
+ | `isEncryptionAvailable()` | R/B | 检查加密可用性 |
942
+ | `encryptString(plainText)` | R/B | 加密字符串 → `Buffer` |
943
+ | `decryptString(encrypted)` | R/B | 解密字符串 (`encrypted`: `Buffer \| ArrayBuffer`) → `string` |
944
+
945
+ ---
946
+
947
+ ## 18. 托盘 (tray)
948
+
949
+ | 方法 | 环境 | 说明 |
950
+ |------|------|------|
951
+ | `create(options)` | R/B | 创建托盘图标 |
952
+ | `destroy()` | R/B | 销毁托盘 |
953
+ | `setIcon(icon)` | R/B | 更新图标 |
954
+ | `setTooltip(tooltip)` | R/B | 设置提示 |
955
+ | `setTitle(title)` | R/B | 设置标题(macOS) |
956
+ | `exists()` | R/B | 检查是否存在 |
957
+
958
+ **TrayOptions**: `icon`, `tooltip`, `title`
959
+
960
+ ---
961
+
962
+ ## 19. 菜单 (menu)
963
+
964
+ | 方法 | 环境 | 说明 |
965
+ |------|------|------|
966
+ | `showContextMenu(items)` | R | 显示右键菜单 → `id | null` |
967
+
968
+ **MenuItemOptions**: `label`, `type`, `checked`, `enabled`, `id`, `submenu`
969
+
970
+ ---
971
+
972
+ ## 20. 网络状态 (network)
973
+
974
+ | 方法 | 环境 | 说明 |
975
+ |------|------|------|
976
+ | `isOnline()` | R/B | 检查是否在线 |
977
+ | `onOnline(callback)` | R | 网络恢复事件 |
978
+ | `onOffline(callback)` | R | 网络断开事件 |
979
+
980
+ ---
981
+
982
+ ## 21. 电源 (power)
983
+
984
+ | 方法 | 环境 | 说明 |
985
+ |------|------|------|
986
+ | `getSystemIdleTime()` | R/B | 获取空闲时间 |
987
+ | `getSystemIdleState(threshold)` | R/B | 获取空闲状态 |
988
+ | `isOnBatteryPower()` | R/B | 是否电池供电 |
989
+ | `getCurrentThermalState()` | R/B | 获取热状态(macOS) |
990
+ | `onSuspend / onResume` | R | 休眠/唤醒事件 |
991
+ | `onAC / onBattery` | R | 电源切换事件 |
992
+ | `onLockScreen / onUnlockScreen` | R | 锁屏事件 |
993
+
994
+ ---
995
+
996
+ ## 22. 媒体 (media)
997
+
998
+ | 方法 | 环境 | 说明 |
999
+ |------|------|------|
1000
+ | `getAccessStatus(type)` | R/B | 获取权限状态 |
1001
+ | `askForAccess(type)` | R/B | 请求权限 |
1002
+ | `hasCameraAccess()` | R/B | 检查摄像头权限 |
1003
+ | `hasMicrophoneAccess()` | R/B | 检查麦克风权限 |
1004
+
1005
+ **type**: `'camera' | 'microphone'`
1006
+
1007
+ ---
1008
+
1009
+ ## 23. 地理位置 (geolocation)
1010
+
1011
+ | 方法 | 环境 | 说明 |
1012
+ |------|------|------|
1013
+ | `getAccessStatus()` | R | 获取权限状态 |
1014
+ | `requestAccess()` | R | 请求权限 |
1015
+ | `canGetPosition()` | R | 是否可获取位置 |
1016
+ | `openSettings()` | R | 打开系统设置 |
1017
+ | `getCurrentPosition()` | R | 获取当前位置 → `GeolocationPosition` |
1018
+
1019
+ ---
1020
+
1021
+ ## 24. TTS 语音合成 (tts)
1022
+
1023
+ | 方法 | 环境 | 说明 |
1024
+ |------|------|------|
1025
+ | `speak(text, options?)` | R | 朗读文本 |
1026
+ | `stop()` | R | 停止朗读 |
1027
+ | `pause() / resume()` | R | 暂停/恢复 |
1028
+ | `getVoices()` | R | 获取语音列表 |
1029
+ | `isSpeaking()` | R | 是否正在朗读 |
1030
+
1031
+ **options**: `lang`, `rate`, `pitch`, `volume`
1032
+
1033
+ ---
1034
+
1035
+ ## 25. Host 调用 (host)
1036
+
1037
+ | 方法 | 环境 | 说明 |
1038
+ |------|------|------|
1039
+ | `invoke(pluginName, method, ...args)` | R | 调用插件后端方法 |
1040
+ | `status(pluginName)` | R | 获取 Host 状态 |
1041
+ | `restart(pluginName)` | R | 重启 Host 进程 |
1042
+
1043
+ ---
1044
+
1045
+ ## 26. Sharp 图像处理 (sharp)
1046
+
1047
+ | 方法 | 说明 |
1048
+ |------|------|
1049
+ | `sharp(input?, options?)` | 创建实例 |
1050
+ | `.resize(w?, h?, opts?)` | 调整尺寸 |
1051
+ | `.extract({ left, top, width, height })` | 裁剪区域 |
1052
+ | `.rotate(angle?)` | 旋转 |
1053
+ | `.flip() / .flop()` | 翻转 |
1054
+ | `.blur(sigma?) / .sharpen()` | 模糊/锐化 |
1055
+ | `.grayscale() / .negate()` | 灰度/反相 |
1056
+ | `.modulate({ brightness, saturation, hue })` | 调整色彩 |
1057
+ | `.composite(images)` | 合成 |
1058
+ | `.png() / .jpeg() / .webp()` | 设置格式 |
1059
+ | `.toBuffer()` | 输出 ArrayBuffer |
1060
+ | `.toFile(path)` | 输出文件 |
1061
+ | `.metadata()` | 获取元数据 |
1062
+
1063
+ ---
1064
+
1065
+ ## 27. FFmpeg 音视频 (ffmpeg)
1066
+
1067
+ | 方法 | 环境 | 说明 |
1068
+ |------|------|------|
1069
+ | `isAvailable()` | R | 检查是否已安装 |
1070
+ | `getVersion()` | R | 获取版本 |
1071
+ | `getPath()` | R | 获取可执行文件路径 |
1072
+ | `download(onProgress?)` | R | 下载 FFmpeg |
1073
+ | `run(args, onProgress?)` | R | 执行命令 → `{ promise, kill, quit }` |
1074
+
1075
+ ---
1076
+
1077
+ ## 28. InBrowser 自动化 (inbrowser)
1078
+
1079
+ | 方法 | 说明 |
1080
+ |------|------|
1081
+ | `.goto(url, headers?, timeout?)` | 导航 |
1082
+ | `.click(selector) / .dblclick(selector)` | 点击 |
1083
+ | `.input(selector, text) / .type(selector, text)` | 输入 |
1084
+ | `.press(key, modifiers?)` | 按键 |
1085
+ | `.hover(selector) / .focus(selector)` | 悬停/聚焦 |
1086
+ | `.wait(ms) / .wait(selector)` | 等待 |
1087
+ | `.evaluate(func, ...args)` | 执行脚本 |
1088
+ | `.screenshot(target?, savePath?)` | 截图 |
1089
+ | `.pdf(options?, savePath?)` | 导出 PDF |
1090
+ | `.cookies(filter?)` | 获取 Cookie |
1091
+ | `.setCookies(...) / .clearCookies()` | 设置/清除 Cookie |
1092
+ | `.viewport(w, h)` | 设置视口 |
1093
+ | `.show() / .hide() / .end()` | 窗口控制 |
1094
+ | `.run(options?)` | 执行队列 |
1095
+
1096
+ ---
1097
+
1098
+ ## 环境标识说明
1099
+
1100
+ - **R** = 渲染进程可用 (`window.intools.xxx`)
1101
+ - **B** = 插件后端可用 (`context.api.xxx`)
1102
+ - **R/B** = 两者都可用