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
package/README.md CHANGED
@@ -9,6 +9,7 @@ cd my-app && npm install
9
9
  npm run dev:web # browser mode, no Electron needed
10
10
  npm run dev # Electron, plugins hot-reload
11
11
  npm run plugin:new orders -- -t backend # scaffold a plugin in ./plugins
12
+ npx fluxplugin init my-app --tailwindcss # …or preconfigure Tailwind
12
13
  npm run build && npm start
13
14
  ```
14
15
 
@@ -36,15 +37,39 @@ export default definePlugin({ import { usePluginIPC } from
36
37
  preload: { orders: { count: "orders.count" } }, meta: { title: "Orders", menu: { label: "Orders" } } }] });
37
38
  });
38
39
  ```
39
- More: [ARCHITECTURE](docs/ARCHITECTURE.md) · [SECURITY](docs/SECURITY.md) · [PLUGIN-AUTHORS](docs/PLUGIN-AUTHORS.md) · [RELEASE](docs/RELEASE.md)
40
+ ## What you get out of the box
41
+ Custom **title bar + menu bar** that adapts to macOS / Windows / Linux · **search** (commands, pages, plugin providers) ·
42
+ **notifications** (bell + toasts) · **themes** (light/dark/system + plugin themes) · icons · command palette · plugin manager with a
43
+ store-style page per plugin (icon, screenshots, YouTube video, Markdown description) · plugins shipped as **JavaScript only** ·
44
+ optional **Tailwind v4** · permissions, signatures, rollback · a **plugin store** (GitHub by default, or your own) with `fluxplugin publish`.
45
+
46
+ ## Docs
47
+ **[STORE](docs/STORE.md)** (develop → publish → install; your own store) · **[CUSTOMIZING](docs/CUSTOMIZING.md)** (add your own APIs, permissions, hooks, title/menu bar, search, notifications, themes, Tailwind) ·
48
+ [PLUGIN-AUTHORS](docs/PLUGIN-AUTHORS.md) · [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) · [ARCHITECTURE](docs/ARCHITECTURE.md) · [SECURITY](docs/SECURITY.md) · [RELEASE](docs/RELEASE.md)
49
+
50
+ ## What `fluxplugin init` generates
51
+ All application code lives in `src/app`, in layers (`shared` → `main` / `preload` / `renderer`):
52
+ ```
53
+ src/app/
54
+ shared/ constants.ts · types.ts (depends on nothing)
55
+ main/ index.ts (Electron) · web.ts (browser) · framework.ts · operations.ts · window.ts · paths.ts
56
+ preload/ index.ts
57
+ renderer/ index.tsx · bootstrap.ts · extension-points.ts · routes.ts · App.tsx
58
+ providers/ · shell/ · pages/ · hooks/ · services/ · styles/
59
+ plugins/ loaded at startup
60
+ ```
40
61
 
41
62
  ## Status — read this
