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
@@ -0,0 +1,53 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { RendererFramework } from "./runtime.js";
3
+ export type ThemeMode = "light" | "dark" | "system";
4
+ export interface ThemeDefinition {
5
+ id: string;
6
+ label: string;
7
+ /** Whether this theme is dark (drives `data-theme` and native overlay colours). */
8
+ dark?: boolean;
9
+ /** CSS custom properties, e.g. `{ "--flux-accent": "#e11d48", "--flux-bg": "#fff" }`. */
10
+ tokens: Record<string, string>;
11
+ }
12
+ export interface SearchResult {
13
+ id: string;
14
+ title: string;
15
+ subtitle?: string;
16
+ group?: string;
17
+ run: () => void | Promise<void>;
18
+ }
19
+ export interface SearchProvider {
20
+ id: string;
21
+ label: string;
22
+ search: (query: string) => SearchResult[] | Promise<SearchResult[]>;
23
+ }
24
+ /** Declares the extension points used by the built-in theme and title-bar components. Call before `framework.start()`. */
25
+ export declare function declareHostPoints(framework: RendererFramework): void;
26
+ interface ThemeCtx {
27
+ mode: ThemeMode;
28
+ setMode(m: ThemeMode): void;
29
+ themeId: string | undefined;
30
+ setThemeId(id: string | undefined): void;
31
+ resolved: "light" | "dark";
32
+ themes: ThemeDefinition[];
33
+ toggle(): void;
34
+ }
35
+ /**
36
+ * Light / dark / system modes plus themes contributed by plugins (extension point `theme`).
37
+ * Applies `data-theme` and the theme's CSS variables on <html>, persists the choice, and reports the resolved
38
+ * colours so the host can update the native window controls (`onResolved`).
39
+ */
40
+ export declare function ThemeProvider({ children, storageKey, onResolved }: {
41
+ children: ReactNode;
42
+ storageKey?: string;
43
+ onResolved?: (r: {
44
+ dark: boolean;
45
+ bg: string;
46
+ fg: string;
47
+ surface: string;
48
+ }) => void;
49
+ }): React.JSX.Element;
50
+ export declare function useTheme(): ThemeCtx;
51
+ /** Sun/moon button; with plugin themes available it also offers a small picker. */
52
+ export declare function ThemeSwitcher(): React.ReactElement;
53
+ export {};
@@ -0,0 +1,28 @@
1
+ import React, { type ReactNode } from "react";
2
+ /** Application menu (File, Edit, View…) built from `menus` registered with `location: "application"`. Plugins add to it. */
3
+ export declare function AppMenuBar(): React.ReactElement | null;
4
+ /** Search box: commands, pages, and results from `titlebar.searchProviders` contributed by plugins. */
5
+ export declare function SearchBox({ placeholder }: {
6
+ placeholder?: string;
7
+ }): React.ReactElement;
8
+ export declare function NotificationBell(): React.ReactElement;
9
+ /** Transient toasts for new notifications (top-right, under the title bar). */
10
+ export declare function ToastHost(): React.ReactElement;
11
+ export interface TitleBarProps {
12
+ title?: string;
13
+ logo?: ReactNode;
14
+ searchPlaceholder?: string;
15
+ /** Hide parts you do not need. */
16
+ show?: {
17
+ menu?: boolean;
18
+ search?: boolean;
19
+ notifications?: boolean;
20
+ theme?: boolean;
21
+ };
22
+ }
23
+ /**
24
+ * Custom window title bar that adapts to the OS: macOS keeps the native traffic lights (space is reserved on the left),
25
+ * Windows/Linux keep native window buttons through the window-controls overlay (space reserved on the right).
26
+ * Layout: [logo · title · menu bar] [search] [slot titlebar.right · theme · notifications].
27
+ */
28
+ export declare function TitleBar({ title, logo, searchPlaceholder, show }: TitleBarProps): React.ReactElement;
@@ -0,0 +1,257 @@
1
+ # Customizing your app and its API
2
+
3
+ This guide shows, step by step, how to **extend what plugins can do** in your app: add your own APIs, custom
4
+ permissions, hooks, a custom title/menu bar, search, notifications, themes, icons and Tailwind.
5
+ Everything below lives in the `src/app/` folder that `fluxplugin init` generated.
6
+
7
+ ```
8
+ src/app/shared/ names used by both processes (constants.ts, types.ts)
9
+ src/app/main/ Electron side (framework.ts, operations.ts, window.ts, window-api.ts)
10
+ src/app/renderer/ React side (bootstrap.ts, extension-points.ts, menus.ts, shell/, pages/ …)
11
+ ```
12
+
13
+ **The mental model.** Your app *offers* extension surfaces; plugins *contribute* to them; nothing is patched.
14
+
15
+ | You want plugins to… | You offer… | Plugin uses… |
16
+ |---|---|---|
17
+ | add a screen | nothing (routes are open) | `routes`, `meta.menu` |
18
+ | add a button/widget in your UI | a `<PluginSlot name="x" />` | `components: [{ slot: "x", component }]` |
19
+ | add *data* to a list (toolbar actions, tabs…) | an extension point + schema | `extensions: [{ point, contribution }]` |
20
+ | react to / change what your app does | a hook operation (`hooks.run`) or an event (`events.emit`) | `hooks`, `events` |
21
+ | call your app's features | a host API (`manager.provide`) | `ctx.host.api("name")` |
22
+ | talk to the main process | an IPC handler | `ipc`, `preload` |
23
+
24
+ ---
25
+
26
+ ## 1. Add your own API for the renderer (host operation)
27
+
28
+ A *host operation* is a function in the main process that your UI calls. Wrap it in a **hook** so plugins can validate,
29
+ change or veto it without touching your code.
30
+
31
+ **`src/app/shared/constants.ts`** — name it once:
32
+ ```ts
33
+ export const CHANNELS = { createItem: "host.item.create", renameItem: "host.item.rename" } as const;
34
+ export const OPERATIONS = { createItem: "item.create", renameItem: "item.rename" } as const;
35
+ ```
36
+
37
+ **`src/app/main/operations.ts`** — implement it (input is validated by the schema):
38
+ ```ts
39
+ import { s } from "fluxplugin";
40
+ fw.manager.ipc.handle(
41
+ CHANNELS.renameItem,
42
+ (input: { id: string; name: string }) =>
43
+ fw.manager.hooks.run(OPERATIONS.renameItem, input, (i) => db.rename(i.id, i.name)), // ← plugins can hook here
44
+ "host",
45
+ { input: s.object({ id: s.string(), name: s.string({ min: 1 }) }) },
46
+ );
47
+ ```
48
+
49
+ **`src/app/main/framework.ts`** — allow the renderer to call it (the renderer can call *only* allow-listed channels):
50
+ ```ts
51
+ allowedRendererChannels: [CHANNELS.createItem, CHANNELS.renameItem],
52
+ ```
53
+
54
+ **`src/app/renderer/services/items.ts`** — one place that knows the channel:
55
+ ```ts
56
+ renameItem: (bridge, id, name) => bridge.invoke(CHANNELS.renameItem, { id, name }),
57
+ ```
58
+
59
+ A plugin can now change your operation **without** editing your app:
60
+ ```ts
61
+ // plugin main entry
62
+ export default definePlugin({
63
+ hooks: [
64
+ { name: "before.item.rename", handler: (ctx) => { ctx.input = { ...ctx.input, name: ctx.input.name.trim() }; } },
65
+ { name: "validate.item.rename", handler: (ctx) => { if (ctx.input.name === "admin") ctx.cancel("reserved name"); } },
66
+ { name: "transform.item.rename", handler: (ctx) => ({ ...ctx.result, renamedBy: "my-plugin" }) },
67
+ { name: "after.item.rename", handler: (ctx) => void audit(ctx.input) }, // errors here never break the operation
68
+ ],
69
+ });
70
+ ```
71
+ Hook kinds: `validate` → `before` → `around` (middleware) → *your function* → `transform` → `after`. Plus `filter.<name>`
72
+ for value pipelines (`await fw.manager.hooks.filter("menu.items", items)`). Plugins use `priority` to order themselves.
73
+
74
+ ## 2. Give plugins controlled access to your app (host APIs + custom permissions)
75
+
76
+ Use this when a plugin needs *your* services (database, billing, files…), not just hooks.
77
+
78
+ ```ts
79
+ // main/framework.ts
80
+ fw.manager.provide(
81
+ "billing", // name
82
+ ({ pluginId }) => ({ // factory: called per plugin, so you know who is asking
83
+ invoices: () => db.invoices.list({ requestedBy: pluginId }),
84
+ }),
85
+ "x.billing.read", // permission required (custom permissions start with "x.")
86
+ );
87
+ ```
88
+ ```jsonc
89
+ // plugin.json
90
+ { "permissions": ["x.billing.read"] }
91
+ ```
92
+ ```ts
93
+ // plugin code
94
+ const billing = ctx.host.api<{ invoices(): Promise<Invoice[]> }>("billing");
95
+ ```
96
+ * Without the permission the call throws `PermissionDeniedError` and the plugin is rolled back.
97
+ * With `requireApproval: true` (framework.ts) users must approve each permission in **Plugins → Details**; they can revoke later, live.
98
+ * Built-in permissions: `ipc services events hooks commands routes menus ui styles storage notifications network filesystem.read filesystem.write database window clipboard shell`.
99
+ *Only the framework's own APIs check them* — `network`, `filesystem.*` etc. are yours to enforce inside your host APIs.
100
+
101
+ ## 3. Events, services and slots
102
+
103
+ ```ts
104
+ fw.manager.events.emit("item.created", item, "host"); // host → plugins (listener: ctx.events.on / `events: []`)
105
+ fw.manager.services.register("clock", { now: () => Date.now() }, "host"); // plugins: ctx.services.get("clock")
106
+ ```
107
+ **Slots** (components) — declare by rendering one; plugins inject (`mode`: `before | after | replace | append | prepend`):
108
+ ```tsx
109
+ <PluginSlot name="dashboard.after" props={{ userId }} /> // host
110
+ // plugin: components: [{ slot: "dashboard.after", component: Widget, priority: 10 }]
111
+ ```
112
+ **Extension points** (validated data) — declare in `renderer/extension-points.ts`, read with a hook:
113
+ ```ts
114
+ framework.manager.extensionPoints.declare({ name: "reports", contributions: { exporters: s.object({ id: s.string(), label: s.string(), run: s.fn() }) } });
115
+ const exporters = useExtensionItems<Exporter>("reports", "exporters"); // host
116
+ // plugin: extensions: [{ point: "reports", contribution: { exporters: [{ id: "pdf", label: "PDF", run }] } }]
117
+ ```
118
+ Declare points **before** `framework.start()` (bootstrap.ts does). Bad contributions are rejected with a clear message.
119
+
120
+ ---
121
+
122
+ ## 4. The custom title bar & menu bar
123
+
124
+ `<TitleBar />` replaces Electron's native menu. It adapts to the OS:
125
+
126
+ | | macOS | Windows / Linux |
127
+ |---|---|---|
128
+ | native window buttons | traffic lights stay (`titleBarStyle: "hiddenInset"`), the bar leaves 82 px on the left | min/max/close stay on the right (`titleBarOverlay`), the bar leaves their width free via `env(titlebar-area-width)` |
129
+ | native menu | minimal one (so ⌘C/⌘V/⌘Q work) | none (`Menu.setApplicationMenu(null)`) |
130
+ | shortcut labels | `⌘⇧R` | `Ctrl+Shift+R` |
131
+ | overlay colour | n/a | follows the theme (`host.window.setTitleBarOverlay`) |
132
+
133
+ Layout: **[logo · title · menu bar] [search] [slot `titlebar.right` · theme · notifications 🔔]**.
134
+ All of it is in `renderer/shell/AppShell.tsx`; hide or replace parts:
135
+ ```tsx
136
+ <TitleBar title="My App" logo={<img src={logo} />} show={{ search: false }} searchPlaceholder="Find anything…" />
137
+ ```
138
+ or compose your own bar from the parts: `AppMenuBar`, `SearchBox`, `NotificationBell`, `ThemeSwitcher`, `PluginSlot`.
139
+
140
+ ### Menus (File / Edit / View …)
141
+ Menus are data in `renderer/menus.ts`. Top-level entries have `children`; items run a **command** (shortcut shown automatically
142
+ from the command's `keybinding`) or open a **route**:
143
+ ```ts
144
+ commands.register({ id: "file.new", title: "New project", category: "File", keybinding: "Mod+N", execute: newProject }, "host");
145
+ menus.register({ id: "app.file", location: "application", label: "File", order: 0, children: [
146
+ { id: "app.file.new", label: "New project", command: "file.new" },
147
+ { id: "app.file.sep", label: "", separator: true },
148
+ { id: "app.file.settings", label: "Settings", path: "/settings" },
149
+ ] }, "host");
150
+ ```
151
+ A **plugin** adds a whole menu *or* items to yours (same `id` namespace; use `order` to place it):
152
+ ```ts
153
+ definePlugin({ menus: [{ id: "p.menu", location: "application", label: "Reports", order: 30,
154
+ children: [{ id: "p.menu.run", label: "Run report", command: "p.run" }] }] })
155
+ ```
156
+ Other menu locations: `sidebar`, `toolbar`, `commandPalette`, `context`, `tray`, `settings` (read them with `useMenu("toolbar")`).
157
+ Use `when: () => boolean` to hide items dynamically.
158
+
159
+ ### Search bar
160
+ The box searches **commands**, **sidebar pages** and anything plugins contribute:
161
+ ```ts
162
+ // plugin
163
+ extensions: [{ point: "titlebar", contribution: { searchProviders: [{
164
+ id: "tickets", label: "Tickets",
165
+ search: async (q) => (await api.find(q)).map((t) => ({ id: t.id, title: t.title, subtitle: "ticket", run: () => (location.hash = "#/tickets/" + t.id) })),
166
+ }] } }]
167
+ ```
168
+ `Ctrl/Cmd+F` focuses it, `↑ ↓ Enter` navigate. A provider that throws is ignored. Command palette: `Ctrl/Cmd+K`.
169
+
170
+ ### Notifications (bell + toasts)
171
+ ```ts
172
+ ctx.notifications.push({ title: "Export finished", body: "report.pdf", level: "success", // plugin (permission "notifications")
173
+ actions: [{ label: "Open", command: "reports.open", args: ["report.pdf"] }], timeoutMs: 8000 }); // 0 = bell only
174
+ framework.manager.notifications.push({ title: "Saved" }); // host
175
+ ```
176
+ Plugins running in the **main process** can push too; they are forwarded to the window. Unread count, history, dismiss and
177
+ "clear all" are built in. These are *in-app* notifications; for OS-level ones call Electron's `Notification` from a host operation.
178
+
179
+ ### Your own window operations
180
+ `main/window-api.ts` shows the pattern (BrowserWindow needed): register an IPC handler, add its channel to
181
+ `allowedRendererChannels`, call it from a `renderer/services/*.ts` file. Example: maximize, always-on-top, zoom.
182
+
183
+ ---
184
+
185
+ ## 5. Themes, colours and icons
186
+
187
+ Everything is styled with CSS variables, so one switch re-themes the app **and** plugins:
188
+
189
+ | Variable | Meaning | Variable | Meaning |
190
+ |---|---|---|---|
191
+ | `--flux-bg` | page background | `--flux-accent` | brand/selection colour |
192
+ | `--flux-fg` | text | `--flux-accent-fg` | text on accent |
193
+ | `--flux-surface` | bars, cards, panels | `--flux-muted` | secondary text |
194
+ | `--flux-border` | lines | `--flux-ok / -warn / -danger` | status colours |
195
+
196
+ Light / dark / system works out of the box (`<ThemeProvider>` in `providers/AppProviders.tsx`, button in the title bar,
197
+ choice persisted). **Plugins can ship themes**:
198
+ ```ts
199
+ extensions: [{ point: "theme", contribution: { themes: [{
200
+ id: "ocean", label: "Ocean", dark: true,
201
+ tokens: { "--flux-bg": "#06202b", "--flux-surface": "#0a2c3a", "--flux-fg": "#e6f6ff", "--flux-border": "#14465a", "--flux-accent": "#22d3ee" },
202
+ }] } }]
203
+ ```
204
+ They appear in the title-bar picker. In code: `const { resolved, mode, setMode, themeId, setThemeId, themes } = useTheme();`.
205
+
206
+ **Icons.** `<Icon name="search|bell|sun|moon|plug|settings|check|close|chevron|play|image|command" />` (24×24, `currentColor`) or
207
+ `<Icon d="M…svg path…" />` for your own. Plugins have their own icon: `"icon": "./assets/icon.svg"` in `plugin.json`
208
+ (shown in **Plugins**; png / svg / webp / jpg).
209
+
210
+ ## 6. Tailwind
211
+
212
+ ```bash
213
+ npx fluxplugin init my-app --tailwindcss # app preconfigured with Tailwind v4
214
+ npx fluxplugin plugin new my-plugin --tailwindcss # plugin with its own compiled utilities
215
+ ```
216
+ * `src/app/renderer/styles/tailwind.css` is compiled by `fluxplugin dev|build` into `dist/tailwind.css`.
217
+ * Theme colours are mapped, so utilities follow light/dark/plugin themes: `bg-bg bg-surface text-fg text-muted border-border bg-accent`.
218
+ * Dark variant: `dark:…` follows `data-theme="dark"`.
219
+ * The app scans `plugins/*/dist` so classes used by bundled plugins are generated.
220
+ * Plugins that users install **later** can't be scanned: create them with `--tailwindcss`, which ships a precompiled
221
+ `dist/tailwind.css` (utilities only, no reset) listed in `manifest.styles`. Plugin CSS is added when the plugin activates and
222
+ **removed when it is disabled**.
223
+ * Need `npm install -D tailwindcss @tailwindcss/cli` if you add Tailwind to an existing app (then create `tailwind.css`).
224
+
225
+ ---
226
+
227
+ ## 7. The plugin "store page" (image, description, video)
228
+
229
+ Each plugin gets a page in **Plugins → Details**, driven by `plugin.json` and its `README.md`:
230
+ ```jsonc
231
+ {
232
+ "icon": "./assets/icon.svg",
233
+ "banner": "./assets/banner.png",
234
+ "screenshots": ["./assets/shot-1.png", "./assets/shot-2.png"],
235
+ "video": "https://www.youtube.com/watch?v=YOUR_ID", // YouTube/Vimeo are embedded; other https links become a button
236
+ "readme": "./README.md", // long description in Markdown (default README.md)
237
+ "categories": ["Productivity"], "keywords": ["reports"],
238
+ "links": { "docs": "https://…", "support": "https://…" },
239
+ "author": "Name", "license": "MIT"
240
+ }
241
+ ```
242
+ The README supports headings, lists, tables, code blocks, quotes, **bold**, *italic*, `code`, and https links.
243
+ Raw HTML and images inside the README are **not** rendered (safety). Put images in `screenshots`/`banner`.
244
+ Your app's CSP must allow the video hosts: `frame-src https://www.youtube-nocookie.com https://player.vimeo.com`
245
+ (already in the generated `index.html`).
246
+
247
+ ## 8. Distributed plugins are JavaScript only
248
+
249
+ Authors work in TypeScript (`src/`), but what is **installed and shipped** is only:
250
+ `plugin.json`, `dist/` (built JS/CSS), `assets/`, `README.md`, `LICENSE`, `CHANGELOG`. `fluxplugin package` and every install
251
+ through the Plugin Manager / registries copy *only* these files — never `src/`, `.ts/.tsx`, sourcemaps, `node_modules`, configs.
252
+ Add `--sign-key` to sign the package (ed25519) and set `trustedKeys` + `requireSignature` in `framework.ts` to refuse the rest.
253
+
254
+ ## 9. Plugin stores (marketplace)
255
+
256
+ Where *Plugins → Available* looks is your choice: the default GitHub store, your own catalog (HTTP/GitHub/local), or code of your own.
257
+ Set it in `fluxplugin.config.json` → `"stores"`. Full guide — developing, publishing, running a store: **[STORE.md](STORE.md)**.
@@ -1,11 +1,38 @@
1
1
  # Plugin authors
2
2
 
3
- 1. `fluxplugin create my-plugin -t basic|dashboard|backend`
4
- 2. Edit `plugin.json` (id, version, `activationEvents`, `permissions`, `dependencies`, `engines`) and `src/main.ts`, `src/renderer.tsx`, `src/styles/*.css`.
5
- 3. `fluxplugin dev` → host hot-reloads. `validate` → `package --sign-key key` → `<id>-<version>.tgz`.
6
- 4. Install: `fluxplugin install my.tgz --dir plugins`, via the Plugin Manager UI, or any `PluginRegistry`.
7
-
8
- Rules: register everything through `ctx`/declarative fields (auto-cleaned); clean timers in `ctx.lifecycle.onDeactivate`;
9
- declare every permission you use; version with semver and declare `engines["fluxplugin"]`.
10
- Hooks: `before.<op>` / `after.<op>` / `around.<op>` / `transform.<op>` / `validate.<op>` / `filter.<name>`.
11
- Global typing: `declare module "fluxplugin" { interface PluginApis { stats: {...} } }` then `pluginApi("stats")`.
3
+ ## 1. Create
4
+ ```bash
5
+ npx fluxplugin plugin new my-plugin -t dashboard # basic | dashboard | backend (add --tailwindcss for Tailwind)
6
+ cd plugins/my-plugin
7
+ ```
8
+ You get `plugin.json`, `src/main.ts`, `src/renderer.tsx`, `src/styles/index.css`, `assets/icon.svg` and a detailed `README.md`.
9
+
10
+ ## 2. Develop (TypeScript)
11
+ `npx fluxplugin dev` in the app rebuilds on save and the running app reloads your plugin. Imports come from the host,
12
+ so there is one React and one runtime: `import { definePlugin, defineIPC, s } from "fluxplugin"` and
13
+ `import { usePluginIPC, usePluginState } from "fluxplugin/react"`.
14
+
15
+ Rules: register everything through `ctx` or the declarative fields (it is cleaned up automatically on disable);
16
+ clear timers in `ctx.lifecycle.onDeactivate`; declare **every** permission you use; use `activationEvents`
17
+ (`onStartup`, `onCommand:id`, `onRoute:/x`, `onService:x`, `onSlot:x`, `onIPC:x`, `onEvent:x`) to load lazily.
18
+
19
+ ## 3. Describe it (the plugin page)
20
+ Fill `plugin.json` and `README.md` — users see them in **Plugins → Details**:
21
+ `icon`, `banner`, `screenshots`, `video` (YouTube/Vimeo), `categories`, `keywords`, `links`, `author`, `license`, and a Markdown
22
+ README (what it does, how to use it, permissions and why, changelog). See [CUSTOMIZING §7](CUSTOMIZING.md).
23
+
24
+ ## 4. Ship (JavaScript only)
25
+ ```bash
26
+ npx fluxplugin validate .
27
+ npx fluxplugin package . --out dist-packages --sign-key epf-signing.private.key
28
+ # → com.acme.my-plugin-1.0.0.tgz contains: plugin.json, dist/, assets/, README.md, LICENSE — never src/ or .ts files
29
+ ```
30
+ To **publish to the store** (GitHub): `npx fluxplugin publish` — see [STORE.md](STORE.md).
31
+ Install: Plugin Manager (*Available*), `npx fluxplugin install <id>`, `install file.tgz`. Updates keep the previous version for rollback.
32
+
33
+ ## 5. What you can do
34
+ Pages (`routes`, lazy, guards, layouts) · sidebar/menu-bar entries (`menus`) · commands & shortcuts · dashboard/any **slot**
35
+ components · **extension points** (toolbar actions, search providers, themes) · hooks on host operations · events · services ·
36
+ main-process IPC + `window.plugins.<ns>` · notifications · settings with auto-generated forms (`config`) · storage · styles.
37
+ How the host *offers* these: [CUSTOMIZING](CUSTOMIZING.md). Typed `window.plugins`:
38
+ `declare module "fluxplugin" { interface PluginApis { stats: { get(): Promise<Stats> } } }` then `pluginApi("stats").get()`.
package/docs/RELEASE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ```bash
4
4
  pnpm install
5
- pnpm typecheck && pnpm test # 121 tests
5
+ pnpm typecheck && pnpm test # ~138 tests
6
6
  pnpm build # dist/ (ESM, shared chunks) + dist/types (.d.ts)
7
7
  npm pack # fluxplugin-1.0.0.tgz — inspect with `tar tzf`
8
8
 
package/docs/SECURITY.md CHANGED
@@ -10,6 +10,11 @@
10
10
  * Archives: path-traversal-safe extraction; HTTP registry checksums verified; asset protocol rejects traversal, manifest and main entry.
11
11
  * Crash isolation: timeouts, rollback, error boundaries, auto-disable.
12
12
 
13
+ * Plugin pages: README Markdown is rendered without raw HTML or images; links must be https and open in the system browser; videos are
14
+ embedded only for YouTube (nocookie) / Vimeo through a sandboxed iframe allowed by the page CSP.
15
+ * Distribution: only runtime files (`plugin.json`, `dist/`, `assets/`, README/LICENSE) are packaged or installed; sources never ship.
16
+ * `npm audit fix --force` can silently jump Electron several majors — keep Electron updates deliberate (see TROUBLESHOOTING).
17
+
13
18
  **What is NOT enforced — do not rely on it**
14
19
  * Plugin `main` code runs in the Electron main process with full Node access. Permissions are an API contract, **not a sandbox**:
15
20
  a malicious plugin can `require("fs")`. Only install plugins you trust; use signatures + `requireSignature`.
package/docs/STORE.md ADDED
@@ -0,0 +1,123 @@
1
+ # Plugin stores: develop a plugin, publish it, install it
2
+
3
+ A **store** is any place plugins are listed and downloaded from. fluxplugin ships three kinds, and **GitHub is the default**:
4
+
5
+ | Store | You configure | How plugins get in | Best for |
6
+ |---|---|---|---|
7
+ | **GitHub topic** *(default)* | nothing | `fluxplugin publish` creates a release in *your own* repo; the repo topic `fluxplugin-plugin` makes it discoverable | open ecosystem, no approval needed |
8
+ | **GitHub catalog** | `{ "type": "github", "repo": "acme/plugin-store" }` | pull requests to a repo containing `index.json` | curated / reviewed store |
9
+ | **HTTP** | `{ "type": "http", "url": "https://plugins.acme.com/" }` | `fluxplugin store add` + upload the folder | company store, GitHub Pages, S3, CDN |
10
+ | **Local** | `{ "type": "local", "path": "./store" }` | copy folders | offline / shared drive |
11
+
12
+ Stores are listed in **`fluxplugin.config.json`** (`"stores"`). The same list is used by your **app** (*Plugins → Available*) and by the **CLI**
13
+ (`fluxplugin search`, `fluxplugin install`). You can have several stores at once.
14
+
15
+ ---
16
+
17
+ ## A. Develop a plugin (5 minutes)
18
+
19
+ ```bash
20
+ # inside your app, or anywhere
21
+ npx fluxplugin plugin new weather -t backend # basic | dashboard | backend (add --tailwindcss if you like)
22
+ cd plugins/weather
23
+ npx fluxplugin dev # rebuilds on save; the running app reloads the plugin
24
+ ```
25
+ 1. **Code** in `src/main.ts` (Electron side: IPC, services) and `src/renderer.tsx` (React: pages, widgets, menus). Learn the API in
26
+ [PLUGIN-AUTHORS](PLUGIN-AUTHORS.md); what the host offers is in [CUSTOMIZING](CUSTOMIZING.md).
27
+ 2. **Describe it** — this is your store page (*Plugins → Details*). Edit `plugin.json` and `README.md`:
28
+ ```jsonc
29
+ {
30
+ "id": "com.acme.weather", // unique, lowercase, never changes
31
+ "name": "Weather", "version": "1.0.0",
32
+ "description": "Forecast widget for your dashboard.",
33
+ "author": "Acme", "license": "MIT",
34
+ "repository": "https://github.com/acme/weather-plugin", // ← where `publish` will release it
35
+ "icon": "./assets/icon.svg",
36
+ "screenshots": ["./assets/shot-1.png"],
37
+ "video": "https://www.youtube.com/watch?v=YOUR_ID",
38
+ "categories": ["Productivity"], "keywords": ["weather"],
39
+ "permissions": ["ipc", "routes", "menus", "ui"], // declare exactly what you use
40
+ "engines": { "fluxplugin": "^1.0.0" }
41
+ }
42
+ ```
43
+ `README.md` is rendered as the long description (headings, lists, tables, code, links; no raw HTML).
44
+ 3. **Check it**: `npx fluxplugin validate .` — then try it in your app. Remove dev leftovers; users only ever get `plugin.json`, `dist/`,
45
+ `assets/`, `README.md`, `LICENSE` — **never your TypeScript, `src/`, or `.map` files** (sourcemaps are only generated by `dev`).
46
+ 4. **Version it** with semver (`1.0.0` → `1.0.1` fix, `1.1.0` feature, `2.0.0` breaking). A published version can't be replaced.
47
+
48
+ ## B. Publish to the default store (GitHub)
49
+
50
+ **Once**: create a *public* GitHub repository for the plugin (`github.com/acme/weather-plugin`), push your code, and create a token:
51
+ GitHub → Settings → Developer settings → *Personal access tokens* → fine-grained, this repository, permissions **Contents: Read & write** and
52
+ **Administration: Read & write** (needed only to add the topic). Export it:
53
+ ```bash
54
+ export GITHUB_TOKEN=github_pat_xxx # PowerShell: $env:GITHUB_TOKEN="github_pat_xxx"
55
+ ```
56
+ **Every release**:
57
+ ```bash
58
+ npx fluxplugin publish --dry-run # builds + packages, shows what would happen, touches nothing
59
+ npx fluxplugin publish # does it
60
+ ```
61
+ What `publish` does: builds the plugin (no sourcemaps) → creates `com.acme.weather-1.0.0.tgz` and `com.acme.weather-1.0.0.fluxplugin.json`
62
+ (name, version, permissions, **sha256**) → creates the GitHub release `v1.0.0` → uploads both files → adds the topic `fluxplugin-plugin`.
63
+ It refuses if the version already exists ("bump `version`"), the repo isn't reachable, or `description` / `icon` / `license` are missing.
64
+ Add `--sign-key my.private.key` to sign the package (`npx fluxplugin keygen` creates the pair).
65
+
66
+ New topics can take a few minutes to appear in GitHub search. Check with `npx fluxplugin search weather`.
67
+
68
+ **Update**: change code → bump `version` → `npx fluxplugin publish`. Apps see it in *Plugins → Updates*; the previous version is kept for **rollback**.
69
+ **Unpublish**: delete the release on GitHub (or remove the topic to hide the whole repository).
70
+
71
+ **Automate with GitHub Actions** (publish when you push a tag):
72
+ ```yaml
73
+ # .github/workflows/release.yml
74
+ on: { push: { tags: ["v*"] } }
75
+ permissions: { contents: write }
76
+ jobs:
77
+ publish:
78
+ runs-on: ubuntu-latest
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ - uses: actions/setup-node@v4
82
+ with: { node-version: 22 }
83
+ - run: npm ci
84
+ - run: npx fluxplugin publish
85
+ env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }
86
+ ```
87
+ (The built-in token can create releases but not edit topics; add the topic `fluxplugin-plugin` once in the repo settings.)
88
+
89
+ ## C. Install from the store
90
+
91
+ * **In an app**: *Plugins → Available* lists plugins with their permissions; **Install**. Enabled automatically (or after the user approves permissions when your app uses `requireApproval`).
92
+ * **CLI**: `npx fluxplugin search weather` · `npx fluxplugin install com.acme.weather` (or `@1.0.0`) · `npx fluxplugin install ./file.tgz`.
93
+ * **Anonymous GitHub search is limited (60 requests/hour).** Set `GITHUB_TOKEN` (any token, no scopes) in the environment of the app or CLI to raise it.
94
+ The app caches results for a minute.
95
+
96
+ ## D. Run your own store
97
+
98
+ ```bash
99
+ npx fluxplugin store init my-store # creates my-store/index.json
100
+ npx fluxplugin package ./weather --out . # → com.acme.weather-1.0.0.tgz
101
+ npx fluxplugin store add com.acme.weather-1.0.0.tgz --dir my-store --base-url https://plugins.acme.com/
102
+ ```
103
+ Upload the `my-store/` folder anywhere that serves static files, then add it to your app (and keep or drop the GitHub one):
104
+ ```jsonc
105
+ // fluxplugin.config.json
106
+ "stores": [
107
+ { "type": "http", "url": "https://plugins.acme.com/" },
108
+ { "type": "github", "topic": "fluxplugin-plugin" }
109
+ ]
110
+ ```
111
+ * **Curated GitHub store**: put the same `my-store/` content in a repo `acme/plugin-store`, use `{ "type": "github", "repo": "acme/plugin-store" }`,
112
+ and review plugin submissions as pull requests that edit `index.json`.
113
+ * **Code**: `new MainFramework({ stores: [...] })` or `registries: [new MyRegistry()]` — implement `PluginRegistry { name; search(query); fetch(entry, destDir) }` to talk to anything (npm, S3, your API).
114
+ * Format of `index.json`: `{ "plugins": [{ "id", "name", "version", "description", "permissions", "url", "sha256" }] }` — `url` may be relative.
115
+
116
+ ## E. What protects your users
117
+
118
+ * Downloads must be **https** (loopback http is allowed for local testing) and match the published **sha256**; entries without a checksum are refused.
119
+ * Archives are extracted with path-traversal protection, size-limited (100 MB), and **sources, TypeScript, `.map` files and `node_modules` are deleted on install** even if a package contains them.
120
+ * Permissions are shown **before** install; with `requireApproval: true` nothing runs until the user approves; they can revoke later.
121
+ * Optional: `trustedKeys` + `requireSignature: true` in your `MainFramework` to accept only plugins signed by keys you trust.
122
+ * Plugin `main` code is **not sandboxed** — a store is a trust decision. Use a curated store or signatures for untrusted ecosystems ([SECURITY](SECURITY.md)).
123
+ * Limits today: public GitHub repositories only (private-repo downloads aren't supported yet).
@@ -0,0 +1,35 @@
1
+ # Troubleshooting
2
+
3
+ ## "Electron failed to install correctly"
4
+ Your package manager blocked Electron's `postinstall` script (npm 11.16+/12 and pnpm 10 do this by default), so the binary was never
5
+ downloaded. **`fluxplugin dev|start` now downloads it automatically.** To make installs clean:
6
+ * the generated `package.json` already contains `"allowScripts": { "electron": true, "esbuild": true }` (npm) and
7
+ `"pnpm": { "onlyBuiltDependencies": ["electron", "esbuild"] }` (pnpm);
8
+ * with an older project add those, or run `npx fluxplugin doctor`, or `node node_modules/electron/install.js`.
9
+ * behind a proxy / in a restricted network: set `ELECTRON_MIRROR`.
10
+ * Do **not** run `npm audit fix --force` on an app: it upgrades Electron across major versions on its own.
11
+
12
+ ## `fluxplugin doctor`
13
+ Checks Node ≥ 20, the Electron binary (and repairs it), esbuild, the Tailwind CLI and the `allowScripts` setting.
14
+
15
+ ## Linux: "Running as root without --no-sandbox" / SUID sandbox
16
+ `fluxplugin dev` detects it and starts Electron with `--no-sandbox`, telling you how to fix it properly:
17
+ `sudo chown root node_modules/electron/dist/chrome-sandbox && sudo chmod 4755 node_modules/electron/dist/chrome-sandbox`.
18
+
19
+ ## A plugin is "disabled" and I did not disable it
20
+ A plugin that crashes 3 times within a minute (activation or rendering) is disabled **and the state is saved**. Open
21
+ **Plugins → Details → Logs** to see why, fix it, then **Enable**.
22
+
23
+ ## Debugging Electron
24
+ `FLUXPLUGIN_ELECTRON_ARGS="--remote-debugging-port=9222" npm run dev` then open `http://127.0.0.1:9222` in Chrome. In dev, `F12` toggles DevTools.
25
+
26
+ ## Plugin changes don't show
27
+ `fluxplugin dev` rebuilds the host and every plugin in `plugins/` on save; plugins reload in the running app. If you work in a plugin
28
+ folder outside the app, run `fluxplugin dev` there. Host changes in `src/app/main` need an app restart.
29
+
30
+ ## Store problems
31
+ * **"GitHub rate limit reached"** — anonymous search is limited to 60 requests/hour. Set `GITHUB_TOKEN` (any token) for the app/CLI.
32
+ * **My published plugin isn't found** — GitHub topics can take a few minutes; check the repo has the topic `fluxplugin-plugin`, is **public**,
33
+ and the latest release contains both the `.tgz` and the `.fluxplugin.json` files (`fluxplugin publish` creates them).
34
+ * **"version X is already published"** — bump `version` in `plugin.json`; releases are immutable.
35
+ * **"downloaded package does not match the published checksum"** — the file changed after publishing; publish a new version.
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "fluxplugin",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Plugin architecture for Electron + React + TypeScript. Build apps that others can extend: routes, menus, commands, UI slots, IPC, hooks, permissions — with a CLI.",
5
5
  "license": "MIT",
6
- "author": "Cellsflux",
7
6
  "keywords": [
8
7
  "electron",
9
8
  "react",