intools-cli 1.0.6 → 1.0.8

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.
package/PLUGIN_API.md CHANGED
@@ -141,56 +141,172 @@
141
141
  "icon": { "type": "svg", "value": "<svg>...</svg>" }
142
142
  ```
143
143
 
144
- ## Preload 配置(自定义 Node.js 能力)
144
+ ## Preload 预加载脚本 核心概念
145
145
 
146
- 配置自定义 preload 脚本,可在渲染进程中直接使用 Node.js 能力。
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
+
160
+ ### 适用场景
161
+
162
+ 以下场景**需要使用** Preload:
163
+
164
+ - 📂 使用 `pdf-lib`、`sharp`、`ffmpeg` 等需要 Node.js 环境的 npm 包
165
+ - 🔐 调用 Node.js 加密模块 (`crypto`)、子进程 (`child_process`)
166
+ - 📁 需要比 `window.intools.filesystem` 更底层的文件操作
167
+ - 🔗 与本地数据库交互 (SQLite、LevelDB 等)
168
+ - 🎯 任何需要原生能力但又想在前端统一调用的场景
169
+
170
+ ---
147
171
 
148
172
  ### 配置方式
149
173
 
174
+ 在 `manifest.json` 中添加 `preload` 字段,指定预加载脚本路径:
175
+
150
176
  ```json
151
177
  {
152
- "preload": "preload.js"
178
+ "name": "my-plugin",
179
+ "version": "1.0.0",
180
+ "displayName": "我的插件",
181
+ "main": "dist/main.js",
182
+ "ui": "ui/index.html",
183
+ "preload": "preload.js", // 👈 指定预加载脚本
184
+ "features": [...]
153
185
  }
154
186
  ```
155
187
 
156
- ### preload.js 示例
188
+ ---
189
+
190
+ ### preload.js 编写规范
157
191
 
158
192
  ```javascript
159
- // preload.js - 遵循 CommonJS 规范
193
+ // preload.js - 必须使用 CommonJS 规范
160
194
  const fs = require('fs')
161
195
  const os = require('os')
162
196
  const path = require('path')
163
-
164
- // 通过 window 暴露给前端
165
- window.myApi = {
197
+ const { PDFDocument } = require('pdf-lib') // 可使用 npm 包
198
+
199
+ /**
200
+ * 通过 window 对象暴露 API 给前端
201
+ * 命名建议:window.{插件名}Api 或 window.{功能名}Api
202
+ */
203
+ window.myPluginApi = {
204
+ // 同步方法
166
205
  getHomeDir: () => os.homedir(),
167
- readFile: (filePath) => fs.readFileSync(filePath, 'utf-8'),
168
- platform: process.platform
206
+ getPlatform: () => process.platform,
207
+
208
+ // 异步方法
209
+ readFile: async (filePath) => {
210
+ return fs.promises.readFile(filePath, 'utf-8')
211
+ },
212
+
213
+ // 复杂功能封装
214
+ mergePDFs: async (pdfPaths, outputPath) => {
215
+ const mergedPdf = await PDFDocument.create()
216
+ for (const pdfPath of pdfPaths) {
217
+ const pdfBytes = fs.readFileSync(pdfPath)
218
+ const pdf = await PDFDocument.load(pdfBytes)
219
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices())
220
+ pages.forEach(page => mergedPdf.addPage(page))
221
+ }
222
+ const bytes = await mergedPdf.save()
223
+ fs.writeFileSync(outputPath, bytes)
224
+ return outputPath
225
+ }
169
226
  }
227
+
228
+ console.log('[Preload] API 已挂载到 window.myPluginApi')
170
229
  ```
171
230
 
172
- ### 前端调用
231
+ ---
232
+
233
+ ### 前端调用示例
173
234
 
174
- ```typescript
175
- // 在 UI 组件中使用
176
- const homeDir = window.myApi?.getHomeDir()
177
- const content = window.myApi?.readFile('/path/to/file.txt')
235
+ ```tsx
236
+ // 在 React 组件中调用
237
+ import { useEffect, useState } from 'react'
178
238
 
179
- // 核心 API 仍然可用
180
- const text = await window.intools.clipboard.readText()
239
+ // 类型声明(推荐单独放在 types.d.ts)
240
+ declare global {
241
+ interface Window {
242
+ myPluginApi?: {
243
+ getHomeDir: () => string
244
+ getPlatform: () => string
245
+ readFile: (path: string) => Promise<string>
246
+ mergePDFs: (paths: string[], output: string) => Promise<string>
247
+ }
248
+ }
249
+ }
250
+
251
+ export function MyComponent() {
252
+ const [homeDir, setHomeDir] = useState('')
253
+
254
+ useEffect(() => {
255
+ // 使用可选链确保安全访问
256
+ if (window.myPluginApi) {
257
+ setHomeDir(window.myPluginApi.getHomeDir())
258
+ }
259
+ }, [])
260
+
261
+ const handleMerge = async () => {
262
+ const result = await window.myPluginApi?.mergePDFs(
263
+ ['/path/to/1.pdf', '/path/to/2.pdf'],
264
+ '/path/to/merged.pdf'
265
+ )
266
+ console.log('合并完成:', result)
267
+ }
268
+
269
+ // 核心 API 仍然可用
270
+ const handleCopy = async () => {
271
+ const text = await window.intools.clipboard.readText()
272
+ console.log('剪贴板内容:', text)
273
+ }
274
+
275
+ return <div>Home: {homeDir}</div>
276
+ }
181
277
  ```
