fluxplugin 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.
Files changed (70) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +50 -0
  3. package/dist/browser/chunks/chunk-Q75AOCXD.js +2544 -0
  4. package/dist/browser/chunks/chunk-ZZ25CRMR.js +759 -0
  5. package/dist/browser/index.js +138 -0
  6. package/dist/browser/manager-ui.js +165 -0
  7. package/dist/browser/react.js +73 -0
  8. package/dist/cli.js +1635 -0
  9. package/dist/node/chunks/chunk-BZ7NZF7I.js +990 -0
  10. package/dist/node/chunks/chunk-XPLGAEBH.js +2623 -0
  11. package/dist/node/cli-api.js +843 -0
  12. package/dist/node/index.js +138 -0
  13. package/dist/node/main.js +61 -0
  14. package/dist/node/preload.js +56 -0
  15. package/dist/types/cli/bin.d.ts +1 -0
  16. package/dist/types/cli/build.d.ts +7 -0
  17. package/dist/types/cli/cli.d.ts +2 -0
  18. package/dist/types/cli/config.d.ts +9 -0
  19. package/dist/types/cli/host-templates.d.ts +6 -0
  20. package/dist/types/cli/host.d.ts +16 -0
  21. package/dist/types/cli/index.d.ts +6 -0
  22. package/dist/types/cli/shared-modules.d.ts +8 -0
  23. package/dist/types/cli/templates.d.ts +10 -0
  24. package/dist/types/core/api.d.ts +11 -0
  25. package/dist/types/core/common.d.ts +69 -0
  26. package/dist/types/core/deps.d.ts +34 -0
  27. package/dist/types/core/events.d.ts +58 -0
  28. package/dist/types/core/hooks.d.ts +55 -0
  29. package/dist/types/core/index.d.ts +18 -0
  30. package/dist/types/core/ipc.d.ts +148 -0
  31. package/dist/types/core/manager.d.ts +155 -0
  32. package/dist/types/core/manifest.d.ts +88 -0
  33. package/dist/types/core/permissions.d.ts +45 -0
  34. package/dist/types/core/plugin.d.ts +214 -0
  35. package/dist/types/core/protocol.d.ts +86 -0
  36. package/dist/types/core/registry.d.ts +88 -0
  37. package/dist/types/core/routes.d.ts +105 -0
  38. package/dist/types/core/schema.d.ts +99 -0
  39. package/dist/types/core/semver.d.ts +16 -0
  40. package/dist/types/core/slots.d.ts +75 -0
  41. package/dist/types/core/storage.d.ts +55 -0
  42. package/dist/types/core/styles.d.ts +34 -0
  43. package/dist/types/main/adapters.d.ts +81 -0
  44. package/dist/types/main/discover.d.ts +21 -0
  45. package/dist/types/main/framework.d.ts +78 -0
  46. package/dist/types/main/fs-storage.d.ts +33 -0
  47. package/dist/types/main/index.d.ts +8 -0
  48. package/dist/types/main/registry.d.ts +75 -0
  49. package/dist/types/main/security.d.ts +24 -0
  50. package/dist/types/main/serve-web.d.ts +14 -0
  51. package/dist/types/main/tar.d.ts +10 -0
  52. package/dist/types/manager-ui/index.d.ts +7 -0
  53. package/dist/types/preload/index.d.ts +27 -0
  54. package/dist/types/react/base-css.d.ts +2 -0
  55. package/dist/types/react/components.d.ts +89 -0
  56. package/dist/types/react/http-bridge.d.ts +10 -0
  57. package/dist/types/react/index.d.ts +6 -0
  58. package/dist/types/react/palette.d.ts +10 -0
  59. package/dist/types/react/router.d.ts +47 -0
  60. package/dist/types/react/runtime.d.ts +99 -0
  61. package/docs/ARCHITECTURE.md +31 -0
  62. package/docs/PLUGIN-AUTHORS.md +11 -0
  63. package/docs/RELEASE.md +20 -0
  64. package/docs/SECURITY.md +18 -0
  65. package/docs/screenshots/command-palette.png +0 -0
  66. package/docs/screenshots/dashboard.png +0 -0
  67. package/docs/screenshots/plugin-details.png +0 -0
  68. package/docs/screenshots/plugin-manager.png +0 -0
  69. package/docs/screenshots/stats.png +0 -0
  70. package/package.json +112 -0
