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
@@ -4,8 +4,9 @@ import {
4
4
  PluginManager,
5
5
  errorMessage,
6
6
  normalizePath,
7
+ s,
7
8
  scopeCss
8
- } from "./chunk-Q75AOCXD.js";
9
+ } from "./chunk-TL63576R.js";
9
10
 
10
11
  // src/react/runtime.ts
11
12
  var RemoteError = class extends Error {
@@ -44,6 +45,7 @@ var PluginsClient = class {
44
45
  configReset = (id) => this.call(MGMT.configReset, { id });
45
46
  configSchema = (id) => this.call(MGMT.configSchema, { id });
46
47
  logs = (id) => this.call(MGMT.logs, { id });
48
+ details = (id) => this.call(MGMT.details, { id });
47
49
  diagnostics = (id) => this.call(MGMT.diagnostics, { id });
48
50
  };
49
51
  var asDefinition = (mod) => {
@@ -107,6 +109,7 @@ var RendererFramework = class {
107
109
  this.bridge.on(MGMT.changed, () => {
108
110
  void this.sync().catch((e) => console.error("[fluxplugin] sync failed", e));
109
111
  }),
112
+ this.bridge.on("flux.notification", (n) => void this.manager.notifications.push(n, n?.owner ?? "main")),
110
113
  this.bridge.on("flux.event.plugin.hotreload", ({ id } = {}) => void this.hotReload(id))
111
114
  );
112
115
  }
@@ -422,8 +425,8 @@ function PluginSlot({ name, children, props = {} }) {
422
425
  }
423
426
  function useExtensionItems(point, key) {
424
427
  const fw = usePluginFramework();
425
- useRegistryVersion(fw.manager.extensionPoints.subscribe, () => fw.manager.extensionPoints.version);
426
- return fw.manager.extensionPoints.items(point, key);
428
+ const version = useRegistryVersion(fw.manager.extensionPoints.subscribe, () => fw.manager.extensionPoints.version);
429
+ return useMemo2(() => fw.manager.extensionPoints.items(point, key), [fw, point, key, version]);
427
430
  }
428
431
  function usePlugin(id) {
429
432
  const fw = usePluginFramework();
@@ -459,7 +462,7 @@ function usePluginEvent(name, onEvent) {
459
462
  };
460
463
  const subs = [fw.manager.events.on(name, (p) => handle(p)), fw.bridge.on(name, handle), fw.bridge.on(`flux.event.${name}`, handle)];
461
464
  return () => {
462
- subs.forEach((s) => typeof s === "function" ? s() : s.dispose());
465
+ subs.forEach((s2) => typeof s2 === "function" ? s2() : s2.dispose());
463
466
  };
464
467
  }, [fw, name]);
465
468
  return last;