182
278
 
279
+ ---
280
+
281
+ ### 与 Main 后端的区别
282
+
283
+ | 对比项 | Preload 脚本 | Main 后端 (main.js) |
284
+ |--------|--------------|---------------------|
285
+ | 执行环境 | 渲染进程(带 Node.js 权限) | 独立 Worker 进程 |
286
+ | 调用方式 | `window.xxxApi.method()` | `window.intools.host.invoke()` |
287
+ | 适合场景 | 同步操作、UI 紧密相关的原生功能 | 后台任务、长时间运行的操作 |
288
+ | 进程通信 | 无需 IPC,直接调用 | 需要 IPC,异步调用 |
289
+ | 生命周期 | 随 UI 窗口创建/销毁 | 独立管理,可持久化 |
290
+
291
+ > [!TIP]
292
+ > **选择建议**:如果功能与 UI 紧密相关且需要快速响应,使用 **Preload**;如果是后台任务或需要在无 UI 时运行,使用 **Main 后端**。
293
+
294
+ ---
295
+
183
296
  ### 注意事项
184
297
 
185
298
  | 项目 | 说明 |
186
299
  |------|------|
187
- | 文件格式 | CommonJS 格式,使用 `require()` 导入模块 |
188
- | 代码规范 | 必须是清晰可读的源码,**不能压缩/混淆** |
189
- | 可用模块 | Node.js 原生模块 + 第三方 npm 模块 |
190
- | API 暴露 | 通过 `window.xxx` 暴露自定义 API |
191
- | 核心 API | `window.intools` 核心 API 仍然可用 |
192
- | 安全性 | 有完整 Node.js 权限,需注意安全风险 |
193
- | 打包 | 运行 `intools pack` 会自动包含 preload 文件 |
300
+ | 📝 文件格式 | **必须是 CommonJS** 格式,使用 `require()` 导入模块 |
301
+ | 🔍 代码规范 | 必须是清晰可读的源码,**禁止压缩/混淆**(安全审查需要) |
302
+ | 📦 可用模块 | Node.js 原生模块 + 已安装的 npm |
303
+ | 🌐 API 暴露 | 通过 `window.xxx` 暴露,建议使用 `window.{插件名}Api` 命名 |
304
+ | 🔧 核心 API | `window.intools` 核心 API Preload 环境中**仍然可用** |
305
+ | ⚠️ 安全性 | 拥有完整 Node.js 权限,**请谨慎处理用户输入** |
306
+ | 📦 打包 | `intools pack` 会自动包含 preload 及其依赖 |
307
+
308
+ > [!CAUTION]
309
+ > Preload 脚本拥有完整的 Node.js 权限,可以访问文件系统、网络等敏感资源。请确保代码安全,避免执行不可信的用户输入。
194
310
 
195
311
  ---
196
312
 
@@ -215,7 +331,7 @@ const text = await window.intools.clipboard.readText()
215
331
  | 方法 | 环境 | 说明 |
216
332
  |------|------|------|
217
333
  | `readFile(path, encoding?)` | R/B | 读取文件 → `Buffer | string` |
218
- | `writeFile(path, data, encoding?)` | R/B | 写入文件 |
334
+ | `writeFile(path, data, encoding?)` | R/B | 写入文件 (`data`: `string \| Buffer \| ArrayBuffer`) |
219
335
  | `exists(path)` | R/B | 检查是否存在 → `boolean` |
220
336
  | `unlink(path)` | R/B | 删除文件 |
221
337
  | `readdir(path)` | R/B | 读取目录 → `string[]` |
@@ -281,10 +397,10 @@ const text = await window.intools.clipboard.readText()
281
397
 
282
398
  | 方法 | 环境 | 说明 |
283
399
  |------|------|------|
284
- | `request(options)` | R/B | 发起请求 → `HttpResponse` |
400
+ | `request(options)` | R/B | 发起请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) → `HttpResponse` |
285
401
  | `get(url, headers?)` | R/B | GET 请求 |
