diff --git a/docs/frame-plugin-author.md b/docs/frame-plugin-author.md
index 115db92..1d00243 100644
--- a/docs/frame-plugin-author.md
+++ b/docs/frame-plugin-author.md
@@ -32,6 +32,9 @@ Frame Control now has an experimental [Framey panel host](framey-loader.md)
for API v2 Decky frontend bundles. It renders common settings controls in the
separate SteamVR dashboard panel, but Steam library and store page patches
remain unavailable. Wearer-visible use is not yet verified.
+The [settings sample](../examples/framey-settings/README.md) shows an official
+Decky JSX build, backend persistence, a toggle and a local toast. Framey offers
+only official-store-owned plugins in its VR panel.
To test safely, use a separate Frame Control managed Decky install, verify the
ZIP digest from the official store, install one plugin at a time, and inspect
@@ -47,6 +50,7 @@ none of these frontend controls has passed a worn-headset check yet.
| Decky API or UI feature | Framey behavior | Verified in |
| --- | --- | --- |
| `definePlugin` with `content`, `titleView` | Renders a React panel and optional title | Local browser with ProtonDB Badges; host unit test |
+| Official `@decky/rollup` JSX (`SP_JSX.jsx`, `jsxs`, `Fragment`) | Creates React elements in the isolated panel | Sample bundle smoke test; host unit test |
| `PanelSection`, rows, buttons, toggles, dropdowns | Local touch-sized controls | Local browser and host unit test |
| Optional `settings`, `frameyQuickActions` | Separate settings tab; up to eight actions with result text | Host unit test; these fields are Framey additions |
| `serverAPI.call` / `callable` | Calls only that plugin's Decky Python backend via the headset service | Fake Frame e2e for setting read/write across CEF calls, update, and reinstall; host unit test. Real Decky persistence remains unverified. |
diff --git a/docs/framey-loader.md b/docs/framey-loader.md
index 905e0b3..e6dded6 100644
--- a/docs/framey-loader.md
+++ b/docs/framey-loader.md
@@ -80,6 +80,9 @@ list before making another change.
## For plugin authors
+Start with the [Framey settings sample](../examples/framey-settings/README.md)
+for a buildable Decky API v2 panel and Python settings backend.
+
Publish a normal [Decky store](https://plugins.deckbrew.xyz/) API v2 release
with `plugin.json`, a single `dist/index.js` ESM frontend entry point, and a
Python backend if needed. Give the frontend a `content` React element returned
diff --git a/examples/framey-settings/.gitignore b/examples/framey-settings/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/examples/framey-settings/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/examples/framey-settings/README.md b/examples/framey-settings/README.md
new file mode 100644
index 0000000..12a25d3
--- /dev/null
+++ b/examples/framey-settings/README.md
@@ -0,0 +1,37 @@
+# Framey settings sample
+
+This is a small Decky API v2 plugin for authors. Its panel uses the official
+`@decky/api`, `@decky/ui`, and `@decky/rollup` packages. A toggle calls a Python
+backend method, stores one boolean under `decky.decky_SETTINGS_DIR`,
+and reads it back when the panel opens. The backend rejects other keys and
+values. The frontend also shows a local toast after a successful save.
+
+## Build
+
+From this directory, run:
+
+```sh
+pnpm install --frozen-lockfile
+pnpm build
+pnpm exec tsc --noEmit
+```
+
+The committed `dist/index.js` is the built frontend. For a Decky release ZIP,
+put `plugin.json`, `main.py`, and `dist/index.js` inside one top-level plugin
+folder. Publish it through the official Decky store if you want Frame Control's
+managed installer to offer it. Framey deliberately opens only plugins that the
+official store installed and marked as owned; copying this example into the
+plugin directory does not enable its VR panel.
+
+## Test and adapt
+
+Run `python3 -m unittest discover -s tests -p test_framey_sample.py` from the
+repository root. It checks backend persistence and evaluates the built bundle
+against Framey's actual host script. The fake Frame store flow is exercised by
+the repository's e2e suite. No worn-headset visibility or controller-input test
+has passed for this sample yet.
+
+The plugin needs no root access or system changes. Its settings remain after
+the managed plugin folder is removed, following Decky's settings behavior.
+For a production plugin, add migration and recovery handling for damaged
+settings files, and test every UI path in a worn headset.
diff --git a/examples/framey-settings/dist/index.js b/examples/framey-settings/dist/index.js
new file mode 100644
index 0000000..565ec66
--- /dev/null
+++ b/examples/framey-settings/dist/index.js
@@ -0,0 +1,71 @@
+const manifest = {"name":"Framey Settings Sample"};
+const API_VERSION = 2;
+const internalAPIConnection = window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyLoaderAPIInit;
+if (!internalAPIConnection) {
+ throw new Error('[@decky/api]: Failed to connect to the loader as as the loader API was not initialized. This is likely a bug in Decky Loader.');
+}
+let api;
+try {
+ api = internalAPIConnection.connect(API_VERSION, manifest.name);
+}
+catch {
+ api = internalAPIConnection.connect(1, manifest.name);
+ console.warn(`[@decky/api] Requested API version ${API_VERSION} but the running loader only supports version 1. Some features may not work.`);
+}
+if (api._version != API_VERSION) {
+ console.warn(`[@decky/api] Requested API version ${API_VERSION} but the running loader only supports version ${api._version}. Some features may not work.`);
+}
+const callable = api.callable;
+const toaster = api.toaster;
+const definePlugin = (fn) => {
+ return (...args) => {
+ return fn(...args);
+ };
+};
+
+const getSetting = callable('get_setting');
+const setSetting = callable('set_setting');
+function Content() {
+ const [enabled, setEnabled] = SP_REACT.useState(false);
+ const [busy, setBusy] = SP_REACT.useState(true);
+ const [message, setMessage] = SP_REACT.useState('Loading setting…');
+ async function refresh() {
+ setBusy(true);
+ try {
+ const value = await getSetting('enabled', false);
+ setEnabled(value);
+ setMessage(`Saved setting: ${value ? 'on' : 'off'}`);
+ }
+ catch (error) {
+ setMessage(`Could not read setting: ${String(error)}`);
+ }
+ finally {
+ setBusy(false);
+ }
+ }
+ SP_REACT.useEffect(() => { void refresh(); }, []);
+ async function change(value) {
+ setBusy(true);
+ try {
+ await setSetting('enabled', value);
+ setEnabled(value);
+ setMessage(`Saved setting: ${value ? 'on' : 'off'}`);
+ toaster.toast({ title: 'Framey sample', body: 'Setting saved' });
+ }
+ catch (error) {
+ setMessage(`Could not save setting: ${String(error)}`);
+ }
+ finally {
+ setBusy(false);
+ }
+ }
+ return SP_JSX.jsxs(DFL.PanelSection, { title: "Framey settings sample", children: [SP_JSX.jsx(DFL.PanelSectionRow, { children: SP_JSX.jsx(DFL.ToggleField, { label: "Example setting", description: "Stored by the Decky Python backend", checked: enabled, disabled: busy, onChange: (value) => { void change(value); } }) }), SP_JSX.jsx(DFL.PanelSectionRow, { children: SP_JSX.jsx(DFL.ButtonItem, { disabled: busy, onClick: () => { void refresh(); }, children: "Reload saved setting" }) }), SP_JSX.jsx(DFL.PanelSectionRow, { children: SP_JSX.jsx("p", { role: "status", children: message }) })] });
+}
+var index = definePlugin(() => ({
+ name: 'Framey Settings Sample',
+ icon: SP_JSX.jsx("span", { "aria-hidden": "true", children: "\u2699" }),
+ content: SP_JSX.jsx(Content, {}),
+}));
+
+export { index as default };
+//# sourceMappingURL=index.js.map
diff --git a/examples/framey-settings/dist/index.js.map b/examples/framey-settings/dist/index.js.map
new file mode 100644
index 0000000..469e504
--- /dev/null
+++ b/examples/framey-settings/dist/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sources":["decky://decky/plugin/Framey%20Settings%20Sample/node_modules/.pnpm/@decky+api@1.1.3/node_modules/@decky/api/dist/index.js","decky://decky/plugin/Framey%20Settings%20Sample/src/index.tsx"],"sourcesContent":["import _manifest from '@decky/manifest';\nexport * from \"./types\";\nconst manifest = _manifest;\nconst API_VERSION = 2;\nif (!manifest?.name) {\n throw new Error('[@decky/api]: Failed to find plugin manifest.');\n}\nconst internalAPIConnection = window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyLoaderAPIInit;\nif (!internalAPIConnection) {\n throw new Error('[@decky/api]: Failed to connect to the loader as as the loader API was not initialized. This is likely a bug in Decky Loader.');\n}\nlet api;\ntry {\n api = internalAPIConnection.connect(API_VERSION, manifest.name);\n}\ncatch {\n api = internalAPIConnection.connect(1, manifest.name);\n console.warn(`[@decky/api] Requested API version ${API_VERSION} but the running loader only supports version 1. Some features may not work.`);\n}\nif (api._version != API_VERSION) {\n console.warn(`[@decky/api] Requested API version ${API_VERSION} but the running loader only supports version ${api._version}. Some features may not work.`);\n}\nexport const call = api.call;\nexport const callable = api.callable;\nexport const addEventListener = api.addEventListener;\nexport const removeEventListener = api.removeEventListener;\nexport const routerHook = api.routerHook;\nexport const toaster = api.toaster;\nexport const openFilePicker = api.openFilePicker;\nexport const executeInTab = api.executeInTab;\nexport const injectCssIntoTab = api.injectCssIntoTab;\nexport const removeCssFromTab = api.removeCssFromTab;\nexport const fetchNoCors = api.fetchNoCors;\nexport const getExternalResourceURL = api.getExternalResourceURL;\nexport const useQuickAccessVisible = api.useQuickAccessVisible;\nexport const definePlugin = (fn) => {\n return (...args) => {\n return fn(...args);\n };\n};\n","import {callable, definePlugin, toaster} from '@decky/api';\nimport {ButtonItem, PanelSection, PanelSectionRow, ToggleField} from '@decky/ui';\nimport {useEffect, useState} from 'react';\n\nconst getSetting = callable<[key: string, fallback: boolean], boolean>('get_setting');\nconst setSetting = callable<[key: string, value: boolean], boolean>('set_setting');\n\nfunction Content() {\n const [enabled, setEnabled] = useState(false);\n const [busy, setBusy] = useState(true);\n const [message, setMessage] = useState('Loading setting…');\n\n async function refresh() {\n setBusy(true);\n try {\n const value = await getSetting('enabled', false);\n setEnabled(value);\n setMessage(`Saved setting: ${value ? 'on' : 'off'}`);\n } catch (error) {\n setMessage(`Could not read setting: ${String(error)}`);\n } finally {\n setBusy(false);\n }\n }\n\n useEffect(() => {void refresh();}, []);\n\n async function change(value: boolean) {\n setBusy(true);\n try {\n await setSetting('enabled', value);\n setEnabled(value);\n setMessage(`Saved setting: ${value ? 'on' : 'off'}`);\n toaster.toast({title: 'Framey sample', body: 'Setting saved'});\n } catch (error) {\n setMessage(`Could not save setting: ${String(error)}`);\n } finally {\n setBusy(false);\n }\n }\n\n return \n \n {void change(value);}} />\n \n {void refresh();}}>Reload saved setting\n {message}
\n ;\n}\n\nexport default definePlugin(() => ({\n name: 'Framey Settings Sample',\n icon: ⚙,\n content: ,\n}));\n"],"names":[],"mappings":"AAEA,MAAA,QAAA,GAAiB,CAAA,MAAA,CAAA,wBAAA,CAAS;AAC1B,MAAA,WAAA,GAAA,CAAA;AAIA,MAAA,qBAAA,GAAA,MAAA,CAAA,2EAAA;AACA,IAAA,CAAA,qBAAA,EAAA;AACA,IAAA,MAAA,IAAA,KAAA,CAAA,+HAAA,CAAA;AACA;AACA,IAAA,GAAA;AACA,IAAA;AACA,IAAA,GAAA,GAAA,qBAAA,CAAA,OAAA,CAAA,WAAA,EAAA,QAAA,CAAA,IAAA,CAAA;AACA;AACA,MAAA;AACA,IAAA,GAAA,GAAA,qBAAA,CAAA,OAAA,CAAA,CAAA,EAAA,QAAA,CAAA,IAAA,CAAA;AACA,IAAA,OAAA,CAAA,IAAA,CAAA,CAAA,mCAAA,EAAA,WAAA,CAAA,4EAAA,CAAA,CAAA;AACA;AACA,IAAA,GAAA,CAAA,QAAA,IAAA,WAAA,EAAA;AACA,IAAA,OAAA,CAAA,IAAA,CAAA,CAAA,mCAAA,EAAA,WAAA,CAAA,8CAAA,EAAA,GAAA,CAAA,QAAA,CAAA,6BAAA,CAAA,CAAA;AACA;AAEA,MAAA,QAAA,GAAA,GAAA,CAAA,QAAA;AAIA,MAAA,OAAA,GAAA,GAAA,CAAA,OAAA;AAQA,MAAA,YAAA,GAAA,CAAA,EAAA,KAAA;AACA,IAAA,OAAA,CAAA,GAAA,IAAA,KAAA;AACA,QAAA,OAAA,EAAA,CAAA,GAAA,IAAA,CAAA;AACA,IAAA,CAAA;AACA,CAAA;;ACnCA,MAAA,UAAA,GAAA,QAAA,CAAA,aAAA,CAAA;AACA,MAAA,UAAA,GAAA,QAAA,CAAA,aAAA,CAAA;AAEA,SAAA,OAAA,GAAA;kCACgC,QAAA,CAAA,QAAQ,CAAA,KAAA,CAAA;4BACd,QAAA,CAAA,QAAQ,CAAA,IAAA,CAAA;kCACF,QAAA,CAAA,QAAQ,CAAA,kBAAA,CAAA;AAEtC,IAAA,eAAA,OAAA,GAAA;;AAEE,QAAA,IAAA;;;AAGE,YAAA,UAAA,CAAA,CAAA,eAAA,EAAA,KAAA,GAAA,IAAA,GAAA,KAAA,CAAA,CAAA,CAAA;;;;;;;;;AAQJ,IAAA,QAAA,CAAA,SAAS,CAAA,MAAA,EAAA,KAAA,OAAA,EAAA,CAAA,CAAA,CAAA,EAAA,EAAA,CAAA;;;AAIP,QAAA,IAAA;AACE,YAAA,MAAA,UAAA,CAAA,SAAA,EAAA,KAAA,CAAA;;AAEA,YAAA,UAAA,CAAA,CAAA,eAAA,EAAA,KAAA,GAAA,IAAA,GAAA,KAAA,CAAA,CAAA,CAAA;AACA,YAAA,OAAA,CAAA,KAAA,CAAA,EAAA,KAAA,EAAA,eAAA,EAAA,IAAA,EAAA,eAAA,EAAA,CAAA;;;;;;;;;AAQJ,IAAA,OAAO,MAAA,CAAA,KAAC,GAAA,CAAA,YAAY,EAAA,EAAA,KAAA,EAAA,wBAAA,EAAA,QAAA,EAAA,CAClB,MAAA,CAAA,GAAA,CAAC,GAAA,CAAA,eAAe,EAAA,EAAA,QAAA,EACd,MAAA,CAAA,IAAC,GAAA,CAAA,WAAW,EAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,oCAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,QAAA,EAAA,CAAA,KAAA,KAAA,EAAA,KAAA,MAAA,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,EAAA,CAAA,EAGd,UAAA,CAAC,GAAA,CAAA,eAAe,EAAA,EAAA,QAAA,EAAC,MAAA,CAAA,GAAA,CAAC,GAAA,CAAA,UAAU,gGAC5B,MAAA,CAAA,IAAC,GAAA,CAAA,eAAe,EAAA,EAAA,QAAA,EAAC,MAAA,CAAA,GAAA,CAAA,GAAA,EAAA,EAAA,IAAA,EAAA,QAAA,EAAA,QAAA,EAAA,OAAA,EAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA;AAErB;AAEA,YAAA,YAAA,CAAA,OAAA;AACE,IAAA,IAAA,EAAA,wBAAA;UACM,MAAA,CAAA,GAAA,CAAA,MAAA,EAAA,EAAA,aAAA,EAAA,MAAA,EAAA,QAAA,EAAA,QAAA,EAAA,CAAA;aACG,MAAA,CAAA,GAAA,CAAA,OAAA,EAAA,EAAA,CAAA;AACV,CAAA,CAAA,CAAA;;","x_google_ignoreList":[0]}
\ No newline at end of file
diff --git a/examples/framey-settings/main.py b/examples/framey-settings/main.py
new file mode 100644
index 0000000..79b1786
--- /dev/null
+++ b/examples/framey-settings/main.py
@@ -0,0 +1,41 @@
+"""Small, unprivileged Decky backend for the Framey author example."""
+import json
+import os
+from pathlib import Path
+import tempfile
+import decky
+
+
+def settings_file():
+ # Decky provides this per-plugin path through its official Python module.
+ return Path(decky.decky_SETTINGS_DIR) / 'settings.json'
+
+
+class Plugin:
+ async def get_setting(self, key: str, default=False):
+ if key != 'enabled':
+ raise ValueError('Unknown setting')
+ try:
+ stored = json.loads(settings_file().read_text())
+ except FileNotFoundError:
+ return default
+ return stored.get(key, default)
+
+ async def set_setting(self, key: str, value: bool):
+ if key != 'enabled' or type(value) is not bool:
+ raise ValueError('Expected an enabled boolean')
+ target = settings_file()
+ target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
+ temporary = None
+ try:
+ with tempfile.NamedTemporaryFile('w', encoding='utf-8', dir=target.parent,
+ prefix='.settings-', delete=False) as output:
+ temporary = Path(output.name)
+ json.dump({'enabled': value}, output)
+ output.write('\n')
+ temporary.chmod(0o600)
+ os.replace(temporary, target)
+ finally:
+ if temporary is not None:
+ temporary.unlink(missing_ok=True)
+ return True
diff --git a/examples/framey-settings/package.json b/examples/framey-settings/package.json
new file mode 100644
index 0000000..8032444
--- /dev/null
+++ b/examples/framey-settings/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "framey-settings-sample",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "rollup -c"
+ },
+ "dependencies": {
+ "@decky/api": "^1.1.3",
+ "@decky/ui": "^4.11.0",
+ "react": "^19.1.1"
+ },
+ "devDependencies": {
+ "@decky/rollup": "^1.0.2",
+ "@types/react": "^19.1.1",
+ "rollup": "^4.53.3",
+ "typescript": "^5.6.2"
+ }
+}
diff --git a/examples/framey-settings/plugin.json b/examples/framey-settings/plugin.json
new file mode 100644
index 0000000..c45034a
--- /dev/null
+++ b/examples/framey-settings/plugin.json
@@ -0,0 +1,6 @@
+{
+ "name": "Framey Settings Sample",
+ "author": "Frame Control contributors",
+ "flags": [],
+ "api_version": 2
+}
diff --git a/examples/framey-settings/pnpm-lock.yaml b/examples/framey-settings/pnpm-lock.yaml
new file mode 100644
index 0000000..40037b5
--- /dev/null
+++ b/examples/framey-settings/pnpm-lock.yaml
@@ -0,0 +1,1239 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@decky/api':
+ specifier: ^1.1.3
+ version: 1.1.3
+ '@decky/ui':
+ specifier: ^4.11.0
+ version: 4.16.0
+ react:
+ specifier: ^19.1.1
+ version: 19.3.1
+ devDependencies:
+ '@decky/rollup':
+ specifier: ^1.0.2
+ version: 1.0.2
+ '@types/react':
+ specifier: ^19.1.1
+ version: 19.3.0
+ rollup:
+ specifier: ^4.53.3
+ version: 4.65.0
+ typescript:
+ specifier: ^5.6.2
+ version: 5.9.3
+
+packages:
+
+ '@decky/api@1.1.3':
+ resolution: {integrity: sha512-XsPCZxfxk5I1UtylIUN3qaWQI31siQbKfbLIskkI5innEatY1m4NQqBv/6hwPaO9mKMbdqYpnh5PSJDeMEOOBA==}
+
+ '@decky/rollup@1.0.2':
+ resolution: {integrity: sha512-ixuHH3msw156ACbgWZi4hgccdzDBIuqYGOVja8sLX4lHFgaWUKji1vomDi7Dk1CamgjmKt+Dqf75Pezv2FB6Bw==}
+
+ '@decky/ui@4.16.0':
+ resolution: {integrity: sha512-yvgwpDSr2tdPrpR1BPc8RvCDtmGbZ/pTULxm6jvwEoWhgxk8v1K0Y3ilpAd+aBYrDQa2fZbnqkKS6ZZTwsmz6A==}
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@jridgewell/sourcemap-codec@1.6.0':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
+
+ '@napi-rs/lzma-linux-x64-gnu@1.5.1':
+ resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@rollup/plugin-commonjs@26.0.3':
+ resolution: {integrity: sha512-2BJcolt43MY+y5Tz47djHkodCC3c1VKVrBDKpVqHKpQ9z9S158kCCqB8NF6/gzxLdNlYW9abB3Ibh+kOWLp8KQ==}
+ engines: {node: '>=16.0.0 || 14 >= 14.17'}
+ peerDependencies:
+ rollup: ^2.68.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-json@6.1.0':
+ resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-node-resolve@15.3.1':
+ resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.78.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-replace@5.0.7':
+ resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-typescript@11.1.6':
+ resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.14.0||^3.0.0||^4.0.0
+ tslib: '*'
+ typescript: '>=3.7.0'
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ tslib:
+ optional: true
+
+ '@rollup/pluginutils@5.5.1':
+ resolution: {integrity: sha512-P9ZvnfouRu4MSC+ilERaw6FmZs8zqKhpHxEs4aGDGWQAMzQGXnhvCSvoNwokfNvdt7t3Y5Y4p6gStQgezeIqug==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.65.0':
+ resolution: {integrity: sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.65.0':
+ resolution: {integrity: sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.65.0':
+ resolution: {integrity: sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.65.0':
+ resolution: {integrity: sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.65.0':
+ resolution: {integrity: sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.65.0':
+ resolution: {integrity: sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.65.0':
+ resolution: {integrity: sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.65.0':
+ resolution: {integrity: sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-arm64-gnu@4.65.0':
+ resolution: {integrity: sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm64-musl@4.65.0':
+ resolution: {integrity: sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-loong64-gnu@4.65.0':
+ resolution: {integrity: sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-loong64-musl@4.65.0':
+ resolution: {integrity: sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.65.0':
+ resolution: {integrity: sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-ppc64-musl@4.65.0':
+ resolution: {integrity: sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.65.0':
+ resolution: {integrity: sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-musl@4.65.0':
+ resolution: {integrity: sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-s390x-gnu@4.65.0':
+ resolution: {integrity: sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-gnu@4.65.0':
+ resolution: {integrity: sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-musl@4.65.0':
+ resolution: {integrity: sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-openbsd-x64@4.65.0':
+ resolution: {integrity: sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.65.0':
+ resolution: {integrity: sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.65.0':
+ resolution: {integrity: sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.65.0':
+ resolution: {integrity: sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.65.0':
+ resolution: {integrity: sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.65.0':
+ resolution: {integrity: sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/react@19.3.0':
+ resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==}
+
+ '@types/resolve@1.20.2':
+ resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
+
+ aggregate-error@3.1.0:
+ resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+ engines: {node: '>=8'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.4.0:
+ resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@1.1.21:
+ resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==}
+
+ brace-expansion@2.1.7:
+ resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ clean-stack@2.2.0:
+ resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+ engines: {node: '>=6'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ commondir@1.0.1:
+ resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ del@6.1.1:
+ resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==}
+ engines: {node: '>=10'}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ estree-walker@0.6.1:
+ resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fastq@1.20.3:
+ resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob@10.5.0:
+ resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ is-core-module@2.17.0:
+ resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-module@1.0.0:
+ resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-path-cwd@2.2.0:
+ resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
+ engines: {node: '>=6'}
+
+ is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+
+ is-reference@1.2.1:
+ resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
+
+ is-reference@3.0.3:
+ resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
+
+ is-what@5.5.7:
+ resolution: {integrity: sha512-b8A+OE0RcNFy/+qmts8Uv+vumkUZHwIa80YMZBfh6+b75Oq2J8qR3v+/F5vKq4TNNiurrgrE7+XsOa8+1y4lYw==}
+ engines: {node: '>=18'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ merge-anything@6.0.9:
+ resolution: {integrity: sha512-Mr75kWbk3zeccaJ/mFYYCl7RINFbWIvi4pqGKQ7CAlpWw48aHWxwg19HmEIWpad0Z5+vbSEhMxQoyO1oms0ovQ==}
+ engines: {node: '>=18'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minimatch@9.0.9:
+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ p-map@4.0.0:
+ resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+ engines: {node: '>=10'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.7:
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ react@19.3.1:
+ resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==}
+ engines: {node: '>=0.10.0'}
+
+ resolve@1.22.13:
+ resolution: {integrity: sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rollup-plugin-delete@2.2.0:
+ resolution: {integrity: sha512-REKtDKWvjZlbrWpPvM9X/fadCs3E9I9ge27AK8G0e4bXwSLeABAAwtjiI1u3ihqZxk6mJeB2IVeSbH4DtOcw7A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ rollup: '*'
+
+ rollup-plugin-external-globals@0.11.0:
+ resolution: {integrity: sha512-LR+sH2WkgWMPxsA5o5rT7uW7BeWXSeygLe60QQi9qoN/ufaCuHDaVOIbndIkqDPnZt/wZugJh5DCzkZFdSWlLQ==}
+ peerDependencies:
+ rollup: ^2.25.0 || ^3.3.0 || ^4.1.4
+
+ rollup-plugin-import-assets@1.1.1:
+ resolution: {integrity: sha512-u5zJwOjguTf2N+wETq2weNKGvNkuVc1UX/fPgg215p5xPvGOaI6/BTc024E9brvFjSQTfIYqgvwogQdipknu1g==}
+ peerDependencies:
+ rollup: '>=1.9.0'
+
+ rollup-pluginutils@2.8.2:
+ resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
+
+ rollup@4.65.0:
+ resolution: {integrity: sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ url-join@4.0.1:
+ resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+snapshots:
+
+ '@decky/api@1.1.3': {}
+
+ '@decky/rollup@1.0.2':
+ dependencies:
+ '@rollup/plugin-commonjs': 26.0.3(rollup@4.65.0)
+ '@rollup/plugin-json': 6.1.0(rollup@4.65.0)
+ '@rollup/plugin-node-resolve': 15.3.1(rollup@4.65.0)
+ '@rollup/plugin-replace': 5.0.7(rollup@4.65.0)
+ '@rollup/plugin-typescript': 11.1.6(rollup@4.65.0)(tslib@2.8.1)(typescript@5.9.3)
+ merge-anything: 6.0.9
+ rollup: 4.65.0
+ rollup-plugin-delete: 2.2.0(rollup@4.65.0)
+ rollup-plugin-external-globals: 0.11.0(rollup@4.65.0)
+ rollup-plugin-import-assets: 1.1.1(rollup@4.65.0)
+ tslib: 2.8.1
+ typescript: 5.9.3
+
+ '@decky/ui@4.16.0': {}
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.2.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@jridgewell/sourcemap-codec@1.6.0': {}
+
+ '@napi-rs/lzma-linux-x64-gnu@1.5.1':
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.3
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@rollup/plugin-commonjs@26.0.3(rollup@4.65.0)':
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ commondir: 1.0.1
+ estree-walker: 2.0.2
+ glob: 10.5.0
+ is-reference: 1.2.1
+ magic-string: 0.30.21
+ optionalDependencies:
+ rollup: 4.65.0
+
+ '@rollup/plugin-json@6.1.0(rollup@4.65.0)':
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ optionalDependencies:
+ rollup: 4.65.0
+
+ '@rollup/plugin-node-resolve@15.3.1(rollup@4.65.0)':
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ '@types/resolve': 1.20.2
+ deepmerge: 4.3.1
+ is-module: 1.0.0
+ resolve: 1.22.13
+ optionalDependencies:
+ rollup: 4.65.0
+
+ '@rollup/plugin-replace@5.0.7(rollup@4.65.0)':
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ magic-string: 0.30.21
+ optionalDependencies:
+ rollup: 4.65.0
+
+ '@rollup/plugin-typescript@11.1.6(rollup@4.65.0)(tslib@2.8.1)(typescript@5.9.3)':
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ resolve: 1.22.13
+ typescript: 5.9.3
+ optionalDependencies:
+ rollup: 4.65.0
+ tslib: 2.8.1
+
+ '@rollup/pluginutils@5.5.1(rollup@4.65.0)':
+ dependencies:
+ '@types/estree': 1.0.9
+ estree-walker: 2.0.2
+ picomatch: 4.0.7
+ optionalDependencies:
+ rollup: 4.65.0
+
+ '@rollup/rollup-android-arm-eabi@4.65.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.65.0':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.65.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.65.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.65.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.65.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.65.0':
+ optional: true
+
+ '@types/estree@1.0.9': {}
+
+ '@types/react@19.3.0':
+ dependencies:
+ csstype: 3.2.3
+
+ '@types/resolve@1.20.2': {}
+
+ aggregate-error@3.1.0:
+ dependencies:
+ clean-stack: 2.2.0
+ indent-string: 4.0.0
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.4.0: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@6.2.3: {}
+
+ array-union@2.1.0: {}
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@1.1.21:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.1.7:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ clean-stack@2.2.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ commondir@1.0.1: {}
+
+ concat-map@0.0.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.2.3: {}
+
+ deepmerge@4.3.1: {}
+
+ del@6.1.1:
+ dependencies:
+ globby: 11.1.0
+ graceful-fs: 4.2.11
+ is-glob: 4.0.3
+ is-path-cwd: 2.2.0
+ is-path-inside: 3.0.3
+ p-map: 4.0.0
+ rimraf: 3.0.2
+ slash: 3.0.0
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ eastasianwidth@0.2.0: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ es-errors@1.3.0: {}
+
+ estree-walker@0.6.1: {}
+
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fastq@1.20.3:
+ dependencies:
+ reusify: 1.1.0
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.5.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.9
+ minipass: 7.1.3
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.5
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ graceful-fs@4.2.11: {}
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ ignore@5.3.2: {}
+
+ indent-string@4.0.0: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ is-core-module@2.17.0:
+ dependencies:
+ hasown: 2.0.4
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-module@1.0.0: {}
+
+ is-number@7.0.0: {}
+
+ is-path-cwd@2.2.0: {}
+
+ is-path-inside@3.0.3: {}
+
+ is-reference@1.2.1:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ is-reference@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ is-what@5.5.7: {}
+
+ isexe@2.0.0: {}
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ lru-cache@10.4.3: {}
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ merge-anything@6.0.9:
+ dependencies:
+ is-what: 5.5.7
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.21
+
+ minimatch@9.0.9:
+ dependencies:
+ brace-expansion: 2.1.7
+
+ minipass@7.1.3: {}
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ p-map@4.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+
+ package-json-from-dist@1.0.1: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.3
+
+ path-type@4.0.0: {}
+
+ picomatch@2.3.2: {}
+
+ picomatch@4.0.7: {}
+
+ queue-microtask@1.2.3: {}
+
+ react@19.3.1: {}
+
+ resolve@1.22.13:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.17.0
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.1.0: {}
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+
+ rollup-plugin-delete@2.2.0(rollup@4.65.0):
+ dependencies:
+ del: 6.1.1
+ rollup: 4.65.0
+
+ rollup-plugin-external-globals@0.11.0(rollup@4.65.0):
+ dependencies:
+ '@rollup/pluginutils': 5.5.1(rollup@4.65.0)
+ estree-walker: 3.0.3
+ is-reference: 3.0.3
+ magic-string: 0.30.21
+ rollup: 4.65.0
+
+ rollup-plugin-import-assets@1.1.1(rollup@4.65.0):
+ dependencies:
+ rollup: 4.65.0
+ rollup-pluginutils: 2.8.2
+ url-join: 4.0.1
+
+ rollup-pluginutils@2.8.2:
+ dependencies:
+ estree-walker: 0.6.1
+
+ rollup@4.65.0:
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@napi-rs/lzma-linux-x64-gnu': 1.5.1
+ '@rollup/rollup-android-arm-eabi': 4.65.0
+ '@rollup/rollup-android-arm64': 4.65.0
+ '@rollup/rollup-darwin-arm64': 4.65.0
+ '@rollup/rollup-darwin-x64': 4.65.0
+ '@rollup/rollup-freebsd-arm64': 4.65.0
+ '@rollup/rollup-freebsd-x64': 4.65.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.65.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.65.0
+ '@rollup/rollup-linux-arm64-gnu': 4.65.0
+ '@rollup/rollup-linux-arm64-musl': 4.65.0
+ '@rollup/rollup-linux-loong64-gnu': 4.65.0
+ '@rollup/rollup-linux-loong64-musl': 4.65.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.65.0
+ '@rollup/rollup-linux-ppc64-musl': 4.65.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.65.0
+ '@rollup/rollup-linux-riscv64-musl': 4.65.0
+ '@rollup/rollup-linux-s390x-gnu': 4.65.0
+ '@rollup/rollup-linux-x64-gnu': 4.65.0
+ '@rollup/rollup-linux-x64-musl': 4.65.0
+ '@rollup/rollup-openbsd-x64': 4.65.0
+ '@rollup/rollup-openharmony-arm64': 4.65.0
+ '@rollup/rollup-win32-arm64-msvc': 4.65.0
+ '@rollup/rollup-win32-ia32-msvc': 4.65.0
+ '@rollup/rollup-win32-x64-gnu': 4.65.0
+ '@rollup/rollup-win32-x64-msvc': 4.65.0
+ fsevents: 2.3.3
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ slash@3.0.0: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.2.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.4.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ url-join@4.0.1: {}
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.2.0
+
+ wrappy@1.0.2: {}
diff --git a/examples/framey-settings/rollup.config.js b/examples/framey-settings/rollup.config.js
new file mode 100644
index 0000000..0cc9ac0
--- /dev/null
+++ b/examples/framey-settings/rollup.config.js
@@ -0,0 +1,3 @@
+import deckyPlugin from '@decky/rollup';
+
+export default deckyPlugin({});
diff --git a/examples/framey-settings/src/index.tsx b/examples/framey-settings/src/index.tsx
new file mode 100644
index 0000000..b93a1f0
--- /dev/null
+++ b/examples/framey-settings/src/index.tsx
@@ -0,0 +1,56 @@
+import {callable, definePlugin, toaster} from '@decky/api';
+import {ButtonItem, PanelSection, PanelSectionRow, ToggleField} from '@decky/ui';
+import {useEffect, useState} from 'react';
+
+const getSetting = callable<[key: string, fallback: boolean], boolean>('get_setting');
+const setSetting = callable<[key: string, value: boolean], boolean>('set_setting');
+
+function Content() {
+ const [enabled, setEnabled] = useState(false);
+ const [busy, setBusy] = useState(true);
+ const [message, setMessage] = useState('Loading setting…');
+
+ async function refresh() {
+ setBusy(true);
+ try {
+ const value = await getSetting('enabled', false);
+ setEnabled(value);
+ setMessage(`Saved setting: ${value ? 'on' : 'off'}`);
+ } catch (error) {
+ setMessage(`Could not read setting: ${String(error)}`);
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ useEffect(() => {void refresh();}, []);
+
+ async function change(value: boolean) {
+ setBusy(true);
+ try {
+ await setSetting('enabled', value);
+ setEnabled(value);
+ setMessage(`Saved setting: ${value ? 'on' : 'off'}`);
+ toaster.toast({title: 'Framey sample', body: 'Setting saved'});
+ } catch (error) {
+ setMessage(`Could not save setting: ${String(error)}`);
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ return
+
+ {void change(value);}} />
+
+ {void refresh();}}>Reload saved setting
+ {message}
+ ;
+}
+
+export default definePlugin(() => ({
+ name: 'Framey Settings Sample',
+ icon: ⚙,
+ content: ,
+}));
diff --git a/examples/framey-settings/tsconfig.json b/examples/framey-settings/tsconfig.json
new file mode 100644
index 0000000..b0ea5fb
--- /dev/null
+++ b/examples/framey-settings/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "outDir": "dist",
+ "module": "ESNext",
+ "target": "ES2020",
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ },
+ "include": ["src"]
+}
diff --git a/tests/page/framey_sample.mjs b/tests/page/framey_sample.mjs
new file mode 100644
index 0000000..e54e2c3
--- /dev/null
+++ b/tests/page/framey_sample.mjs
@@ -0,0 +1,25 @@
+import assert from 'node:assert/strict';
+import {readFileSync} from 'node:fs';
+import vm from 'node:vm';
+
+const host=readFileSync(new URL('../../ui/framey-host.js',import.meta.url),'utf8')
+ .replace(/\n start\(\);\n\}\)\(\);\s*$/, '\n})();');
+const bundle=readFileSync(new URL('../../examples/framey-settings/dist/index.js',import.meta.url),'utf8');
+assert.match(bundle,/SP_JSX\.jsx/);
+const React={Fragment:Symbol('fragment'),Component:class {},
+ createElement:(type,props,...children)=>({type,props:props||{},children}),
+ useState:value=>[value,()=>{}],useEffect(){},isValidElement:value=>!!value?.type};
+const window={React,ReactDOM:{createRoot:()=>({render(){}})},addEventListener(){}};
+const context=vm.createContext({window,document:{getElementById:()=>({})},
+ navigator:{language:'en'},parent:{postMessage(){}},addEventListener(){},
+ setTimeout:()=>1,clearTimeout(){}});
+vm.runInContext(host,context);
+Object.assign(context,{SP_REACT:window.SP_REACT,SP_JSX:window.SP_JSX,DFL:window.DFL});
+vm.runInContext(bundle.replace(/export \{ index as default \};/, 'window.sample = index;'),context);
+const plugin=window.sample();
+assert.equal(plugin.name,'Framey Settings Sample');
+assert.equal(typeof plugin.content.type,'function');
+const content=plugin.content.type();
+assert.equal(content.type,window.DFL.PanelSection);
+assert.equal(content.props.title,'Framey settings sample');
+assert.equal(content.children[0][0].type,window.DFL.PanelSectionRow);
diff --git a/tests/test_framey_sample.py b/tests/test_framey_sample.py
new file mode 100644
index 0000000..7761e51
--- /dev/null
+++ b/tests/test_framey_sample.py
@@ -0,0 +1,50 @@
+"""The author example stores settings as a normal Decky user plugin."""
+import asyncio
+import importlib.util
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+import types
+import unittest
+from unittest.mock import patch
+
+
+EXAMPLE = Path(__file__).resolve().parents[1] / 'examples/framey-settings/main.py'
+
+
+def backend(settings_dir):
+ decky = types.ModuleType('decky')
+ decky.decky_SETTINGS_DIR = settings_dir
+ spec = importlib.util.spec_from_file_location('framey_sample', EXAMPLE)
+ module = importlib.util.module_from_spec(spec)
+ with patch.dict(sys.modules, {'decky': decky}):
+ spec.loader.exec_module(module)
+ return module.Plugin()
+
+
+class SamplePlugin(unittest.TestCase):
+ def test_frontend_bundle_loads_in_framey_host(self):
+ result = subprocess.run(['node', str(Path(__file__).parent / 'page/framey_sample.mjs')],
+ capture_output=True, text=True, timeout=10)
+ self.assertEqual(result.returncode, 0, result.stderr)
+
+ def test_setting_survives_backend_instance_restart(self):
+ with tempfile.TemporaryDirectory() as folder:
+ first = backend(folder)
+ self.assertFalse(asyncio.run(first.get_setting('enabled', False)))
+ self.assertTrue(asyncio.run(first.set_setting('enabled', True)))
+ second = backend(folder)
+ self.assertTrue(asyncio.run(second.get_setting('enabled', False)))
+ self.assertTrue((Path(folder) / 'settings.json').is_file())
+
+ def test_backend_rejects_unknown_keys_and_non_boolean_values(self):
+ with tempfile.TemporaryDirectory() as folder:
+ plugin = backend(folder)
+ with self.assertRaises(ValueError): asyncio.run(plugin.get_setting('other'))
+ with self.assertRaises(ValueError): asyncio.run(plugin.set_setting('enabled', 'yes'))
+ self.assertFalse((Path(folder) / 'settings.json').exists())
+
+
+if __name__ == '__main__':
+ unittest.main()