@ethisyscore/components-react 1.7.1

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.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # `@ethisyscore/components-react`
2
+
3
+ Host-bound React contract for EthisysCore PlatformReact pages (RFC 0003).
4
+
5
+ Provides the typed page-props contract every PlatformReact page receives from
6
+ the host at surface mount time, plus a `definePlatformReactPage` helper so
7
+ plugin authors get type-safe access to the host realm without depending on
8
+ the host app's internal modules.
9
+
10
+ ```tsx
11
+ import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
12
+
13
+ const Dashboard = ({ extensionId, organisationId, pageId }: PlatformReactPageProps) => (
14
+ <section>
15
+ <h1>Dashboard</h1>
16
+ <p>Extension {extensionId} for org {organisationId} (page: {pageId})</p>
17
+ </section>
18
+ );
19
+
20
+ export default definePlatformReactPage(Dashboard);
21
+ ```
22
+
23
+ The plugin's `feature.manifest.json` references this module via
24
+ `ui.platformReactPages[].moduleSpecifier`; the host loads the module at surface
25
+ mount time, reads the declared `exportName`, and renders the component with the
26
+ typed props.
27
+
28
+ ## Why this package exists
29
+
30
+ PlatformReact is the high-trust platform-extension tier — the plugin's bundle
31
+ runs in the host realm, so the plugin can use any npm package the author
32
+ bundles. This package is the **one** dependency that has to come from the host
33
+ side: the contract for what the host passes to the mounted page. Keeping it as
34
+ a focused npm package means:
35
+
36
+ - The host's internal modules (`coreconnect-web`'s `PlatformReactSurfaceMount`)
37
+ do not leak as plugin dependencies.
38
+ - The contract grows with explicit versioning — additive changes are
39
+ backward-compatible, breaking changes bump the major.
40
+ - Plugin authors get strict TypeScript at the host-↔-plugin seam — no `any`,
41
+ no shape drift.
42
+
43
+ ## What's in the package
44
+
45
+ - `PlatformReactPageProps` — the props every page receives from the host
46
+ (`extensionId`, `organisationId`, optional `tenantId`, `pageId`).
47
+ - `definePlatformReactPage(Component)` — pass-through helper today; the
48
+ indirection point for future host integrations (boundary injection,
49
+ suspense fallbacks, error contracts) to land without every plugin author
50
+ updating their export.
51
+
52
+ See `docs/authoring/platform-react.md` for the full PlatformReact authoring
53
+ guide.
package/dist/index.cjs ADDED
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // src/navigation.ts
4
+ function emitNavigation(to) {
5
+ if (typeof window === "undefined") {
6
+ return;
7
+ }
8
+ const event = new CustomEvent("ethisys:navigate", {
9
+ detail: { to },
10
+ cancelable: true
11
+ });
12
+ window.dispatchEvent(event);
13
+ if (!event.defaultPrevented) {
14
+ window.location.assign(to);
15
+ }
16
+ }
17
+
18
+ // src/index.ts
19
+ function definePlatformReactPage(component) {
20
+ return component;
21
+ }
22
+
23
+ exports.definePlatformReactPage = definePlatformReactPage;
24
+ exports.emitNavigation = emitNavigation;
25
+ //# sourceMappingURL=index.cjs.map
26
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/navigation.ts","../src/index.ts"],"names":[],"mappings":";;;AAmBO,SAAS,eAAe,EAAA,EAAkB;AAC/C,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,kBAAA,EAAoB;AAAA,IAChD,MAAA,EAAQ,EAAE,EAAA,EAAG;AAAA,IACb,UAAA,EAAY;AAAA,GACb,CAAA;AACD,EAAA,MAAA,CAAO,cAAc,KAAK,CAAA;AAG1B,EAAA,IAAI,CAAC,MAAM,gBAAA,EAAkB;AAC3B,IAAA,MAAA,CAAO,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,EAC3B;AACF;;;ACmCO,SAAS,wBACd,SAAA,EACkB;AAClB,EAAA,OAAO,SAAA;AACT","file":"index.cjs","sourcesContent":["/**\n * Host navigation bridge for PlatformReact pages (RFC 0003).\n *\n * PlatformReact pages run inside the host's React tree but cannot import the\n * host's `useNavigate` directly — that would couple the plugin's bundle to\n * the host's router internals and break the realm boundary the contract\n * preserves. Instead, plugins dispatch a `CustomEvent(\"ethisys:navigate\")`\n * on `window`; the host's `useExtensionNavigation` listener intercepts the\n * event and translates it into a React Router push.\n *\n * `cancelable: true` is load-bearing — without it the host's\n * `event.preventDefault()` is a no-op (CustomEvent defaults to\n * non-cancelable), so `defaultPrevented` stays `false`, the\n * `window.location.assign` fallback fires AFTER the SPA navigation has\n * already run, and the user sees an unwanted full-page reload. The flag is\n * the entire contract between plugin and host on this seam, which is why\n * the helper exists — getting it right per-plugin is exactly the kind of\n * detail that should be baked into the SDK rather than the README.\n */\nexport function emitNavigation(to: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const event = new CustomEvent(\"ethisys:navigate\", {\n detail: { to },\n cancelable: true,\n });\n window.dispatchEvent(event);\n // Fallback for hosts that haven't wired the listener yet — full-document\n // navigation isn't ideal but keeps the page functional.\n if (!event.defaultPrevented) {\n window.location.assign(to);\n }\n}\n","import type { ComponentType } from \"react\";\n\n// ── Public contract ────────────────────────────────────────────────\n\n/**\n * The injected props every PlatformReact page component receives from the\n * host at surface mount time (RFC 0003).\n *\n * The host's surface mount (`PlatformReactSurfaceMount.tsx` in\n * `coreconnect-web`) reads each declared page's `exportName` from the loaded\n * module and renders it with these props. Plugin authors typing their page\n * components against this interface get strict type-checking for everything\n * the host actually supplies — no `any` at the seam.\n *\n * The shape is intentionally minimal. Additional context the host wires up\n * (theme, navigation, telemetry) reaches plugins via cross-cutting providers\n * the host wraps around the mounted component, not through this props\n * object — so growing the host's contextual surface does not change this\n * interface.\n */\nexport interface PlatformReactPageProps {\n /**\n * The extension's GUID as declared on the manifest's `id` field. Stable\n * across enable/disable lifecycle transitions within a tenant.\n */\n extensionId: string;\n /**\n * The active organisation. Plugins MUST scope every data read and write to\n * this id — the host enforces tenant isolation downstream, but propagating\n * the id keeps the dependency direction explicit and testable.\n */\n organisationId: string;\n /**\n * Optional tenant id. Present for tenant-bound deployments; absent for\n * platform-wide pages. Pages that branch on this MUST handle `undefined`.\n */\n tenantId?: string;\n /**\n * The host-side surface key for this page. Matches the `id` declared on\n * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags\n * and for pages that share a component implementation across multiple\n * surfaces.\n */\n pageId: string;\n}\n\n/**\n * Type-safe declaration helper for a PlatformReact page component.\n *\n * The plugin author exports the result of this call from the module path\n * referenced by the manifest's `moduleSpecifier`:\n *\n * @example\n * ```tsx\n * import { definePlatformReactPage, type PlatformReactPageProps } from \"@ethisyscore/components-react\";\n *\n * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (\n * <div>Dashboard for {extensionId} / {organisationId}</div>\n * );\n *\n * export default definePlatformReactPage(Dashboard);\n * ```\n *\n * The helper currently returns the input component unchanged — it exists as\n * a stable indirection point so future host integrations (boundary-injection,\n * suspense fallbacks, error contracts) can land transparently without every\n * plugin author updating their export.\n */\nexport function definePlatformReactPage<P extends PlatformReactPageProps>(\n component: ComponentType<P>,\n): ComponentType<P> {\n return component;\n}\n\nexport { emitNavigation } from \"./navigation\";\n"]}
@@ -0,0 +1,89 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ /**
4
+ * Host navigation bridge for PlatformReact pages (RFC 0003).
5
+ *
6
+ * PlatformReact pages run inside the host's React tree but cannot import the
7
+ * host's `useNavigate` directly — that would couple the plugin's bundle to
8
+ * the host's router internals and break the realm boundary the contract
9
+ * preserves. Instead, plugins dispatch a `CustomEvent("ethisys:navigate")`
10
+ * on `window`; the host's `useExtensionNavigation` listener intercepts the
11
+ * event and translates it into a React Router push.
12
+ *
13
+ * `cancelable: true` is load-bearing — without it the host's
14
+ * `event.preventDefault()` is a no-op (CustomEvent defaults to
15
+ * non-cancelable), so `defaultPrevented` stays `false`, the
16
+ * `window.location.assign` fallback fires AFTER the SPA navigation has
17
+ * already run, and the user sees an unwanted full-page reload. The flag is
18
+ * the entire contract between plugin and host on this seam, which is why
19
+ * the helper exists — getting it right per-plugin is exactly the kind of
20
+ * detail that should be baked into the SDK rather than the README.
21
+ */
22
+ declare function emitNavigation(to: string): void;
23
+
24
+ /**
25
+ * The injected props every PlatformReact page component receives from the
26
+ * host at surface mount time (RFC 0003).
27
+ *
28
+ * The host's surface mount (`PlatformReactSurfaceMount.tsx` in
29
+ * `coreconnect-web`) reads each declared page's `exportName` from the loaded
30
+ * module and renders it with these props. Plugin authors typing their page
31
+ * components against this interface get strict type-checking for everything
32
+ * the host actually supplies — no `any` at the seam.
33
+ *
34
+ * The shape is intentionally minimal. Additional context the host wires up
35
+ * (theme, navigation, telemetry) reaches plugins via cross-cutting providers
36
+ * the host wraps around the mounted component, not through this props
37
+ * object — so growing the host's contextual surface does not change this
38
+ * interface.
39
+ */
40
+ interface PlatformReactPageProps {
41
+ /**
42
+ * The extension's GUID as declared on the manifest's `id` field. Stable
43
+ * across enable/disable lifecycle transitions within a tenant.
44
+ */
45
+ extensionId: string;
46
+ /**
47
+ * The active organisation. Plugins MUST scope every data read and write to
48
+ * this id — the host enforces tenant isolation downstream, but propagating
49
+ * the id keeps the dependency direction explicit and testable.
50
+ */
51
+ organisationId: string;
52
+ /**
53
+ * Optional tenant id. Present for tenant-bound deployments; absent for
54
+ * platform-wide pages. Pages that branch on this MUST handle `undefined`.
55
+ */
56
+ tenantId?: string;
57
+ /**
58
+ * The host-side surface key for this page. Matches the `id` declared on
59
+ * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags
60
+ * and for pages that share a component implementation across multiple
61
+ * surfaces.
62
+ */
63
+ pageId: string;
64
+ }
65
+ /**
66
+ * Type-safe declaration helper for a PlatformReact page component.
67
+ *
68
+ * The plugin author exports the result of this call from the module path
69
+ * referenced by the manifest's `moduleSpecifier`:
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
74
+ *
75
+ * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (
76
+ * <div>Dashboard for {extensionId} / {organisationId}</div>
77
+ * );
78
+ *
79
+ * export default definePlatformReactPage(Dashboard);
80
+ * ```
81
+ *
82
+ * The helper currently returns the input component unchanged — it exists as
83
+ * a stable indirection point so future host integrations (boundary-injection,
84
+ * suspense fallbacks, error contracts) can land transparently without every
85
+ * plugin author updating their export.
86
+ */
87
+ declare function definePlatformReactPage<P extends PlatformReactPageProps>(component: ComponentType<P>): ComponentType<P>;
88
+
89
+ export { type PlatformReactPageProps, definePlatformReactPage, emitNavigation };
@@ -0,0 +1,89 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ /**
4
+ * Host navigation bridge for PlatformReact pages (RFC 0003).
5
+ *
6
+ * PlatformReact pages run inside the host's React tree but cannot import the
7
+ * host's `useNavigate` directly — that would couple the plugin's bundle to
8
+ * the host's router internals and break the realm boundary the contract
9
+ * preserves. Instead, plugins dispatch a `CustomEvent("ethisys:navigate")`
10
+ * on `window`; the host's `useExtensionNavigation` listener intercepts the
11
+ * event and translates it into a React Router push.
12
+ *
13
+ * `cancelable: true` is load-bearing — without it the host's
14
+ * `event.preventDefault()` is a no-op (CustomEvent defaults to
15
+ * non-cancelable), so `defaultPrevented` stays `false`, the
16
+ * `window.location.assign` fallback fires AFTER the SPA navigation has
17
+ * already run, and the user sees an unwanted full-page reload. The flag is
18
+ * the entire contract between plugin and host on this seam, which is why
19
+ * the helper exists — getting it right per-plugin is exactly the kind of
20
+ * detail that should be baked into the SDK rather than the README.
21
+ */
22
+ declare function emitNavigation(to: string): void;
23
+
24
+ /**
25
+ * The injected props every PlatformReact page component receives from the
26
+ * host at surface mount time (RFC 0003).
27
+ *
28
+ * The host's surface mount (`PlatformReactSurfaceMount.tsx` in
29
+ * `coreconnect-web`) reads each declared page's `exportName` from the loaded
30
+ * module and renders it with these props. Plugin authors typing their page
31
+ * components against this interface get strict type-checking for everything
32
+ * the host actually supplies — no `any` at the seam.
33
+ *
34
+ * The shape is intentionally minimal. Additional context the host wires up
35
+ * (theme, navigation, telemetry) reaches plugins via cross-cutting providers
36
+ * the host wraps around the mounted component, not through this props
37
+ * object — so growing the host's contextual surface does not change this
38
+ * interface.
39
+ */
40
+ interface PlatformReactPageProps {
41
+ /**
42
+ * The extension's GUID as declared on the manifest's `id` field. Stable
43
+ * across enable/disable lifecycle transitions within a tenant.
44
+ */
45
+ extensionId: string;
46
+ /**
47
+ * The active organisation. Plugins MUST scope every data read and write to
48
+ * this id — the host enforces tenant isolation downstream, but propagating
49
+ * the id keeps the dependency direction explicit and testable.
50
+ */
51
+ organisationId: string;
52
+ /**
53
+ * Optional tenant id. Present for tenant-bound deployments; absent for
54
+ * platform-wide pages. Pages that branch on this MUST handle `undefined`.
55
+ */
56
+ tenantId?: string;
57
+ /**
58
+ * The host-side surface key for this page. Matches the `id` declared on
59
+ * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags
60
+ * and for pages that share a component implementation across multiple
61
+ * surfaces.
62
+ */
63
+ pageId: string;
64
+ }
65
+ /**
66
+ * Type-safe declaration helper for a PlatformReact page component.
67
+ *
68
+ * The plugin author exports the result of this call from the module path
69
+ * referenced by the manifest's `moduleSpecifier`:
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
74
+ *
75
+ * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (
76
+ * <div>Dashboard for {extensionId} / {organisationId}</div>
77
+ * );
78
+ *
79
+ * export default definePlatformReactPage(Dashboard);
80
+ * ```
81
+ *
82
+ * The helper currently returns the input component unchanged — it exists as
83
+ * a stable indirection point so future host integrations (boundary-injection,
84
+ * suspense fallbacks, error contracts) can land transparently without every
85
+ * plugin author updating their export.
86
+ */
87
+ declare function definePlatformReactPage<P extends PlatformReactPageProps>(component: ComponentType<P>): ComponentType<P>;
88
+
89
+ export { type PlatformReactPageProps, definePlatformReactPage, emitNavigation };
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ // src/navigation.ts
2
+ function emitNavigation(to) {
3
+ if (typeof window === "undefined") {
4
+ return;
5
+ }
6
+ const event = new CustomEvent("ethisys:navigate", {
7
+ detail: { to },
8
+ cancelable: true
9
+ });
10
+ window.dispatchEvent(event);
11
+ if (!event.defaultPrevented) {
12
+ window.location.assign(to);
13
+ }
14
+ }
15
+
16
+ // src/index.ts
17
+ function definePlatformReactPage(component) {
18
+ return component;
19
+ }
20
+
21
+ export { definePlatformReactPage, emitNavigation };
22
+ //# sourceMappingURL=index.js.map
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/navigation.ts","../src/index.ts"],"names":[],"mappings":";AAmBO,SAAS,eAAe,EAAA,EAAkB;AAC/C,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,kBAAA,EAAoB;AAAA,IAChD,MAAA,EAAQ,EAAE,EAAA,EAAG;AAAA,IACb,UAAA,EAAY;AAAA,GACb,CAAA;AACD,EAAA,MAAA,CAAO,cAAc,KAAK,CAAA;AAG1B,EAAA,IAAI,CAAC,MAAM,gBAAA,EAAkB;AAC3B,IAAA,MAAA,CAAO,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,EAC3B;AACF;;;ACmCO,SAAS,wBACd,SAAA,EACkB;AAClB,EAAA,OAAO,SAAA;AACT","file":"index.js","sourcesContent":["/**\n * Host navigation bridge for PlatformReact pages (RFC 0003).\n *\n * PlatformReact pages run inside the host's React tree but cannot import the\n * host's `useNavigate` directly — that would couple the plugin's bundle to\n * the host's router internals and break the realm boundary the contract\n * preserves. Instead, plugins dispatch a `CustomEvent(\"ethisys:navigate\")`\n * on `window`; the host's `useExtensionNavigation` listener intercepts the\n * event and translates it into a React Router push.\n *\n * `cancelable: true` is load-bearing — without it the host's\n * `event.preventDefault()` is a no-op (CustomEvent defaults to\n * non-cancelable), so `defaultPrevented` stays `false`, the\n * `window.location.assign` fallback fires AFTER the SPA navigation has\n * already run, and the user sees an unwanted full-page reload. The flag is\n * the entire contract between plugin and host on this seam, which is why\n * the helper exists — getting it right per-plugin is exactly the kind of\n * detail that should be baked into the SDK rather than the README.\n */\nexport function emitNavigation(to: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const event = new CustomEvent(\"ethisys:navigate\", {\n detail: { to },\n cancelable: true,\n });\n window.dispatchEvent(event);\n // Fallback for hosts that haven't wired the listener yet — full-document\n // navigation isn't ideal but keeps the page functional.\n if (!event.defaultPrevented) {\n window.location.assign(to);\n }\n}\n","import type { ComponentType } from \"react\";\n\n// ── Public contract ────────────────────────────────────────────────\n\n/**\n * The injected props every PlatformReact page component receives from the\n * host at surface mount time (RFC 0003).\n *\n * The host's surface mount (`PlatformReactSurfaceMount.tsx` in\n * `coreconnect-web`) reads each declared page's `exportName` from the loaded\n * module and renders it with these props. Plugin authors typing their page\n * components against this interface get strict type-checking for everything\n * the host actually supplies — no `any` at the seam.\n *\n * The shape is intentionally minimal. Additional context the host wires up\n * (theme, navigation, telemetry) reaches plugins via cross-cutting providers\n * the host wraps around the mounted component, not through this props\n * object — so growing the host's contextual surface does not change this\n * interface.\n */\nexport interface PlatformReactPageProps {\n /**\n * The extension's GUID as declared on the manifest's `id` field. Stable\n * across enable/disable lifecycle transitions within a tenant.\n */\n extensionId: string;\n /**\n * The active organisation. Plugins MUST scope every data read and write to\n * this id — the host enforces tenant isolation downstream, but propagating\n * the id keeps the dependency direction explicit and testable.\n */\n organisationId: string;\n /**\n * Optional tenant id. Present for tenant-bound deployments; absent for\n * platform-wide pages. Pages that branch on this MUST handle `undefined`.\n */\n tenantId?: string;\n /**\n * The host-side surface key for this page. Matches the `id` declared on\n * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags\n * and for pages that share a component implementation across multiple\n * surfaces.\n */\n pageId: string;\n}\n\n/**\n * Type-safe declaration helper for a PlatformReact page component.\n *\n * The plugin author exports the result of this call from the module path\n * referenced by the manifest's `moduleSpecifier`:\n *\n * @example\n * ```tsx\n * import { definePlatformReactPage, type PlatformReactPageProps } from \"@ethisyscore/components-react\";\n *\n * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (\n * <div>Dashboard for {extensionId} / {organisationId}</div>\n * );\n *\n * export default definePlatformReactPage(Dashboard);\n * ```\n *\n * The helper currently returns the input component unchanged — it exists as\n * a stable indirection point so future host integrations (boundary-injection,\n * suspense fallbacks, error contracts) can land transparently without every\n * plugin author updating their export.\n */\nexport function definePlatformReactPage<P extends PlatformReactPageProps>(\n component: ComponentType<P>,\n): ComponentType<P> {\n return component;\n}\n\nexport { emitNavigation } from \"./navigation\";\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ethisyscore/components-react",
3
+ "version": "1.7.1",
4
+ "description": "Host-bound React contract for EthisysCore PlatformReact pages (RFC 0003). Provides the typed page-props contract and `definePlatformReactPage` helper so plugin authors get type-safe access to the host realm — extensionId, organisationId, mcp transport — without depending on the host app's internal modules.",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "lint": "tsc --noEmit"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^25.5.0",
30
+ "@types/react": "^19.0.0",
31
+ "react": "^19.0.0",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.9.3",
34
+ "vitest": "^4.1.0"
35
+ },
36
+ "keywords": [
37
+ "ethisyscore",
38
+ "platform-react",
39
+ "rfc-0003",
40
+ "plugin"
41
+ ],
42
+ "license": "Apache-2.0",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/ethisysltd/ethisyscore-plugin-sdk"
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
49
+ }
50
+ }