@@ -487,7 +490,7 @@ function usePluginIPC(endpoint, opts = {}) {
487
490
  useEffect2(() => () => void (alive.current = false), []);
488
491
  const call = useCallback2(
489
492
  async (input) => {
490
- setState((s) => ({ ...s, loading: true, error: void 0 }));
493
+ setState((s2) => ({ ...s2, loading: true, error: void 0 }));
491
494
  try {
492
495
  const data = await fw.bridge.invoke(name, input, opts.caller);
493
496
  if (alive.current) setState({ data, error: void 0, loading: false });
@@ -572,6 +575,7 @@ function useKeybindings(opts = {}) {
572
575
  useEffect3(() => {
573
576
  if (opts.enabled === false) return;
574
577
  const onKey = (e) => {
578
+ if (e.defaultPrevented) return;
575
579
  const target = e.target;
576
580
  if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) && !(e.ctrlKey || e.metaKey)) return;
577
581
  const parts = [];
@@ -673,6 +677,38 @@ var baseCss = `
673
677
  .flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}
674
678
  .flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}
675
679
  .flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}
680
+
681
+ :root[data-theme="dark"]{--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}
682
+ :root[data-theme="light"]{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-surface:#f8fafc}
683
+ .flux-icon{flex:none;vertical-align:middle}
684
+ .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}
685
+ .flux-titlebar[data-electron][data-os="mac"]{padding-left:82px}
686
+ .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)}
687
+ .flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}
688
+ .flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:4px;min-width:0}.flux-tb-right{justify-content:flex-end}
689
+ .flux-brand{display:flex;align-items:center;gap:8px;font-weight:600;margin-right:8px;white-space:nowrap}
690
+ .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}
691
+ .flux-tb-btn:hover,.flux-tb-btn.is-open{background:var(--flux-border)}
692
+ .flux-menubar{display:flex}.flux-menu{position:relative}
693
+ .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}
694
+ .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}
695
+ .flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}
696
+ .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}
697
+ .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)}
698
+ .flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);font:inherit;outline:none}
699
+ .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}
700
+ .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}
701
+ .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}
702
+ .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)}
703
+ .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)}
704
+ .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)}
705
+ .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}
706
+ .flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:12px;padding:0}
707
+ .flux-toasts{position:fixed;top:48px;right:12px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}
708
+ .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}
709
+ .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)}
710
+ .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}
711
+ .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)}
676
712
  `;
677
713
 
678
714
  // src/react/http-bridge.ts
@@ -721,6 +757,428 @@ function createHttpBridge(baseUrl = "") {
721
757
  };
722
758
  }
723
759
 
760
+ // src/react/os.ts
761
+ import { useMemo as useMemo4 } from "react";
762
+ function detectOS(ua = typeof navigator === "undefined" ? "" : navigator.userAgent) {
763
+ if (/Mac|iPhone|iPad/i.test(ua)) return "mac";
764
+ if (/Windows/i.test(ua)) return "windows";
765
+ if (/Linux|X11|CrOS/i.test(ua)) return "linux";
766
+ return "other";
767
+ }
768
+ var useOS = () => useMemo4(() => detectOS(), []);
769
+ function formatShortcut(binding, os = detectOS()) {
770
+ const mac = os === "mac";
771
+ const map = mac ? { mod: "\u2318", ctrl: "\u2303", shift: "\u21E7", alt: "\u2325" } : { mod: "Ctrl", ctrl: "Ctrl", shift: "Shift", alt: "Alt" };
772
+ const parts = binding.split("+").map((p) => map[p.toLowerCase()] ?? (p.length === 1 ? p.toUpperCase() : p));
773
+ return mac ? parts.join("") : parts.join("+");
774
+ }
775
+
776
+ // src/react/icons.tsx
777
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
778
+ var PATHS = {
779
+ search: "M11 4a7 7 0 105.2 11.7l3.5 3.5 1.4-1.4-3.5-3.5A7 7 0 0011 4zm0 2a5 5 0 110 10 5 5 0 010-10z",
780
+ bell: "M12 3a6 6 0 00-6 6v3.6L4.3 15.3A1 1 0 005 17h14a1 1 0 00.7-1.7L18 12.6V9a6 6 0 00-6-6zm-2 16a2 2 0 004 0h-4z",
781
+ sun: "M12 7a5 5 0 100 10 5 5 0 000-10zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM1 11h3v2H1v-2zm19 0h3v2h-3v-2zM4.2 5.6l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zm12.1 12.1l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zM5.6 19.8l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1zM18.4 7.7l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1z",
782
+ moon: "M20 14.5A8.5 8.5 0 019.5 4a8.5 8.5 0 1010.5 10.5z",
783
+ plug: "M9 2v5H7v3a5 5 0 004 4.9V19H8v2h8v-2h-3v-4.1A5 5 0 0017 10V7h-2V2h-2v5h-2V2H9z",
784
+ settings: "M12 8a4 4 0 100 8 4 4 0 000-8zm8.9 5l1.7 1.3-2 3.4-2-.8a7.6 7.6 0 01-1.7 1l-.3 2.1h-4l-.3-2.1a7.6 7.6 0 01-1.7-1l-2 .8-2-3.4L5.3 13a7 7 0 010-2L3.6 9.7l2-3.4 2 .8a7.6 7.6 0 011.7-1L9.6 4h4l.3 2.1a7.6 7.6 0 011.7 1l2-.8 2 3.4L17.9 11a7 7 0 010 2z",
785
+ check: "M9 16.2l-3.5-3.5L4 14.2l5 5 11-11-1.5-1.5L9 16.2z",
786
+ close: "M6.4 5l-1.4 1.4L10.6 12 5 17.6 6.4 19l5.6-5.6 5.6 5.6 1.4-1.4-5.6-5.6L19 6.4 17.6 5 12 10.6 6.4 5z",
787
+ chevron: "M7 10l5 5 5-5H7z",
788
+ play: "M8 5v14l11-7L8 5z",
789
+ image: "M4 5h16a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V6a1 1 0 011-1zm3 3a2 2 0 100 4 2 2 0 000-4zm-3 9h16v-3l-4-4-5 5-3-3-4 4v1z",
790
+ command: "M7 3a4 4 0 00-1 7.9v2.2A4 4 0 107 21a4 4 0 004-4h2a4 4 0 104-4 4 4 0 00-1-.1V10.9A4 4 0 1017 3a4 4 0 00-4 4h-2a4 4 0 00-4-4zm4 9H9V10h2v2zm4 0h-2V10h2v2z"
791
+ };
792
+ function Icon({ name, d, size = 16, title }) {
793
+ const path = d ?? (name ? PATHS[name] : void 0) ?? PATHS["plug"];
794
+ return /* @__PURE__ */ jsxs4("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": title ? void 0 : true, role: title ? "img" : void 0, className: "flux-icon", children: [
795
+ title && /* @__PURE__ */ jsx4("title", { children: title }),
796
+ /* @__PURE__ */ jsx4("path", { d: path })
797
+ ] });
798
+ }
799
+ var iconNames = Object.keys(PATHS);
800
+
801
+ // src/react/markdown.tsx
802
+ import React4 from "react";
803
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
804
+ var safeHref = (h) => /^https:\/\/[^\s]+$/i.test(h) || h.startsWith("#") ? h : void 0;
805
+ function inline(src, key = 0) {
806
+ const out = [];
807
+ const re = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*\s][^*]*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
808
+ let last = 0;
809
+ let m;
810
+ let i = 0;
811
+ while (m = re.exec(src)) {
812
+ if (m.index > last) out.push(src.slice(last, m.index));
813
+ const tok = m[0];
814
+ const k = `${key}-${i++}`;
815
+ if (tok.startsWith("`")) out.push(/* @__PURE__ */ jsx5("code", { children: tok.slice(1, -1) }, k));
816
+ else if (tok.startsWith("**")) out.push(/* @__PURE__ */ jsx5("strong", { children: tok.slice(2, -2) }, k));
817
+ else if (tok.startsWith("*")) out.push(/* @__PURE__ */ jsx5("em", { children: tok.slice(1, -1) }, k));
818
+ else {
819
+ const mm = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(tok);
820
+ const href = safeHref(mm[2]);
821
+ out.push(href ? /* @__PURE__ */ jsx5("a", { href, target: "_blank", rel: "noopener noreferrer", children: mm[1] }, k) : mm[1]);
822
+ }
823
+ last = m.index + tok.length;
824
+ }
825
+ if (last < src.length) out.push(src.slice(last));
826
+ return out;
827
+ }
828
+ function Markdown({ source }) {
829
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
830
+ const blocks = [];
831
+ let i = 0;
832
+ let k = 0;
833
+ while (i < lines.length) {
834
+ const line = lines[i];
835
+ if (!line.trim()) {
836
+ i++;
837
+ continue;
838
+ }
839
+ if (line.startsWith("```")) {
840
+ const buf2 = [];
841
+ i++;
842
+ while (i < lines.length && !lines[i].startsWith("```")) buf2.push(lines[i++]);
843
+ i++;
844
+ blocks.push(/* @__PURE__ */ jsx5("pre", { children: /* @__PURE__ */ jsx5("code", { children: buf2.join("\n") }) }, k++));
845
+ continue;
846
+ }
847
+ const h = /^(#{1,4})\s+(.*)$/.exec(line);
848
+ if (h) {
849
+ const L = `h${h[1].length + 1}`;
850
+ blocks.push(React4.createElement(L, { key: k++ }, inline(h[2])));
851
+ i++;
852
+ continue;
853
+ }
854
+ if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
855
+ blocks.push(/* @__PURE__ */ jsx5("hr", {}, k++));
856
+ i++;
857
+ continue;
858
+ }
859
+ if (line.startsWith(">")) {
860
+ const buf2 = [];
861
+ while (i < lines.length && lines[i].startsWith(">")) buf2.push(lines[i++].replace(/^>\s?/, ""));
862
+ blocks.push(/* @__PURE__ */ jsx5("blockquote", { children: inline(buf2.join(" ")) }, k++));
863
+ continue;
864
+ }
865
+ if (line.includes("|") && /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(lines[i + 1] ?? "")) {
866
+ const cells = (l) => l.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
867
+ const head = cells(line);
868
+ i += 2;
869
+ const rows = [];
870
+ while (i < lines.length && lines[i].includes("|") && lines[i].trim()) rows.push(cells(lines[i++]));
871
+ blocks.push(
872
+ /* @__PURE__ */ jsxs5("table", { children: [
873
+ /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsx5("tr", { children: head.map((c, j) => /* @__PURE__ */ jsx5("th", { children: inline(c) }, j)) }) }),
874
+ /* @__PURE__ */ jsx5("tbody", { children: rows.map((r, ri) => /* @__PURE__ */ jsx5("tr", { children: r.map((c, j) => /* @__PURE__ */ jsx5("td", { children: inline(c) }, j)) }, ri)) })
875
+ ] }, k++)
876
+ );
877
+ continue;
878
+ }
879
+ const li = /^(\s*)([-*]|\d+\.)\s+/;
880
+ if (li.test(line)) {
881
+ const ordered = /^\s*\d+\./.test(line);
882
+ const items = [];
883
+ while (i < lines.length && li.test(lines[i])) items.push(/* @__PURE__ */ jsx5("li", { children: inline(lines[i++].replace(li, "")) }, items.length));
884
+ blocks.push(ordered ? /* @__PURE__ */ jsx5("ol", { children: items }, k++) : /* @__PURE__ */ jsx5("ul", { children: items }, k++));
885
+ continue;
886
+ }
887
+ const buf = [];
888
+ while (i < lines.length && lines[i].trim() && !/^(#{1,4}\s|```|>|\s*([-*]|\d+\.)\s)/.test(lines[i]) && !(lines[i].includes("|") && /^\s*\|?\s*:?-{2,}/.test(lines[i + 1] ?? ""))) buf.push(lines[i++]);
889
+ blocks.push(/* @__PURE__ */ jsx5("p", { children: inline(buf.join(" ")) }, k++));
890
+ }
891
+ return /* @__PURE__ */ jsx5("div", { className: "flux-md", children: blocks });
892
+ }
893
+
894
+ // src/react/theme.tsx
895
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo5, useState as useState4 } from "react";
896
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
897
+ function declareHostPoints(framework) {
898
+ const ep = framework.manager.extensionPoints;
899
+ if (!ep.definition("theme")) {
900
+ ep.declare({ name: "theme", contributions: { themes: s.object({ id: s.string(), label: s.string(), dark: s.boolean().optional(), tokens: s.record(s.string()) }) } });
901
+ }
902
+ if (!ep.definition("titlebar")) {
903
+ ep.declare({ name: "titlebar", contributions: { searchProviders: s.object({ id: s.string(), label: s.string(), search: s.fn() }) } });
904
+ }
905
+ }
906
+ var Ctx = createContext2(null);
907
+ var read = (k) => {
908
+ try {
909
+ return localStorage.getItem(k);
910
+ } catch {
911
+ return null;
912
+ }
913
+ };
914
+ var write = (k, v) => {
915
+ try {
916
+ v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, v);
917
+ } catch {
918
+ }
919
+ };
920
+ function ThemeProvider({ children, storageKey = "fluxplugin.theme", onResolved }) {
921
+ const themes = useExtensionItems("theme", "themes");
922
+ const [mode, setModeState] = useState4(() => read(storageKey + ".mode") || "system");
923
+ const [themeId, setThemeIdState] = useState4(() => read(storageKey + ".id") ?? void 0);
924
+ const [systemDark, setSystemDark] = useState4(() => typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches);
925
+ useEffect4(() => {
926
+ if (typeof matchMedia === "undefined") return;
927
+ const mq = matchMedia("(prefers-color-scheme: dark)");
928
+ const l = (e) => setSystemDark(e.matches);
929
+ mq.addEventListener("change", l);
930
+ return () => mq.removeEventListener("change", l);
931
+ }, []);
932
+ const theme = themes.find((t) => t.id === themeId);
933
+ const resolved = theme ? theme.dark ? "dark" : "light" : mode === "system" ? systemDark ? "dark" : "light" : mode;
934
+ useEffect4(() => {
935
+ const root = document.documentElement;
936
+ root.setAttribute("data-theme", resolved);
937
+ const applied = Object.entries(theme?.tokens ?? {}).filter(([k]) => /^--[\w-]+$/.test(k));
938
+ for (const [k, v] of applied) root.style.setProperty(k, v);
939
+ const cs = getComputedStyle(root);
940
+ onResolved?.({ dark: resolved === "dark", bg: cs.getPropertyValue("--flux-bg").trim(), fg: cs.getPropertyValue("--flux-fg").trim(), surface: cs.getPropertyValue("--flux-surface").trim() });
941
+ return () => applied.forEach(([k]) => root.style.removeProperty(k));
942
+ }, [resolved, theme]);
943
+ const setMode = useCallback3((m) => (setModeState(m), setThemeIdState(void 0), write(storageKey + ".mode", m), write(storageKey + ".id", void 0)), [storageKey]);
944
+ const setThemeId = useCallback3((id) => (setThemeIdState(id), write(storageKey + ".id", id)), [storageKey]);
945
+ const value = useMemo5(() => ({ mode, setMode, themeId, setThemeId, resolved, themes, toggle: () => setMode(resolved === "dark" ? "light" : "dark") }), [mode, setMode, themeId, setThemeId, resolved, themes]);
946
+ return /* @__PURE__ */ jsx6(Ctx.Provider, { value, children });
947
+ }
948
+ function useTheme() {
949
+ const c = useContext2(Ctx);
950
+ if (!c) throw new Error("useTheme must be used inside <ThemeProvider>");
951
+ return c;
952
+ }
953
+ function ThemeSwitcher() {
954
+ const { resolved, toggle, themes, themeId, setThemeId, mode, setMode } = useTheme();
955
+ usePluginFramework();
956
+ return /* @__PURE__ */ jsxs6("span", { className: "flux-theme-switch", children: [
957
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "flux-tb-btn", onClick: toggle, title: `Theme: ${resolved} (click to switch)`, "aria-label": "Toggle theme", "data-testid": "theme-toggle", children: /* @__PURE__ */ jsx6(Icon, { name: resolved === "dark" ? "moon" : "sun" }) }),
958
+ themes.length > 0 && /* @__PURE__ */ jsxs6("select", { className: "flux-select", "aria-label": "Theme", value: themeId ?? `mode:${mode}`, onChange: (e) => e.target.value.startsWith("mode:") ? setMode(e.target.value.slice(5)) : setThemeId(e.target.value), children: [
959
+ /* @__PURE__ */ jsx6("option", { value: "mode:system", children: "System" }),
960
+ /* @__PURE__ */ jsx6("option", { value: "mode:light", children: "Light" }),
961
+ /* @__PURE__ */ jsx6("option", { value: "mode:dark", children: "Dark" }),
962
+ themes.map((t) => /* @__PURE__ */ jsx6("option", { value: t.id, children: t.label }, t.id))
963
+ ] })
964
+ ] });
965
+ }
966
+
967
+ // src/react/titlebar.tsx
968
+ import { useEffect as useEffect5, useMemo as useMemo6, useRef as useRef3, useState as useState5, useSyncExternalStore as useSyncExternalStore4 } from "react";
969
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
970
+ function AppMenuBar() {
971
+ const fw = usePluginFramework();
972
+ const os = useOS();
973
+ const menus = fw.manager.menus;
974
+ useSyncExternalStore4(menus.subscribe, () => menus.version, () => menus.version);
975
+ const [open, setOpen] = useState5(null);
976
+ const ref = useRef3(null);
977
+ useEffect5(() => {
978
+ const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(null);
979
+ const key = (e) => e.key === "Escape" && setOpen(null);
980
+ window.addEventListener("mousedown", off);
981
+ window.addEventListener("keydown", key);
982
+ return () => (window.removeEventListener("mousedown", off), window.removeEventListener("keydown", key));
983
+ }, []);
984
+ const top = menus.items("application");
985
+ if (!top.length) return null;
986
+ const run = (item) => {
987
+ setOpen(null);
988
+ if (item.command) void fw.manager.commands.execute(item.command, ...item.args ?? []).catch((e) => fw.manager.logs.logger("host").error(String(e)));
989
+ else if (item.path) navigate(item.path);
990
+ };
991
+ const shortcut = (item) => {
992
+ const k = item.command ? fw.manager.commands.get(item.command)?.keybinding : void 0;
993
+ return k ? formatShortcut(k, os) : "";
994
+ };
995
+ return /* @__PURE__ */ jsx7("div", { className: "flux-menubar", role: "menubar", ref, children: top.map((m) => /* @__PURE__ */ jsxs7("div", { className: "flux-menu", children: [
996
+ /* @__PURE__ */ jsx7(
997
+ "button",
998
+ {
999
+ type: "button",
1000
+ role: "menuitem",
1001
+ className: "flux-tb-btn" + (open === m.id ? " is-open" : ""),
1002
+ "aria-haspopup": !!m.children?.length,
1003
+ "aria-expanded": open === m.id,
1004
+ onClick: () => m.children?.length ? setOpen(open === m.id ? null : m.id) : run(m),
1005
+ onMouseEnter: () => open && m.children?.length && setOpen(m.id),
1006
+ children: m.label
1007
+ }
1008
+ ),
1009
+ open === m.id && m.children && /* @__PURE__ */ jsx7("ul", { className: "flux-dropdown", role: "menu", children: m.children.map(
1010
+ (c) => c.separator ? /* @__PURE__ */ jsx7("li", { className: "flux-sep", role: "separator" }, c.id) : /* @__PURE__ */ jsx7("li", { role: "none", children: /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", className: "flux-dd-item", onClick: () => run(c), children: [
1011
+ /* @__PURE__ */ jsx7("span", { children: c.label }),
1012
+ /* @__PURE__ */ jsx7("kbd", { children: shortcut(c) })
1013
+ ] }) }, c.id)
1014
+ ) })
1015
+ ] }, m.id)) });
1016
+ }
1017
+ function SearchBox({ placeholder = "Search\u2026" }) {
1018
+ const fw = usePluginFramework();
1019
+ const providers = useExtensionItems("titlebar", "searchProviders");
1020
+ const [q, setQ] = useState5("");
1021
+ const [results, setResults] = useState5([]);
1022
+ const [idx, setIdx] = useState5(0);
1023
+ const [focus, setFocus] = useState5(false);
1024
+ const input = useRef3(null);
1025
+ useEffect5(() => {
1026
+ const k = (e) => (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "f" && !e.shiftKey && (e.preventDefault(), input.current?.focus());
1027
+ window.addEventListener("keydown", k);
1028
+ return () => window.removeEventListener("keydown", k);
1029
+ }, []);
1030
+ useEffect5(() => {
1031
+ const query = q.trim();
1032
+ if (!query) return setResults((prev) => prev.length ? [] : prev);
1033
+ let live = true;
1034
+ (async () => {
1035
+ const lower = query.toLowerCase();
1036
+ const own = [
1037
+ ...fw.manager.commands.palette(query).slice(0, 5).map((c) => ({ id: "cmd:" + c.id, title: c.title, subtitle: c.category, group: "Commands", run: () => void fw.manager.commands.execute(c.id) })),
1038
+ ...fw.manager.menus.items("sidebar").filter((m) => m.path && m.label.toLowerCase().includes(lower)).map((m) => ({ id: "page:" + m.id, title: m.label, group: "Pages", run: () => navigate(m.path) }))
1039
+ ];
1040
+ const extra = await Promise.all(providers.map(async (p) => {
1041
+ try {
1042
+ return (await p.search(query) ?? []).map((r) => ({ ...r, group: r.group ?? p.label }));
1043
+ } catch {
1044
+ return [];
1045
+ }
1046
+ }));
1047
+ if (live) setResults([...own, ...extra.flat()].slice(0, 12)), setIdx(0);
1048
+ })();
1049
+ return () => {
1050
+ live = false;
1051
+ };
1052
+ }, [q, fw, providers]);
1053
+ const go = (r) => {
1054
+ if (!r) return;
1055
+ setQ("");
1056
+ input.current?.blur();
1057
+ void r.run();
1058
+ };
1059
+ return /* @__PURE__ */ jsxs7("div", { className: "flux-search", "data-testid": "searchbox", children: [
1060
+ /* @__PURE__ */ jsx7(Icon, { name: "search" }),
1061
+ /* @__PURE__ */ jsx7(
1062
+ "input",
1063
+ {
1064
+ ref: input,
1065
+ value: q,
1066
+ placeholder,
1067
+ "aria-label": "Search",
1068
+ onChange: (e) => setQ(e.target.value),
1069
+ onFocus: () => setFocus(true),
1070
+ onBlur: () => setTimeout(() => setFocus(false), 120),
1071
+ onKeyDown: (e) => {
1072
+ if (e.key === "ArrowDown") e.preventDefault(), setIdx((i) => Math.min(i + 1, results.length - 1));
1073
+ else if (e.key === "ArrowUp") e.preventDefault(), setIdx((i) => Math.max(i - 1, 0));
1074
+ else if (e.key === "Enter") go(results[idx]);
1075
+ else if (e.key === "Escape") setQ(""), input.current?.blur();
1076
+ }
1077
+ }
1078
+ ),
1079
+ focus && q.trim() && /* @__PURE__ */ jsxs7("ul", { className: "flux-dropdown flux-search-results", role: "listbox", children: [
1080
+ results.length === 0 && /* @__PURE__ */ jsx7("li", { className: "flux-empty", children: "No results" }),
1081
+ results.map((r, i) => /* @__PURE__ */ jsxs7("li", { role: "option", "aria-selected": i === idx, children: [
1082
+ (i === 0 || results[i - 1].group !== r.group) && /* @__PURE__ */ jsx7("div", { className: "flux-group", children: r.group }),
1083
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "flux-dd-item" + (i === idx ? " is-active" : ""), onMouseDown: (e) => (e.preventDefault(), go(r)), children: [
1084
+ /* @__PURE__ */ jsx7("span", { children: r.title }),
1085
+ r.subtitle && /* @__PURE__ */ jsx7("small", { children: r.subtitle })
1086
+ ] })
1087
+ ] }, r.id))
1088
+ ] })
1089
+ ] });
1090
+ }
1091
+ function useNotifications() {
1092
+ const { manager } = usePluginFramework();
1093
+ const n = manager.notifications;
1094
+ useSyncExternalStore4(n.subscribe, () => n.version, () => n.version);
1095
+ return n;
1096
+ }
1097
+ function NotificationBell() {
1098
+ const fw = usePluginFramework();
1099
+ const center = useNotifications();
1100
+ const [open, setOpen] = useState5(false);
1101
+ const ref = useRef3(null);
1102
+ useEffect5(() => {
1103
+ const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(false);
1104
+ window.addEventListener("mousedown", off);
1105
+ return () => window.removeEventListener("mousedown", off);
1106
+ }, []);
1107
+ const unread = center.unread;
1108
+ return /* @__PURE__ */ jsxs7("div", { className: "flux-bell", ref, children: [
1109
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "flux-tb-btn", "aria-label": "Notifications", "data-testid": "bell", onClick: () => (setOpen(!open), !open && center.markRead()), children: [
1110
+ /* @__PURE__ */ jsx7(Icon, { name: "bell" }),
1111
+ unread > 0 && /* @__PURE__ */ jsx7("span", { className: "flux-badge", "data-testid": "bell-count", children: unread })
1112
+ ] }),
1113
+ open && /* @__PURE__ */ jsxs7("div", { className: "flux-dropdown flux-notif-panel", role: "dialog", "aria-label": "Notifications", children: [
1114
+ /* @__PURE__ */ jsxs7("div", { className: "flux-notif-head", children: [
1115
+ /* @__PURE__ */ jsx7("strong", { children: "Notifications" }),
1116
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", onClick: () => center.clear(), children: "Clear all" })
1117
+ ] }),
1118
+ center.list().length === 0 && /* @__PURE__ */ jsx7("div", { className: "flux-empty", children: "You're all caught up" }),
1119
+ center.list().map((n) => /* @__PURE__ */ jsxs7("div", { className: "flux-notif flux-" + n.level, children: [
1120
+ /* @__PURE__ */ jsxs7("div", { className: "flux-notif-main", children: [
1121
+ /* @__PURE__ */ jsx7("strong", { children: n.title }),
1122
+ n.body && /* @__PURE__ */ jsx7("p", { children: n.body }),
1123
+ /* @__PURE__ */ jsxs7("small", { children: [
1124
+ n.owner,
1125
+ " \xB7 ",
1126
+ new Date(n.time).toLocaleTimeString()
1127
+ ] })
1128
+ ] }),
1129
+ /* @__PURE__ */ jsxs7("div", { className: "flux-notif-actions", children: [
1130
+ n.actions.map((a) => /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", onClick: () => void fw.manager.commands.execute(a.command, ...a.args ?? []), children: a.label }, a.label)),
1131
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", "aria-label": "Dismiss", onClick: () => center.dismiss(n.id), children: /* @__PURE__ */ jsx7(Icon, { name: "close", size: 12 }) })
1132
+ ] })
1133
+ ] }, n.id))
1134
+ ] })
1135
+ ] });
1136
+ }
1137
+ function ToastHost() {
1138
+ const { manager } = usePluginFramework();
1139
+ const [toasts, setToasts] = useState5([]);
1140
+ useEffect5(() => {
1141
+ const timers = [];
1142
+ const show = (n, remaining = n.timeoutMs) => {
1143
+ if (n.timeoutMs <= 0 || remaining <= 0) return;
1144
+ setToasts((t) => [n, ...t.filter((x) => x.id !== n.id)].slice(0, 4));
1145
+ timers.push(setTimeout(() => setToasts((t) => t.filter((x) => x.id !== n.id)), remaining));
1146
+ };
1147
+ for (const n of [...manager.notifications.list()].reverse()) if (!n.read) show(n, n.timeoutMs - (Date.now() - n.time));
1148
+ const sub = manager.notifications.onPush((n) => show(n));
1149
+ return () => (sub.dispose(), timers.forEach(clearTimeout));
1150
+ }, [manager]);
1151
+ return /* @__PURE__ */ jsx7("div", { className: "flux-toasts", "aria-live": "polite", "data-testid": "toasts", children: toasts.map((n) => /* @__PURE__ */ jsxs7("div", { className: "flux-toast flux-" + n.level, role: "status", children: [
1152
+ /* @__PURE__ */ jsx7("strong", { children: n.title }),
1153
+ n.body && /* @__PURE__ */ jsxs7("span", { children: [
1154
+ " ",
1155
+ n.body
1156
+ ] })
1157
+ ] }, n.id)) });
1158
+ }
1159
+ function TitleBar({ title, logo, searchPlaceholder, show }) {
1160
+ const os = useOS();
1161
+ const electron = typeof window !== "undefined" && !!window.__fluxplugin;
1162
+ const s2 = { menu: true, search: true, notifications: true, theme: true, ...show };
1163
+ const brand = useMemo6(() => /* @__PURE__ */ jsxs7("div", { className: "flux-brand", children: [
1164
+ logo,
1165
+ title && /* @__PURE__ */ jsx7("span", { children: title })
1166
+ ] }), [logo, title]);
1167
+ return /* @__PURE__ */ jsxs7("header", { className: "flux-titlebar", "data-os": os, "data-electron": electron || void 0, "data-testid": "titlebar", children: [
1168
+ /* @__PURE__ */ jsxs7("div", { className: "flux-tb-left", children: [
1169
+ brand,
1170
+ s2.menu && /* @__PURE__ */ jsx7(AppMenuBar, {}),
1171
+ /* @__PURE__ */ jsx7(PluginSlot, { name: "titlebar.left" })
1172
+ ] }),
1173
+ /* @__PURE__ */ jsx7("div", { className: "flux-tb-center", children: s2.search && /* @__PURE__ */ jsx7(SearchBox, { placeholder: searchPlaceholder }) }),
1174
+ /* @__PURE__ */ jsxs7("div", { className: "flux-tb-right", children: [
1175
+ /* @__PURE__ */ jsx7(PluginSlot, { name: "titlebar.right" }),
1176
+ s2.theme && /* @__PURE__ */ jsx7(ThemeSwitcher, {}),
1177
+ s2.notifications && /* @__PURE__ */ jsx7(NotificationBell, {})
1178
+ ] })
1179
+ ] });
1180
+ }
1181
+
724
1182
  export {
725
1183
  RemoteError,
726
1184
  PluginsClient,
@@ -755,5 +1213,20 @@ export {
755
1213
  CommandPalette,
756
1214
  baseCss,
757
1215
  HttpBridgeError,
758
- createHttpBridge
1216
+ createHttpBridge,
1217
+ detectOS,
1218
+ useOS,
1219
+ formatShortcut,
1220
+ Icon,
1221
+ iconNames,
1222
+ Markdown,
1223
+ declareHostPoints,
1224
+ ThemeProvider,
1225
+ useTheme,
1226
+ ThemeSwitcher,
1227
+ AppMenuBar,
1228
+ SearchBox,
1229
+ NotificationBell,
1230
+ ToastHost,
1231
+ TitleBar
759
1232
  };
@@ -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,9 @@ import {
65
66
  serializeError,
66
67
  toDisposable,
67
68
  validateManifest,
69
+ videoEmbedUrl,
68
70
  withTimeout
69
- } from "./chunks/chunk-Q75AOCXD.js";
71
+ } from "./chunks/chunk-TL63576R.js";
70
72
  export {
71
73
  BUILTIN_PERMISSIONS,
72
74
  CHANNELS,
@@ -86,6 +88,7 @@ export {
86
88
  MGMT,
87
89
  MemoryStorageBackend,
88
90
  MenuRegistry,
91
+ NotificationCenter,
89
92
  PermissionDeniedError,
90
93
  PermissionManager,
91
94
  PluginManager,
@@ -134,5 +137,6 @@ export {
134
137
  serializeError,
135
138
  toDisposable,
136
139
  validateManifest,
140
+ videoEmbedUrl,
137
141
  withTimeout
138
142
  };