@@ -0,0 +1,86 @@
1
+ import type { PluginInfo } from "./plugin.js";
2
+ import type { PluginManifest, Permission } from "./manifest.js";
3
+ import type { IpcErrorPayload, PreloadApiSpec } from "./ipc.js";
4
+ /** Electron IPC channel names used by the framework (all prefixed to avoid clashing with host channels). */
5
+ export declare const CHANNELS: {
6
+ readonly invoke: "flux:invoke";
7
+ readonly send: "flux:send";
8
+ readonly event: "flux:event";
9
+ readonly bootstrap: "flux:bootstrap";
10
+ };
11
+ export interface RendererPluginDescriptor {
12
+ id: string;
13
+ manifest: PluginManifest;
14
+ enabled: boolean;
15
+ /** URL the renderer imports for this plugin's renderer entry (undefined when it has none). */
16
+ rendererUrl?: string;
17
+ /** URLs of CSS files declared in the manifest `styles`. */
18
+ styleUrls: string[];
19
+ granted: Permission[];
20
+ state: PluginInfo["state"];
21
+ }
22
+ export interface Bootstrap {
23
+ frameworkVersion: string;
24
+ plugins: RendererPluginDescriptor[];
25
+ preload: Record<string, PreloadApiSpec>;
26
+ /** Monotonic counter bumped whenever the plugin set / state changes. */
27
+ revision: number;
28
+ }
29
+ export interface InvokeRequest {
30
+ channel: string;
31
+ input?: unknown;
32
+ /** Plugin on whose behalf the renderer calls (used for permission checks). */
33
+ caller?: string;
34
+ }
35
+ export type InvokeResponse = {
36
+ ok: true;
37
+ value: unknown;
38
+ } | {
39
+ ok: false;
40
+ error: IpcErrorPayload;
41
+ };
42
+ export interface EventMessage {
43
+ channel: string;
44
+ payload: unknown;
45
+ }
46
+ /** The only surface the preload exposes to renderer code (`window.__fluxplugin`). Everything is plain data + functions. */
47
+ export interface FluxBridge {
48
+ invoke(channel: string, input?: unknown, caller?: string): Promise<unknown>;
49
+ send(channel: string, payload?: unknown, caller?: string): void;
50
+ on(channel: string, listener: (payload: unknown) => void): () => void;
51
+ bootstrap(): Promise<Bootstrap>;
52
+ }
53
+ /** Management channels served by the main framework to the host UI (`flux.plugins.*`). */
54
+ export declare const MGMT: {
55
+ readonly list: "flux.plugins.list";
56
+ readonly enable: "flux.plugins.enable";
57
+ readonly disable: "flux.plugins.disable";
58
+ readonly reload: "flux.plugins.reload";
59
+ readonly grant: "flux.plugins.grant";
60
+ readonly revoke: "flux.plugins.revoke";
61
+ readonly uninstall: "flux.plugins.uninstall";
62
+ readonly install: "flux.plugins.install";
63
+ readonly update: "flux.plugins.update";
64
+ readonly rollback: "flux.plugins.rollback";
65
+ readonly search: "flux.plugins.search";
66
+ readonly updates: "flux.plugins.updates";
67
+ readonly configGet: "flux.plugins.config.get";
68
+ readonly configSet: "flux.plugins.config.set";
69
+ readonly configReset: "flux.plugins.config.reset";
70
+ readonly configSchema: "flux.plugins.config.schema";
71
+ readonly logs: "flux.plugins.logs";
72
+ readonly diagnostics: "flux.plugins.diagnostics";
73
+ readonly changed: "flux.plugins.changed";
74
+ };
75
+ /** Serializable description of a plugin offered by a registry (marketplace entry). */
76
+ export interface RegistryEntryLike {
77
+ id: string;
78
+ name: string;
79
+ version: string;
80
+ description?: string;
81
+ author?: string;
82
+ permissions: string[];
83
+ location: string;
84
+ source: string;
85
+ sha256?: string;
86
+ }
@@ -0,0 +1,88 @@
1
+ import { type Disposable } from "./common.js";
2
+ import type { MenuLocation } from "./manifest.js";
3
+ /** Base registry with ownership tracking, change notification and a version counter (for `useSyncExternalStore`). */
4
+ export declare class Registry<T> {
5
+ protected readonly kind: string;
6
+ protected table: Map<string, {
7
+ item: T;
8
+ owner?: string;
9
+ }>;
10
+ private subscribers;
11
+ private _version;
12
+ constructor(kind: string);
13
+ get version(): number;
14
+ protected add(key: string, item: T, owner?: string): Disposable;
15
+ has(key: string): boolean;
16
+ get(key: string): T | undefined;
17
+ ownerOf(key: string): string | undefined;
18
+ list(): T[];
19
+ listByOwner(owner: string): T[];
20
+ unregisterByOwner(owner: string): number;
21
+ clear(): void;
22
+ /** Subscribes to any change. Returns an unsubscribe function compatible with `useSyncExternalStore`. */
23
+ subscribe: (listener: () => void) => (() => void);
24
+ protected changed(): void;
25
+ }
26
+ export interface CommandDefinition<A extends unknown[] = any[], R = unknown> {
27
+ id: string;
28
+ title: string;
29
+ category?: string;
30
+ description?: string;
31
+ /** Keyboard shortcut, e.g. `Mod+Shift+A` (`Mod` = Ctrl on Windows/Linux, Cmd on macOS). */
32
+ keybinding?: string;
33
+ icon?: unknown;
34
+ /** Evaluated by hosts to hide or disable the command in menus / palette. */
35
+ when?: () => boolean;
36
+ execute: (...args: A) => R | Promise<R>;
37
+ }
38
+ export declare class CommandRegistry extends Registry<CommandDefinition> {
39
+ /** Set by the plugin manager: activates plugins that declared `onCommand:<id>` before execution. */
40
+ beforeExecute?: (id: string) => Promise<void>;
41
+ /** Observer used for diagnostics / hooks. */
42
+ onExecuted?: (id: string, error?: unknown) => void;
43
+ constructor();
44
+ register(command: CommandDefinition<any[], any>, owner?: string): Disposable;
45
+ execute<R = unknown>(id: string, ...args: unknown[]): Promise<R>;
46
+ /** Commands sorted for display in a palette. */
47
+ palette(query?: string): CommandDefinition[];
48
+ /** Finds the command bound to a keyboard shortcut. */
49
+ byKeybinding(binding: string): CommandDefinition | undefined;
50
+ }
51
+ export interface MenuItemDefinition {
52
+ id: string;
53
+ location: MenuLocation;
54
+ label: string;
55
+ icon?: unknown;
56
+ /** Command executed on click. */
57
+ command?: string;
58
+ args?: unknown[];
59
+ /** Visual grouping and ordering inside the location. */
60
+ group?: string;
61
+ order?: number;
62
+ when?: () => boolean;
63
+ separator?: boolean;
64
+ children?: Omit<MenuItemDefinition, "location">[];
65
+ /** Short label shown as a badge. */
66
+ badge?: string | number;
67
+ /** Route path, for sidebar entries that navigate without a command. */
68
+ path?: string;
69
+ }
70
+ export declare class MenuRegistry extends Registry<MenuItemDefinition> {
71
+ constructor();
72
+ register(item: MenuItemDefinition, owner?: string): Disposable;
73
+ items(location: MenuLocation): MenuItemDefinition[];
74
+ }
75
+ export interface ServiceEntry<T = unknown> {
76
+ id: string;
77
+ implementation: T;
78
+ }
79
+ export declare class ServiceRegistry extends Registry<ServiceEntry> {
80
+ /** Set by the plugin manager: activates plugins declaring `onService:<name>` on first lookup. */
81
+ beforeResolve?: (name: string) => Promise<void>;
82
+ constructor();
83
+ register<T>(name: string, implementation: T, owner?: string): Disposable;
84
+ /** Synchronous lookup — returns undefined when the providing plugin is not active. */
85
+ resolve<T = unknown>(name: string): T | undefined;
86
+ /** Looks a service up, activating its provider lazily if necessary. */
87
+ resolveAsync<T = unknown>(name: string): Promise<T>;
88
+ }
@@ -0,0 +1,105 @@
1
+ import { type Disposable } from "./common.js";
2
+ import type { MenuLocation, Permission } from "./manifest.js";
3
+ import { Registry } from "./registry.js";
4
+ export type RouteParams = Record<string, string>;
5
+ export interface RouteGuardContext {
6
+ path: string;
7
+ params: RouteParams;
8
+ route: ResolvedRoute;
9
+ /** Host-provided state (current user, feature flags...) — see `RouteRegistry.guardContext`. */
10
+ host: Record<string, unknown>;
11
+ }
12
+ /** Return `true`/`undefined` to allow, `false` to block, or a string path to redirect. */
13
+ export type RouteGuard = (ctx: RouteGuardContext) => boolean | string | void | Promise<boolean | string | void>;
14
+ export interface RouteMeta {
15
+ title?: string;
16
+ icon?: unknown;
17
+ /** Static label or function computing it from params (e.g. loaded entity name). */
18
+ breadcrumb?: string | ((params: RouteParams) => string);
19
+ /** Adds an automatic menu entry for this route. */
20
+ menu?: false | {
21
+ location?: MenuLocation;
22
+ label?: string;
23
+ order?: number;
24
+ group?: string;
25
+ };
26
+ [key: string]: unknown;
27
+ }
28
+ export interface RouteDefinition<C = unknown> {
29
+ path: string;
30
+ /** Component to render (any framework — the core does not care). */
31
+ component?: C;
32
+ /** Code-split loader. Resolved by the renderer adapter. */
33
+ lazy?: () => Promise<{
34
+ default: C;
35
+ } | C>;
36
+ /** Id of a registered layout, or a component used directly as the layout. */
37
+ layout?: string | C;
38
+ children?: RouteDefinition<C>[];
39
+ /** `protected` routes require the host `isAuthenticated` guard (see RouteRegistry.guardContext) — default is `public`. */
40
+ access?: "public" | "protected";
41
+ /** Permission the *plugin* must hold (declared in manifest) to register this route. */
42
+ permission?: Permission;
43
+ guards?: RouteGuard[];
44
+ meta?: RouteMeta;
45
+ }
46
+ export interface ResolvedRoute<C = unknown> {
47
+ /** Full normalized path pattern, e.g. `/settings/analytics/:id`. */
48
+ fullPath: string;
49
+ definition: RouteDefinition<C>;
50
+ owner?: string;
51
+ /** Ancestors from root to this route (excluding itself). */
52
+ parents: ResolvedRoute<C>[];
53
+ segments: Seg[];
54
+ score: number;
55
+ }
56
+ type Seg = {
57
+ type: "static";
58
+ value: string;
59
+ } | {
60
+ type: "param";
61
+ name: string;
62
+ } | {
63
+ type: "splat";
64
+ };
65
+ export interface RouteMatch<C = unknown> {
66
+ route: ResolvedRoute<C>;
67
+ params: RouteParams;
68
+ /** Ancestors + route, root first (used to nest layouts and build breadcrumbs). */
69
+ chain: ResolvedRoute<C>[];
70
+ }
71
+ export interface Breadcrumb {
72
+ label: string;
73
+ path: string;
74
+ }
75
+ export type GuardResult = {
76
+ allow: true;
77
+ } | {
78
+ allow: false;
79
+ redirect?: string;
80
+ reason: string;
81
+ };
82
+ export declare function normalizePath(path: string): string;
83
+ export declare class RouteRegistry<C = unknown> extends Registry<ResolvedRoute<C>> {
84
+ /** Host-provided values handed to guards (e.g. `{ isAuthenticated: true }`). */
85
+ guardContext: Record<string, unknown>;
86
+ /** Set by the plugin manager to lazily activate plugins declaring `onRoute:<path>`. */
87
+ beforeResolve?: (path: string) => Promise<void>;
88
+ /** Called for every route with `meta.menu`: lets the manager create sidebar entries automatically. */
89
+ onRouteRegistered?: (route: ResolvedRoute<C>, owner?: string) => Disposable | void;
90
+ private layouts;
91
+ constructor();
92
+ registerLayout(id: string, component: C): Disposable;
93
+ getLayout(id: string): C | undefined;
94
+ private bump;
95
+ /** Registers a route (and its children). Returns one disposable removing all of them. */
96
+ register(def: RouteDefinition<C>, owner?: string): Disposable;
97
+ /** Finds the best route for a concrete pathname. */
98
+ match(pathname: string): RouteMatch<C> | undefined;
99
+ /** Same as `match` but first lets lazily-activated plugins register their routes. */
100
+ matchAsync(pathname: string): Promise<RouteMatch<C> | undefined>;
101
+ /** Evaluates `access`, then every guard from the root layout down to the route itself. */
102
+ checkGuards(m: RouteMatch<C>, pathname: string): Promise<GuardResult>;
103
+ breadcrumbs(m: RouteMatch<C>): Breadcrumb[];
104
+ }
105
+ export {};
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Tiny dependency-free schema library used for manifest validation,
3
+ * IPC input/output validation and plugin configuration.
4
+ * Provides static type inference via `Infer<T>`.
5
+ */
6
+ export interface Issue {
7
+ path: string;
8
+ message: string;
9
+ }
10
+ export type ParseResult<T> = {
11
+ ok: true;
12
+ value: T;
13
+ } | {
14
+ ok: false;
15
+ issues: Issue[];
16
+ };
17
+ export interface SchemaDescription {
18
+ type: string;
19
+ title?: string;
20
+ description?: string;
21
+ default?: unknown;
22
+ enum?: readonly unknown[];
23
+ properties?: Record<string, SchemaDescription>;
24
+ required?: string[];
25
+ items?: SchemaDescription;
26
+ optional?: boolean;
27
+ min?: number;
28
+ max?: number;
29
+ }
30
+ export interface Schema<T> {
31
+ readonly kind: string;
32
+ readonly _type?: T;
33
+ parse(input: unknown, path?: string): ParseResult<T>;
34
+ /** Describes the schema as JSON-schema-like data (used to auto-generate config UIs). */
35
+ describe(): SchemaDescription;
36
+ optional(): Schema<T | undefined>;
37
+ default(value: T): Schema<T>;
38
+ }
39
+ export type Infer<S> = S extends Schema<infer T> ? T : never;
40
+ export declare class ValidationError extends Error {
41
+ readonly issues: Issue[];
42
+ constructor(issues: Issue[], what?: string);
43
+ }
44
+ export interface StringOpts {
45
+ min?: number;
46
+ max?: number;
47
+ pattern?: RegExp;
48
+ title?: string;
49
+ description?: string;
50
+ }
51
+ export interface NumberOpts {
52
+ min?: number;
53
+ max?: number;
54
+ int?: boolean;
55
+ title?: string;
56
+ description?: string;
57
+ }
58
+ type OptionalKeys<P> = {
59
+ [K in keyof P]: undefined extends Infer<P[K]> ? K : never;
60
+ }[keyof P];
61
+ type Simplify<T> = {
62
+ [K in keyof T]: T[K];
63
+ } & {};
64
+ type ObjectOf<P extends Record<string, Schema<any>>> = Simplify<{
65
+ [K in Exclude<keyof P, OptionalKeys<P>>]: Infer<P[K]>;
66
+ } & {
67
+ [K in OptionalKeys<P>]?: Exclude<Infer<P[K]>, undefined>;
68
+ }>;
69
+ export declare const s: {
70
+ string(o?: StringOpts): Schema<string>;
71
+ number(o?: NumberOpts): Schema<number>;
72
+ boolean(o?: {
73
+ title?: string;
74
+ description?: string;
75
+ }): Schema<boolean>;
76
+ unknown(): Schema<unknown>;
77
+ literal<const V extends string | number | boolean>(v: V): Schema<V>;
78
+ enum<const V extends readonly string[]>(values: V, o?: {
79
+ title?: string;
80
+ description?: string;
81
+ }): Schema<V[number]>;
82
+ array<T>(item: Schema<T>, o?: {
83
+ min?: number;
84
+ max?: number;
85
+ }): Schema<T[]>;
86
+ record<T>(value: Schema<T>): Schema<Record<string, T>>;
87
+ object<P extends Record<string, Schema<any>>>(shape: P, o?: {
88
+ strict?: boolean;
89
+ title?: string;
90
+ description?: string;
91
+ }): Schema<ObjectOf<P>>;
92
+ /** Accepts a value if any of the schemas matches. */
93
+ union<T extends readonly Schema<any>[]>(...options: T): Schema<Infer<T[number]>>;
94
+ /** Escape hatch for values that cannot be validated structurally (functions, React components...). */
95
+ custom<T>(check: (v: unknown) => boolean, message?: string): Schema<T>;
96
+ fn<T = (...args: any[]) => any>(): Schema<T>;
97
+ };
98
+ export declare function parseOrThrow<T>(schema: Schema<T>, input: unknown, what?: string): T;
99
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Minimal, dependency-free semver implementation covering what the framework needs:
3
+ * parse, compare, and range satisfaction for `^`, `~`, `>=`, `>`, `<=`, `<`, `=`, `*`, `x`-ranges
4
+ * and space-separated comparator sets (AND) as well as `||` (OR).
5
+ */
6
+ export interface SemVer {
7
+ major: number;
8
+ minor: number;
9
+ patch: number;
10
+ prerelease: string[];
11
+ }
12
+ export declare function parseVersion(v: string): SemVer | null;
13
+ export declare function isValidVersion(v: string): boolean;
14
+ export declare function compareVersions(a: string, b: string): number;
15
+ export declare function isValidRange(range: string): boolean;
16
+ export declare function satisfies(version: string, range: string): boolean;
@@ -0,0 +1,75 @@
1
+ import { type Disposable } from "./common.js";
2
+ import { Registry } from "./registry.js";
3
+ import type { Schema } from "./schema.js";
4
+ export type SlotMode = "before" | "after" | "replace" | "append" | "prepend";
5
+ export interface SlotContribution<C = unknown> {
6
+ /** Optional id so the contribution can be overridden or inspected. Generated when omitted. */
7
+ id?: string;
8
+ slot: string;
9
+ component: C;
10
+ mode?: SlotMode;
11
+ /** Higher renders first within its group. Default 0. */
12
+ priority?: number;
13
+ /** Evaluated at render time with the slot props. */
14
+ when?: (props: Record<string, unknown>) => boolean;
15
+ /** Props merged into the component props. */
16
+ props?: Record<string, unknown>;
17
+ }
18
+ export interface ResolvedSlotContribution<C = unknown> extends Required<Pick<SlotContribution<C>, "id" | "slot" | "component" | "mode" | "priority">> {
19
+ props?: Record<string, unknown>;
20
+ owner?: string;
21
+ }
22
+ export interface ResolvedSlot<C = unknown> {
23
+ /** Everything rendered before the host default content (prepend, then before). */
24
+ before: ResolvedSlotContribution<C>[];
25
+ /** When set, replaces the host default content. */
26
+ replacement?: ResolvedSlotContribution<C>;
27
+ /** Everything rendered after the host default content (after, then append). */
28
+ after: ResolvedSlotContribution<C>[];
29
+ }
30
+ export declare class SlotRegistry<C = unknown> extends Registry<ResolvedSlotContribution<C> & {
31
+ when?: SlotContribution<C>["when"];
32
+ }> {
33
+ private counter;
34
+ /** Set by the plugin manager to activate plugins declaring `onSlot:<name>`. */
35
+ onSlotRendered?: (slot: string) => void;
36
+ private declared;
37
+ constructor();
38
+ /** Hosts may declare their slots so typos in plugins are caught early. Declaring is optional. */
39
+ declareSlot(name: string): void;
40
+ declaredSlots(): string[];
41
+ register(c: SlotContribution<C>, owner?: string, opts?: {
42
+ strict?: boolean;
43
+ }): Disposable;
44
+ resolve(slot: string, props?: Record<string, unknown>): ResolvedSlot<C>;
45
+ /** Slots that currently have at least one contribution. */
46
+ activeSlots(): string[];
47
+ }
48
+ export interface ExtensionPointDefinition {
49
+ name: string;
50
+ description?: string;
51
+ /** Allowed contribution keys, each optionally validated by a schema applied to every contributed item. */
52
+ contributions: Record<string, Schema<any> | null>;
53
+ }
54
+ interface PointContribution {
55
+ priority: number;
56
+ value: Record<string, unknown>;
57
+ }
58
+ /**
59
+ * Declarative extension points: the host says *what* can be extended (`toolbar`, `tabs`, `actions`...),
60
+ * plugins contribute plain data validated against the host's schema. No DOM access, no monkey patching.
61
+ */
62
+ export declare class ExtensionPointRegistry extends Registry<PointContribution & {
63
+ point: string;
64
+ }> {
65
+ private points;
66
+ private counter;
67
+ constructor();
68
+ declare(def: ExtensionPointDefinition): Disposable;
69
+ definition(name: string): ExtensionPointDefinition | undefined;
70
+ declaredPoints(): ExtensionPointDefinition[];
71
+ contribute(point: string, value: Record<string, unknown>, owner?: string, priority?: number): Disposable;
72
+ /** Merged, priority-ordered items contributed under `key` of `point`. */
73
+ items<T = unknown>(point: string, key: string): T[];
74
+ }
75
+ export {};
@@ -0,0 +1,55 @@
1
+ import { type Disposable } from "./common.js";
2
+ import { type Schema } from "./schema.js";
3
+ /** Persistence backend: one JSON document per namespace (= plugin id). */
4
+ export interface StorageBackend {
5
+ read(namespace: string): Promise<Record<string, unknown>>;
6
+ write(namespace: string, data: Record<string, unknown>): Promise<void>;
7
+ remove?(namespace: string): Promise<void>;
8
+ }
9
+ export declare class MemoryStorageBackend implements StorageBackend {
10
+ private data;
11
+ read(ns: string): Promise<Record<string, unknown>>;
12
+ write(ns: string, data: Record<string, unknown>): Promise<void>;
13
+ remove(ns: string): Promise<void>;
14
+ }
15
+ export interface PluginStorage {
16
+ get<T = unknown>(key: string): Promise<T | undefined>;
17
+ get<T>(key: string, fallback: T): Promise<T>;
18
+ set<T = unknown>(key: string, value: T): Promise<void>;
19
+ delete(key: string): Promise<void>;
20
+ keys(): Promise<string[]>;
21
+ clear(): Promise<void>;
22
+ }
23
+ /**
24
+ * Storage scoped to a single plugin. Writes are serialized per namespace so concurrent `set` calls never
25
+ * lose updates, and values are cloned (structured clone) so callers cannot mutate stored state by reference.
26
+ */
27
+ export declare function createPluginStorage(backend: StorageBackend, namespace: string): PluginStorage;
28
+ export interface ConfigDefinition<T extends Record<string, any> = Record<string, any>> {
29
+ schema: Schema<T>;
30
+ /** Current schema version. Stored configs with a lower version go through `migrate`. */
31
+ version?: number;
32
+ migrate?: (old: Record<string, unknown>, fromVersion: number) => Record<string, unknown>;
33
+ }
34
+ export interface PluginConfig<T extends Record<string, any> = Record<string, any>> {
35
+ /** Loads (and migrates/validates) the configuration; always resolves with a fully defaulted object. */
36
+ get(): Promise<T>;
37
+ /** Merges a partial update, validates the result and persists it. Rejects with `ValidationError`. */
38
+ set(patch: Partial<T>): Promise<T>;
39
+ reset(): Promise<T>;
40
+ onChange(listener: (next: T, prev: T) => void): Disposable;
41
+ /** JSON-schema-like description for building settings forms automatically. */
42
+ describe(): ReturnType<Schema<T>["describe"]>;
43
+ }
44
+ export declare function createPluginConfig<T extends Record<string, any>>(backend: StorageBackend, namespace: string, def: ConfigDefinition<T>, onChanged?: (next: T, prev: T) => void): PluginConfig<T>;
45
+ export interface PluginState_ {
46
+ get<T = unknown>(key: string): T | undefined;
47
+ get<T>(key: string, fallback: T): T;
48
+ set<T>(key: string, value: T): void;
49
+ delete(key: string): void;
50
+ /** Subscribes to changes of one key (or every key when omitted). */
51
+ subscribe(listener: (key: string, value: unknown) => void, key?: string): Disposable;
52
+ snapshot(): Record<string, unknown>;
53
+ }
54
+ /** Volatile, reactive per-plugin state (lives as long as the plugin runtime; use `storage` for persistence). */
55
+ export declare function createPluginState(): PluginState_;
@@ -0,0 +1,34 @@
1
+ import { Registry } from "./registry.js";
2
+ import type { Disposable } from "./common.js";
3
+ export interface StyleEntry {
4
+ id: string;
5
+ /** Inline CSS text (already compiled). */
6
+ css?: string;
7
+ /** URL of a stylesheet (served by the host — e.g. `fluxplugin://<id>/styles/index.css`). */
8
+ href?: string;
9
+ /** Lower numbers load first; later entries win ties in the cascade. Default 100. */
10
+ priority: number;
11
+ /** Wrap rules in `@scope ([data-fluxplugin="<id>"])` so they only affect the plugin's own subtree. */
12
+ scoped: boolean;
13
+ owner: string;
14
+ }
15
+ export interface StyleInput {
16
+ id?: string;
17
+ css?: string;
18
+ href?: string;
19
+ priority?: number;
20
+ scoped?: boolean;
21
+ }
22
+ /**
23
+ * Data-only registry of the styles contributed by plugins. A renderer adapter (see `fluxplugin/react`'s
24
+ * `StyleHost`) mirrors this registry into `<style>`/`<link>` elements, which is what makes
25
+ * disable → remove and hot-reload → replace work without touching the DOM from plugin code.
26
+ */
27
+ export declare class StyleRegistry extends Registry<StyleEntry> {
28
+ private n;
29
+ constructor();
30
+ register(input: StyleInput, owner: string): Disposable;
31
+ /** Entries in cascade order. */
32
+ ordered(): StyleEntry[];
33
+ }
34
+ export declare function scopeCss(css: string, pluginId: string): string;
@@ -0,0 +1,81 @@
1
+ import http from "node:http";
2
+ import type { MainFramework } from "./framework.js";
3
+ /** Minimal structural types so this package never imports `electron` (works with any Electron version). */
4
+ export interface IpcMainLike {
5
+ handle(channel: string, listener: (event: {
6
+ sender: {
7
+ id: number;
8
+ };
9
+ }, ...args: any[]) => unknown): void;
10
+ on(channel: string, listener: (event: {
11
+ sender: {
12
+ id: number;
13
+ };
14
+ }, ...args: any[]) => void): void;
15
+ removeHandler(channel: string): void;
16
+ removeListener(channel: string, listener: (...args: any[]) => void): void;
17
+ }
18
+ export interface WebContentsLike {
19
+ id: number;
20
+ send(channel: string, ...args: unknown[]): void;
21
+ isDestroyed(): boolean;
22
+ }
23
+ export interface ElectronLike {
24
+ ipcMain: IpcMainLike;
25
+ webContents: {
26
+ getAllWebContents(): WebContentsLike[];
27
+ };
28
+ }
29
+ /**
30
+ * Connects the framework to Electron's ipcMain. Call after `app.whenReady()`.
31
+ * Returns a detach function.
32
+ */
33
+ export declare function attachElectron(fw: MainFramework, electron: ElectronLike): () => void;
34
+ /** Privileged scheme definition. Must be passed to `protocol.registerSchemesAsPrivileged` *before* `app.ready`. */
35
+ export declare const PLUGIN_SCHEME: {
36
+ readonly scheme: "fluxplugin";
37
+ readonly privileges: {
38
+ readonly standard: true;
39
+ readonly secure: true;
40
+ readonly supportFetchAPI: true;
41
+ readonly corsEnabled: true;
42
+ readonly stream: true;
43
+ };
44
+ };
45
+ /**
46
+ * Resolves `fluxplugin://<id>/<path>` to a file inside the plugin's directory. Rejects traversal and anything that
47
+ * is not an asset (the manifest and main entry are never served).
48
+ */
49
+ export declare function resolvePluginAsset(fw: MainFramework, url: string): Promise<{
50
+ file: string;
51
+ mime: string;
52
+ } | null>;
53
+ /** Registers the `fluxplugin://` handler on an Electron `protocol` object (`protocol.handle`, Electron >= 25). */
54
+ export declare function registerPluginProtocol(fw: MainFramework, protocol: {
55
+ handle(scheme: string, handler: (req: {
56
+ url: string;
57
+ }) => Promise<Response> | Response): void;
58
+ }): void;
59
+ export interface HttpAdapterOptions {
60
+ port?: number;
61
+ host?: string;
62
+ /** URL prefix under which plugin assets are served. Default `/@plugins`. */
63
+ assetPrefix?: string;
64
+ /** Extra request handler (e.g. serve the Vite build). Return true when handled. */
65
+ fallback?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean | Promise<boolean>;
66
+ }
67
+ /**
68
+ * Serves the same wire protocol over HTTP (POST /flux/invoke, POST /flux/send, GET /flux/bootstrap, SSE /flux/events,
69
+ * GET /@plugins/<id>/<file>). Used for browser-based development, headless E2E tests and screenshots.
70
+ * Bind to loopback only: there is no authentication, exactly like the Electron-only bridge it replaces.
71
+ */
72
+ export declare function attachHttp(fw: MainFramework, opts?: HttpAdapterOptions): Promise<{
73
+ server: http.Server;
74
+ port: number;
75
+ close(): Promise<void>;
76
+ }>;
77
+ /**
78
+ * In-process bridge connecting a renderer runtime directly to a `MainFramework` (no IPC, no serialization
79
+ * except a JSON round trip to mimic structured cloning). Used by integration tests and storybook-like setups.
80
+ */
81
+ export declare function createLoopbackBridge(fw: MainFramework): import("../core/index.js").FluxBridge;
@@ -0,0 +1,21 @@
1
+ import type { PluginManifest, PluginSource } from "../core/index.js";
2
+ import { type VerifyOptions } from "./security.js";
3
+ export declare const MANIFEST_FILE = "plugin.json";
4
+ export interface DiscoveredPlugin {
5
+ root: string;
6
+ manifest: Record<string, unknown>;
7
+ source: PluginSource;
8
+ /** Errors found while reading the plugin (it is still returned so the UI can display the failure). */
9
+ problem?: string;
10
+ }
11
+ /** Imports an ESM/CJS entry and returns its default export. A unique query string defeats the module cache (hot reload). */
12
+ export declare function importFresh(file: string): Promise<unknown>;
13
+ export interface DiscoverOptions extends VerifyOptions {
14
+ /** Entry loader override (tests, bundlers). */
15
+ loadEntry?: (file: string) => Promise<unknown>;
16
+ }
17
+ /** Reads one plugin directory. */
18
+ export declare function readPluginDir(root: string, opts?: DiscoverOptions): Promise<DiscoveredPlugin>;
19
+ /** Scans `dir` for sub-directories containing a plugin.json. Plugins that fail verification are reported, not skipped. */
20
+ export declare function discoverPlugins(dir: string, opts?: DiscoverOptions): Promise<DiscoveredPlugin[]>;
21
+ export type { PluginManifest };