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,1164 @@
1
+ # Mulby Plugin Development Guide
2
+
3
+ > **Architecture**: Mulby is built on the **Electron** framework. Plugins run in a multi-process environment.
4
+ > **Contexts**: `UI` = **Renderer Process** (`window.mulby.{module}`), `Main` = **Main Process** (`context.api.{module}`). Most APIs are available in both contexts (marked as **R/B**).
5
+
6
+ ## 1. Project Structure
7
+
8
+ ```text
9
+ my-plugin/
10
+ ├── manifest.json # Plugin Configuration (Manifest V2)
11
+ ├── package.json # Dependencies (React, Vite, etc.)
12
+ ├── tsconfig.json # TypeScript Configuration
13
+ ├── vite.config.ts # Vite Configuration
14
+ ├── preload.cjs # Node.js Bridge (Optional, MUST be .cjs)
15
+ ├── src/
16
+ │ ├── main.ts # Backend Logic (Node.js context)
17
+ │ └── ui/ # Frontend Logic (React context)
18
+ │ ├── main.tsx # Entry Point
19
+ │ ├── App.tsx # Main Component
20
+ │ ├── styles.css # Styles
21
+ │ ├── hooks/ # Custom Hooks
22
+ │ │ └── useMulby.ts
23
+ │ └── vite-env.d.ts
24
+ └── assets/ # Icons, etc.
25
+ ```
26
+
27
+ ## 1.1 Entry Points & Patterns
28
+
29
+ **Backend (`src/main.ts`)**:
30
+ ```typescript
31
+ interface PluginContext {
32
+ api: { clipboard: any; notification: any; /* ... */ }
33
+ input?: string; // Initial input (if triggered by text)
34
+ featureCode?: string; // Feature code structure
35
+ }
36
+
37
+ // Lifecycle Hooks within export
38
+ export function onLoad() { /* Plugin Loaded */ }
39
+ export function onUnload() { /* Plugin Unloaded */ }
40
+ export async function run(context: PluginContext) {
41
+ const { notification } = context.api;
42
+ notification.show('Plugin Started');
43
+ }
44
+ export default { onLoad, onUnload, run };
45
+ ```
46
+
47
+ **Frontend (`src/ui/App.tsx`)**:
48
+ ```typescript
49
+ import { useMulby } from './hooks/useMulby';
50
+
51
+ export default function App() {
52
+ const { clipboard, notification } = useMulby(); // Use provided hook
53
+ // ...
54
+ }
55
+ ```
56
+
57
+ ## 2. Manifest Configuration (`manifest.json`)
58
+
59
+ ```json
60
+ {
61
+ "name": "my-plugin", // Unique ID (required)
62
+ "displayName": "My Plugin", // UI Name (required)
63
+ "version": "1.0.0",
64
+ "author": "Your Name",
65
+ "type":"utility/productivity/developer/system/media/network/ai/entertainment/other",
66
+ "homepage": "https://github.com/...",
67
+ "description": "Plugin description",
68
+ "main": "dist/main.js", // Backend Entry (required)
69
+ "ui": "ui/index.html", // UI Entry (required for UI plugins)
70
+ "preload": "preload.cjs", // Preload path (optional)
71
+ "icon": "icon.png", // Logo path
72
+ "pluginSetting": {
73
+ "single": true, // Run as singleton (no multi-window)
74
+ "height": 400 // Initial height
75
+ },
76
+ "window": { // Detached window config
77
+ "width": 800,
78
+ "height": 600,
79
+ "minWidth": 400,
80
+ "minHeight": 300
81
+ },
82
+ "features": [ // Feature list
83
+ {
84
+ "code": "format",
85
+ "explain": "Format JSON",
86
+ "cmds": [
87
+ // Keyword Trigger
88
+ { "type": "keyword", "value": "json" },
89
+ // Regex Trigger
90
+ { "type": "regex", "match": "^\\{.*\\}$", "minLength": 2 },
91
+ // File Trigger
92
+ { "type": "files", "exts": [".json"], "fileType": "file" },
93
+ // Image Trigger
94
+ { "type": "img", "exts": [".png", ".jpg"] },
95
+ // Text Selection Trigger
96
+ { "type": "over", "label": "Format Selection", "minLength": 1 }
97
+ ],
98
+ "mainPush": true, // Push content to search bar
99
+ "mainHide": true // Hide main window on trigger
100
+ }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ ### Plugin & Window Settings
106
+ | Field | Type | Default | Description |
107
+ |---|---|---|---|
108
+ | `pluginSetting.single` | bool | `true` | Prevent multiple instances |
109
+ | `pluginSetting.height` | number | - | Initial height |
110
+ | `window.width` / `height` | number | 500/400 | Default size (detached) |
111
+ | `window.minWidth` / `Height` | number | 300/200 | Minimum size |
112
+ | `window.maxWidth` / `Height` | number | - | Maximum size |
113
+
114
+ ### Icon Configuration
115
+ - **Formats**: Path (`"icon.png"`), URL (`"https://..."`), Emoji (`"🚀"`), or SVG Code (`"<svg>..."`).
116
+ - **Object Notation**: `{ "type": "file", "value": "path/to/icon.png" }`.
117
+
118
+ ### Feature Configuration (`features`)
119
+ | Field | Type | Description |
120
+ |---|---|---|
121
+ | `code` | string | Unique feature identifier (required) |
122
+ | `explain` | string | Human readable description (required) |
123
+ | `cmds` | array | List of triggers (keyword, regex, files, etc.) |
124
+ | `mode` | string | `ui` (default), `silent` (bg only), `detached` (new window) |
125
+ | `route` | string | Frontend route to navigate to (e.g. `/settings`) |
126
+ | `icon` | string/obj | Feature-specific icon |
127
+ | `mainPush` | boolean | Push input text to search bar |
128
+ | `mainHide` | boolean | Hide main window when triggered |
129
+
130
+ ### Command Triggers (`cmds`)
131
+ | Type | Fields | Description |
132
+ |---|---|---|
133
+ | `keyword` | `value` | Exact keyword match |
134
+ | `regex` | `match`, `label`, `explain` | Regex match against input |
135
+ | `files` | `exts`, `fileType`, `match` | File/Dir drop (`match`=name regex) |
136
+ | `img` | `exts` | Image drag & drop |
137
+ | `over` | `label`, `exclude`, `minLength` | Text selection from other apps |
138
+
139
+ ## 3. Preload Script (`preload.cjs`)
140
+
141
+ > **Rules**:
142
+ > 1. **Extension**: MUST be `.cjs` (project is ESM).
143
+ > 2. **Module System**: MUST use CommonJS (`require`).
144
+ > 3. **purpose**: Access Node.js APIs (`fs`, `crypto`, `child_process`) or Electron APIs (`clipboard`, `shell`).
145
+ > 4. **Constraint**: Do NOT put UI logic (DOM, Canvas) here.
146
+
147
+ ```javascript
148
+ const fs = require('fs');
149
+ // const { PDFDocument } = require('pdf-lib'); // npm packages allowed
150
+
151
+ window.myPluginApi = {
152
+ readFile: (path) => fs.readFileSync(path, 'utf8'),
153
+ // Expose to frontend as: window.myPluginApi.readFile(path)
154
+ }
155
+ ```
156
+
157
+ ## 4. API Reference (TypeScript Definition)
158
+
159
+ > **R/B** = Available in both Renderer (`window.mulby`) and Backend (`context.api`).
160
+ > **R** = Renderer only. **B** = Backend only.
161
+
162
+ ### Core Modules
163
+
164
+ ```typescript
165
+ // notification (R/B) - System Notifications
166
+ interface Notification {
167
+ show(message: string, type?: 'none'|'info'|'error'|'question'|'warning'): void;
168
+ }
169
+
170
+ // shell (R/B) - System Operations
171
+ interface Shell {
172
+ openPath(path: string): Promise<string>;
173
+ openExternal(url: string): Promise<void>;
174
+ showItemInFolder(path: string): Promise<void>;
175
+ openFolder(path: string): Promise<string>;
176
+ trashItem(path: string): Promise<void>;
177
+ beep(): void;
178
+ }
179
+
180
+ // filesystem (R/B) - Node.js-link FS Access
181
+ interface FileSystem {
182
+ readFile(path: string, encoding?: string): Promise<string | Buffer>;
183
+ writeFile(path: string, data: string | Buffer, encoding?: string): Promise<void>;
184
+ exists(path: string): Promise<boolean>;
185
+ unlink(path: string): Promise<void>;
186
+ readdir(path: string): Promise<string[]>;
187
+ mkdir(path: string): Promise<void>;
188
+ stat(path: string): Promise<{ size: number; isFile: boolean; isDirectory: boolean; adjusted: number }>;
189
+ copy(src: string, dest: string): Promise<void>;
190
+ move(src: string, dest: string): Promise<void>;
191
+ // Backend Only Path Utils
192
+ extname?(path: string): string;
193
+ join?(...paths: string[]): string;
194
+ dirname?(path: string): string;
195
+ basename?(path: string, ext?: string): string;
196
+ }
197
+
198
+ // http (R/B) - Network Requests
199
+ interface Http {
200
+ request(opts: { url: string; method?: string; headers?: any; body?: any; timeout?: number }): Promise<{ status: number; data: string; headers: any }>;
201
+ get(url: string, headers?: any): Promise<HttpResponse>;
202
+ post(url: string, body?: any, headers?: any): Promise<HttpResponse>;
203
+ put(url: string, body?: any): Promise<HttpResponse>;
204
+ delete(url: string, headers?: any): Promise<HttpResponse>;
205
+ }
206
+
207
+ // ai (R/B) - AI (tools only in backend)
208
+ interface Ai {
209
+ call(option: AiOption, onChunk?: (chunk: AiMessage) => void): AiPromiseLike<AiMessage>;
210
+ allModels(): Promise<AiModel[]>;
211
+ abort(requestId: string): Promise<void>;
212
+ skills: {
213
+ // Available in both Renderer and Backend
214
+ listEnabled(): Promise<AiSkillRecord[]>;
215
+ // Backend name; Renderer uses preview(...)
216
+ previewForCall?(input: { option?: Partial<AiOption>; skillIds?: string[]; prompt?: string }): Promise<AiSkillPreview>;
217
+ // Renderer-only manager
218
+ list?(): Promise<AiSkillRecord[]>;
219
+ refresh?(): Promise<AiSkillRecord[]>;
220
+ get?(skillId: string): Promise<AiSkillRecord | null>;
221
+ listCreateModels?(): Promise<AiSkillCreateModelOption[]>;
222
+ createWithAi?(input: AiSkillCreateWithAiInput): Promise<AiSkillCreateWithAiResult>;
223
+ createWithAiStream?(
224
+ input: AiSkillCreateWithAiInput,
225
+ onChunk: (chunk: AiSkillCreateProgressChunk) => void
226
+ ): AiPromiseLike<AiSkillCreateWithAiResult>;
227
+ create?(input: {
228
+ id?: string;
229
+ name: string;
230
+ description?: string;
231
+ promptTemplate?: string;
232
+ tags?: string[];
233
+ triggerPhrases?: string[];
234
+ mode?: 'manual' | 'auto' | 'both';
235
+ capabilities?: string[];
236
+ internalTools?: string[];
237
+ enabled?: boolean;
238
+ trustLevel?: AiSkillTrustLevel;
239
+ mcpPolicy?: AiSkillMcpPolicy;
240
+ }): Promise<AiSkillRecord>;
241
+ install?(input: {
242
+ source: 'local-dir' | 'zip';
243
+ ref: string;
244
+ trustLevel?: AiSkillTrustLevel;
245
+ enabled?: boolean;
246
+ }): Promise<AiSkillRecord[]>;
247
+ importFromJson?(input: {
248
+ json: string;
249
+ trustLevel?: AiSkillTrustLevel;
250
+ enabled?: boolean;
251
+ }): Promise<AiSkillRecord[]>;
252
+ update?(skillId: string, patch: Partial<AiSkillRecord>): Promise<AiSkillRecord>;
253
+ remove?(skillId: string): Promise<void>;
254
+ enable?(skillId: string): Promise<AiSkillRecord>;
255
+ disable?(skillId: string): Promise<AiSkillRecord>;
256
+ preview?(input: { option?: Partial<AiOption>; skillIds?: string[]; prompt?: string }): Promise<AiSkillPreview>;
257
+ resolve?(option: AiOption): Promise<AiSkillResolveResult>;
258
+ };
259
+ tokens: {
260
+ estimate(input: { model?: string; messages: AiMessage[]; outputText?: string }): Promise<{ inputTokens: number; outputTokens: number }>;
261
+ };
262
+ attachments: {
263
+ upload(input: { filePath?: string; buffer?: ArrayBuffer; mimeType: string; purpose?: string }): Promise<AiAttachmentRef>;
264
+ get(attachmentId: string): Promise<AiAttachmentRef | null>;
265
+ delete(attachmentId: string): Promise<void>;
266
+ uploadToProvider(input: { attachmentId: string; model?: string; providerId?: string; purpose?: string }): Promise<{ providerId: string; fileId: string; uri?: string }>;
267
+ };
268
+ images: {
269
+ generate(input: { model: string; prompt: string; size?: string; count?: number }): Promise<{ images: string[]; tokens: AiTokenBreakdown }>;
270
+ generateStream(
271
+ input: { model: string; prompt: string; size?: string; count?: number },
272
+ onChunk: (chunk: AiImageGenerateProgressChunk) => void
273
+ ): AiPromiseLike<{ images: string[]; tokens: AiTokenBreakdown }>;
274
+ edit(input: { model: string; imageAttachmentId: string; prompt: string }): Promise<{ images: string[]; tokens: AiTokenBreakdown }>;
275
+ };
276
+ // Renderer-only helpers
277
+ models?: {
278
+ fetch(input: { providerId: string; baseURL?: string; apiKey?: string }): Promise<{ models: AiModel[]; message?: string }>;
279
+ };
280
+ testConnection?: (input?: { providerId?: string; model?: string; baseURL?: string; apiKey?: string }) => Promise<{ success: boolean; message?: string }>;
281
+ testConnectionStream?: (
282
+ input: { providerId?: string; model?: string; baseURL?: string; apiKey?: string },
283
+ onChunk: (chunk: { type: 'reasoning' | 'content'; text: string }) => void
284
+ ) => AiPromiseLike<{ success: boolean; message?: string; reasoning?: string }>;
285
+ settings?: {
286
+ get(): Promise<AiSettings>;
287
+ update(next: Partial<AiSettings>): Promise<AiSettings>;
288
+ };
289
+ // Renderer-only MCP manager (window.mulby.ai.mcp)
290
+ // Backend context.api.ai currently does not expose mcp.*
291
+ mcp?: {
292
+ listServers(): Promise<AiMcpServer[]>;
293
+ getServer(serverId: string): Promise<AiMcpServer | null>;
294
+ upsertServer(server: AiMcpServer): Promise<AiMcpServer>;
295
+ removeServer(serverId: string): Promise<void>;
296
+ activateServer(serverId: string): Promise<AiMcpServer>;
297
+ deactivateServer(serverId: string): Promise<AiMcpServer>;
298
+ restartServer(serverId: string): Promise<AiMcpServer>;
299
+ checkServer(serverId: string): Promise<{ ok: boolean; message?: string }>;
300
+ listTools(serverId: string): Promise<AiMcpTool[]>;
301
+ abort(callId: string): Promise<boolean>;
302
+ getLogs(serverId: string): Promise<AiMcpServerLogEntry[]>;
303
+ };
304
+ }
305
+
306
+ type AiMessage = {
307
+ role: 'system' | 'user' | 'assistant';
308
+ content?: string | AiMessageContent[];
309
+ reasoning_content?: string;
310
+ chunkType?: 'meta' | 'text' | 'reasoning' | 'tool-call' | 'tool-result' | 'error' | 'end';
311
+ capability_debug?: {
312
+ requested: string[];
313
+ allowed: string[];
314
+ denied: string[];
315
+ reasons: string[];
316
+ selectedSkills?: { id: string; source: AiSkillSource; trustLevel: AiSkillTrustLevel }[];
317
+ };
318
+ policy_debug?: {
319
+ skills: {
320
+ requested?: AiSkillSelection;
321
+ selectedSkillIds: string[];
322
+ selectedSkillNames: string[];
323
+ reasons: string[];
324
+ };
325
+ mcp: { requested?: AiMcpSelection; resolved?: AiMcpSelection };
326
+ toolContext: { requested?: AiToolContext; resolved?: AiToolContext };
327
+ capabilities: { requested: string[]; resolved: string[] };
328
+ internalTools: { requested: string[]; resolved: string[] };
329
+ };
330
+ tool_call?: { id: string; name: string; args?: unknown };
331
+ tool_result?: { id: string; name: string; result?: unknown };
332
+ error?: { message: string; code?: string; category?: string; retryable?: boolean; statusCode?: number };
333
+ usage?: AiTokenBreakdown;
334
+ };
335
+ type AiMessageContent =
336
+ | { type: 'text'; text: string }
337
+ | { type: 'image'; attachmentId: string; mimeType?: string }
338
+ | { type: 'file'; attachmentId: string; mimeType?: string; filename?: string };
339
+ type AiOption = {
340
+ model?: string;
341
+ messages: AiMessage[];
342
+ tools?: AiTool[];
343
+ capabilities?: string[];
344
+ internalTools?: string[]; // deprecated: prefer capabilities
345
+ toolingPolicy?: {
346
+ enableInternalTools?: boolean;
347
+ capabilityAllowList?: string[];
348
+ capabilityDenyList?: string[];
349
+ };
350
+ mcp?: AiMcpSelection;
351
+ skills?: AiSkillSelection;
352
+ params?: AiModelParameters;
353
+ toolContext?: AiToolContext;
354
+ maxToolSteps?: number; // default 20, max 100
355
+ };
356
+ type AiTool = {
357
+ type: 'function';
358
+ function?: {
359
+ name: string;
360
+ description: string;
361
+ parameters: {
362
+ type: 'object';
363
+ properties: Record<string, unknown>;
364
+ required?: string[];
365
+ additionalProperties?: boolean;
366
+ };
367
+ required?: string[];
368
+ };
369
+ };
370
+ type AiModelParameters = {
371
+ contextWindow?: number;
372
+ temperatureEnabled?: boolean;
373
+ topPEnabled?: boolean;
374
+ maxOutputTokensEnabled?: boolean;
375
+ temperature?: number;
376
+ topP?: number;
377
+ topK?: number;
378
+ maxOutputTokens?: number;
379
+ presencePenalty?: number;
380
+ frequencyPenalty?: number;
381
+ stopSequences?: string[];
382
+ seed?: number;
383
+ };
384
+ type AiEndpointType =
385
+ | 'openai'
386
+ | 'openai-response'
387
+ | 'anthropic'
388
+ | 'gemini'
389
+ | 'image-generation'
390
+ | 'jina-rerank';
391
+ type AiModel = {
392
+ id: string;
393
+ label: string;
394
+ description: string;
395
+ icon?: string;
396
+ providerRef?: string;
397
+ providerLabel?: string;
398
+ endpointType?: AiEndpointType;
399
+ supportedEndpointTypes?: AiEndpointType[];
400
+ params?: AiModelParameters;
401
+ capabilities?: Array<{
402
+ type: 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling' | 'web_search' | 'rerank';
403
+ isUserSelected?: boolean;
404
+ }>;
405
+ };
406
+ type AiSettings = {
407
+ providers: AiProviderConfig[];
408
+ models?: AiModel[];
409
+ defaultModel?: string;
410
+ defaultParams?: AiModelParameters;
411
+ mcp?: AiMcpSettings;
412
+ skills?: AiSkillSettings;
413
+ };
414
+ type AiProviderConfig = {
415
+ id: string;
416
+ type?: string;
417
+ label?: string;
418
+ enabled: boolean;
419
+ apiKey?: string;
420
+ baseURL?: string;
421
+ apiVersion?: string;
422
+ anthropicBaseURL?: string;
423
+ headers?: Record<string, string>;
424
+ defaultModel?: string;
425
+ defaultParams?: AiModelParameters;
426
+ };
427
+ type AiMcpSelection = { mode?: 'off' | 'manual' | 'auto'; serverIds?: string[]; allowedToolIds?: string[] };
428
+ type AiToolContext = {
429
+ pluginName?: string;
430
+ internalTag?: string;
431
+ mcpScope?: { allowedServerIds?: string[]; allowedToolIds?: string[] };
432
+ };
433
+ type AiMcpServer = {
434
+ id: string;
435
+ name: string;
436
+ type: 'stdio' | 'sse' | 'streamableHttp';
437
+ isActive: boolean;
438
+ description?: string;
439
+ baseUrl?: string;
440
+ command?: string;
441
+ args?: string[];
442
+ env?: Record<string, string>;
443
+ headers?: Record<string, string>;
444
+ timeoutSec?: number;
445
+ longRunning?: boolean;
446
+ disabledTools?: string[];
447
+ disabledAutoApproveTools?: string[];
448
+ installSource?: 'manual' | 'protocol' | 'builtin';
449
+ isTrusted?: boolean;
450
+ trustedAt?: number;
451
+ installedAt?: number;
452
+ };
453
+ type AiMcpSettings = {
454
+ servers: AiMcpServer[];
455
+ defaults?: { timeoutMs?: number; longRunningMaxMs?: number; approvalMode?: 'always' | 'auto-approved-only' | 'never' };
456
+ };
457
+ type AiMcpTool = {
458
+ id: string;
459
+ name: string;
460
+ description?: string;
461
+ serverId: string;
462
+ serverName: string;
463
+ inputSchema?: unknown;
464
+ outputSchema?: unknown;
465
+ };
466
+ type AiMcpServerLogEntry = {
467
+ timestamp: number;
468
+ level: 'debug' | 'info' | 'warn' | 'error';
469
+ message: string;
470
+ source?: string;
471
+ data?: unknown;
472
+ };
473
+ type AiSkillSource = 'manual' | 'local-dir' | 'zip' | 'json' | 'builtin' | 'system';
474
+ type AiSkillTrustLevel = 'untrusted' | 'reviewed' | 'trusted';
475
+ type AiSkillMcpPolicy = {
476
+ serverIds?: string[];
477
+ allowedToolIds?: string[];
478
+ blockedToolIds?: string[];
479
+ };
480
+ type AiSkillSelection = {
481
+ mode?: 'off' | 'manual' | 'auto';
482
+ skillIds?: string[];
483
+ variables?: Record<string, string>;
484
+ };
485
+ type AiSkillRecord = {
486
+ id: string;
487
+ source: AiSkillSource;
488
+ origin?: 'system' | 'app';
489
+ readonly?: boolean;
490
+ sourceRef?: string;
491
+ installPath?: string;
492
+ skillMdPath?: string;
493
+ contentHash: string;
494
+ enabled: boolean;
495
+ trustLevel: AiSkillTrustLevel;
496
+ installedAt: number;
497
+ updatedAt: number;
498
+ descriptor: {
499
+ id: string;
500
+ name: string;
501
+ description?: string;
502
+ version?: string;
503
+ author?: string;
504
+ tags?: string[];
505
+ triggerPhrases?: string[];
506
+ mode?: 'manual' | 'auto' | 'both';
507
+ promptTemplate?: string;
508
+ mcpPolicy?: AiSkillMcpPolicy;
509
+ capabilities?: string[];
510
+ internalTools?: string[];
511
+ };
512
+ };
513
+ type AiSkillSettings = {
514
+ enabled: boolean;
515
+ activeSkillIds: string[];
516
+ autoSelect?: { enabled?: boolean; maxSkillsPerCall?: number; minScore?: number };
517
+ records: AiSkillRecord[];
518
+ };
519
+ type AiSkillPreview = {
520
+ selected: AiSkillRecord[];
521
+ systemPrompt: string;
522
+ mcpImpact: { serverIds?: string[]; allowedToolIds?: string[]; blockedToolIds?: string[] };
523
+ reasons: string[];
524
+ };
525
+ type AiSkillResolveResult = {
526
+ selectedSkillIds: string[];
527
+ selectedSkillNames: string[];
528
+ selectedSkills?: Array<{ id: string; source: AiSkillSource; trustLevel: AiSkillTrustLevel }>;
529
+ systemPrompts: string[];
530
+ mergedMcp?: AiMcpSelection;
531
+ toolContextPatch?: AiToolContext['mcpScope'];
532
+ capabilities?: string[];
533
+ internalTools?: string[];
534
+ reasons?: string[];
535
+ };
536
+ type AiSkillCreateModelOption = {
537
+ id: string;
538
+ label: string;
539
+ providerRef?: string;
540
+ providerLabel?: string;
541
+ };
542
+ type AiSkillCreateWithAiInput = {
543
+ requirements: string;
544
+ model: string;
545
+ previousRawText?: string;
546
+ replaceSkillId?: string;
547
+ enabled?: boolean;
548
+ trustLevel?: AiSkillTrustLevel;
549
+ modePreference?: 'manual' | 'auto' | 'both';
550
+ };
551
+ type AiSkillCreateWithAiResult = {
552
+ record: AiSkillRecord;
553
+ generation: { model: string; rawText: string; notes?: string[] };
554
+ };
555
+ type AiSkillCreateProgressChunk = {
556
+ type: 'status' | 'content' | 'reasoning';
557
+ text: string;
558
+ stage?: 'generating' | 'parsing' | 'validating' | 'writing' | 'completed';
559
+ stageStatus?: 'start' | 'done' | 'error';
560
+ };
561
+ type AiAttachmentRef = { attachmentId: string; mimeType: string; size: number; filename?: string; expiresAt?: string; purpose?: string };
562
+ type AiTokenBreakdown = { inputTokens: number; outputTokens: number };
563
+ type AiImageGenerateProgressChunk = {
564
+ type: 'status' | 'preview';
565
+ stage?: 'start' | 'partial' | 'finalizing' | 'completed' | 'fallback';
566
+ message?: string;
567
+ image?: string;
568
+ index?: number;
569
+ received?: number;
570
+ total?: number;
571
+ };
572
+ type AiPromiseLike<T> = Promise<T> & { abort: () => void };
573
+
574
+ // clipboard (R/B)
575
+ interface Clipboard {
576
+ readText(): Promise<string>;
577
+ writeText(text: string): Promise<void>;
578
+ readImage(): Promise<Buffer | null>;
579
+ writeImage(image: string | Buffer): Promise<void>;
580
+ readFiles(): Promise<{ path: string; name: string; size: number; isDirectory: boolean }[]>;
581
+ writeFiles(paths: string[]): Promise<void>; // R only
582
+ getFormat(): Promise<'text'|'image'|'files'|'html'|'empty'>;
583
+ }
584
+
585
+ // dialog (R/B) - Native Dialogs
586
+ interface Dialog {
587
+ showOpenDialog(opts?: { title?: string; defaultPath?: string; buttonLabel?: string; filters?: any[]; properties?: string[] }): Promise<string[]>;
588
+ showSaveDialog(opts?: { title?: string; defaultPath?: string; filters?: any[] }): Promise<string | null>;
589
+ showMessageBox(opts: { type?: string; title?: string; message: string; buttons?: string[] }): Promise<{ response: number }>;
590
+ showErrorBox(title: string, content: string): void;
591
+ }
592
+
593
+ // system (R/B) - System Info
594
+ interface System {
595
+ getSystemInfo(): Promise<{ platform: string; arch: string; hostname: string; cpus: number; totalmem: number }>;
596
+ getAppInfo(): Promise<{ name: string; version: string; userDataPath: string }>;
597
+ getPath(name: 'home'|'appData'|'temp'|'desktop'|'downloads'|'documents'|'pictures'|'music'|'videos'): Promise<string>;
598
+ getEnv(name: string): Promise<string>;
599
+ getIdleTime(): Promise<number>;
600
+ getFileIcon(path: string): Promise<string>; // Base64
601
+ getNativeId(): Promise<string>;
602
+ isDev(): Promise<boolean>;
603
+ isMacOS(): Promise<boolean>;
604
+ isWindows(): Promise<boolean>;
605
+ isLinux(): Promise<boolean>;
606
+ }
607
+
608
+ // storage (R/B) - Persistent KV Store
609
+ interface Storage {
610
+ get(key: string, namespace?: string): Promise<any>;
611
+ set(key: string, value: any, namespace?: string): Promise<boolean>;
612
+ remove(key: string, namespace?: string): Promise<boolean>;
613
+ clear(): void; // B only
614
+ keys(): string[]; // B only
615
+ }
616
+
617
+ // messaging (R/B) - Plugin-to-Plugin Communication
618
+ interface Messaging {
619
+ send(targetPluginId: string, type: string, payload: unknown): Promise<void>;
620
+ broadcast(type: string, payload: unknown): Promise<void>;
621
+ on(handler: (message: { id: string; from: string; to?: string; type: string; payload: unknown; timestamp: number }) => void | Promise<void>): void;
622
+ off(handler?: (message: any) => void): void;
623
+ }
624
+
625
+ // scheduler (B) - Task Scheduler
626
+ interface Scheduler {
627
+ schedule(task: TaskInput): Promise<Task>;
628
+ cancel(taskId: string): Promise<void>;
629
+ pause(taskId: string): Promise<void>;
630
+ resume(taskId: string): Promise<void>;
631
+ get(taskId: string): Promise<Task | null>;
632
+ list(filter?: { status?: string; type?: string; limit?: number }): Promise<Task[]>;
633
+ getExecutions(taskId: string, limit?: number): Promise<TaskExecution[]>;
634
+ validateCron(expression: string): boolean;
635
+ getNextCronTime(expression: string, after?: Date): Date;
636
+ describeCron(expression: string): string;
637
+ }
638
+
639
+ interface TaskInput {
640
+ name: string;
641
+ type: 'once' | 'repeat' | 'delay';
642
+ callback: string;
643
+ description?: string;
644
+ payload?: any;
645
+ time?: number; // For 'once' type
646
+ cron?: string; // For 'repeat' type (6-field: sec min hour day month weekday)
647
+ delay?: number; // For 'delay' type
648
+ timezone?: string;
649
+ maxRetries?: number;
650
+ retryDelay?: number;
651
+ timeout?: number;
652
+ endTime?: number; // For 'repeat' type
653
+ maxExecutions?: number; // For 'repeat' type
654
+ }
655
+
656
+ interface Task extends TaskInput {
657
+ id: string;
658
+ pluginId: string;
659
+ status: 'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
660
+ nextRunTime?: number;
661
+ lastRunTime?: number;
662
+ executionCount: number;
663
+ failureCount: number;
664
+ lastError?: string;
665
+ createdAt: number;
666
+ updatedAt: number;
667
+ }
668
+
669
+ interface TaskExecution {
670
+ id: string;
671
+ taskId: string;
672
+ startTime: number;
673
+ endTime?: number;
674
+ status: 'success' | 'failed' | 'timeout';
675
+ result?: any;
676
+ error?: string;
677
+ duration?: number;
678
+ }
679
+ ```
680
+
681
+ ### Advanced System Modules
682
+
683
+ ```typescript
684
+ // network (R/B)
685
+ interface Network {
686
+ isOnline(): Promise<boolean>;
687
+ onOnline(cb: () => void): void; // R only
688
+ onOffline(cb: () => void): void; // R only
689
+ }
690
+
691
+ // power (R/B)
692
+ interface Power {
693
+ getSystemIdleTime(): Promise<number>;
694
+ getSystemIdleState(threshold: number): Promise<'active'|'idle'|'locked'|'unknown'>;
695
+ isOnBatteryPower(): Promise<boolean>;
696
+ onSuspend(cb: () => void): void; // R only
697
+ onResume(cb: () => void): void; // R only
698
+ onAC(cb: () => void): void; // R only
699
+ onBattery(cb: () => void): void; // R only
700
+ }
701
+
702
+ // security (R/B) - Encrypted Storage
703
+ interface Security {
704
+ isEncryptionAvailable(): Promise<boolean>;
705
+ encryptString(plain: string): Promise<Buffer>;
706
+ decryptString(encrypted: Buffer): Promise<string>;
707
+ }
708
+
709
+ // shortcut (R/B) - Global Hotkeys
710
+ interface Shortcut {
711
+ register(accelerator: string): Promise<boolean>;
712
+ unregister(accelerator: string): Promise<void>;
713
+ unregisterAll(): Promise<void>;
714
+ isRegistered(accelerator: string): Promise<boolean>;
715
+ onTriggered(cb: (acc: string) => void): void;
716
+ }
717
+
718
+ // geolocation (R)
719
+ interface Geolocation {
720
+ getAccessStatus(): Promise<string>;
721
+ requestAccess(): Promise<string>; // macOS only
722
+ canGetPosition(): Promise<boolean>;
723
+ getCurrentPosition(): Promise<{ latitude: number; longitude: number }>;
724
+ openSettings(): Promise<void>;
725
+ }
726
+
727
+ // host (R) - Call Backend from UI
728
+ interface Host {
729
+ // Call main process API (e.g., clipboard.readText)
730
+ invoke(pluginId: string, method: string, ...args: any[]): Promise<any>;
731
+ // Call plugin custom methods (exported in main.ts)
732
+ call(pluginId: string, method: string, ...args: any[]): Promise<{ data: any }>;
733
+ status(pluginId: string): Promise<{ ready: boolean; active: boolean }>;
734
+ restart(pluginId: string): Promise<boolean>;
735
+ }
736
+ ```
737
+
738
+ ### UI & Interaction Modules
739
+
740
+ ```typescript
741
+ // window (R) - Window Control
742
+ interface Window {
743
+ hide(): void;
744
+ show(): void;
745
+ setSize(w: number, h: number): void;
746
+ setExpendHeight(h: number): void;
747
+ center(): void;
748
+ setAlwaysOnTop(flag: boolean): void;
749
+ detach(): void;
750
+ close(): void;
751
+ create(url: string, opts?: any): Promise<ChildWindowHandle>;
752
+ startDrag(path: string): void;
753
+ }
754
+
755
+ // subInput (R) - Secondary Input Bar
756
+ interface SubInput {
757
+ set(placeholder?: string, isFocus?: boolean): void;
758
+ setValue(text: string): void;
759
+ focus(): void;
760
+ blur(): void;
761
+ select(): void;
762
+ remove(): void;
763
+ onChange(cb: (e: { text: string }) => void): void;
764
+ }
765
+
766
+ // plugin (R) - Plugin Management
767
+ interface Plugin {
768
+ getAll(): Promise<PluginInfo[]>;
769
+ search(query: string): Promise<PluginSearchResult[]>;
770
+ run(id: string, code: string, input?: any): Promise<void>;
771
+ install(path: string): Promise<void>;
772
+ enable(name: string): Promise<{ success: boolean; error?: string }>;
773
+ disable(name: string): Promise<{ success: boolean; error?: string }>;
774
+ uninstall(id: string): Promise<void>;
775
+ outPlugin(kill?: boolean): Promise<void>;
776
+ redirect(label: string, payload?: any): Promise<boolean>;
777
+ getReadme(id: string): Promise<string>;
778
+
779
+ // Background & Process Management
780
+ listBackground(): Promise<any[]>;
781
+ startBackground(pluginId: string): Promise<{ success: boolean; error?: string }>;
782
+ stopBackground(pluginId: string): Promise<{ success: boolean }>;
783
+ getBackgroundInfo(pluginId: string): Promise<any>;
784
+ stopPlugin(pluginId: string): Promise<void>;
785
+ }
786
+
787
+ // theme (R)
788
+ interface Theme {
789
+ get(): Promise<{ mode: string; actual: string }>;
790
+ set(mode: 'light'|'dark'|'system'): Promise<void>;
791
+ onThemeChange(cb: (mode: string) => void): void;
792
+ }
793
+
794
+ // menu (R) - Context Menu
795
+ interface Menu {
796
+ showContextMenu(items: { label: string; id?: string; type?: string; submenu?: any[] }[]): Promise<string | null>;
797
+ }
798
+
799
+ // tray (R/B)
800
+ interface Tray {
801
+ create(opts: { icon: string; tooltip?: string; title?: string }): Promise<boolean>;
802
+ destroy(): Promise<void>;
803
+ setIcon(icon: string): Promise<void>;
804
+ setTooltip(tip: string): Promise<void>;
805
+ setTitle(title: string): Promise<void>;
806
+ }
807
+
808
+ // tts (R) - Text to Speech
809
+ interface Tts {
810
+ speak(text: string, opts?: { lang?: string; rate?: number }): Promise<void>;
811
+ stop(): void;
812
+ getVoices(): Promise<any[]>;
813
+ }
814
+
815
+ // features (B) - Dynamic Features
816
+ interface Features {
817
+ getFeatures(codes?: string[]): DynamicFeature[];
818
+ setFeature(feature: { code: string; cmds: any[]; mode?: string }): void;
819
+ removeFeature(code: string): boolean;
820
+ }
821
+ ```
822
+
823
+ ### Media, Screen & Automation
824
+
825
+ ```typescript
826
+ // screen (R/B)
827
+ interface Screen {
828
+ getAllDisplays(): Promise<any[]>;
829
+ getPrimaryDisplay(): Promise<any>;
830
+ getCursorScreenPoint(): Promise<{ x: number; y: number }>;
831
+ capture(opts?: { sourceId?: string; format?: 'png'|'jpeg' }): Promise<Buffer>;
832
+ captureRegion(rect: { x: number; y: number; w: number; h: number }, opts?: any): Promise<Buffer>;
833
+ screenCapture(): Promise<string>; // R only, Interactive
834
+ colorPick(): Promise<{ hex: string }>; // R only
835
+ getSources(opts?: { types: string[] }): Promise<any[]>;
836
+ }
837
+
838
+ // input (R/B) - Simulate Input
839
+ interface Input {
840
+ hideMainWindowPasteText(text: string): Promise<boolean>;
841
+ hideMainWindowPasteImage(img: string|Buffer): Promise<boolean>;
842
+ hideMainWindowPasteFile(path: string|string[]): Promise<boolean>;
843
+ hideMainWindowTypeString(text: string): Promise<boolean>;
844
+ restoreWindows(): Promise<boolean>; // Restore hidden windows after input
845
+ simulateKeyboardTap(key: string, ...modifiers: string[]): Promise<boolean>;
846
+ simulateMouseMove(x: number, y: number): Promise<boolean>;
847
+ simulateMouseClick(x: number, y: number): Promise<boolean>;
848
+ }
849
+
850
+ // media (R/B) - Permission
851
+ interface MediaPerm {
852
+ getAccessStatus(type: 'microphone'|'camera'): Promise<string>;
853
+ askForAccess(type: 'microphone'|'camera'): Promise<boolean>;
854
+ }
855
+
856
+ // sharp (R) - Image Processing
857
+ interface Sharp { (input: string|Buffer): SharpInstance; }
858
+
859
+ // ffmpeg (R) - Video Processing
860
+ interface FFmpeg {
861
+ run(args: string[], onProgress?: (p: any) => void): { promise: Promise<void>; kill: () => void };
862
+ isAvailable(): Promise<boolean>;
863
+ download(cb?: (p: any) => void): Promise<{ success: boolean }>;
864
+ }
865
+
866
+ // inbrowser (R) - Browser Automation
867
+ interface InBrowser {
868
+ // Navigation & Window
869
+ goto(url: string, headers?: Record<string, string>, timeout?: number): this;
870
+ useragent(ua: string): this;
871
+ device(nameOrOption: string | { userAgent: string; size: { width: number; height: number } }): this;
872
+ viewport(width: number, height: number): this;
873
+ show(): this;
874
+ hide(): this;
875
+ devTools(mode?: 'right' | 'bottom' | 'undocked' | 'detach'): this;
876
+
877
+ // Interaction
878
+ click(selector: string | number, mouseButtonOrY?: string | number, mouseButton?: string): this;
879
+ mousedown(selector: string | number, mouseButtonOrY?: string | number, mouseButton?: string): this;
880
+ mouseup(selector: string | number, mouseButtonOrY?: string | number, mouseButton?: string): this;
881
+ dblclick(selector: string | number, mouseButtonOrY?: string | number, mouseButton?: string): this;
882
+ hover(selector: string | number, y?: number): this;
883
+ type(selector: string, text: string): this;
884
+ input(selectorOrText: string, text?: string): this;
885
+ press(key: string, modifiers?: string[]): this;
886
+ focus(selector: string): this;
887
+ scroll(selectorOrY: string | number, optionalOrY?: any): this;
888
+ paste(text: string): this;
889
+ file(selector: string, payload: string | string[] | Buffer): this;
890
+ drop(selectorOrX: string | number, optionalYOrPayload: any, payload?: any): this;
891
+
892
+ // Content & State
893
+ value(selector: string, val: string): this;
894
+ check(selector: string, checked: boolean): this;
895
+ css(cssText: string): this;
896
+ cookies(nameOrFilter?: string | any): this;
897
+ setCookies(nameOrCookies: string | any[], value?: string): this;
898
+ removeCookies(name: string): this;
899
+ clearCookies(url?: string): this;
900
+
901
+ // Flow Control
902
+ wait(msOrSelectorOrFunc: number | string | Function, ...params: any[]): this;
903
+ when(selectorOrFunc: string | Function, ...params: any[]): this;
904
+ end(): this;
905
+
906
+ // Output Data (In run() result)
907
+ evaluate<T>(func: string | Function, ...params: any[]): this;
908
+ screenshot(target?: string | object, savePath?: string): this;
909
+ pdf(options?: any, savePath?: string): this;
910
+ markdown(selector?: string): this;
911
+ download(urlOrFunc: string | Function, savePath?: string, ...params: any[]): this;
912
+
913
+ // Execution
914
+ run(): Promise<any[]>;
915
+
916
+ // Management
917
+ getIdleInBrowsers(): Promise<any[]>;
918
+ setInBrowserProxy(config: any): Promise<boolean>;
919
+ clearInBrowserCache(): Promise<boolean>;
920
+ }
921
+ ```
922
+
923
+ ## 5. Background Plugin Development
924
+
925
+ Enable plugins to run in the background (e.g., for cron jobs, monitoring).
926
+
927
+ ### 5.1 Configuration (`manifest.json`)
928
+
929
+ Explicitly enable background mode:
930
+
931
+ ```json
932
+ {
933
+ "pluginSetting": {
934
+ "background": true, // Enable background mode
935
+ "persistent": true, // Auto-restore on app restart
936
+ "maxRuntime": 0 // Max runtime in ms (0 = unlimited)
937
+ }
938
+ }
939
+ ```
940
+
941
+ ### 5.2 Lifecycle Hooks (`src/main.ts`)
942
+
943
+ Handle background transitions:
944
+
945
+ ```typescript
946
+ export function onBackground() {
947
+ // Triggered when window closes (if background: true)
948
+ console.log('Moved to background');
949
+ // Start background tasks (e.g., cron jobs)
950
+ }
951
+
952
+ export function onForeground() {
953
+ // Triggered when window re-opens
954
+ console.log('Back to foreground');
955
+ // Optional: Update UI or optimize resources
956
+ }
957
+
958
+ // Standard hooks
959
+ export function onLoad() { /* Init */ }
960
+ export function onUnload() { /* Cleanup (Called on app exit or manual stop) */ }
961
+ ```
962
+
963
+ ### 5.3 Management API
964
+
965
+ - **Check Status**: `api.plugin.listBackground()`
966
+ - **Stop**: `api.plugin.stopBackground(pluginId)`
967
+ - **Start**: `api.plugin.startBackground(pluginId)`
968
+
969
+ ### 5.4 UI Plugin Integration
970
+
971
+ For plugins with UI (`"ui": "..."` in manifest):
972
+ 1. Background logic MUST go in **backend** (`src/main.ts`).
973
+ 2. **Frontend** (`src/ui/...`) stops when window closes.
974
+ 3. Use IPC (via `api.messaging` or `host` module) to communicate between active UI and background backend.
975
+
976
+ ## 6. Task Scheduler (Backend Only)
977
+
978
+ Schedule tasks to run at specific times or intervals. Tasks persist across app restarts.
979
+
980
+ ### 6.1 Creating Tasks
981
+
982
+ ```typescript
983
+ // One-time: Execute at timestamp
984
+ await api.scheduler.schedule({
985
+ name: 'Meeting Reminder',
986
+ type: 'once',
987
+ time: Date.now() + 3600000, // 1 hour later
988
+ callback: 'onReminder',
989
+ payload: { message: 'Meeting starts soon' }
990
+ });
991
+
992
+ // Repeat: Execute via Cron (6-field: sec min hour day month weekday)
993
+ await api.scheduler.schedule({
994
+ name: 'Daily Backup',
995
+ type: 'repeat',
996
+ cron: '0 0 2 * * *', // Daily at 2 AM
997
+ callback: 'onBackup'
998
+ });
999
+
1000
+ // Delay: Execute after milliseconds
1001
+ await api.scheduler.schedule({
1002
+ name: 'Delayed Task',
1003
+ type: 'delay',
1004
+ delay: 5000, // 5 seconds
1005
+ callback: 'onTask'
1006
+ });
1007
+ ```
1008
+
1009
+ ### 6.2 Task Callbacks
1010
+
1011
+ Export callback functions in `src/main.ts`:
1012
+
1013
+ ```typescript
1014
+ export async function onReminder({ api, payload, task }) {
1015
+ api.notification.show(payload.message);
1016
+ // Return value is logged in execution history
1017
+ return { success: true };
1018
+ }
1019
+
1020
+ export async function onBackup({ api }) {
1021
+ try {
1022
+ await api.filesystem.copy('/source', '/backup');
1023
+ return { files: 100 };
1024
+ } catch (error) {
1025
+ throw error; // Triggers retry if maxRetries configured
1026
+ }
1027
+ }
1028
+ ```
1029
+
1030
+ ### 6.3 Managing Tasks
1031
+
1032
+ ```typescript
1033
+ // List tasks (current plugin only)
1034
+ const tasks = await api.scheduler.list();
1035
+ const pending = await api.scheduler.list({ status: 'pending' });
1036
+
1037
+ // Pagination
1038
+ const page1 = await api.scheduler.list({ limit: 20, offset: 0 });
1039
+ const totalCount = await api.scheduler.count({ status: 'pending' });
1040
+
1041
+ // Control tasks
1042
+ await api.scheduler.pause(taskId);
1043
+ await api.scheduler.resume(taskId);
1044
+ await api.scheduler.cancel(taskId);
1045
+
1046
+ // Batch operations
1047
+ await api.scheduler.deleteTasks([taskId1, taskId2, taskId3]);
1048
+
1049
+ // Cleanup old tasks (completed/failed/cancelled)
1050
+ await api.scheduler.cleanup(); // Default: 7 days ago
1051
+ await api.scheduler.cleanup(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days
1052
+
1053
+ // Query
1054
+ const task = await api.scheduler.get(taskId);
1055
+ const history = await api.scheduler.getExecutions(taskId, 10);
1056
+ ```
1057
+
1058
+ ### 6.4 Advanced Options
1059
+
1060
+ ```typescript
1061
+ await api.scheduler.schedule({
1062
+ name: 'Robust Task',
1063
+ type: 'once',
1064
+ time: Date.now() + 1000,
1065
+ callback: 'onTask',
1066
+ maxRetries: 3, // Retry on failure
1067
+ retryDelay: 30000, // Wait 30s between retries
1068
+ timeout: 60000, // Kill after 60s
1069
+ payload: { data: 'important' }
1070
+ });
1071
+
1072
+ // Repeat with limits
1073
+ await api.scheduler.schedule({
1074
+ name: 'Limited Repeat',
1075
+ type: 'repeat',
1076
+ cron: '0 0 * * * *',
1077
+ callback: 'onTask',
1078
+ endTime: Date.now() + 86400000, // Stop after 24h
1079
+ maxExecutions: 100 // Or after 100 runs
1080
+ });
1081
+ ```
1082
+
1083
+ ### 6.5 Cron Helpers
1084
+
1085
+ ```typescript
1086
+ // Validate expression
1087
+ api.scheduler.validateCron('0 0 2 * * *'); // true
1088
+
1089
+ // Get next run time
1090
+ api.scheduler.getNextCronTime('0 0 2 * * *'); // Date object
1091
+
1092
+ // Human-readable description
1093
+ api.scheduler.describeCron('0 0 2 * * *'); // "每天凌晨2点"
1094
+ ```
1095
+
1096
+ **Common Cron Examples**:
1097
+ - `'0 * * * * *'` - Every minute
1098
+ - `'0 0 * * * *'` - Every hour
1099
+ - `'0 0 2 * * *'` - Daily at 2 AM
1100
+ - `'0 0 9 * * 1-5'` - Weekdays at 9 AM
1101
+ - `'0 */30 * * * *'` - Every 30 minutes
1102
+
1103
+ ## 7. Host API - UI 调用后端方法
1104
+
1105
+ Host API 允许插件 UI 调用后端(main.ts)中导出的自定义方法。
1106
+
1107
+ ### 7.1 后端导出方法
1108
+
1109
+ 支持三种导出方式(按优先级查找):
1110
+
1111
+ ```typescript
1112
+ // 方式1:直接导出函数
1113
+ export async function quickAction(context: PluginContext, text: string) {
1114
+ context.api.notification.show(`处理: ${text}`)
1115
+ return { success: true }
1116
+ }
1117
+
1118
+ // 方式2:host 对象(推荐)
1119
+ export const host = {
1120
+ async processData(context: PluginContext, data: any) {
1121
+ const { notification, storage } = context.api
1122
+ notification.show('处理中...')
1123
+ await storage.set('lastResult', data)
1124
+ return { processed: true, result: data }
1125
+ }
1126
+ }
1127
+
1128
+ // 方式3:api/methods 等对象
1129
+ export const api = {
1130
+ async customMethod(context: PluginContext, params: any) {
1131
+ return { success: true, received: params }
1132
+ }
1133
+ }
1134
+ ```
1135
+
1136
+ **注意**:所有方法的第一个参数必须是 `context`,包含 `context.api`。
1137
+
1138
+ ### 7.2 UI 中调用
1139
+
1140
+ ```typescript
1141
+ import { useMulby } from './hooks/useMulby'
1142
+
1143
+ export default function App() {
1144
+ const { host, notification } = useMulby('my-plugin')
1145
+
1146
+ const handleClick = async () => {
1147
+ try {
1148
+ // 调用后端方法
1149
+ const result = await host.call('processData', { value: 123 })
1150
+ console.log(result.data) // { processed: true, result: {...} }
1151
+ notification.show('成功')
1152
+ } catch (err) {
1153
+ notification.show(`错误: ${err.message}`, 'error')
1154
+ }
1155
+ }
1156
+
1157
+ return <button onClick={handleClick}>处理数据</button>
1158
+ }
1159
+ ```
1160
+
1161
+ ### 7.3 与 host.invoke 的区别
1162
+
1163
+ - **host.call(method, ...args)** - 调用插件自定义方法(main.ts 中导出的)
1164
+ - **host.invoke(method, ...args)** - 调用主进程 API(如 clipboard.readText)