@abraca/plugin 2.3.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.
@@ -0,0 +1,642 @@
1
+ //#region packages/plugin/src/types.d.ts
2
+ /**
3
+ * Shared plugin contract for cou-shell and `@abraca/nuxt`.
4
+ *
5
+ * `AbraPlugin` is the universal interface every plugin satisfies. App-specific
6
+ * supersets (`CouPlugin`, `AbracadabraPlugin`) extend or intersect this with
7
+ * tightened contribution shapes — TipTap toolbar items, Nuxt UI dropdown
8
+ * items, page-type renderers, file importers, etc.
9
+ *
10
+ * Zero peer-dep types. `Y.Doc`, `@tiptap/core`'s `Extension`, and Vue's
11
+ * `Component` are all referenced via local structural markers (`AbraYDoc`,
12
+ * `AbraTiptapExtension`, `unknown`) so this package compiles in isolation
13
+ * and can be consumed by hosts on any TipTap / yjs / Vue version without
14
+ * dragging duplicate-type fights along.
15
+ */
16
+ /**
17
+ * Structural marker for `yjs`'s `Y.Doc`. Defined locally — and intentionally
18
+ * permissive — so the plugin contract doesn't pin a specific yjs version. Any
19
+ * `Y.Doc` from any minor yjs version (13.6.29, 13.6.30, …) is structurally
20
+ * compatible. Consumers cast back to their own `Y.Doc` as needed.
21
+ */
22
+ interface AbraYDoc {
23
+ readonly clientID?: number;
24
+ }
25
+ /**
26
+ * Structural marker for `@tiptap/core`'s `Extension`. Defined locally — and
27
+ * intentionally permissive — so the plugin contract doesn't pin a specific
28
+ * TipTap version. Any `Extension` from any minor TipTap version (3.22.5,
29
+ * 3.23.2, …) is structurally compatible. Consumers cast back to their own
30
+ * `Extension` as needed.
31
+ */
32
+ interface AbraTiptapExtension {
33
+ readonly name?: string;
34
+ readonly type?: string;
35
+ }
36
+ /**
37
+ * The universal plugin shape. Every plugin in the Abracadabra ecosystem
38
+ * (built-in or third-party, in cou-shell or any `@abraca/nuxt` consumer)
39
+ * satisfies this interface.
40
+ *
41
+ * Contribution fields use widened shapes (`unknown`, `Record<string, unknown>`)
42
+ * where the concrete type is consumer-defined. App-level plugin aliases
43
+ * (e.g. `CouPlugin`, `AbracadabraPlugin`) narrow them.
44
+ */
45
+ interface AbraPlugin {
46
+ /** Unique slug. Must be stable across versions, e.g. `core`, `kanban`, `my-plugin`. */
47
+ name: string;
48
+ /** Human-readable label shown in plugin browsers. Falls back to `name`. */
49
+ label?: string;
50
+ /** Plugin version. Should follow semver. */
51
+ version?: string;
52
+ /** One-line plugin description shown in browsers and scorecards. */
53
+ description?: string;
54
+ /**
55
+ * Whether a built-in plugin is enabled by default. `true` if omitted.
56
+ * Only consulted for plugins that ship as built-ins; external plugins
57
+ * are opt-in regardless.
58
+ */
59
+ defaultEnabled?: boolean;
60
+ /** TipTap extensions (nodes, marks, extensions) contributed by this plugin. */
61
+ extensions?: () => AbraTiptapExtension[];
62
+ /**
63
+ * Optional promise that resolves when this plugin's extensions are fully
64
+ * loaded. Used by `PluginRegistry.waitForExtensions()` so the editor
65
+ * doesn't initialise before async extension bundles are ready.
66
+ */
67
+ extensionsReady?: Promise<void>;
68
+ /** Page-type renderers keyed by type slug. Concrete shape is consumer-defined. */
69
+ pageTypes?: Record<string, unknown>;
70
+ /**
71
+ * Custom editor handlers (e.g. `table`, `fileUpload`). Each entry merges
72
+ * into the host's handler map. Handler signatures are consumer-defined —
73
+ * cou-shell types them as `EditorHandler` from `@nuxt/ui` (which uses
74
+ * specific arg counts that wouldn't match `(...args: any[]) => any`), the
75
+ * module uses a different shape; the shared contract just promises a
76
+ * record of any values.
77
+ */
78
+ customHandlers?: () => Record<string, any>;
79
+ /**
80
+ * Editor toolbar / menu / slash-command / drag-handle contributions.
81
+ *
82
+ * The context shape and item-group shape are both consumer-defined. Method
83
+ * shorthand syntax is used deliberately — interface-method positions are
84
+ * bivariant in TypeScript, so app-specific aliases (`AbracadabraPlugin`,
85
+ * `CouPlugin`) can narrow `ctx` to their own tightened context types
86
+ * without `Omit`-and-rewrite gymnastics.
87
+ */
88
+ toolbarItems?(ctx: EditorPluginCtx): readonly (readonly unknown[])[];
89
+ bubbleMenuItems?(ctx: EditorPluginCtx): readonly (readonly unknown[])[];
90
+ suggestionItems?(ctx: EditorPluginCtx): readonly (readonly unknown[])[];
91
+ dragHandleItems?(ctx: DragHandlePluginCtx): readonly (readonly unknown[])[];
92
+ /** Mention (@) providers contributing items to the mention popup. */
93
+ mentionProviders?: AbraMentionProvider[];
94
+ /** Awareness field declarations — keys this plugin reads/writes on awareness. */
95
+ awarenessContributions?: AbraAwarenessContribution[];
96
+ /** Global command-palette (⌘K) items. */
97
+ commandPaletteItems?(ctx: CommandPaletteCtx): readonly AbraCommandItem[] | Promise<readonly AbraCommandItem[]>;
98
+ /** Extra tabs rendered in the child-document slideover panel. */
99
+ nodePanelSlots?: AbraNodePanelSlot[];
100
+ /** Settings panel contributed to the global settings view. */
101
+ settingsPanel?: AbraSettingsPanel;
102
+ /** Global keyboard shortcuts (document-level, not inside TipTap scope). */
103
+ keyboardShortcuts?: AbraKeyboardShortcut[];
104
+ /**
105
+ * Client-side setup hook — called once after the client + provider are
106
+ * ready. May return a teardown function called on app unmount.
107
+ */
108
+ clientSetup?(ctx: ClientPluginCtx): undefined | void | (() => void) | Promise<undefined | void | (() => void)>;
109
+ /** TipTap extensions safe for server-side Y.Doc → HTML rendering (no Vue NodeViews). */
110
+ serverExtensions?: () => AbraTiptapExtension[];
111
+ /**
112
+ * Custom cache renderer for non-doc page types. Receives the Y.Doc and
113
+ * returns serialisable data for the host's cache. Method shorthand —
114
+ * bivariant, so consumer plugins can narrow the `ydoc` parameter to a
115
+ * real `Y.Doc` from their own yjs version.
116
+ */
117
+ serverCacheRenderer?(docId: string, ydoc: AbraYDoc): unknown;
118
+ /**
119
+ * Server-side background runners (Nitro context, service-role identity).
120
+ *
121
+ * Typed as `unknown[]` in the shared contract because each host wraps
122
+ * the runner ctx with its own `AbracadabraClient` / `AbracadabraProvider`
123
+ * / `DocCacheAPI` / etc. — a structural override here would force every
124
+ * consumer to either match the shared base structure exactly or fight
125
+ * input-contravariance. Consumers narrow this to their own
126
+ * `ServerRunnerDefinition[]` in their `CouPlugin` / `AbracadabraPlugin`
127
+ * aliases.
128
+ */
129
+ serverRunners?: readonly unknown[];
130
+ }
131
+ /**
132
+ * Context passed to editor-scoped plugin hooks.
133
+ *
134
+ * `editor` is the TipTap editor wrapped in whatever ref-like the host uses
135
+ * (`ShallowRef<Editor | null>` in Vue 3 hosts). Typed as a structural ref to
136
+ * avoid pulling `vue` into the type closure of plugin consumers that don't
137
+ * use Vue.
138
+ */
139
+ interface EditorPluginCtx<TEditor = unknown> {
140
+ editor: EditorRefLike<TEditor>;
141
+ docId: string;
142
+ }
143
+ /**
144
+ * Context passed to drag-handle plugin hooks. Carries the currently-selected
145
+ * node plus its document position.
146
+ */
147
+ interface DragHandlePluginCtx<TEditor = unknown, TNode = unknown> extends EditorPluginCtx<TEditor> {
148
+ selectedNode: RefLike<{
149
+ node: TNode | null;
150
+ pos: number;
151
+ } | undefined>;
152
+ }
153
+ /**
154
+ * Context passed to `clientSetup`. `abracadabra` is the host's full reactive
155
+ * state — typed as `unknown` here; app-specific plugin aliases tighten this
156
+ * to `AbracadabraState` / `ReturnType<typeof useAbracadabra>` / similar.
157
+ */
158
+ interface ClientPluginCtx<TState = unknown> {
159
+ abracadabra: TState;
160
+ }
161
+ /** Context passed to command-palette plugin hooks. */
162
+ interface CommandPaletteCtx<TState = unknown, TTree = unknown> {
163
+ docId: string | null;
164
+ /** `useRouter()`'s return value. Typed as `unknown` to avoid a `vue-router` peer dep. */
165
+ router: unknown;
166
+ abracadabra: TState;
167
+ tree: TTree | null;
168
+ }
169
+ /** Context passed to mention provider hooks. */
170
+ interface MentionPluginCtx {
171
+ docId: string;
172
+ connectedUsers: readonly CollaborationUser[];
173
+ rootDoc: AbraYDoc;
174
+ }
175
+ /**
176
+ * Minimal shape of a Vue `Ref`/`ShallowRef`/Solid signal. Defined locally so
177
+ * the plugin contract doesn't depend on a specific reactivity library.
178
+ */
179
+ interface RefLike<T> {
180
+ value: T;
181
+ }
182
+ /** A read-mostly ref for the TipTap editor instance. */
183
+ type EditorRefLike<TEditor = unknown> = RefLike<TEditor | null>;
184
+ interface AbraMentionProvider {
185
+ /** Group label shown in the suggestion popup. */
186
+ label: string;
187
+ /**
188
+ * Method shorthand (not arrow property) — TS treats this position as
189
+ * bivariant, so consumer plugins can narrow `ctx` to their own tightened
190
+ * mention-context type without contravariance fights.
191
+ */
192
+ getItems(query: string, ctx: MentionPluginCtx): readonly AbraMentionItem[] | Promise<readonly AbraMentionItem[]>;
193
+ }
194
+ interface AbraMentionItem {
195
+ id: string;
196
+ label: string;
197
+ avatar?: string;
198
+ color?: string;
199
+ attrs?: Record<string, unknown>;
200
+ }
201
+ interface AbraAwarenessContribution {
202
+ /** Namespaced keys this plugin writes, e.g. `['cursor', 'presence:kanban']`. */
203
+ keys: readonly string[];
204
+ /**
205
+ * Optional Vue component rendered as a presence overlay. Typed as
206
+ * `unknown` so the plugin contract stays Vue-free at the type level.
207
+ * Concrete consumers cast to `Component` / `DefineComponent` as needed.
208
+ */
209
+ cursorComponent?: unknown;
210
+ }
211
+ interface AbraCommandItem {
212
+ id: string;
213
+ label: string;
214
+ description?: string;
215
+ icon?: string;
216
+ group?: string;
217
+ shortcut?: string;
218
+ /** Method shorthand — bivariant, so consumer types can narrow `ctx`. */
219
+ when?(ctx: CommandPaletteCtx): boolean;
220
+ /** Method shorthand — bivariant, so consumer types can narrow `ctx`. */
221
+ handler(ctx: CommandPaletteCtx): void | Promise<void>;
222
+ }
223
+ interface AbraNodePanelSlot {
224
+ id: string;
225
+ label: string;
226
+ icon: string;
227
+ /** Method shorthand — bivariant, so consumer types can narrow `ctx`. */
228
+ when(ctx: NodePanelCtx): boolean;
229
+ component: unknown;
230
+ }
231
+ interface NodePanelCtx {
232
+ childId: string;
233
+ childDoc: AbraYDoc | null;
234
+ parentDocId: string;
235
+ parentType: string | undefined;
236
+ meta: Record<string, unknown>;
237
+ tree: unknown;
238
+ }
239
+ interface AbraSettingsPanel {
240
+ label: string;
241
+ icon: string;
242
+ component: unknown;
243
+ }
244
+ interface AbraKeyboardShortcut {
245
+ /** Key spec, e.g. `'Meta+Shift+K'`, `'Control+Alt+P'`. */
246
+ key: string;
247
+ description: string;
248
+ handler: () => void | Promise<void>;
249
+ }
250
+ interface CollaborationUser {
251
+ name: string;
252
+ color: string;
253
+ publicKey?: string;
254
+ docId?: string;
255
+ }
256
+ /**
257
+ * Cleanup function returned by a runner's `start()`. Called on graceful
258
+ * server shutdown.
259
+ */
260
+ type RunnerCleanup = (() => void | Promise<void>) | undefined | void;
261
+ /**
262
+ * A named background task that runs inside the host's server process
263
+ * (Nitro, in `@abraca/nuxt`'s case).
264
+ *
265
+ * The runner context is parameterised so each host can extend it with its
266
+ * own client/provider/storage shapes without the plugin contract dragging
267
+ * `@abraca/dabra` along.
268
+ */
269
+ interface ServerRunnerDefinition<TCtx = ServerRunnerContextBase> {
270
+ name: string;
271
+ /** Method shorthand — bivariant, so consumer types can narrow `ctx`. */
272
+ start(ctx: TCtx): Promise<RunnerCleanup> | RunnerCleanup;
273
+ }
274
+ /**
275
+ * Minimum context every server runner receives. Hosts extend this with
276
+ * `client`, `wsp`, `rootProvider`, etc.
277
+ */
278
+ interface ServerRunnerContextBase {
279
+ /** Root Y.Doc (the world / hub doc, or the space root doc for space-scoped runners). */
280
+ rootDoc: AbraYDoc;
281
+ /** Space ID if this runner is scoped to a space; `null` for hub-scoped runners. */
282
+ spaceId?: string | null;
283
+ }
284
+ //#endregion
285
+ //#region packages/plugin/src/registry.d.ts
286
+ /** Extract the value type from a `Record<string, V>`-shaped optional field. */
287
+ type RecordValueOf<T> = T extends Record<string, infer V> ? V : never;
288
+ /** Extract the item type from a `(ctx) => readonly (readonly T[])[]` field. */
289
+ type GroupedItemOf<T> = T extends ((...args: never[]) => readonly (infer Group)[]) ? Group extends readonly (infer Item)[] ? Item : never : never;
290
+ /** Extract the array element type from an optional array field. */
291
+ type ElementOf<T> = T extends readonly (infer V)[] ? V : never;
292
+ /** Result-shape of `commandPaletteItems` collapsed to its item type. */
293
+ type CommandItemOf<T> = T extends ((...args: never[]) => infer R) ? R extends Promise<readonly (infer Item)[]> ? Item : R extends readonly (infer Item)[] ? Item : never : never;
294
+ type PageTypeOf<P> = P extends {
295
+ pageTypes?: infer R;
296
+ } ? RecordValueOf<R> : never;
297
+ type CustomHandlerOf<P> = P extends {
298
+ customHandlers?: () => infer R;
299
+ } ? R : never;
300
+ type ToolbarItemOf<P> = P extends {
301
+ toolbarItems?: infer F;
302
+ } ? GroupedItemOf<F> : never;
303
+ type BubbleItemOf<P> = P extends {
304
+ bubbleMenuItems?: infer F;
305
+ } ? GroupedItemOf<F> : never;
306
+ type SuggestionItemOf<P> = P extends {
307
+ suggestionItems?: infer F;
308
+ } ? GroupedItemOf<F> : never;
309
+ type DragHandleItemOf<P> = P extends {
310
+ dragHandleItems?: infer F;
311
+ } ? GroupedItemOf<F> : never;
312
+ type MentionProviderOf<P> = P extends {
313
+ mentionProviders?: infer A;
314
+ } ? ElementOf<A> : never;
315
+ type AwarenessContributionOf<P> = P extends {
316
+ awarenessContributions?: infer A;
317
+ } ? ElementOf<A> : never;
318
+ type CommandPaletteItemOf<P> = P extends {
319
+ commandPaletteItems?: infer F;
320
+ } ? CommandItemOf<F> : never;
321
+ type NodePanelSlotOf<P> = P extends {
322
+ nodePanelSlots?: infer A;
323
+ } ? ElementOf<A> : never;
324
+ type SettingsPanelOf<P> = P extends {
325
+ settingsPanel?: infer V;
326
+ } ? NonNullable<V> : never;
327
+ type KeyboardShortcutOf<P> = P extends {
328
+ keyboardShortcuts?: infer A;
329
+ } ? ElementOf<A> : never;
330
+ declare class PluginRegistry<P extends AbraPlugin = AbraPlugin> {
331
+ private _builtins;
332
+ private _spacePlugins;
333
+ private _frozen;
334
+ private _spaceVersion;
335
+ /**
336
+ * Register a built-in plugin. No-ops with a warning after `freeze()`.
337
+ * Use `registerSpacePlugin()` for plugins that arrive after boot.
338
+ */
339
+ register(plugin: P): void;
340
+ /** Lock the registry. Called once after all built-in + external plugins are loaded. */
341
+ freeze(): void;
342
+ isFrozen(): boolean;
343
+ /** Reactive registration channel for space-driven plugins (bypasses freeze). */
344
+ registerSpacePlugin(plugin: P): void;
345
+ unregisterSpacePlugin(name: string): void;
346
+ clearSpacePlugins(): void;
347
+ /** Monotonically increasing counter — bump when space plugins change. */
348
+ getSpacePluginVersion(): number;
349
+ getBuiltinPlugins(): readonly P[];
350
+ getSpacePlugins(): readonly P[];
351
+ /** All active plugins — built-ins first, then space plugins. */
352
+ getPlugins(): readonly P[];
353
+ getAllExtensions(): AbraTiptapExtension[];
354
+ getServerExtensions(): AbraTiptapExtension[];
355
+ /** Resolves once every plugin with `extensionsReady` has settled. */
356
+ waitForExtensions(): Promise<void>;
357
+ getAllPageTypes(): Record<string, PageTypeOf<P>>;
358
+ getAllCustomHandlers(): CustomHandlerOf<P>;
359
+ getAllToolbarItems(ctx: EditorPluginCtx): ToolbarItemOf<P>[][];
360
+ getAllBubbleMenuItems(ctx: EditorPluginCtx): BubbleItemOf<P>[][];
361
+ getAllSuggestionItems(ctx: EditorPluginCtx): SuggestionItemOf<P>[][];
362
+ getAllDragHandleItems(ctx: DragHandlePluginCtx): DragHandleItemOf<P>[][];
363
+ getAllMentionProviders(): MentionProviderOf<P>[];
364
+ getAllAwarenessContributions(): AwarenessContributionOf<P>[];
365
+ getAllCommandPaletteItems(ctx: CommandPaletteCtx): Promise<CommandPaletteItemOf<P>[]>;
366
+ getAllNodePanelSlots(): NodePanelSlotOf<P>[];
367
+ getSettingsPanels(): SettingsPanelOf<P>[];
368
+ getAllKeyboardShortcuts(): KeyboardShortcutOf<P>[];
369
+ }
370
+ //#endregion
371
+ //#region packages/plugin/src/source-spec.d.ts
372
+ /**
373
+ * Plugin source specification — the user-typed shorthand that resolves to a
374
+ * loadable URL.
375
+ *
376
+ * Today the registry server (Phase C) hosts artifacts at canonical URLs.
377
+ * Until then, both `cou-shell` and `@abraca/nuxt` accept three forms:
378
+ *
379
+ * - `npm:<package>[@version]` → jsDelivr npm CDN
380
+ * - `github:<user>/<repo>[@ref]` → jsDelivr GitHub CDN
381
+ * - any other string → returned as-is (must be `https://` or `http://localhost`)
382
+ *
383
+ * Once Phase C ships we add:
384
+ *
385
+ * - `abra:<plugin-id>[@version]` → the official registry
386
+ *
387
+ * The output URL points at the plugin's bundle entry. Convention is
388
+ * `<root>/dist/plugin.js`.
389
+ */
390
+ /** A locally-installed external plugin record (persisted to host storage). */
391
+ interface ExternalPluginEntry {
392
+ /** Resolved fetch URL — the output of `normalizePluginUrl`. */
393
+ url: string;
394
+ /** Plugin `name` from the loaded bundle's manifest / default export. */
395
+ name: string;
396
+ label?: string;
397
+ version?: string;
398
+ description?: string;
399
+ enabled: boolean;
400
+ /** Most recent load error, if any. Cleared on next successful load. */
401
+ error?: string;
402
+ installedAt: number;
403
+ /**
404
+ * Integrity hash from the plugin's manifest, if known. Verified on every
405
+ * load — a silent change indicates supply-chain tampering and the host
406
+ * should refuse to instantiate the plugin.
407
+ */
408
+ integrity?: string;
409
+ }
410
+ /**
411
+ * Resolve a user-typed plugin spec to a fetchable URL.
412
+ *
413
+ * - `npm:foo@1.2.3` → `https://cdn.jsdelivr.net/npm/foo@1.2.3/dist/plugin.js`
414
+ * - `github:org/repo@main` → `https://cdn.jsdelivr.net/gh/org/repo@main/dist/plugin.js`
415
+ * - anything else → returned trimmed, unchanged
416
+ */
417
+ declare function normalizePluginUrl(input: string): string;
418
+ /**
419
+ * Reject any URL that isn't HTTPS or localhost over HTTP. Hosts should call
420
+ * this before importing — `normalizePluginUrl` does not enforce the policy
421
+ * (npm:/github: shorthand always produces HTTPS, so callers only need this
422
+ * for raw third-party URLs).
423
+ */
424
+ declare function isSafePluginUrl(url: string): boolean;
425
+ //#endregion
426
+ //#region packages/plugin/src/manifest.d.ts
427
+ /**
428
+ * Plugin manifest v1 — the declarative `manifest.json` colocated with a
429
+ * plugin's bundle. Hosts parse this before executing the bundle, and the
430
+ * registry server (Phase C) validates it server-side too.
431
+ *
432
+ * Phase A scope: types only. Zod validators + JSON Schema generation live
433
+ * in `@abraca/schema` (Phase B).
434
+ */
435
+ /**
436
+ * Capability tokens declared by a plugin's manifest. The host enforces these
437
+ * at the plugin-host boundary — a plugin without `network:*` cannot `fetch()`,
438
+ * a plugin without `clipboard:write` cannot write the clipboard, etc.
439
+ *
440
+ * Phase 1 enforcement is contract-based (we wrap host APIs). Phase 4 adds
441
+ * Wasm / Web Worker / iframe sandboxing so the contract is enforced at the
442
+ * runtime boundary, not just the host-API boundary.
443
+ */
444
+ type PluginCapability = /** Outbound HTTP — `network` (any host) or `network:<host>` (exact match, wildcards allowed). */`network${"" | `:${string}`}` /** Filesystem reads — only meaningful in Tauri / native hosts. */ | "fs:read" /** Filesystem writes — Tauri / native only. */ | "fs:write" /** Read the clipboard. */ | "clipboard:read" /** Write the clipboard. */ | "clipboard:write" /** Read any Y.Doc the host has loaded. */ | "doc-read" /** Mutate Y.Docs (transact_mut against the host's provider). */ | "doc-write" /** Broadcast presence / awareness state. */ | "awareness" /** Register an RPC v1 handler on the service runner. */ | "server-runner" /** Contribute items to the global command palette. */ | "command-palette" /** Contribute editor toolbar items. */ | "toolbar" /** Contribute bubble-menu items (text selection). */ | "bubble-menu" /** Contribute slash-command suggestion items. */ | "slash-command" /** Contribute drag-handle items. */ | "drag-handle" /** Register a page-type renderer with the given slug. */ | `page-type:${string}` /** Send messages on the in-doc chat channel. */ | "chat:send" /** Read in-doc chat history. */ | "chat:read" /** Show user-visible notifications. */ | "notifications:show";
445
+ interface PluginManifestAuthor {
446
+ /** GitHub login. Identifies the author in the registry. */
447
+ github?: string;
448
+ /** Human-readable display name. */
449
+ name?: string;
450
+ url?: string;
451
+ email?: string;
452
+ }
453
+ /** Pricing label as defined in the registry — see registry docs for semantics. */
454
+ type PluginPricing = "free" | "optional" | "paid";
455
+ /** Submission / publication status as exposed by the registry's catalog API. */
456
+ type PluginVersionStatus = "live" | "pending" | "rejected" | "superseded";
457
+ /** Declared contributions — the plugin's static claim about what it ships. */
458
+ interface PluginManifestContributes {
459
+ pageTypes?: readonly string[];
460
+ extensions?: readonly string[];
461
+ awarenessFields?: readonly string[];
462
+ serverRunners?: readonly string[];
463
+ commands?: readonly string[];
464
+ settingsPanels?: readonly string[];
465
+ }
466
+ /**
467
+ * The static manifest colocated with the plugin entry. Versioned via the
468
+ * top-level `manifestVersion` field — the only field guaranteed to exist
469
+ * verbatim across all manifest versions.
470
+ */
471
+ interface PluginManifest {
472
+ /** Schema version of this manifest format. Currently always `1`. */
473
+ manifestVersion: 1;
474
+ /** Unique lowercase-kebab slug. Stable across versions. */
475
+ id: string;
476
+ /** Display name. Falls back to `id`. */
477
+ name?: string;
478
+ /** Plugin version (semver). */
479
+ version: string;
480
+ /** Author / maintainer info. */
481
+ author: PluginManifestAuthor;
482
+ /** SPDX license identifier (e.g. `MIT`, `Apache-2.0`). */
483
+ license: string;
484
+ /** One- to two-sentence summary shown in browsers and scorecards. */
485
+ description: string;
486
+ /** Long-form description / changelog — markdown allowed. */
487
+ readme?: string;
488
+ homepage?: string;
489
+ /** `github:user/repo` shorthand or any URL. */
490
+ repository?: string;
491
+ /** Minimum Abracadabra SDK version required (semver range). */
492
+ minAbracadabraVersion?: string;
493
+ /** Path to the bundle entry, relative to the manifest. */
494
+ entry: string;
495
+ /**
496
+ * SHA-256 of the bundle entry, hex-encoded. Verified by hosts before
497
+ * `import()`. Mismatch → refuse to load.
498
+ */
499
+ integrity: string;
500
+ /** Pricing tier as defined in the registry. */
501
+ pricing?: PluginPricing;
502
+ /** Screenshot paths (relative to manifest), shown on the detail page. */
503
+ screenshots?: readonly string[];
504
+ /** Free-form category tags for catalog filtering. */
505
+ categories?: readonly string[];
506
+ /** Required capabilities — host MUST grant these or refuse the install. */
507
+ capabilities: {
508
+ required: readonly PluginCapability[];
509
+ optional?: readonly PluginCapability[];
510
+ };
511
+ /** Declared contributions. Static check; verified against runtime registrations. */
512
+ contributes?: PluginManifestContributes;
513
+ /**
514
+ * Peer dependencies the plugin expects the host to provide. Hosts publish
515
+ * the versions they ship via `globalThis.__ABRACA_SHARED__`.
516
+ */
517
+ peerDeps?: Record<string, string>;
518
+ }
519
+ /** Registry-decorated manifest — what the catalog API returns. */
520
+ interface PluginRegistryEntry {
521
+ manifest: PluginManifest;
522
+ /** Resolved bundle URL (R2 / jsDelivr / etc). */
523
+ artifactUrl: string;
524
+ /** Registry-side artifact hash, mirrors `manifest.integrity`. */
525
+ artifactSha256: string;
526
+ /** Status as seen by the registry. */
527
+ status: PluginVersionStatus;
528
+ publishedAt: string;
529
+ /**
530
+ * Scorecard summary — `null` until the registry has scanned this version.
531
+ * Concrete shape lives in `@abraca/schema` (Phase H).
532
+ */
533
+ scorecard: unknown | null;
534
+ }
535
+ //#endregion
536
+ //#region packages/plugin/src/host.d.ts
537
+ /**
538
+ * Thrown when a plugin tries to use an API it didn't declare in its
539
+ * manifest. Distinct from runtime failures (network 500, file not found,
540
+ * etc.) so the host can show the user "the plugin tried to do X without
541
+ * permission" instead of a generic error.
542
+ */
543
+ declare class CapabilityDenied extends Error {
544
+ readonly capability: PluginCapability | string;
545
+ readonly pluginId: string;
546
+ constructor(pluginId: string, capability: PluginCapability | string, detail?: string);
547
+ }
548
+ /** Subset of the global `fetch` signature the host exposes. */
549
+ type GuardedFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
550
+ /** Subset of `navigator.clipboard` the host exposes. */
551
+ interface GuardedClipboard {
552
+ readText(): Promise<string>;
553
+ writeText(text: string): Promise<void>;
554
+ }
555
+ /** Permission-aware desktop notification. */
556
+ interface GuardedNotifications {
557
+ show(title: string, options?: NotificationOptions): Promise<void>;
558
+ }
559
+ /**
560
+ * Everything a plugin is allowed to touch through the host contract.
561
+ * Each plugin gets its own instance via `createPluginHost(manifest)`.
562
+ */
563
+ interface PluginHost {
564
+ readonly pluginId: string;
565
+ readonly capabilities: ReadonlySet<PluginCapability>;
566
+ fetch: GuardedFetch;
567
+ clipboard: GuardedClipboard;
568
+ notifications: GuardedNotifications;
569
+ /** Test whether a capability is granted without invoking the wrapped API. */
570
+ can(capability: PluginCapability | string): boolean;
571
+ }
572
+ interface PluginHostOptions {
573
+ /**
574
+ * Override the underlying `fetch`. Tests inject a stub here; production
575
+ * hosts leave this unset and use the global.
576
+ */
577
+ fetch?: typeof fetch;
578
+ /** Override `navigator.clipboard`. */
579
+ clipboard?: {
580
+ readText?(): Promise<string>;
581
+ writeText?(text: string): Promise<void>;
582
+ };
583
+ /**
584
+ * Override the host's notification surface. The default uses the
585
+ * browser's `Notification` API — fine for web hosts; native hosts
586
+ * (Tauri, Electron) inject their own.
587
+ */
588
+ showNotification?(title: string, options?: NotificationOptions): Promise<void>;
589
+ }
590
+ /**
591
+ * Build a `PluginHost` from a manifest. The set of granted capabilities
592
+ * is `manifest.capabilities.required ∪ optional`; runtime gates check
593
+ * membership before forwarding to the underlying API.
594
+ *
595
+ * Network capabilities support host patterns:
596
+ * - `network` — any host
597
+ * - `network:api.foo.io` — exact match
598
+ * - `network:*.foo.io` — wildcard subdomain match
599
+ * - `network:foo.io,bar.io` — comma-separated list
600
+ */
601
+ declare function createPluginHost(manifest: Pick<PluginManifest, "id" | "capabilities">, options?: PluginHostOptions): PluginHost;
602
+ /**
603
+ * Network capability matcher — `network`, `network:exact.host`,
604
+ * `network:*.wildcard.host`, `network:a,b,c` comma-list. Exported so
605
+ * the iframe sandbox can reuse the same rules without forking the
606
+ * vocabulary.
607
+ */
608
+ declare function matchesNetworkCapability(granted: ReadonlySet<PluginCapability>, host: string): boolean;
609
+ //#endregion
610
+ //#region packages/plugin/src/sandbox.d.ts
611
+ /** Public result of `loadSandboxedPlugin` — what the host actually keeps. */
612
+ interface SandboxedPlugin {
613
+ /** Stable id from the manifest. */
614
+ pluginId: string;
615
+ /** Capabilities granted to this sandbox. */
616
+ capabilities: ReadonlySet<PluginCapability>;
617
+ /** The plugin's default export, copied across the postMessage boundary. */
618
+ exported: unknown;
619
+ /** Tear down: removes the iframe + closes the bridge. */
620
+ dispose(): void;
621
+ }
622
+ interface SandboxOptions extends PluginHostOptions {
623
+ /**
624
+ * Override the document the iframe attaches to. Tests inject a jsdom
625
+ * document; production hosts use the default `document` global.
626
+ */
627
+ doc?: Document;
628
+ /**
629
+ * Hard timeout (ms) on the plugin's `ready` handshake. Defaults to
630
+ * 10 s. Plugins that don't post `ready` within the window get the
631
+ * sandbox torn down + a load failure.
632
+ */
633
+ readyTimeoutMs?: number;
634
+ }
635
+ /**
636
+ * Spin up a sandboxed iframe, load the plugin bundle inside it, wait
637
+ * for the plugin to post `ready`, then return a handle. The host bridges
638
+ * capability-gated APIs to the iframe via postMessage.
639
+ */
640
+ declare function loadSandboxedPlugin(manifest: Pick<PluginManifest, "id" | "capabilities">, artifactUrl: string, options?: SandboxOptions): Promise<SandboxedPlugin>;
641
+ //#endregion
642
+ export { type AbraAwarenessContribution, type AbraCommandItem, type AbraKeyboardShortcut, type AbraMentionItem, type AbraMentionProvider, type AbraNodePanelSlot, type AbraPlugin, type AbraSettingsPanel, type AbraTiptapExtension, type AbraYDoc, CapabilityDenied, type ClientPluginCtx, type CollaborationUser, type CommandPaletteCtx, type DragHandlePluginCtx, type EditorPluginCtx, type EditorRefLike, type ExternalPluginEntry, type GuardedClipboard, type GuardedFetch, type GuardedNotifications, type MentionPluginCtx, type NodePanelCtx, type PluginCapability, type PluginHost, type PluginHostOptions, type PluginManifest, type PluginManifestAuthor, type PluginManifestContributes, type PluginPricing, PluginRegistry, type PluginRegistryEntry, type PluginVersionStatus, type RefLike, type RunnerCleanup, type SandboxOptions, type SandboxedPlugin, type ServerRunnerContextBase, type ServerRunnerDefinition, createPluginHost, isSafePluginUrl, loadSandboxedPlugin, matchesNetworkCapability, normalizePluginUrl };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@abraca/plugin",
3
+ "version": "2.3.0",
4
+ "description": "Shared plugin contract + registry for @abraca/nuxt and cou-shell.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/abracadabra-plugin.cjs",
8
+ "module": "dist/abracadabra-plugin.esm.js",
9
+ "types": "dist/index.d.ts",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "exports": {
14
+ "source": {
15
+ "import": "./src/index.ts"
16
+ },
17
+ "default": {
18
+ "import": "./dist/abracadabra-plugin.esm.js",
19
+ "require": "./dist/abracadabra-plugin.cjs",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "files": [
24
+ "src",
25
+ "dist"
26
+ ],
27
+ "peerDependencies": {},
28
+ "devDependencies": {},
29
+ "scripts": {
30
+ "test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
31
+ }
32
+ }