@isdk/tool-electron 1.0.0
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/README.cn.md +588 -0
- package/README.md +461 -0
- package/dist/index.d.mts +463 -0
- package/dist/index.d.ts +463 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/docs/README.md +465 -0
- package/docs/_media/README.cn.md +588 -0
- package/docs/classes/ElectronClientPubSubTransport.md +163 -0
- package/docs/classes/ElectronServerPubSubTransport.md +384 -0
- package/docs/classes/IpcClientToolTransport.md +416 -0
- package/docs/classes/IpcServerToolTransport.md +479 -0
- package/docs/globals.md +20 -0
- package/docs/type-aliases/Bridge.md +37 -0
- package/docs/type-aliases/Channels.md +27 -0
- package/docs/type-aliases/PubSubBridge.md +81 -0
- package/docs/type-aliases/ServerIpcMain.md +55 -0
- package/docs/type-aliases/ServerPubSubIpcMain.md +55 -0
- package/package.json +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# @isdk/tool-electron
|
|
2
|
+
|
|
3
|
+
> β¨ **Electron-native IPC Transport for the `ToolFunc` Framework**
|
|
4
|
+
> Build decoupled, type-safe, real-time Electron apps with RPC tools and Pub/Sub events over IPC.
|
|
5
|
+
|
|
6
|
+
[π¨π³ δΈζζζ‘£](./README.cn.md) | π English
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@isdk/tool-electron)
|
|
9
|
+
[](https://vitest.dev/)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @isdk/tool-electron
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Built on [`@isdk/tool-func`](https://github.com/isdk/ai-tools) β Define reusable, self-documenting functions.
|
|
18
|
+
|
|
19
|
+
## π Features
|
|
20
|
+
|
|
21
|
+
Designed to pair with `@isdk/tool-rpc` / `@isdk/tool-event`. Define your business logic once as tools, then call them from the renderer like local methods β no HTTP required.
|
|
22
|
+
|
|
23
|
+
* β
**Zero network overhead** β uses Electron IPC
|
|
24
|
+
* β
**RPC Tools over IPC** β Call server-defined functions from renderer
|
|
25
|
+
* β
**Real-time Event Bus** β Bidirectional Pub/Sub with auto session management
|
|
26
|
+
* β
**Unified error model** via `@isdk/common-error`
|
|
27
|
+
* β
**AbortSignal support** β cancel waiting on the client
|
|
28
|
+
* β
**Safe Preload Bridge** β Securely expose APIs via `contextBridge` with injectable `Bridge`, `ServerIpcMain`, `PubSubBridge`, and `ServerPubSubIpcMain` types
|
|
29
|
+
* β
**Dynamic Namespaces** β Run multiple isolated tool/event buses via URI scheme (`electron://my-app`)
|
|
30
|
+
* β
**URI Scheme Routing** β `apiUrl` follows URI convention; `electron://` routes to IPC, `http://` routes to HTTP; auto-registered with `RpcTransportManager`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## π Secure Bridge Pattern
|
|
35
|
+
|
|
36
|
+
When `contextIsolation` is enabled (the default since Electron 12), the renderer process has no direct access to Node.js or Electron APIs. The recommended approach is to:
|
|
37
|
+
|
|
38
|
+
1. **Preload** β Expose a minimal `invoke`-only bridge via `contextBridge`
|
|
39
|
+
2. **Main process** β Optionally inject a custom `ipcMain`-like object for testing or custom setups
|
|
40
|
+
3. **Renderer** β Create transport instances with the injected bridge
|
|
41
|
+
|
|
42
|
+
### 1. Main Process (Server)
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// main.ts
|
|
46
|
+
import { ServerTools } from '@isdk/tool-rpc';
|
|
47
|
+
import { EventServer } from '@isdk/tool-event';
|
|
48
|
+
import {
|
|
49
|
+
IpcServerToolTransport,
|
|
50
|
+
ElectronServerPubSubTransport,
|
|
51
|
+
} from '@isdk/tool-electron';
|
|
52
|
+
|
|
53
|
+
// Register a tool
|
|
54
|
+
ServerTools.register({
|
|
55
|
+
name: 'getUser',
|
|
56
|
+
func: async ({ id }: { id: string }) => ({ id, name: 'Alice' }),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Mount RPC with the URI 'electron://my-app'
|
|
60
|
+
const server = new IpcServerToolTransport({ apiUrl: 'electron://my-app' });
|
|
61
|
+
server.addDiscoveryHandler('electron://my-app', () => ServerTools.toJSON());
|
|
62
|
+
server.addRpcHandler('electron://my-app');
|
|
63
|
+
await server.start();
|
|
64
|
+
|
|
65
|
+
// Setup event bus
|
|
66
|
+
EventServer.setPubSubTransport(
|
|
67
|
+
new ElectronServerPubSubTransport('my-app-events'),
|
|
68
|
+
);
|
|
69
|
+
EventServer.get()?.register();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> **Testing tip:** Pass a mock `ipcMain` object via the `ipcMain` option:
|
|
73
|
+
>
|
|
74
|
+
> ```ts
|
|
75
|
+
> import { IpcServerToolTransport } from '@isdk/tool-electron';
|
|
76
|
+
> const server = new IpcServerToolTransport({
|
|
77
|
+
> apiUrl: 'electron://test',
|
|
78
|
+
> // Works with any test framework β wrap with vi.fn() / jest.fn() / sinon.spy() as needed
|
|
79
|
+
> ipcMain: { handle: () => {}, removeHandler: () => {} },
|
|
80
|
+
> });
|
|
81
|
+
> ```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 2. Preload Script (Secure Bridge)
|
|
86
|
+
|
|
87
|
+
The preload script is the **only** place with access to `ipcRenderer`. Expose only what's needed:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// preload.ts
|
|
91
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
92
|
+
|
|
93
|
+
// Expose a minimal invoke bridge to the renderer
|
|
94
|
+
export type ElectronIpcBridge = {
|
|
95
|
+
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
contextBridge.exposeInMainWorld('electronIpc', {
|
|
99
|
+
invoke: (channel: string, ...args: any[]) =>
|
|
100
|
+
ipcRenderer.invoke(channel, ...args),
|
|
101
|
+
} satisfies ElectronIpcBridge);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### 3. Renderer Process (Client)
|
|
107
|
+
|
|
108
|
+
In the renderer, create the transport using the bridge exposed by the preload:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// renderer.ts
|
|
112
|
+
import {
|
|
113
|
+
IpcClientToolTransport,
|
|
114
|
+
type Bridge,
|
|
115
|
+
} from '@isdk/tool-electron';
|
|
116
|
+
|
|
117
|
+
// The bridge exposed by preload.ts
|
|
118
|
+
const bridge: Bridge = (window as any).electronIpc;
|
|
119
|
+
|
|
120
|
+
// Create the transport with the injectable bridge
|
|
121
|
+
const transport = new IpcClientToolTransport('electron://my-app', { bridge });
|
|
122
|
+
|
|
123
|
+
// Mount and load tool definitions
|
|
124
|
+
await transport.loadApis();
|
|
125
|
+
|
|
126
|
+
// Call a remote tool
|
|
127
|
+
const result = await transport._fetch('getUser', { id: '42' });
|
|
128
|
+
console.log(result); // { id: '42', name: 'Alice' }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### β‘ Full Preload + Pub/Sub Example
|
|
134
|
+
|
|
135
|
+
Here's a complete end-to-end example showing both RPC and Pub/Sub with the injectable bridge types:
|
|
136
|
+
|
|
137
|
+
**main.ts (server)**
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { ServerTools } from '@isdk/tool-rpc';
|
|
141
|
+
import {
|
|
142
|
+
IpcServerToolTransport,
|
|
143
|
+
ElectronServerPubSubTransport,
|
|
144
|
+
} from '@isdk/tool-electron';
|
|
145
|
+
|
|
146
|
+
// Register a tool
|
|
147
|
+
ServerTools.register({
|
|
148
|
+
name: 'getUser',
|
|
149
|
+
func: async ({ id }: { id: string }) => ({ id, name: 'Alice' }),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// RPC transport
|
|
153
|
+
const rpcServer = new IpcServerToolTransport({ apiUrl: 'electron://my-app' });
|
|
154
|
+
rpcServer.addDiscoveryHandler('electron://my-app', () => ServerTools.toJSON());
|
|
155
|
+
rpcServer.addRpcHandler('electron://my-app');
|
|
156
|
+
await rpcServer.start();
|
|
157
|
+
|
|
158
|
+
// Pub/Sub transport
|
|
159
|
+
const pubSubServer = new ElectronServerPubSubTransport('electron://my-app-events');
|
|
160
|
+
pubSubServer.listen();
|
|
161
|
+
|
|
162
|
+
// π‘ Testing: inject a mock ipcMain via ServerPubSubIpcMain
|
|
163
|
+
// import type { ServerPubSubIpcMain } from '@isdk/tool-electron';
|
|
164
|
+
// const mockIpcMain: ServerPubSubIpcMain = {
|
|
165
|
+
// on: () => {},
|
|
166
|
+
// removeAllListeners: () => {},
|
|
167
|
+
// };
|
|
168
|
+
// const pubSubServer = new ElectronServerPubSubTransport('electron://test', { ipcMain: mockIpcMain });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**preload.ts**
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
175
|
+
|
|
176
|
+
const electronIpc = {
|
|
177
|
+
invoke: (ch: string, ...args: any[]) => ipcRenderer.invoke(ch, ...args),
|
|
178
|
+
on: (ch: string, listener: (event: any, ...args: any[]) => void) => {
|
|
179
|
+
ipcRenderer.on(ch, listener);
|
|
180
|
+
},
|
|
181
|
+
off: (ch: string, listener: (event: any, ...args: any[]) => void) => {
|
|
182
|
+
ipcRenderer.removeListener(ch, listener);
|
|
183
|
+
},
|
|
184
|
+
send: (ch: string, ...args: any[]) => ipcRenderer.send(ch, ...args),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
contextBridge.exposeInMainWorld('electronIpc', electronIpc);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**renderer.ts**
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import {
|
|
194
|
+
IpcClientToolTransport,
|
|
195
|
+
ElectronClientPubSubTransport,
|
|
196
|
+
type Bridge,
|
|
197
|
+
type PubSubBridge,
|
|
198
|
+
} from '@isdk/tool-electron';
|
|
199
|
+
|
|
200
|
+
const bridge: Bridge = (window as any).electronIpc;
|
|
201
|
+
const pubsubBridge: PubSubBridge = (window as any).electronIpc;
|
|
202
|
+
|
|
203
|
+
// Create RPC transport
|
|
204
|
+
const rpc = new IpcClientToolTransport('electron://my-app', { bridge });
|
|
205
|
+
await rpc.loadApis();
|
|
206
|
+
|
|
207
|
+
// Create Pub/Sub transport with the same bridge
|
|
208
|
+
const pubsub = new ElectronClientPubSubTransport('electron://my-app-events', {
|
|
209
|
+
bridge: pubsubBridge,
|
|
210
|
+
});
|
|
211
|
+
const stream = pubsub.connect('electron://my-app-events');
|
|
212
|
+
|
|
213
|
+
stream.on('server-event', (data) => {
|
|
214
|
+
console.log('Received:', data);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## π Quick Start (Simple Setup)
|
|
221
|
+
|
|
222
|
+
If you don't need `contextIsolation` or want a minimal setup:
|
|
223
|
+
|
|
224
|
+
### 1. Main Process
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// main.ts
|
|
228
|
+
import { ServerTools } from '@isdk/tool-rpc';
|
|
229
|
+
import {
|
|
230
|
+
IpcServerToolTransport,
|
|
231
|
+
} from '@isdk/tool-electron';
|
|
232
|
+
|
|
233
|
+
ServerTools.register({
|
|
234
|
+
name: 'greet',
|
|
235
|
+
func: async ({ name }: { name: string }) => `Hello, ${name}!`,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const server = new IpcServerToolTransport({ apiUrl: 'electron://my-app' });
|
|
239
|
+
server.addDiscoveryHandler('electron://my-app', () => ServerTools.toJSON());
|
|
240
|
+
server.addRpcHandler('electron://my-app');
|
|
241
|
+
await server.start();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 2. Preload Script
|
|
245
|
+
|
|
246
|
+
Use the same preload script as the [Secure Bridge Pattern](#2-preload-script-secure-bridge) above:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
// preload.ts β same as the Secure Bridge Pattern example
|
|
250
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
251
|
+
|
|
252
|
+
contextBridge.exposeInMainWorld('electronIpc', {
|
|
253
|
+
invoke: (ch: string, ...args: any[]) => ipcRenderer.invoke(ch, ...args),
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 3. Renderer Process
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// renderer.ts
|
|
261
|
+
import { IpcClientToolTransport } from '@isdk/tool-electron';
|
|
262
|
+
|
|
263
|
+
const transport = new IpcClientToolTransport('electron://my-app', {
|
|
264
|
+
bridge: (window as any).electronIpc,
|
|
265
|
+
});
|
|
266
|
+
const defs = await transport.loadApis();
|
|
267
|
+
const result = await transport._fetch('greet', { name: 'World' });
|
|
268
|
+
console.log(result); // "Hello, World!"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## π Architecture
|
|
274
|
+
|
|
275
|
+
```mermaid
|
|
276
|
+
graph LR
|
|
277
|
+
subgraph "Main Process"
|
|
278
|
+
A[ServerTools] --> B[IpcServer]
|
|
279
|
+
C[EventServer] --> D[PubSub Server]
|
|
280
|
+
B -->|ipcMain.handle| E[(IPC Channel)]
|
|
281
|
+
D -->|ipcMain.on/send| E
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
subgraph "Preload (contextBridge)"
|
|
285
|
+
P["electronIpc.invoke"] -->|ipcRenderer.invoke| E
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
subgraph "Renderer Process"
|
|
289
|
+
F[IpcClientToolTransport] -->|bridge.invoke| P
|
|
290
|
+
G[ElectronClientPubSubTransport] -->|ipcRenderer.on/send| E
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The preload script acts as a security boundary β the renderer never imports `electron` directly.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## βοΈ Advanced
|
|
299
|
+
|
|
300
|
+
### Custom ServerIpcMain (Testing / Custom IPC)
|
|
301
|
+
|
|
302
|
+
Inject a mock or custom `ipcMain`-like object for testing:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { IpcServerToolTransport, type ServerIpcMain } from '@isdk/tool-electron';
|
|
306
|
+
|
|
307
|
+
// Plain no-op mocks β wrap with vitest/jest/sinon spies for assertions
|
|
308
|
+
const mockIpcMain: ServerIpcMain = {
|
|
309
|
+
handle: (_channel, _handler) => {},
|
|
310
|
+
removeHandler: (_channel) => {},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const server = new IpcServerToolTransport({
|
|
314
|
+
apiUrl: 'electron://test',
|
|
315
|
+
ipcMain: mockIpcMain,
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Error Handling
|
|
320
|
+
|
|
321
|
+
`_fetch` throws a `CommonError` directly when the server returns an error response. No need to call `toObject()` first β errors are thrown immediately:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { CommonError } from '@isdk/common-error';
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = await transport._fetch('getUser', { id: 'invalid' });
|
|
328
|
+
} catch (err: any) {
|
|
329
|
+
if (err instanceof CommonError) {
|
|
330
|
+
console.error(`Error [${err.code}]: ${err.message}`, err.data);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
> **Note:** The `toObject()` method is still available for backward compatibility, but when used on a successful response it simply passes the data through. For new code, just use `await transport._fetch(...)` directly.
|
|
336
|
+
|
|
337
|
+
### Timeout & Cancellation
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
const ctrl = new AbortController();
|
|
341
|
+
setTimeout(() => ctrl.abort(), 5000);
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const result = await transport._fetch('slowTool', params, undefined, undefined, {
|
|
345
|
+
signal: ctrl.signal,
|
|
346
|
+
});
|
|
347
|
+
} catch (err: any) {
|
|
348
|
+
if (err.name === 'AbortError') {
|
|
349
|
+
console.log('Cancelled or timed out');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Dynamic Namespaces
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// Switch to a different namespace at runtime
|
|
358
|
+
transport.setApiUrl('electron://my-app-v2');
|
|
359
|
+
await transport.loadApis();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## π¦ API
|
|
365
|
+
|
|
366
|
+
### API URL Convention
|
|
367
|
+
|
|
368
|
+
All IPC transports use URI-formatted `apiUrl` values with the `electron://` scheme:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// Server: options object
|
|
372
|
+
const server = new IpcServerToolTransport({ apiUrl: 'electron://my-app' });
|
|
373
|
+
|
|
374
|
+
// Client: first argument
|
|
375
|
+
const client = new IpcClientToolTransport('electron://my-app', { bridge });
|
|
376
|
+
|
|
377
|
+
// PubSub: constructor argument
|
|
378
|
+
const pubsub = new ElectronServerPubSubTransport('electron://my-app');
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
The `electron` scheme is auto-registered with `RpcTransportManager` on import,
|
|
382
|
+
so `RpcTransportManager.instance.getClient('electron://my-app')` works automatically.
|
|
383
|
+
The host component is extracted as the IPC channel namespace:
|
|
384
|
+
`'electron://my-app'` β channels `my-app:discover`, `pubsub-downstream:my-app`, etc.
|
|
385
|
+
|
|
386
|
+
### Exported Types (Bridge Pattern)
|
|
387
|
+
|
|
388
|
+
| Type | Description | Methods | Used By |
|
|
389
|
+
|------|-------------|---------|---------|
|
|
390
|
+
| `Bridge` | Client-side RPC bridge. Inject into `IpcClientToolTransport`. | `invoke(channel, ...args): Promise<any>` | `IpcClientToolTransport` |
|
|
391
|
+
| `ServerIpcMain` | Server-side RPC bridge. Inject into `IpcServerToolTransport`. | `handle(channel, handler)`, `removeHandler(channel)` | `IpcServerToolTransport` |
|
|
392
|
+
| `PubSubBridge` | Client-side Pub/Sub bridge. Inject into `ElectronClientPubSubTransport`. | `on(channel, listener)`, `off(channel, listener)`, `send(channel, ...args)` | `ElectronClientPubSubTransport` |
|
|
393
|
+
| `ServerPubSubIpcMain` | Server-side Pub/Sub bridge. Inject into `ElectronServerPubSubTransport`. | `on(channel, listener)`, `removeAllListeners(channel?)` | `ElectronServerPubSubTransport` |
|
|
394
|
+
|
|
395
|
+
All four types are exported from the package root:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import {
|
|
399
|
+
type Bridge,
|
|
400
|
+
type ServerIpcMain,
|
|
401
|
+
type PubSubBridge,
|
|
402
|
+
type ServerPubSubIpcMain,
|
|
403
|
+
} from '@isdk/tool-electron';
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Exported Classes
|
|
407
|
+
|
|
408
|
+
| Class | Process | Purpose |
|
|
409
|
+
|-------|---------|---------|
|
|
410
|
+
| `IpcClientToolTransport` | Renderer | RPC transport for calling server tools |
|
|
411
|
+
| `IpcServerToolTransport` | Main | RPC transport for registering and serving tools |
|
|
412
|
+
| `ElectronClientPubSubTransport` | Renderer | Pub/Sub transport for receiving/sending events |
|
|
413
|
+
| `ElectronServerPubSubTransport` | Main | Pub/Sub transport for broadcasting events |
|
|
414
|
+
| `ServerTools` | Main | Tool registry (re-exported from `@isdk/tool-rpc`) |
|
|
415
|
+
| `EventServer` | Main | Event server (re-exported from `@isdk/tool-event`) |
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## π§ͺ Testing
|
|
420
|
+
|
|
421
|
+
Run unit tests with mocked Electron IPC:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
npm test # run once
|
|
425
|
+
npm run test:watch # dev mode
|
|
426
|
+
npm run coverage # generate report
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Mocks: `test/mocks/electron.ts`
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## π Docs
|
|
434
|
+
|
|
435
|
+
- [ToolFunc Core](https://github.com/isdk/ai-tools/tree/main/packages/tool-func)
|
|
436
|
+
- [RPC Transports Guide](https://github.com/isdk/ai-tools/tree/main/packages/tool-rpc)
|
|
437
|
+
- [Events Guide](https://github.com/isdk/ai-tools/tree/main/packages/tool-event)
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## π€ Contributing
|
|
442
|
+
|
|
443
|
+
We β€οΈ contributions!
|
|
444
|
+
|
|
445
|
+
1. Fork β `git clone`
|
|
446
|
+
2. Create branch β `git checkout -b feat/your-feature`
|
|
447
|
+
3. Commit β `git commit -m 'feat: add XYZ'`
|
|
448
|
+
4. Push β `git push origin feat/your-feature`
|
|
449
|
+
5. Open PR π
|
|
450
|
+
|
|
451
|
+
Please ensure tests pass and types are clean.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## π License
|
|
456
|
+
|
|
457
|
+
MIT Β© [ISDK](https://github.com/isdk) β See [LICENSE](LICENSE)
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
> π‘ **Pro Tip**: Use `EventServer.forward([...events])` to auto-relay global events to all connected clients!
|