286
- | `post(url, body?, headers?)` | R/B | POST 请求 |
287
- | `put(url, body?, headers?)` | R/B | PUT 请求 |
402
+ | `post(url, body?, headers?)` | R/B | POST 请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) |
403
+ | `put(url, body?, headers?)` | R/B | PUT 请求 (`body`: `string \| object \| Buffer \| ArrayBuffer`) |
288
404
  | `delete(url, headers?)` | R/B | DELETE 请求 |
289
405
 
290
406
  **HttpRequestOptions**: `url`, `method`, `headers`, `body`, `timeout`
@@ -332,7 +448,7 @@ const text = await window.intools.clipboard.readText()
332
448
  | 方法 | 环境 | 说明 |
333
449
  |------|------|------|
334
450
  | `hideMainWindowPasteText(text)` | R/B | 粘贴文本到焦点应用 |
335
- | `hideMainWindowPasteImage(image)` | R/B | 粘贴图片到焦点应用 |
451
+ | `hideMainWindowPasteImage(image)` | R/B | 粘贴图片到焦点应用 (`image`: `Path \| Buffer \| DataURL \| ArrayBuffer`) |
336
452
  | `hideMainWindowPasteFile(paths)` | R/B | 粘贴文件到焦点应用 |
337
453
  | `hideMainWindowTypeString(text)` | R/B | 模拟键入文本 |
338
454
  | `simulateKeyboardTap(key, ...modifiers)` | R/B | 模拟按键 |
@@ -489,7 +605,7 @@ const text = await window.intools.clipboard.readText()
489
605
  |------|------|------|
490
606
  | `isEncryptionAvailable()` | R/B | 检查加密可用性 |
491
607
  | `encryptString(plainText)` | R/B | 加密字符串 → `Buffer` |
492
- | `decryptString(encrypted)` | R/B | 解密字符串 → `string` |
608
+ | `decryptString(encrypted)` | R/B | 解密字符串 (`encrypted`: `Buffer \| ArrayBuffer`) → `string` |
493
609
 
494
610
  ---
495
611
 