42
- * **Verified:** 121 automated tests; typecheck strict; the packed tarball installed into a *freshly generated app*
43
- (`init` → `npm install` → typecheck → `build` → run in browser mode); a Playwright E2E against that app with
44
- 3 plugins (menus, slots, extension points, hooks, validated IPC, push events, live enable/disable, palette, manager UI);
45
- `plugin new` + `build` hot-loads into a running app.
46
- * **Not verified:** a real Electron binary (unavailable in the build sandbox). `main.ts`/`preload.ts` templates and
47
- `attachElectron` follow the Electron API and are tested against fakes — expect small adjustments on first `npm run dev`.
48
- * **Not published:** the package is not on npm. Run the steps in `docs/RELEASE.md`; check the name is free first.
49
- * **Security:** plugin `main` code is not sandboxed (permissions are an API contract). Read `docs/SECURITY.md`.
50
- * **Not done:** per-plugin process isolation, hosted marketplace (only the `PluginRegistry` abstraction + local/HTTP), Tailwind wiring in the starter.
63
+ * **Verified:** ~140 automated tests and strict typecheck; the packed tarball installed into freshly generated apps (default and
64
+ `--tailwindcss`) → build → run; the generated app run in **real Electron 44** (Linux/Xvfb) *with Electron's install script blocked*
65
+ (auto-download works): custom title bar, plugin menus, search, notifications, theme, plugin page (icon/README), no Node leak
66
+ into the page; a Playwright E2E in a browser with 3 plugins.
67
+ * **Store:** publish → search → install is tested end to end against an in-process imitation of the GitHub API (release, asset upload,
68
+ topics, checksum, tampering, rate limit, pruning of `.map`/`.ts`), the GitHub search request shape was confirmed against the real API
69
+ (read-only, HTTP 200), and installing from a custom HTTP store was run through the app's UI. **`fluxplugin publish` itself has not been run
70
+ against real github.com** (no credentials here): try it with `--dry-run` first, then on a throw-away repository.
71
+ * **Not verified:** Windows and macOS runs (only Linux was available — the OS-specific branches follow Electron's documented API:
72
+ `hiddenInset` on macOS, `titleBarOverlay` on Windows/Linux); installers/auto-update (electron-builder/forge not set up);
73
+ YouTube playback itself (no internet video in the test environment — the embed URL and sandboxed iframe are asserted).
74
+ * **Not published** on npm — see `docs/RELEASE.md`. **Security:** plugin `main` code is not sandboxed (see `docs/SECURITY.md`).
75
+ * **Not done:** per-plugin process isolation, hosted marketplace (only `PluginRegistry` + local/HTTP), OS-level (native) notifications.
@@ -1,81 +1,3 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/core/index.ts
8
- var core_exports = {};
9
- __export(core_exports, {
10
- BUILTIN_PERMISSIONS: () => BUILTIN_PERMISSIONS,
11
- CHANNELS: () => CHANNELS,
12
- CommandRegistry: () => CommandRegistry,
13
- DisposableStore: () => DisposableStore,
14
- EventBus: () => EventBus,
15
- ExtensionPointRegistry: () => ExtensionPointRegistry,
16
- FRAMEWORK_ENGINE_KEY: () => FRAMEWORK_ENGINE_KEY,
17
- FRAMEWORK_VERSION: () => FRAMEWORK_VERSION,
18
- FrameworkError: () => FrameworkError,
19
- HookCancelledError: () => HookCancelledError,
20
- HookSystem: () => HookSystem,
21
- IpcError: () => IpcError,
22
- IpcRegistry: () => IpcRegistry,
23
- LifecycleError: () => LifecycleError,
24
- LogStore: () => LogStore,
25
- MGMT: () => MGMT,
26
- MemoryStorageBackend: () => MemoryStorageBackend,
27
- MenuRegistry: () => MenuRegistry,
28
- PermissionDeniedError: () => PermissionDeniedError,
29
- PermissionManager: () => PermissionManager,
30
- PluginManager: () => PluginManager,
31
- PreloadRegistry: () => PreloadRegistry,
32
- Registry: () => Registry,
33
- RouteRegistry: () => RouteRegistry,
34
- ServiceRegistry: () => ServiceRegistry,
35
- SlotRegistry: () => SlotRegistry,
36
- StyleRegistry: () => StyleRegistry,
37
- TimeoutError: () => TimeoutError,
38
- ValidationError: () => ValidationError,
39
- activationOrderFor: () => activationOrderFor,
40
- buildDependencyGraph: () => buildDependencyGraph,
41
- compareVersions: () => compareVersions,
42
- createPluginConfig: () => createPluginConfig,
43
- createPluginState: () => createPluginState,
44
- createPluginStorage: () => createPluginStorage,
45
- defineCommand: () => defineCommand,
46
- defineComponent: () => defineComponent,
47
- defineEvent: () => defineEvent,
48
- defineHook: () => defineHook,
49
- defineIPC: () => defineIPC,
50
- defineIpcContract: () => defineIpcContract,
51
- defineMenu: () => defineMenu,
52
- definePlugin: () => definePlugin,
53
- defineRoute: () => defineRoute,
54
- defineService: () => defineService,
55
- dependentsOf: () => dependentsOf,
56
- describeIssue: () => describeIssue,
57
- errorMessage: () => errorMessage,
58
- hasPluginApi: () => hasPluginApi,
59
- isValidRange: () => isValidRange,
60
- isValidVersion: () => isValidVersion,
61
- manifestSchema: () => manifestSchema,
62
- matchesPattern: () => matchesPattern,
63
- menuLocations: () => menuLocations,
64
- normalizePath: () => normalizePath,
65
- parseOrThrow: () => parseOrThrow,
66
- parseVersion: () => parseVersion,
67
- pluginApi: () => pluginApi,
68
- preloadMember: () => preloadMember,
69
- routePatternMatches: () => routePatternMatches,
70
- s: () => s,
71
- satisfies: () => satisfies,
72
- scopeCss: () => scopeCss,
73
- serializeError: () => serializeError,
74
- toDisposable: () => toDisposable,
75
- validateManifest: () => validateManifest,
76
- withTimeout: () => withTimeout
77
- });
78
-
79
1
  // src/core/common.ts
