fluxplugin 1.0.0 → 1.0.2

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 (63) hide show
  1. package/README.md +35 -10
  2. package/dist/{node/chunks/chunk-XPLGAEBH.js → browser/chunks/chunk-TL63576R.js} +106 -81
  3. package/dist/browser/chunks/{chunk-ZZ25CRMR.js → chunk-VXQJIDXW.js} +479 -6
  4. package/dist/browser/index.js +5 -1
  5. package/dist/browser/manager-ui.js +93 -18
  6. package/dist/browser/react.js +34 -4
  7. package/dist/cli.js +955 -318
  8. package/dist/node/chunks/chunk-3FH6NAJG.js +38 -0
  9. package/dist/node/chunks/{chunk-BZ7NZF7I.js → chunk-C7VWZZOX.js} +259 -44
  10. package/dist/{browser/chunks/chunk-Q75AOCXD.js → node/chunks/chunk-EVX2O65A.js} +185 -2
  11. package/dist/node/chunks/chunk-WSFCRVEQ.js +9 -0
  12. package/dist/node/chunks/tailwind-TJQVTBG5.js +9 -0
  13. package/dist/node/cli-api.js +663 -288
  14. package/dist/node/index.js +6 -1
  15. package/dist/node/main.js +19 -2
  16. package/dist/node/preload.js +2 -1
  17. package/dist/types/cli/build.d.ts +1 -1
  18. package/dist/types/cli/config.d.ts +3 -0
  19. package/dist/types/cli/host-files.generated.d.ts +2 -0
  20. package/dist/types/cli/host-templates.d.ts +3 -0
  21. package/dist/types/cli/host.d.ts +10 -1
  22. package/dist/types/cli/package.d.ts +18 -0
  23. package/dist/types/cli/publish.d.ts +23 -0
  24. package/dist/types/cli/store.d.ts +7 -0
  25. package/dist/types/cli/tailwind.d.ts +10 -0
  26. package/dist/types/cli/templates.d.ts +2 -0
  27. package/dist/types/core/index.d.ts +1 -0
  28. package/dist/types/core/manager.d.ts +2 -0
  29. package/dist/types/core/manifest.d.ts +9 -0
  30. package/dist/types/core/notifications.d.ts +48 -0
  31. package/dist/types/core/plugin.d.ts +7 -0
  32. package/dist/types/core/protocol.d.ts +27 -0
  33. package/dist/types/main/framework.d.ts +7 -1
  34. package/dist/types/main/github-registry.d.ts +49 -0
  35. package/dist/types/main/index.d.ts +2 -0
  36. package/dist/types/main/registry.d.ts +30 -0
  37. package/dist/types/main/stores.d.ts +29 -0
  38. package/dist/types/react/base-css.d.ts +1 -1
  39. package/dist/types/react/icons.d.ts +12 -0
  40. package/dist/types/react/index.d.ts +5 -0
  41. package/dist/types/react/markdown.d.ts +8 -0
  42. package/dist/types/react/os.d.ts +5 -0
  43. package/dist/types/react/runtime.d.ts +2 -1
  44. package/dist/types/react/theme.d.ts +53 -0
  45. package/dist/types/react/titlebar.d.ts +28 -0
  46. package/docs/CUSTOMIZING.md +257 -0
  47. package/docs/PLUGIN-AUTHORS.md +36 -9
  48. package/docs/RELEASE.md +1 -1
  49. package/docs/SECURITY.md +5 -0
  50. package/docs/STORE.md +123 -0
  51. package/docs/TROUBLESHOOTING.md +35 -0
  52. package/docs/screenshots/command-palette.png +0 -0
  53. package/docs/screenshots/dashboard.png +0 -0
  54. package/docs/screenshots/electron-app.png +0 -0
  55. package/docs/screenshots/electron-plugin-page.png +0 -0
  56. package/docs/screenshots/electron-titlebar.png +0 -0
  57. package/docs/screenshots/plugin-details.png +0 -0
  58. package/docs/screenshots/plugin-manager.png +0 -0
  59. package/docs/screenshots/plugin-video.png +0 -0
  60. package/docs/screenshots/stats.png +0 -0
  61. package/docs/screenshots/store-available.png +0 -0
  62. package/docs/screenshots/tailwind-dark.png +0 -0
  63. package/package.json +1 -2