@@ -55,6 +55,14 @@ async function createBasicProject(targetDir, name) {
55
55
  const mainTs = (0, basic_1.buildBasicMain)(name);
56
56
  fs.writeFileSync(path.join(targetDir, 'src/main.ts'), mainTs);
57
57
  console.log(chalk_1.default.green(' ✓ src/main.ts'));
58
+ // 创建 .gitignore
59
+ const gitignore = (0, basic_1.buildGitignore)();
60
+ fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
61
+ console.log(chalk_1.default.green(' ✓ .gitignore'));
62
+ // 创建 README.md
63
+ const readme = (0, basic_1.buildBasicReadme)(name);
64
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
65
+ console.log(chalk_1.default.green(' ✓ README.md'));
58
66
  // 复制 API 参考文档
59
67
  const apiDocSrc = path.join(__dirname, '../../..', 'PLUGIN_API.md');
60
68
  if (fs.existsSync(apiDocSrc)) {
@@ -54,6 +54,8 @@ async function createReactProject(targetDir, name) {
54
54
  createBackendMain(targetDir, name);
55
55
  createReactUI(targetDir, name);
56
56
  createIntoolsTypes(targetDir);
57
+ createGitignore(targetDir);
58
+ createReadme(targetDir, name);
57
59
  // 复制 API 参考文档
58
60
  const apiDocSrc = path.join(__dirname, '../../..', 'PLUGIN_API.md');
59
61
  if (fs.existsSync(apiDocSrc)) {
@@ -110,3 +112,13 @@ function createIntoolsTypes(targetDir) {
110
112
  fs.writeFileSync(path.join(targetDir, 'src/types/intools.d.ts'), typesDts);
111
113
  console.log(chalk_1.default.green(' ✓ src/types/intools.d.ts'));
112
114
  }
115
+ function createGitignore(targetDir) {
116
+ const gitignore = (0, react_1.buildGitignore)();
117
+ fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
118
+ console.log(chalk_1.default.green(' ✓ .gitignore'));
119
+ }
120
+ function createReadme(targetDir, name) {
121
+ const readme = (0, react_1.buildReactReadme)(name);
122
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
123
+ console.log(chalk_1.default.green(' ✓ README.md'));
124
+ }
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildBasicManifest = buildBasicManifest;
4
4
  exports.buildBasicPackageJson = buildBasicPackageJson;
5
5
  exports.buildBasicMain = buildBasicMain;
6
+ exports.buildGitignore = buildGitignore;
7
+ exports.buildBasicReadme = buildBasicReadme;
6
8
  function buildBasicManifest(name) {
7
9
  return {
8
10
  id: name,
@@ -93,3 +95,68 @@ const plugin = { onLoad, onUnload, onEnable, onDisable, run }
93
95
  export default plugin
94
96
  `;
95
97
  }
98
+ function buildGitignore() {
99
+ return `node_modules
100
+ dist
101
+ .DS_Store
102
+ *.log
103
+ `;
104
+ }
105
+ function buildBasicReadme(name) {
106
+ return `# ${name}
107
+
108
+ 插件描述
109
+
110
+ ## 功能特性
111
+
112
+ - 功能 1
113
+ - 功能 2
114
+ - 功能 3
115
+
116
+ ## 触发方式
117
+
118
+ - \`${name}\` - 主功能
119
+
120
+ ## 开发
121
+
122
+ ### 安装依赖
123
+
124
+ \`\`\`bash
125
+ npm install
126
+ \`\`\`
127
+
128
+ ### 开发模式
129
+
130
+ \`\`\`bash
131
+ npm run dev
132
+ \`\`\`
133
+
134
+ ### 构建
135
+
136
+ \`\`\`bash
137
+ npm run build
138
+ \`\`\`
139
+
140
+ ### 打包
141
+
142
+ \`\`\`bash
143
+ npm run pack
144
+ \`\`\`
145
+
146
+ ## 项目结构
147
+
148
+ \`\`\`
149
+ ${name}/
150
+ ├── manifest.json # 插件配置
151
+ ├── package.json
152
+ ├── src/
153
+ │ └── main.ts # 后端入口
154
+ ├── dist/ # 构建输出
155
+ └── icon.png # 插件图标
156
+ \`\`\`
157
+
158
+ ## 许可证
159
+
160
+ MIT License
161
+ `;
162
+ }
@@ -10,6 +10,8 @@ exports.buildMainTsx = buildMainTsx;
10
10
  exports.buildAppTsx = buildAppTsx;
11
11
  exports.buildStylesCss = buildStylesCss;
12
12
  exports.buildUseIntools = buildUseIntools;
13
+ exports.buildGitignore = buildGitignore;
14
+ exports.buildReactReadme = buildReactReadme;
13
15
  exports.buildIntoolsTypes = buildIntoolsTypes;
14
16
  function buildReactManifest(name) {
15
17
  return {
@@ -676,6 +678,82 @@ export function useIntools(pluginId?: string) {
676
678
  }
677
679
  `;
678
680
  }
681
+ function buildGitignore() {
682
+ return `node_modules
683
+ dist
684
+ ui
685
+ .DS_Store
686
+ *.log
687
+ `;
688
+ }
689
+ function buildReactReadme(name) {
690
+ return `# ${name}
691
+
692
+ 插件描述
693
+
694
+ ## 功能特性
695
+
696
+ - 功能 1
697
+ - 功能 2
698
+ - 功能 3
699
+
700
+ ## 触发方式
701
+
702
+ - \`${name}\` - 主功能
703
+
704
+ ## 开发
705
+
706
+ ### 安装依赖
707
+
708
+ \`\`\`bash
709
+ npm install
710
+ \`\`\`
711
+
712
+ ### 开发模式
713
+
714
+ \`\`\`bash
715
+ npm run dev
716
+ \`\`\`
717
+
718
+ ### 构建
719
+
720
+ \`\`\`bash
721
+ npm run build
722
+ \`\`\`
723
+
724
+ ### 打包
725
+
726
+ \`\`\`bash
727
+ npm run pack
728
+ \`\`\`
729
+
730
+ ## 项目结构
731
+
732
+ \`\`\`
733
+ ${name}/
734
+ ├── manifest.json # 插件配置
735
+ ├── package.json
736
+ ├── src/
737
+ │ ├── main.ts # 后端入口
738
+ │ ├── ui/
739
+ │ │ ├── App.tsx # 主应用
740
+ │ │ ├── main.tsx # UI 入口
741
+ │ │ ├── index.html # HTML 模板
742
+ │ │ ├── styles.css # 全局样式
743
+ │ │ ├── hooks/
744
+ │ │ │ └── useIntools.ts # InTools API Hook
745
+ │ │ └── types/
746
+ │ │ └── intools.d.ts # 类型定义
747
+ ├── dist/ # 后端构建输出
748
+ ├── ui/ # UI 构建输出
749
+ └── icon.png # 插件图标
750
+ \`\`\`
751
+
752
+ ## 许可证
753
+
754
+ MIT License
755
+ `;
756
+ }
679
757
  function buildIntoolsTypes() {
680
758
  return `// InTools API 类型定义
681
759
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intools-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "InTools 插件开发 CLI 工具",
5
5
  "main": "dist/index.js",
6
6
  "files": [