80
2
  var toDisposable = (fn) => {
81
3
  let done = false;
@@ -603,6 +525,18 @@ var manifestSchema = s.object({
603
525
  repository: s.union(s.string(), s.object({ type: s.string().optional(), url: s.string() })).optional(),
604
526
  license: s.string().optional(),
605
527
  icon: s.string().optional(),
528
+ /** Wide image shown at the top of the plugin page (png/jpg/webp/svg inside the plugin). */
529
+ banner: s.string().optional(),
530
+ /** Screenshots shown in a gallery (paths inside the plugin). */
531
+ screenshots: s.array(s.string()).default([]),
532
+ /** Detailed description as Markdown (default: ./README.md). Rendered safely: no raw HTML. */
533
+ readme: s.string().optional(),
534
+ /** Explanation video (https URL; YouTube and Vimeo are embedded, other hosts become a link). */
535
+ video: s.string({ pattern: /^https:\/\/\S+$/ }).optional(),
536
+ categories: s.array(s.string()).default([]),
537
+ keywords: s.array(s.string()).default([]),
538
+ /** Extra links: docs, support, changelog, funding... */
539
+ links: s.record(s.string({ pattern: /^https:\/\/\S+$/ })).default({}),
606
540
  /** Entry executed in the Electron main process. */
607
541
  main: s.string().optional(),
608
542
  /** Entry executed in the preload context (sandbox-compatible, no Node). */
@@ -659,6 +593,25 @@ function validateManifest(raw, opts = {}) {
659
593
  }
660
594
  return { ok: issues.length === 0, manifest: issues.length ? void 0 : manifest, issues, warnings };
661
595
  }
596
+ function videoEmbedUrl(url) {
597
+ if (!url) return void 0;
598
+ let u;
599
+ try {
600
+ u = new URL(url);
601
+ } catch {
602
+ return void 0;
603
+ }
604
+ const host = u.hostname.replace(/^www\./, "");
605
+ let id;
606
+ if (host === "youtu.be") id = u.pathname.slice(1);
607
+ else if (host === "youtube.com" || host === "m.youtube.com") id = u.searchParams.get("v") ?? (/^\/(embed|shorts)\//.test(u.pathname) ? u.pathname.split("/")[2] : void 0);
608
+ if (id && /^[\w-]{6,20}$/.test(id)) return `https://www.youtube-nocookie.com/embed/${id}`;
609
+ if (host === "vimeo.com" || host === "player.vimeo.com") {
610
+ const vid = u.pathname.split("/").filter(Boolean).pop();
611
+ if (vid && /^\d+$/.test(vid)) return `https://player.vimeo.com/video/${vid}`;
612
+ }
613
+ return void 0;
614
+ }
662
615
 
663
616
  // src/core/events.ts
664
617
  function matchesPattern(pattern, name) {
@@ -1795,6 +1748,70 @@ var defineEvent = (name, handler, opts = {}) => ({ name, handler, ...opts });
1795
1748
  var defineComponent = (c) => c;
1796
1749
  var defineMenu = (m) => m;
1797
1750
 
1751
+ // src/core/notifications.ts
1752
+ var NotificationCenter = class {
1753
+ constructor(capacity = 200) {
1754
+ this.capacity = capacity;
1755
+ }
1756
+ capacity;
1757
+ items = [];
1758
+ listeners = /* @__PURE__ */ new Set();
1759
+ pushListeners = /* @__PURE__ */ new Set();
1760
+ seq = 0;
1761
+ version = 0;
1762
+ push(input, owner = "host") {
1763
+ const id = input.id ?? `${owner}#${Date.now().toString(36)}-${this.seq++}`;
1764
+ this.items = this.items.filter((n) => n.id !== id);
1765
+ const item = {
1766
+ id,
1767
+ title: input.title,
1768
+ body: input.body,
1769
+ level: input.level ?? "info",
1770
+ actions: input.actions ?? [],
1771
+ timeoutMs: input.timeoutMs ?? 5e3,
1772
+ owner,
1773
+ time: Date.now(),
1774
+ read: false
1775
+ };
1776
+ this.items.unshift(item);
1777
+ if (this.items.length > this.capacity) this.items.length = this.capacity;
1778
+ this.changed();
1779
+ for (const l of [...this.pushListeners]) l(item);
1780
+ return { id, dismiss: () => this.dismiss(id) };
1781
+ }
1782
+ list() {
1783
+ return this.items;
1784
+ }
1785
+ get unread() {
1786
+ return this.items.filter((n) => !n.read).length;
1787
+ }
1788
+ markRead(id) {
1789
+ for (const n of this.items) if (!id || n.id === id) n.read = true;
1790
+ this.changed();
1791
+ }
1792
+ dismiss(id) {
1793
+ this.items = this.items.filter((n) => n.id !== id);
1794
+ this.changed();
1795
+ }
1796
+ clear() {
1797
+ this.items = [];
1798
+ this.changed();
1799
+ }
1800
+ subscribe = (l) => {
1801
+ this.listeners.add(l);
1802
+ return () => void this.listeners.delete(l);
1803
+ };
1804
+ /** Called for every new notification (toast host, native bridges, main→renderer forwarding). */
1805
+ onPush(l) {
1806
+ this.pushListeners.add(l);
1807
+ return toDisposable(() => this.pushListeners.delete(l));
1808
+ }
1809
+ changed() {
1810
+ this.version++;
1811
+ for (const l of [...this.listeners]) l();
1812
+ }
1813
+ };
1814
+
1798
1815
  // src/core/manager.ts
1799
1816
  var DEFAULT_TIMEOUTS = { load: 15e3, initialize: 1e4, activate: 15e3, ready: 1e4, deactivate: 1e4, unload: 1e4 };
1800
1817
  var PluginManager = class {
@@ -1826,6 +1843,7 @@ var PluginManager = class {
1826
1843
  ipc = new IpcRegistry();
1827
1844
  preload = new PreloadRegistry();
1828
1845
  styles = new StyleRegistry();
1846
+ notifications = new NotificationCenter();
1829
1847
  storageBackend;
1830
1848
  records = /* @__PURE__ */ new Map();
1831
1849
  states = /* @__PURE__ */ new Map();
@@ -1889,7 +1907,11 @@ var PluginManager = class {
1889
1907
  optionalDependencies: {},
1890
1908
  peerDependencies: {},
1891
1909
  engines: {},
1892
- contributes: {}
1910
+ contributes: {},
1911
+ screenshots: [],
1912
+ categories: [],
1913
+ keywords: [],
1914
+ links: {}
1893
1915
  };
1894
1916
  const rec = {
1895
1917
  id,
@@ -2472,6 +2494,7 @@ var PluginManager = class {
2472
2494
  run: (op, input, exec) => (need("hooks", "hooks.run"), this.hooks.run(op, input, exec)),
2473
2495
  filter: (name, value, extra) => (need("hooks", "hooks.filter"), this.hooks.filter(name, value, extra))
2474
2496
  },
2497
+ notifications: { push: (n) => (need("notifications", "notifications.push"), this.notifications.push(n, id)) },
2475
2498
  styles: { add: (st) => (need("styles", "styles.add"), track(this.styles.register(st, id))) },
2476
2499
  host: {
2477
2500
  has: (name) => this.hostApis.has(name),
@@ -2537,6 +2560,7 @@ var MGMT = {
2537
2560
  configSchema: "flux.plugins.config.schema",
2538
2561
  logs: "flux.plugins.logs",
2539
2562
  diagnostics: "flux.plugins.diagnostics",
2563
+ details: "flux.plugins.details",
2540
2564
  changed: "flux.plugins.changed"
2541
2565
  };
2542
2566
 
@@ -2576,6 +2600,7 @@ export {
2576
2600
  menuLocations,
2577
2601
  manifestSchema,
2578
2602
  validateManifest,
2603
+ videoEmbedUrl,
2579
2604
  matchesPattern,
2580
2605
  EventBus,
2581
2606
  HookSystem,
@@ -2613,11 +2638,11 @@ export {
2613
2638
  defineEvent,
2614
2639
  defineComponent,
2615
2640
  defineMenu,
2641
+ NotificationCenter,
2616
2642
  PluginManager,
2617
2643
  routePatternMatches,
2618
2644
  CHANNELS,
2619
2645
  MGMT,
2620
2646
  pluginApi,
2621
- hasPluginApi,
2622
- core_exports
2647
+ hasPluginApi
2623
2648
  };