@@ -17,6 +17,7 @@ import {
17
17
  MGMT,
18
18
  MemoryStorageBackend,
19
19
  MenuRegistry,
20
+ NotificationCenter,
20
21
  PermissionDeniedError,
21
22
  PermissionManager,
22
23
  PluginManager,
@@ -65,8 +66,10 @@ import {
65
66
  serializeError,
66
67
  toDisposable,
67
68
  validateManifest,
69
+ videoEmbedUrl,
68
70
  withTimeout
69
- } from "./chunks/chunk-XPLGAEBH.js";
71
+ } from "./chunks/chunk-EVX2O65A.js";
72
+ import "./chunks/chunk-WSFCRVEQ.js";
70
73
  export {
71
74
  BUILTIN_PERMISSIONS,
72
75
  CHANNELS,
@@ -86,6 +89,7 @@ export {
86
89
  MGMT,
87
90
  MemoryStorageBackend,
88
91
  MenuRegistry,
92
+ NotificationCenter,
89
93
  PermissionDeniedError,
90
94
  PermissionManager,
91
95
  PluginManager,
@@ -134,5 +138,6 @@ export {
134
138
  serializeError,
135
139
  toDisposable,
136
140
  validateManifest,
141
+ videoEmbedUrl,
137
142
  withTimeout
138
143
  };
package/dist/node/main.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import {
2
+ DEFAULT_STORE,
3
+ DEFAULT_TOPIC,
2
4
  FsStorageBackend,
5
+ GitHubRegistry,
3
6
  HttpRegistry,
4
7
  IntegrityError,
5
8
  JsonStateFile,
@@ -13,13 +16,18 @@ import {
13
16
  canonicalJson,
14
17
  computeIntegrity,
15
18
  copyDir,
19
+ copyPluginRuntime,
16
20
  createLoopbackBridge,
21
+ createRegistries,
17
22
  createTarGz,
18
23
  discoverPlugins,
24
+ entriesFromIndex,
19
25
  extractTarGz,
20
26
  generateSigningKeyPair,
21
27
  importFresh,
28
+ pruneNonRuntime,
22
29
  readPluginDir,
30
+ readPluginManifest,
23
31
  readTarGz,
24
32
  registerPluginProtocol,
25
33
  resolvePluginAsset,
@@ -27,10 +35,14 @@ import {
27
35
  sha256File,
28
36
  signManifest,
29
37
  verifyPlugin
30
- } from "./chunks/chunk-BZ7NZF7I.js";
31
- import "./chunks/chunk-XPLGAEBH.js";
38
+ } from "./chunks/chunk-C7VWZZOX.js";
39
+ import "./chunks/chunk-EVX2O65A.js";
40
+ import "./chunks/chunk-WSFCRVEQ.js";
32
41
  export {
42
+ DEFAULT_STORE,
43
+ DEFAULT_TOPIC,
33
44
  FsStorageBackend,
45
+ GitHubRegistry,
34
46
  HttpRegistry,
35
47
  IntegrityError,
36
48
  JsonStateFile,
@@ -44,13 +56,18 @@ export {
44
56
  canonicalJson,
45
57
  computeIntegrity,
46
58
  copyDir,
59
+ copyPluginRuntime,
47
60
  createLoopbackBridge,
61
+ createRegistries,
48
62
  createTarGz,
49
63
  discoverPlugins,
64
+ entriesFromIndex,
50
65
  extractTarGz,
51
66
  generateSigningKeyPair,
52
67
  importFresh,
68
+ pruneNonRuntime,
53
69
  readPluginDir,
70
+ readPluginManifest,
54
71
  readTarGz,
55
72
  registerPluginProtocol,
56
73
  resolvePluginAsset,
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  CHANNELS
3
- } from "./chunks/chunk-XPLGAEBH.js";
3
+ } from "./chunks/chunk-EVX2O65A.js";
4
+ import "./chunks/chunk-WSFCRVEQ.js";
4
5
 
5
6
  // src/preload/index.ts
6
7
  var IpcCallError = class extends Error {
@@ -1,6 +1,6 @@
1
1
  import { type BuildOptions } from "esbuild";
2
2
  /** Convention: dist/X is built from src/X (main.ts, renderer.tsx, styles/*.css). */
3
- export declare function buildOptions(root: string): Promise<BuildOptions[]>;
3
+ export declare function buildOptions(root: string, dev?: boolean): Promise<BuildOptions[]>;
4
4
  export declare function buildPlugin(root: string, watch?: boolean, log?: {
5
5
  (...data: any[]): void;
6
6
  (message?: any, ...optionalParams: any[]): void;
@@ -1,6 +1,9 @@
1
+ import { type StoreSpec } from "../main/index.js";
1
2
  export interface CliConfig {
2
3
  pluginsDir: string;
3
4
  dataDir: string;
5
+ /** Plugin stores used by `search` / `install <id>` (same list your app uses). Default: the GitHub topic store. */
6
+ stores: StoreSpec[];
4
7
  }
5
8
  /** Resolves `--dir/--data` flags, then `fluxplugin.config.json` in the cwd, then defaults. */
6
9
  export declare function loadConfig(flags: {
@@ -0,0 +1,2 @@
1
+ export declare const HOST_APP_FILES: Record<string, string>;
2
+ export declare const HOST_TAILWIND_FILES: Record<string, string>;
@@ -1,6 +1,9 @@
1
1
  export interface HostVars {
2
2
  name: string;
3
3
  fluxSpec: string;
4
+ /** Electron semver range (init resolves the latest published version). */
5
+ electron?: string;
6
+ tailwind?: boolean;
4
7
  }
5
8
  /** Files of a new host application (`fluxplugin init`). Plain strings so the CLI stays a single self-contained file. */
6
9
  export declare function hostFiles(v: HostVars, withSample?: boolean): Record<string, string>;
@@ -5,11 +5,20 @@ export declare function buildHost(cwd: string, opts?: {
5
5
  watch?: boolean;
6
6
  log?: (m: string) => void;
7
7
  }): Promise<() => Promise<void>>;
8
- /** Starts the app: Electron when available (unless `web`), otherwise browser mode. Resolves when it exits. */
8
+ /**
9
+ * Returns the Electron binary, downloading it if the package manager skipped Electron's postinstall
10
+ * (npm 11.16+/12 block install scripts by default, pnpm 10 too). This is the same download `npm i electron` does.
11
+ */
12
+ export declare function ensureElectron(cwd: string, log?: (m: string) => void): {
13
+ bin?: string;
14
+ error?: string;
15
+ };
16
+ /** Starts the app: Electron by default, browser mode with `web`. Throws a readable error if Electron cannot run. */
9
17
  export declare function startApp(cwd: string, opts?: {
10
18
  web?: boolean;
11
19
  dev?: boolean;
12
20
  port?: number;
21
+ log?: (m: string) => void;
13
22
  }): {
14
23
  child: ChildProcess;
15
24
  mode: "electron" | "web";
@@ -0,0 +1,18 @@
1
+ import { type PluginManifest } from "../core/index.js";
2
+ export interface PackageResult {
3
+ file: string;
4
+ descriptorFile: string;
5
+ sha256: string;
6
+ manifest: PluginManifest;
7
+ shipped: string[];
8
+ signed: boolean;
9
+ }
10
+ /**
11
+ * Builds the plugin (no sourcemaps) and writes `<id>-<version>.tgz` + `<id>-<version>.fluxplugin.json` (the descriptor stores
12
+ * read to list the plugin without downloading it). Only runtime files are packaged: plugin.json, dist/, assets/, README/LICENSE.
13
+ */
14
+ export declare function packagePlugin(root: string, opts?: {
15
+ out?: string;
16
+ signKey?: string;
17
+ log?: (m: string) => void;
18
+ }): Promise<PackageResult>;
@@ -0,0 +1,23 @@
1
+ export interface PublishOptions {
2
+ repo?: string;
3
+ token?: string;
4
+ signKey?: string;
5
+ dryRun?: boolean;
6
+ topic?: string;
7
+ apiBase?: string;
8
+ fetchImpl?: typeof fetch;
9
+ log?: (m: string) => void;
10
+ }
11
+ /** "https://github.com/acme/tool.git" | "git@github.com:acme/tool.git" | "acme/tool" → "acme/tool" */
12
+ export declare function parseGitHubRepo(input: string | undefined): string | undefined;
13
+ /**
14
+ * Publishes a plugin to the default store: builds + packages it, creates a GitHub release `v<version>` in the plugin's
15
+ * repository, uploads the package and its descriptor, and adds the discovery topic. Anyone using the store can then find and
16
+ * install it. Needs a token that can write to the repository (GITHUB_TOKEN / GH_TOKEN / --token).
17
+ */
18
+ export declare function publishPlugin(root: string, o?: PublishOptions): Promise<{
19
+ release?: string;
20
+ repo: string;
21
+ version: string;
22
+ id: string;
23
+ }>;
@@ -0,0 +1,7 @@
1
+ export declare function storeInit(dir: string): Promise<void>;
2
+ /** Adds (or updates) a package in the store folder and rewrites index.json. */
3
+ export declare function storeAdd(pkgFile: string, dir: string, baseUrl?: string): Promise<{
4
+ id: string;
5
+ version: string;
6
+ sha256: string;
7
+ }>;
@@ -0,0 +1,10 @@
1
+ /** Finds `@tailwindcss/cli` (Tailwind v4) from the given folders (a plugin folder usually has no node_modules: the app's is used). */
2
+ export declare function resolveTailwindCli(startDirs: string[]): string | undefined;
3
+ /** Compiles a Tailwind entry CSS to `output`. With `watch` it keeps running and returns a function that stops it. */
4
+ export declare function compileTailwind(opts: {
5
+ cwd: string;
6
+ input: string;
7
+ output: string;
8
+ watch?: boolean;
9
+ searchFrom?: string[];
10
+ }, log?: (m: string) => void): () => void;
@@ -8,3 +8,5 @@ export interface TemplateVars {
8
8
  }
9
9
  export declare const TEMPLATES: Record<TemplateName, (v: TemplateVars) => Record<string, string>>;
10
10
  export declare function varsFromName(name: string): TemplateVars;
11
+ /** Adds Tailwind v4 to a plugin: utilities only (no preflight, so the host's styles are never reset). */
12
+ export declare function withTailwind(files: Record<string, string>, v: TemplateVars): Record<string, string>;
@@ -16,3 +16,4 @@ export * from "./plugin.js";
16
16
  export * from "./manager.js";
17
17
  export * from "./protocol.js";
18
18
  export * from "./api.js";
19
+ export * from "./notifications.js";
@@ -10,6 +10,7 @@ import { RouteRegistry } from "./routes.js";
10
10
  import { ExtensionPointRegistry, SlotRegistry } from "./slots.js";
11
11
  import { type PluginConfig, type PluginState_, type StorageBackend } from "./storage.js";
12
12
  import { StyleRegistry } from "./styles.js";
13
+ import { NotificationCenter } from "./notifications.js";
13
14
  export interface PluginSource {
14
15
  /** Raw manifest (validated on registration). */
15
16
  manifest: unknown;
@@ -72,6 +73,7 @@ export declare class PluginManager {
72
73
  readonly ipc: IpcRegistry;
73
74
  readonly preload: PreloadRegistry;
74
75
  readonly styles: StyleRegistry;
76
+ readonly notifications: NotificationCenter;
75
77
  readonly storageBackend: StorageBackend;
76
78
  private readonly records;
77
79
  private readonly states;
@@ -12,6 +12,10 @@ export declare const manifestSchema: import("./schema.js").Schema<{
12
12
  id: string;
13
13
  name: string;
14
14
  version: string;
15
+ screenshots: string[];
16
+ categories: string[];
17
+ keywords: string[];
18
+ links: Record<string, string>;
15
19
  activationEvents: string[];
16
20
  deactivationEvents: string[];
17
21
  permissions: Permission[];
@@ -57,6 +61,9 @@ export declare const manifestSchema: import("./schema.js").Schema<{
57
61
  } | undefined;
58
62
  license?: string | undefined;
59
63
  icon?: string | undefined;
64
+ banner?: string | undefined;
65
+ readme?: string | undefined;
66
+ video?: string | undefined;
60
67
  main?: string | undefined;
61
68
  preload?: string | undefined;
62
69
  renderer?: string | undefined;
@@ -86,3 +93,5 @@ export interface ManifestValidation {
86
93
  export declare function validateManifest(raw: unknown, opts?: {
87
94
  frameworkVersion?: string;
88
95
  }): ManifestValidation;
96
+ /** Turns a YouTube/Vimeo URL into a privacy-friendly embed URL; returns undefined for any other host. */
97
+ export declare function videoEmbedUrl(url: string | undefined): string | undefined;
@@ -0,0 +1,48 @@
1
+ import { type Disposable } from "./common.js";
2
+ export type NotificationLevel = "info" | "success" | "warning" | "error";
3
+ export interface NotificationAction {
4
+ label: string;
5
+ /** Command executed when the user clicks the action. */
6
+ command: string;
7
+ args?: unknown[];
8
+ }
9
+ export interface NotificationInput {
10
+ id?: string;
11
+ title: string;
12
+ body?: string;
13
+ level?: NotificationLevel;
14
+ actions?: NotificationAction[];
15
+ /** Toast duration in ms; 0 keeps it only in the notification center. Default 5000. */
16
+ timeoutMs?: number;
17
+ }
18
+ export interface NotificationItem extends Required<Pick<NotificationInput, "id" | "title" | "level">> {
19
+ body?: string;
20
+ actions: NotificationAction[];
21
+ timeoutMs: number;
22
+ owner: string;
23
+ time: number;
24
+ read: boolean;
25
+ }
26
+ /** In-app notification center (bell + toasts). Plugins push through `ctx.notifications.push` (permission "notifications"). */
27
+ export declare class NotificationCenter {
28
+ private readonly capacity;
29
+ private items;
30
+ private listeners;
31
+ private pushListeners;
32
+ private seq;
33
+ version: number;
34
+ constructor(capacity?: number);
35
+ push(input: NotificationInput, owner?: string): {
36
+ id: string;
37
+ dismiss(): void;
38
+ };
39
+ list(): NotificationItem[];
40
+ get unread(): number;
41
+ markRead(id?: string): void;
42
+ dismiss(id: string): void;
43
+ clear(): void;
44
+ subscribe: (l: () => void) => (() => void);
45
+ /** Called for every new notification (toast host, native bridges, main→renderer forwarding). */
46
+ onPush(l: (n: NotificationItem) => void): Disposable;
47
+ private changed;
48
+ }
@@ -89,6 +89,13 @@ export interface PluginContext<Cfg extends Record<string, any> = Record<string,
89
89
  readonly styles: {
90
90
  add(style: StyleInput): Disposable;
91
91
  };
92
+ /** Bell + toasts in the host UI (permission "notifications"). */
93
+ readonly notifications: {
94
+ push(n: import("./notifications.js").NotificationInput): {
95
+ id: string;
96
+ dismiss(): void;
97
+ };
98
+ };
92
99
  /** Controlled APIs exposed by the host (see `host.provide`), permission-checked per plugin. */
93
100
  readonly host: {
94
101
  api<T = unknown>(name: string): T;
@@ -70,6 +70,7 @@ export declare const MGMT: {
70
70
  readonly configSchema: "flux.plugins.config.schema";
71
71
  readonly logs: "flux.plugins.logs";
72
72
  readonly diagnostics: "flux.plugins.diagnostics";
73
+ readonly details: "flux.plugins.details";
73
74
  readonly changed: "flux.plugins.changed";
74
75
  };
75
76
  /** Serializable description of a plugin offered by a registry (marketplace entry). */
@@ -83,4 +84,30 @@ export interface RegistryEntryLike {
83
84
  location: string;
84
85
  source: string;
85
86
  sha256?: string;
87
+ homepage?: string;
88
+ stars?: number;
89
+ icon?: string;
90
+ }
91
+ /** Everything a UI needs to render a plugin "store page": icon, banner, screenshots, video, long description. */
92
+ export interface PluginDetails {
93
+ id: string;
94
+ name: string;
95
+ version: string;
96
+ description?: string;
97
+ author?: string;
98
+ license?: string;
99
+ homepage?: string;
100
+ repository?: string;
101
+ categories: string[];
102
+ keywords: string[];
103
+ links: Record<string, string>;
104
+ iconUrl?: string;
105
+ bannerUrl?: string;
106
+ screenshotUrls: string[];
107
+ video?: {
108
+ url: string;
109
+ embedUrl?: string;
110
+ };
111
+ /** Markdown source of the long description (rendered by the UI without raw HTML). */
112
+ readme?: string;
86
113
  }
@@ -1,6 +1,7 @@
1
- import { LogStore, PluginManager, type Bootstrap, type InvokeRequest, type InvokeResponse, type PluginManagerOptions } from "../core/index.js";
1
+ import { LogStore, PluginManager, type Bootstrap, type PluginDetails, type InvokeRequest, type InvokeResponse, type PluginManagerOptions } from "../core/index.js";
2
2
  import { type DiscoverOptions } from "./discover.js";
3
3
  import { PluginInstaller, type PluginRegistry } from "./registry.js";
4
+ import { type StoreSpec } from "./stores.js";
4
5
  export interface MainFrameworkOptions {
5
6
  /** Directories scanned for plugins (each sub-directory with a plugin.json). */
6
7
  pluginDirs: string[];
@@ -8,7 +9,10 @@ export interface MainFrameworkOptions {
8
9
  dataDir: string;
9
10
  /** Where installs/uninstalls happen. Defaults to the first of `pluginDirs`. */
10
11
  installDir?: string;
12
+ /** Plugin stores (default: GitHub topic store). Ready-made registries, or `stores` specs below. */
11
13
  registries?: PluginRegistry[];
14
+ /** Store specs, same shape as `fluxplugin.config.json` → "stores". Ignored when `registries` is given. */
15
+ stores?: StoreSpec[];
12
16
  /** Third-party plugins start with no permissions until the user approves them. Default: false (all declared granted). */
13
17
  requireApproval?: boolean;
14
18
  /** Reject plugins without a valid signature from one of these keys. */
@@ -65,6 +69,8 @@ export declare class MainFramework {
65
69
  handleInvoke(req: InvokeRequest, senderId?: number | string): Promise<InvokeResponse>;
66
70
  handleSend(channel: string, payload: unknown, caller?: string, senderId?: number | string): void;
67
71
  private registerManagementApi;
72
+ /** Store-page data for one plugin: icon, banner, screenshots, video and the README (Markdown). */
73
+ details(id: string): Promise<PluginDetails>;
68
74
  private requireConfig;
69
75
  private installAndLoad;
70
76
  /** Channels used by transports. */
@@ -0,0 +1,49 @@
1
+ import { type PluginRegistry, type RegistryEntry } from "./registry.js";
2
+ import type { PluginManifest } from "../core/index.js";
3
+ export declare const DEFAULT_TOPIC = "fluxplugin-plugin";
4
+ export interface GitHubRegistryOptions {
5
+ /** Curated catalog: a repository holding `index.json` (see `fluxplugin store add`), e.g. "acme/plugin-store". */
6
+ repo?: string;
7
+ ref?: string;
8
+ indexPath?: string;
9
+ /** Decentralized discovery: every public repo with this topic and a release containing a `.fluxplugin.json` descriptor. */
10
+ topic?: string;
11
+ /** Lifts GitHub's anonymous rate limit (60 req/h). Only ever sent to GitHub hosts. */
12
+ token?: string;
13
+ name?: string;
14
+ apiBase?: string;
15
+ rawBase?: string;
16
+ fetchImpl?: typeof fetch;
17
+ }
18
+ export interface PluginDescriptor {
19
+ id: string;
20
+ name?: string;
21
+ version: string;
22
+ description?: string;
23
+ author?: string;
24
+ permissions?: string[];
25
+ file: string;
26
+ sha256: string;
27
+ icon?: string;
28
+ }
29
+ /**
30
+ * GitHub as a plugin store. Two modes (they can be combined by adding two registries):
31
+ * - `topic` (default): developers publish a GitHub *release* in their own repo with `fluxplugin publish`; it is discovered
32
+ * through the repository topic. No central approval.
33
+ * - `repo`: a curated catalog repository with an `index.json`.
34
+ */
35
+ export declare class GitHubRegistry implements PluginRegistry {
36
+ private readonly o;
37
+ readonly name: string;
38
+ private cache;
39
+ constructor(o?: GitHubRegistryOptions);
40
+ private get api();
41
+ private get raw();
42
+ private trusted;
43
+ private get;
44
+ private json;
45
+ search(query?: string): Promise<RegistryEntry[]>;
46
+ private catalog;
47
+ private discover;
48
+ fetch(entry: RegistryEntry, dest: string): Promise<PluginManifest>;
49
+ }
@@ -6,3 +6,5 @@ export * from "./registry.js";
6
6
  export * from "./fs-storage.js";
7
7
  export * from "./tar.js";
8
8
  export * from "./serve-web.js";
9
+ export * from "./github-registry.js";
10
+ export * from "./stores.js";
@@ -12,6 +12,9 @@ export interface RegistryEntry {
12
12
  source: string;
13
13
  /** sha256 hex of the archive when the registry knows it. */
14
14
  sha256?: string;
15
+ homepage?: string;
16
+ stars?: number;
17
+ icon?: string;
15
18
  }
16
19
  /**
17
20
  * Abstraction over where plugins come from. Implement it for npm, GitHub releases, a private HTTP registry,
@@ -23,7 +26,34 @@ export interface PluginRegistry {
23
26
  /** Materializes a plugin into `destDir` (which does not exist yet). Returns its manifest. */
24
27
  fetch(entry: RegistryEntry, destDir: string): Promise<PluginManifest>;
25
28
  }
29
+ export declare function readPluginManifest(dir: string): Promise<{
30
+ raw: Record<string, unknown>;
31
+ manifest: PluginManifest;
32
+ }>;
33
+ /**
34
+ * Copies only what a *distributed* plugin needs at runtime: plugin.json, dist/, assets/, README/LICENSE/CHANGELOG and any
35
+ * file the manifest references. Source code (`src/`, .ts/.tsx, sourcemaps), node_modules and tooling files never ship.
36
+ */
37
+ export declare function copyPluginRuntime(src: string, dest: string): Promise<string[]>;
26
38
  export declare function copyDir(src: string, dest: string): Promise<void>;
39
+ /** Shape of a store catalog (`index.json`). Used by HTTP stores, GitHub catalog stores and `fluxplugin store add`. */
40
+ export interface StoreIndex {
41
+ plugins: {
42
+ id: string;
43
+ name?: string;
44
+ version: string;
45
+ description?: string;
46
+ author?: string;
47
+ permissions?: string[];
48
+ url: string;
49
+ sha256?: string;
50
+ homepage?: string;
51
+ icon?: string;
52
+ }[];
53
+ }
54
+ export declare function entriesFromIndex(index: StoreIndex, baseUrl: string, source: string, query?: string): RegistryEntry[];
55
+ /** Removes everything that is not runtime code from an extracted plugin: sources, TypeScript, sourcemaps, node_modules. */
56
+ export declare function pruneNonRuntime(dir: string): Promise<string[]>;
27
57
  /** A directory whose sub-directories are plugins (e.g. a shared network folder or a repo checkout). */
28
58
  export declare class LocalRegistry implements PluginRegistry {
29
59
  private readonly dir;
@@ -0,0 +1,29 @@
1
+ import { type PluginRegistry } from "./registry.js";
2
+ /**
3
+ * A plugin store, as written in `fluxplugin.config.json` → `"stores"` (or passed to `MainFramework`).
4
+ * - `github` + `topic`: every repo with that topic that published a release with `fluxplugin publish` (default store)
5
+ * - `github` + `repo`: a curated catalog repository ("owner/name") containing `index.json`
6
+ * - `http`: any static host serving `index.json` + `.tgz` files (GitHub Pages, S3, your server)
7
+ * - `local`: a folder (network share, checkout)
8
+ */
9
+ export type StoreSpec = {
10
+ type: "github";
11
+ topic?: string;
12
+ repo?: string;
13
+ ref?: string;
14
+ indexPath?: string;
15
+ name?: string;
16
+ } | {
17
+ type: "http";
18
+ url: string;
19
+ } | {
20
+ type: "local";
21
+ path: string;
22
+ };
23
+ export declare const DEFAULT_STORE: StoreSpec;
24
+ /** Builds registries from store specs. The GitHub token (if any) comes from GITHUB_TOKEN / GH_TOKEN, never from config files. */
25
+ export declare function createRegistries(specs?: StoreSpec[], opts?: {
26
+ env?: Record<string, string | undefined>;
27
+ baseDir?: string;
28
+ fetchImpl?: typeof fetch;
29
+ }): PluginRegistry[];
@@ -1,2 +1,2 @@
1
1
  /** Minimal CSS for the framework's own components. Uses CSS variables so hosts can theme it; Tailwind is not required. */
2
- export declare const baseCss = "\n:root{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-accent:#4f46e5;--flux-accent-fg:#fff;--flux-surface:#f8fafc;--flux-danger:#dc2626;--flux-ok:#16a34a;--flux-warn:#d97706;--flux-radius:10px}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}}\n.fluxplugin-error{padding:8px 12px;border:1px solid var(--flux-danger);border-radius:var(--flux-radius);color:var(--flux-danger);font-size:13px}\n.flux-breadcrumbs{font-size:13px;color:var(--flux-muted)}.flux-breadcrumbs a{color:inherit}\n.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}\n.flux-palette-backdrop{position:fixed;inset:0;background:rgba(2,6,23,.45);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}\n.flux-palette{width:min(560px,92vw);background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;box-shadow:0 24px 60px rgba(0,0,0,.35);overflow:hidden}\n.flux-palette-input{width:100%;box-sizing:border-box;padding:14px 16px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:15px;outline:none}\n.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:340px;overflow:auto}\n.flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 12px;border-radius:8px;cursor:pointer;font-size:14px}\n.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}\n.flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}\n";
2
+ export declare const baseCss = "\n:root{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-accent:#4f46e5;--flux-accent-fg:#fff;--flux-surface:#f8fafc;--flux-danger:#dc2626;--flux-ok:#16a34a;--flux-warn:#d97706;--flux-radius:10px}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}}\n.fluxplugin-error{padding:8px 12px;border:1px solid var(--flux-danger);border-radius:var(--flux-radius);color:var(--flux-danger);font-size:13px}\n.flux-breadcrumbs{font-size:13px;color:var(--flux-muted)}.flux-breadcrumbs a{color:inherit}\n.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}\n.flux-palette-backdrop{position:fixed;inset:0;background:rgba(2,6,23,.45);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}\n.flux-palette{width:min(560px,92vw);background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;box-shadow:0 24px 60px rgba(0,0,0,.35);overflow:hidden}\n.flux-palette-input{width:100%;box-sizing:border-box;padding:14px 16px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:15px;outline:none}\n.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:340px;overflow:auto}\n.flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 12px;border-radius:8px;cursor:pointer;font-size:14px}\n.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}\n.flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}\n\n:root[data-theme=\"dark\"]{--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}\n:root[data-theme=\"light\"]{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-surface:#f8fafc}\n.flux-icon{flex:none;vertical-align:middle}\n.flux-titlebar{--flux-titlebar-h:40px;height:var(--flux-titlebar-h);box-sizing:border-box;display:grid;grid-template-columns:1fr minmax(160px,400px) 1fr;align-items:center;gap:8px;padding:0 8px;background:var(--flux-surface);border-bottom:1px solid var(--flux-border);color:var(--flux-fg);-webkit-app-region:drag;user-select:none;position:relative;z-index:50}\n.flux-titlebar[data-electron][data-os=\"mac\"]{padding-left:82px}\n.flux-titlebar[data-electron][data-os=\"windows\"],.flux-titlebar[data-electron][data-os=\"linux\"]{padding-right:calc(100vw - env(titlebar-area-x,0px) - env(titlebar-area-width,100vw) + 8px)}\n.flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}\n.flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:4px;min-width:0}.flux-tb-right{justify-content:flex-end}\n.flux-brand{display:flex;align-items:center;gap:8px;font-weight:600;margin-right:8px;white-space:nowrap}\n.flux-tb-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:inherit;border-radius:6px;padding:5px 9px;font:inherit;cursor:pointer;position:relative}\n.flux-tb-btn:hover,.flux-tb-btn.is-open{background:var(--flux-border)}\n.flux-menubar{display:flex}.flux-menu{position:relative}\n.flux-dropdown{position:absolute;top:calc(100% + 4px);left:0;min-width:220px;max-width:360px;margin:0;padding:6px;list-style:none;background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:0 14px 36px rgba(0,0,0,.28);z-index:60}\n.flux-dd-item{display:flex;justify-content:space-between;align-items:center;gap:16px;width:100%;border:0;background:transparent;color:inherit;font:inherit;text-align:left;padding:7px 10px;border-radius:7px;cursor:pointer}\n.flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-dd-item kbd,.flux-dd-item small{opacity:.65;font:11px ui-monospace,monospace}.flux-sep{height:1px;background:var(--flux-border);margin:5px 4px}\n.flux-search{position:relative;display:flex;align-items:center;gap:8px;background:var(--flux-bg);border:1px solid var(--flux-border);border-radius:8px;padding:0 10px;height:28px;color:var(--flux-muted)}\n.flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);font:inherit;outline:none}\n.flux-search-results{left:0;right:0;max-width:none;top:calc(100% + 6px)}.flux-group{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--flux-muted);padding:6px 10px 2px}\n.flux-empty{padding:12px;color:var(--flux-muted);font-size:13px}.flux-select{margin-left:4px;background:var(--flux-bg);color:inherit;border:1px solid var(--flux-border);border-radius:6px;padding:3px 4px;font:inherit}\n.flux-bell{position:relative}.flux-badge{position:absolute;top:0;right:0;min-width:15px;height:15px;border-radius:99px;background:var(--flux-danger);color:#fff;font-size:10px;line-height:15px;text-align:center}\n.flux-notif-panel{left:auto;right:0;width:340px;max-height:420px;overflow:auto;padding:0}.flux-notif-head{display:flex;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--flux-border)}\n.flux-notif{display:flex;justify-content:space-between;gap:10px;padding:10px 12px;border-bottom:1px solid var(--flux-border);border-left:3px solid var(--flux-accent)}\n.flux-notif.flux-success{border-left-color:var(--flux-ok)}.flux-notif.flux-warning{border-left-color:var(--flux-warn)}.flux-notif.flux-error{border-left-color:var(--flux-danger)}\n.flux-notif p{margin:2px 0 4px;font-size:13px}.flux-notif small{color:var(--flux-muted)}.flux-notif-actions{display:flex;flex-direction:column;align-items:flex-end;gap:4px}\n.flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:12px;padding:0}\n.flux-toasts{position:fixed;top:48px;right:12px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}\n.flux-toast{pointer-events:auto;background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-left:4px solid var(--flux-accent);border-radius:10px;padding:10px 14px;box-shadow:0 10px 28px rgba(0,0,0,.25);max-width:340px;font-size:13px}\n.flux-toast.flux-success{border-left-color:var(--flux-ok)}.flux-toast.flux-warning{border-left-color:var(--flux-warn)}.flux-toast.flux-error{border-left-color:var(--flux-danger)}\n.flux-md{line-height:1.6}.flux-md h2,.flux-md h3,.flux-md h4,.flux-md h5{margin:1.1em 0 .4em}.flux-md pre{background:var(--flux-surface);padding:10px;border-radius:8px;overflow:auto}\n.flux-md code{background:var(--flux-surface);padding:1px 5px;border-radius:4px;font-size:.92em}.flux-md pre code{padding:0;background:none}.flux-md table{border-collapse:collapse;margin:8px 0;font-size:13px}.flux-md th,.flux-md td{border:1px solid var(--flux-border);padding:5px 10px;text-align:left}.flux-md th{background:var(--flux-surface)}.flux-md blockquote{margin:8px 0;padding:2px 12px;border-left:3px solid var(--flux-border);color:var(--flux-muted)}\n";
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ declare const PATHS: Record<string, string>;
3
+ export type IconName = keyof typeof PATHS | (string & {});
4
+ /** Small built-in icon set (24×24, currentColor). Pass your own SVG path with `d` for custom icons. */
5
+ export declare function Icon({ name, d, size, title }: {
6
+ name?: IconName;
7
+ d?: string;
8
+ size?: number;
9
+ title?: string;
10
+ }): React.ReactElement;
11
+ export declare const iconNames: string[];
12
+ export {};
@@ -4,3 +4,8 @@ export * from "./router.js";
4
4
  export * from "./palette.js";
5
5
  export { baseCss } from "./base-css.js";
6
6
  export * from "./http-bridge.js";
7
+ export * from "./os.js";
8
+ export * from "./icons.js";
9
+ export * from "./markdown.js";
10
+ export * from "./theme.js";
11
+ export * from "./titlebar.js";
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ /**
3
+ * Small, safe Markdown renderer for plugin descriptions: headings, paragraphs, lists, quotes, code blocks, rules,
4
+ * bold/italic/inline code and https links. Raw HTML is never interpreted (it is shown as text) and images are not loaded.
5
+ */
6
+ export declare function Markdown({ source }: {
7
+ source: string;
8
+ }): React.ReactElement;
@@ -0,0 +1,5 @@
1
+ export type OS = "mac" | "windows" | "linux" | "other";
2
+ export declare function detectOS(ua?: string): OS;
3
+ export declare const useOS: () => OS;
4
+ /** `Mod+Shift+R` → `⌘⇧R` on macOS, `Ctrl+Shift+R` elsewhere. */
5
+ export declare function formatShortcut(binding: string, os?: OS): string;
@@ -1,4 +1,4 @@
1
- import { PluginManager, type Bootstrap, type FluxBridge, type IpcContract, type IpcErrorPayload, type Permission, type PluginConfig, type PluginInfo, type PluginManagerOptions, type PreloadApiSpec, type RegistryEntryLike, type RendererPluginDescriptor } from "../core/index.js";
1
+ import { PluginManager, type Bootstrap, type FluxBridge, type IpcContract, type IpcErrorPayload, type Permission, type PluginConfig, type PluginDetails, type PluginInfo, type PluginManagerOptions, type PreloadApiSpec, type RegistryEntryLike, type RendererPluginDescriptor } from "../core/index.js";
2
2
  export declare class RemoteError extends Error {
3
3
  readonly payload: IpcErrorPayload;
4
4
  constructor(payload: IpcErrorPayload);
@@ -35,6 +35,7 @@ export declare class PluginsClient {
35
35
  configReset: <T = Record<string, unknown>>(id: string) => Promise<T>;
36
36
  configSchema: (id: string) => Promise<import("../core/schema.js").SchemaDescription | null>;
37
37
  logs: (id?: string) => Promise<import("../core/common.js").LogEntry[]>;
38
+ details: (id: string) => Promise<PluginDetails>;
38
39
  diagnostics: (id: string) => Promise<{
39
40
  info: PluginInfo;
40
41
  logs: import("../core/index.js").LogEntry[];