@notehub.md/cli 0.1.6

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.
@@ -0,0 +1,599 @@
1
+ # 🔌 Notehub Plugin Developer Guide
2
+
3
+ > Complete documentation for creating plugins for Notehub.md
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Getting Started](#getting-started)
10
+ 2. [Plugin Architecture](#plugin-architecture)
11
+ 3. [API Reference](#api-reference)
12
+ 4. [Widgets (Portals)](#widgets-portals)
13
+ 5. [Settings Integration](#settings-integration)
14
+ 6. [Context Menu](#context-menu)
15
+ 7. [Plugin Examples](#plugin-examples)
16
+
17
+ ---
18
+
19
+ # Getting Started
20
+
21
+ This guide will walk you through creating your first Notehub.md plugin.
22
+
23
+ ## Prerequisites
24
+
25
+ - **Node.js** v18+ with npm/pnpm
26
+ - **TypeScript** (recommended, but JavaScript works too)
27
+ - **Bundler**: esbuild, Vite, or Rollup
28
+ - A Notehub.md vault to test in
29
+
30
+ ## Plugin Structure
31
+
32
+ Every plugin needs at minimum:
33
+
34
+ ```
35
+ my-plugin/
36
+ ├── manifest.json # Plugin metadata (required)
37
+ ├── main.js # Entry point (compiled)
38
+ └── src/ # Source files (optional)
39
+ └── index.ts
40
+ ```
41
+
42
+ ## manifest.json
43
+
44
+ The manifest describes your plugin to Notehub:
45
+
46
+ ```json
47
+ {
48
+ "id": "my-awesome-plugin",
49
+ "name": "My Awesome Plugin",
50
+ "version": "1.0.0",
51
+ "main": "main.js",
52
+ "dependencies": []
53
+ }
54
+ ```
55
+
56
+ | Field | Required | Description |
57
+ |-------|----------|-------------|
58
+ | `id` | ✅ | Unique identifier (lowercase, hyphens allowed) |
59
+ | `name` | ✅ | Human-readable display name |
60
+ | `version` | ✅ | Semantic version (e.g., `1.0.0`) |
61
+ | `main` | ❌ | Entry point file, defaults to `main.js` |
62
+ | `dependencies` | ❌ | Array of internal plugin IDs this plugin requires |
63
+
64
+ ---
65
+
66
+ ## Quick Start with CLI
67
+
68
+ The fastest way to create a new plugin:
69
+
70
+ ```bash
71
+ npx @notehub.md/cli create ext.my-plugin --name "My Plugin"
72
+ ```
73
+
74
+ This generates the complete plugin structure with all necessary files.
75
+
76
+ ## Manual Setup
77
+
78
+ ### Step 1: Create the folder structure
79
+
80
+ ```bash
81
+ mkdir my-plugin
82
+ cd my-plugin
83
+ npm init -y
84
+ npm install @notehub/api typescript esbuild --save-dev
85
+ ```
86
+
87
+ ### Step 2: Create src/index.ts
88
+
89
+ ```typescript
90
+ import { NotehubPlugin, PluginContext } from '@notehub/api';
91
+
92
+ export default class HelloWorldPlugin extends NotehubPlugin {
93
+ async onload(ctx: PluginContext): Promise<void> {
94
+ await ctx.invokeApi('logger:info', 'HelloWorld', 'Hello from my plugin!');
95
+
96
+ ctx.registerApi('hello:say', (message: string) => {
97
+ console.log(`[HelloWorld] ${message}`);
98
+ });
99
+
100
+ ctx.subscribe<{ path: string }>('explorer:file-selected', (payload) => {
101
+ console.log('File selected:', payload.path);
102
+ });
103
+ }
104
+
105
+ async onunload(): Promise<void> {
106
+ console.log('HelloWorld plugin unloaded');
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Step 3: Build and Install
112
+
113
+ ```bash
114
+ npm run build
115
+ ```
116
+
117
+ Copy to your vault:
118
+ ```
119
+ MyVault/.notehub/plugins/hello-world/
120
+ ├── manifest.json
121
+ └── main.js
122
+ ```
123
+
124
+ ## Hot Reload
125
+
126
+ Notehub watches the `.notehub/plugins/` directory. When you update your plugin:
127
+
128
+ 1. The old version is automatically unloaded
129
+ 2. The new version is loaded
130
+ 3. All your API registrations are cleaned up automatically!
131
+
132
+ ---
133
+
134
+ # Plugin Architecture
135
+
136
+ ## Microkernel Architecture
137
+
138
+ Notehub.md follows a **microkernel** design where the core is minimal and all functionality comes from plugins:
139
+
140
+ ```
141
+ ┌─────────────────────────────────────────────────────────────┐
142
+ │ NotehubCore │
143
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
144
+ │ │ EventBus │ │ ApiBus │ │ Plugin Registry │ │
145
+ │ │ (pub/sub) │ │ (RPC calls) │ │ (lifecycle mgmt) │ │
146
+ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │
147
+ └─────────────────────────────────────────────────────────────┘
148
+ ▲ ▲ ▲
149
+ │ │ │
150
+ ┌────┴───┐ ┌─────┴────┐ ┌─────┴────┐
151
+ │ Logger │ │ Editor │ │ Explorer │
152
+ │ Plugin │ │ Plugin │ │ Plugin │
153
+ └────────┘ └──────────┘ └──────────┘
154
+ ```
155
+
156
+ ## PluginContext
157
+
158
+ Your gateway to the Notehub ecosystem:
159
+
160
+ ```typescript
161
+ interface PluginContext {
162
+ registerApi(name: string, handler: (...args: unknown[]) => unknown): void;
163
+ invokeApi<T>(name: string, ...args: unknown[]): Promise<T>;
164
+ subscribe<T>(event: string, handler: (payload: T) => void): void;
165
+ }
166
+ ```
167
+
168
+ ### Auto-Cleanup Magic
169
+
170
+ When your plugin is unloaded, `PluginContext` automatically:
171
+
172
+ - ✅ Unregisters all APIs you registered
173
+ - ✅ Unsubscribes from all events
174
+ - ✅ Removes editor widgets you registered
175
+ - ✅ Removes settings tabs/groups/items you added
176
+
177
+ ---
178
+
179
+ # API Reference
180
+
181
+ ## Logger API
182
+
183
+ ```typescript
184
+ await ctx.invokeApi('logger:info', 'MyPlugin', 'Operation completed');
185
+ await ctx.invokeApi('logger:warn', 'MyPlugin', 'Config not found');
186
+ await ctx.invokeApi('logger:error', 'MyPlugin', 'Failed to load');
187
+ ```
188
+
189
+ ## Config API (Persistent)
190
+
191
+ ```typescript
192
+ // Get value
193
+ const fontSize = await ctx.invokeApi<number>('config:get', 'editor.font-size', 14);
194
+
195
+ // Set value (auto-saved)
196
+ await ctx.invokeApi('config:set', 'my-plugin.option', true);
197
+
198
+ // Delete
199
+ await ctx.invokeApi('config:delete', 'my-plugin.option');
200
+ ```
201
+
202
+ ## State API (Runtime)
203
+
204
+ ```typescript
205
+ await ctx.invokeApi('state:set', 'my-plugin.cache', { data: [...] });
206
+ const cache = await ctx.invokeApi<MyData>('state:get', 'my-plugin.cache');
207
+ await ctx.invokeApi('state:delete', 'my-plugin.cache');
208
+ ```
209
+
210
+ ## Filesystem API
211
+
212
+ ```typescript
213
+ // Read file
214
+ const content = await ctx.invokeApi<string>('fs:read-text-file', '/path/to/file.md');
215
+
216
+ // Write file
217
+ await ctx.invokeApi('fs:write-text-file', '/path/to/file.md', '# Hello World');
218
+
219
+ // Check existence
220
+ const exists = await ctx.invokeApi<boolean>('fs:exists', '/path/to/file.md');
221
+
222
+ // Read directory
223
+ const entries = await ctx.invokeApi<DirEntry[]>('fs:read-dir', '/path/to/folder');
224
+
225
+ // Create directory
226
+ await ctx.invokeApi('fs:create-dir', '/path/to/new-folder', { recursive: true });
227
+
228
+ // Delete file
229
+ await ctx.invokeApi('fs:remove-file', '/path/to/file.md');
230
+
231
+ // Rename/move
232
+ await ctx.invokeApi('fs:rename', '/old/path.md', '/new/path.md');
233
+
234
+ // Watch for changes
235
+ const unwatch = await ctx.invokeApi<() => void>(
236
+ 'fs:watch',
237
+ '/path/to/folder',
238
+ (event) => console.log('Change:', event)
239
+ );
240
+ ```
241
+
242
+ ## Dialog API
243
+
244
+ ```typescript
245
+ // Alert
246
+ await ctx.invokeApi('dialog:alert', 'Warning', 'File has been deleted');
247
+
248
+ // Confirm
249
+ const confirmed = await ctx.invokeApi<boolean>(
250
+ 'dialog:confirm', 'Delete File', 'Are you sure?'
251
+ );
252
+
253
+ // Prompt
254
+ const name = await ctx.invokeApi<string | null>(
255
+ 'dialog:prompt', 'Rename File', 'Enter new name:', 'default.md'
256
+ );
257
+ ```
258
+
259
+ ## Theme API
260
+
261
+ ```typescript
262
+ // Register theme
263
+ await ctx.invokeApi('theme:register', 'my-theme', {
264
+ 'bg-main': '#1a1a2e',
265
+ 'accent-primary': '#e94560',
266
+ // ...
267
+ });
268
+
269
+ // Apply theme
270
+ await ctx.invokeApi('theme:set', 'my-theme');
271
+
272
+ // Get current
273
+ const theme = await ctx.invokeApi<string>('theme:get-current');
274
+
275
+ // List all
276
+ const themes = await ctx.invokeApi<string[]>('theme:list');
277
+ ```
278
+
279
+ ## Editor Widget API
280
+
281
+ ```typescript
282
+ // Register widget
283
+ await ctx.invokeApi(
284
+ 'editor:register-widget',
285
+ 'my-plugin:progress-bar',
286
+ /\[progress:(\d+)\]/g,
287
+ ProgressBarComponent
288
+ );
289
+
290
+ // Unregister (optional - auto-cleaned)
291
+ await ctx.invokeApi('editor:unregister-widget', 'my-plugin:progress-bar');
292
+ ```
293
+
294
+ ## Settings API
295
+
296
+ ```typescript
297
+ // Register tab
298
+ await ctx.invokeApi('settings:register-tab', {
299
+ id: 'my-plugin', label: 'My Plugin', icon: 'puzzle', order: 100
300
+ });
301
+
302
+ // Register group
303
+ await ctx.invokeApi('settings:register-group', {
304
+ id: 'my-group', tabId: 'my-plugin', label: 'General', order: 0
305
+ });
306
+
307
+ // Register item
308
+ await ctx.invokeApi('settings:register-item', {
309
+ key: 'my-plugin.enabled',
310
+ type: 'toggle', // 'toggle' | 'text' | 'number' | 'select' | 'color'
311
+ label: 'Enable plugin',
312
+ groupId: 'my-group',
313
+ order: 0,
314
+ defaultValue: true
315
+ });
316
+
317
+ // Open/close settings
318
+ await ctx.invokeApi('settings:open');
319
+ await ctx.invokeApi('settings:close');
320
+ ```
321
+
322
+ ## Context Menu API
323
+
324
+ ```typescript
325
+ await ctx.invokeApi(
326
+ 'context-menu:register',
327
+ 'explorer-item',
328
+ (payload: { path: string }) => [
329
+ {
330
+ type: 'action',
331
+ id: 'my-action',
332
+ label: 'My Action',
333
+ icon: 'star',
334
+ onClick: () => console.log('Clicked:', payload.path)
335
+ },
336
+ { type: 'separator' },
337
+ {
338
+ type: 'submenu',
339
+ label: 'More Options',
340
+ items: [/* ... */]
341
+ }
342
+ ]
343
+ );
344
+ ```
345
+
346
+ ## Shell API
347
+
348
+ ```typescript
349
+ await ctx.invokeApi('shell:open', 'https://notehub.md');
350
+ ```
351
+
352
+ ## Vault API
353
+
354
+ ```typescript
355
+ await ctx.invokeApi('vault:close');
356
+ ```
357
+
358
+ ---
359
+
360
+ # Widgets (Portals)
361
+
362
+ Portals are custom React components that render inline within the editor.
363
+
364
+ ## How Portals Work
365
+
366
+ 1. You define a **regex pattern** that matches text in the document
367
+ 2. You provide a **React component** to render for each match
368
+ 3. Notehub replaces matched text with your component in **view mode**
369
+ 4. When the cursor enters the match, it switches to **edit mode**
370
+
371
+ ## Complete Example: Progress Bar
372
+
373
+ ```typescript
374
+ import { NotehubPlugin, PluginContext } from '@notehub/api';
375
+ import React from 'react';
376
+
377
+ const ProgressBar: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
378
+ const percentage = parseInt(match[1], 10);
379
+
380
+ return (
381
+ <span style={{
382
+ display: 'inline-flex',
383
+ alignItems: 'center',
384
+ gap: '8px',
385
+ padding: '2px 8px',
386
+ background: 'var(--nh-bg-surface)',
387
+ borderRadius: '4px',
388
+ }}>
389
+ <span style={{
390
+ width: '100px',
391
+ height: '8px',
392
+ background: 'var(--nh-bg-secondary)',
393
+ borderRadius: '4px',
394
+ overflow: 'hidden',
395
+ }}>
396
+ <span style={{
397
+ width: `${percentage}%`,
398
+ height: '100%',
399
+ background: 'var(--nh-accent-primary)',
400
+ display: 'block',
401
+ }} />
402
+ </span>
403
+ <span style={{ fontSize: '12px', color: 'var(--nh-text-muted)' }}>
404
+ {percentage}%
405
+ </span>
406
+ </span>
407
+ );
408
+ };
409
+
410
+ export default class ProgressBarPlugin extends NotehubPlugin {
411
+ async onload(ctx: PluginContext): Promise<void> {
412
+ await ctx.invokeApi(
413
+ 'editor:register-widget',
414
+ 'progress-bar',
415
+ /\[progress:(\d+)\]/g,
416
+ ProgressBar
417
+ );
418
+ }
419
+
420
+ async onunload(): Promise<void> {}
421
+ }
422
+ ```
423
+
424
+ **Usage in documents:**
425
+ ```markdown
426
+ Project completion: [progress:75]
427
+ ```
428
+
429
+ ---
430
+
431
+ # Settings Integration
432
+
433
+ ## Structure
434
+
435
+ ```
436
+ Settings Modal
437
+ └── Tab (e.g., "My Plugin")
438
+ └── Group (e.g., "Appearance")
439
+ └── Item (e.g., "Enable dark mode")
440
+ ```
441
+
442
+ ## Item Types
443
+
444
+ | Type | Description |
445
+ |------|-------------|
446
+ | `toggle` | Boolean switch |
447
+ | `text` | Text input |
448
+ | `number` | Number input with min/max/step |
449
+ | `select` | Dropdown with options |
450
+ | `color` | Color picker |
451
+
452
+ ## Reading Settings
453
+
454
+ ```typescript
455
+ const isEnabled = await ctx.invokeApi<boolean>('config:get', 'my-plugin.enabled', true);
456
+ const maxItems = await ctx.invokeApi<number>('config:get', 'my-plugin.max-items', 10);
457
+ ```
458
+
459
+ ---
460
+
461
+ # Context Menu
462
+
463
+ ## Menu Item Types
464
+
465
+ ### Action
466
+
467
+ ```typescript
468
+ {
469
+ type: 'action',
470
+ id: 'my-action',
471
+ label: 'My Action',
472
+ icon: 'star',
473
+ onClick: (payload) => { /* handle */ }
474
+ }
475
+ ```
476
+
477
+ ### Separator
478
+
479
+ ```typescript
480
+ { type: 'separator' }
481
+ ```
482
+
483
+ ### Submenu
484
+
485
+ ```typescript
486
+ {
487
+ type: 'submenu',
488
+ label: 'More Options',
489
+ items: [/* nested items */]
490
+ }
491
+ ```
492
+
493
+ ---
494
+
495
+ # Plugin Examples
496
+
497
+ ## Hello World
498
+
499
+ ```typescript
500
+ import { NotehubPlugin, PluginContext } from '@notehub/api';
501
+
502
+ export default class HelloWorld extends NotehubPlugin {
503
+ async onload(ctx: PluginContext): Promise<void> {
504
+ await ctx.invokeApi('logger:info', 'HelloWorld', '👋 Hello!');
505
+
506
+ ctx.registerApi('hello:greet', (name: string) => {
507
+ return `Hello, ${name}!`;
508
+ });
509
+ }
510
+
511
+ async onunload(): Promise<void> {}
512
+ }
513
+ ```
514
+
515
+ ## Word Counter Widget
516
+
517
+ ```typescript
518
+ const WordCounter: React.FC<{ match: RegExpExecArray }> = ({ match }) => {
519
+ const text = match[1];
520
+ const words = text.trim().split(/\s+/).filter(w => w.length > 0).length;
521
+
522
+ return <span>📝 {words} words</span>;
523
+ };
524
+
525
+ // Register with pattern: {{count: your text here}}
526
+ await ctx.invokeApi(
527
+ 'editor:register-widget',
528
+ 'word-counter',
529
+ /\{\{count:\s*(.+?)\}\}/g,
530
+ WordCounter
531
+ );
532
+ ```
533
+
534
+ ## Full-Featured Plugin
535
+
536
+ See the [examples documentation](https://github.com/khton-tech/notehub.md/tree/main/docs/forPluginMakers/en/07-examples.md) for complete plugin code combining widgets, settings, and context menus.
537
+
538
+ ---
539
+
540
+ ## Build Configuration
541
+
542
+ ### package.json
543
+
544
+ ```json
545
+ {
546
+ "scripts": {
547
+ "build": "nhp build"
548
+ },
549
+ "devDependencies": {
550
+ "@notehub/api": "workspace:*",
551
+ "@types/react": "^18.3.0",
552
+ "typescript": "^5.6.0"
553
+ },
554
+ "peerDependencies": {
555
+ "react": "^18.3.0"
556
+ }
557
+ }
558
+ ```
559
+
560
+ ### External Dependencies
561
+
562
+ These packages are provided by Notehub - mark them as **external**:
563
+
564
+ - `@notehub/api`
565
+ - `react`
566
+ - `react-dom`
567
+
568
+ ---
569
+
570
+ ## Debugging Tips
571
+
572
+ 1. **Open DevTools** (Ctrl+Shift+I) to see console logs
573
+ 2. **Use `logger:info`** API for structured logging
574
+ 3. **Check the Synapse plugin** logs for load/unload events
575
+
576
+ ---
577
+
578
+ ## CSS Variables
579
+
580
+ Use these for consistent styling:
581
+
582
+ - `--nh-bg-main`, `--nh-bg-sidebar`, `--nh-bg-surface`
583
+ - `--nh-text-primary`, `--nh-text-secondary`, `--nh-text-muted`
584
+ - `--nh-accent-primary`, `--nh-accent-secondary`
585
+ - `--nh-border-accent`, `--nh-border-subtle`
586
+
587
+ ---
588
+
589
+ ## Resources
590
+
591
+ - [GitHub Repository](https://github.com/khton-tech/notehub.md)
592
+ - [API Package](https://github.com/khton-tech/notehub.md/tree/main/packages/api)
593
+ - [Example Plugins](https://github.com/khton-tech/notehub.md/tree/main/packages/plugins)
594
+
595
+ ---
596
+
597
+ <p align="center">
598
+ <strong>Happy coding! 🎉</strong>
599
+ </p>