@unoverse-platform/studio 0.1.0

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 (57) hide show
  1. package/bin/studio.mjs +124 -0
  2. package/index.html +12 -0
  3. package/local/catalog.ts +105 -0
  4. package/local/keys.ts +118 -0
  5. package/local/nodes.ts +77 -0
  6. package/local/plugin.ts +401 -0
  7. package/local/scaffold.d.mts +13 -0
  8. package/local/scaffold.mjs +85 -0
  9. package/package.json +43 -0
  10. package/public/logo.png +0 -0
  11. package/screens/AtomsView.tsx +206 -0
  12. package/screens/ComponentsView.tsx +551 -0
  13. package/screens/ConfigForm.tsx +217 -0
  14. package/screens/DesignSystemView.tsx +324 -0
  15. package/screens/IdentityHeader.tsx +88 -0
  16. package/screens/PromptBlocksView.tsx +137 -0
  17. package/screens/SkillsView.tsx +167 -0
  18. package/screens/TemplatesView.tsx +1005 -0
  19. package/screens/index.ts +26 -0
  20. package/screens/states.ts +44 -0
  21. package/src/App.tsx +252 -0
  22. package/src/ConnectUniverse.tsx +232 -0
  23. package/src/NewProject.tsx +89 -0
  24. package/src/NodeKeys.tsx +98 -0
  25. package/src/NodesView.tsx +443 -0
  26. package/src/PublishDialog.tsx +309 -0
  27. package/src/UniverseSession.tsx +121 -0
  28. package/src/host.ts +165 -0
  29. package/src/index.css +7 -0
  30. package/src/main.tsx +11 -0
  31. package/src/universes.ts +210 -0
  32. package/vendor/sdk/appEngine.tsx +219 -0
  33. package/vendor/sdk/lib/UnoverseComponent.tsx +120 -0
  34. package/vendor/sdk/lib/connection.tsx +302 -0
  35. package/vendor/sdk/lib/core/actions.ts +84 -0
  36. package/vendor/sdk/lib/core/client.ts +134 -0
  37. package/vendor/sdk/lib/core/conditions.ts +43 -0
  38. package/vendor/sdk/lib/core/connection.ts +142 -0
  39. package/vendor/sdk/lib/core/index.ts +32 -0
  40. package/vendor/sdk/lib/core/store.ts +455 -0
  41. package/vendor/sdk/lib/core/types.ts +194 -0
  42. package/vendor/sdk/lib/index.ts +59 -0
  43. package/vendor/sdk/lib/isolate.tsx +70 -0
  44. package/vendor/sdk/lib/primitives.tsx +453 -0
  45. package/vendor/sdk/lib/realtime/audioUtils.ts +40 -0
  46. package/vendor/sdk/lib/realtime/types.ts +45 -0
  47. package/vendor/sdk/lib/realtime/useAudioCapture.ts +237 -0
  48. package/vendor/sdk/lib/realtime/useAudioPlayback.ts +145 -0
  49. package/vendor/sdk/lib/realtime/useRealtimeWebSocket.ts +164 -0
  50. package/vendor/sdk/lib/render.tsx +178 -0
  51. package/vendor/sdk/lib/streamed.tsx +65 -0
  52. package/vendor/sdk/lib/style.ts +227 -0
  53. package/vendor/sdk/lib/template.tsx +521 -0
  54. package/vendor/sdk/lib/theme.ts +55 -0
  55. package/vendor/sdk/lib/voice.tsx +311 -0
  56. package/vendor/sdk/main.tsx +96 -0
  57. package/vite.config.ts +70 -0
package/bin/studio.mjs ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * unoverse-studio — the authoring tool, run from a developer's own project.
4
+ *
5
+ * Studio is a TOOL, not a starter kit. A developer's repository holds only their assets
6
+ * (rx/, prompts/, nodes/); this is installed alongside and reads them. That is why there
7
+ * is no second repository to keep in step, and why their project contains no Docker, no
8
+ * ansible and no platform source.
9
+ *
10
+ * IT RUNS VITE, DELIBERATELY. Studio's file-reading host is a Vite plugin that loads the
11
+ * universe server's own disk loaders (local/plugin.ts, local/catalog.ts) rather than
12
+ * reimplementing them. Reproducing that in a bespoke server would fork those readers and
13
+ * they would drift apart within weeks, which is the exact problem local Studio was built
14
+ * to avoid. So the tool starts the same dev server `npm run dev` does.
15
+ *
16
+ * No database, no Redis, no engine, no Docker, and no account: those belong to running a
17
+ * universe, which is a different job. See docs/architecture/LOCAL_STUDIO.md.
18
+ */
19
+ import { createServer } from "vite";
20
+ import { dirname, resolve, join } from "path";
21
+ import { fileURLToPath } from "url";
22
+ import { existsSync } from "fs";
23
+ import { createInterface } from "readline/promises";
24
+ import { createFirstProject, checkProjectName } from "../local/scaffold.mjs";
25
+
26
+ const here = dirname(fileURLToPath(import.meta.url));
27
+ const appRoot = resolve(here, "..");
28
+
29
+ /**
30
+ * A project, if there is not one here yet.
31
+ *
32
+ * Studio USED to fail with "No rx/ folder found. Create rx/<your-project>/ to start one",
33
+ * which is a tool telling you to do the thing it could do. The first thirty seconds of a
34
+ * new install should end with something on screen, not with a mkdir.
35
+ *
36
+ * It asks rather than assumes: creating folders in whatever directory somebody happens to
37
+ * be in is not a thing to do quietly. Answer no and the original message stands.
38
+ */
39
+ /** The same walk `catalog.ts` does: the nearest folder above us holding rx/. */
40
+ function findProjectRootFromCwd() {
41
+ for (let dir = process.cwd(); ; ) {
42
+ if (existsSync(join(dir, "rx"))) return dir;
43
+ if (existsSync(join(dir, "apps/unoverse/rx"))) return join(dir, "apps/unoverse");
44
+ const parent = resolve(dir, "..");
45
+ if (parent === dir) return process.cwd();
46
+ dir = parent;
47
+ }
48
+ }
49
+
50
+ async function scaffoldIfEmpty() {
51
+ const cwd = process.cwd();
52
+ // The same walk findProjectRoot does. A project ABOVE us is a project; do not offer to
53
+ // make a second one inside it.
54
+ for (let dir = cwd; ; ) {
55
+ if (existsSync(join(dir, "rx")) || existsSync(join(dir, "apps/unoverse/rx"))) return;
56
+ const parent = resolve(dir, "..");
57
+ if (parent === dir) break;
58
+ dir = parent;
59
+ }
60
+
61
+ console.log(`\n No project here yet.\n\n ${cwd}\n`);
62
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
63
+
64
+ // Ask until the name is usable. It becomes a folder AND an item's `org` column, so a
65
+ // rejection here is cheaper than a rename after something has been published.
66
+ let name;
67
+ for (;;) {
68
+ name = (await rl.question(" Name your project [my-project]: ")).trim() || "my-project";
69
+ const bad = checkProjectName(name);
70
+ if (!bad) break;
71
+ console.log(` ${bad}\n`);
72
+ }
73
+ rl.close();
74
+
75
+ // The same call the UI's New project makes, so the first project and the fifth are
76
+ // identical (local/scaffold.mjs).
77
+ createFirstProject(cwd, name);
78
+ console.log(`\n Created rx/${name}/, prompts/ and nodes/\n`);
79
+ }
80
+
81
+ await scaffoldIfEmpty();
82
+
83
+ /**
84
+ * POINT `base` AT THIS DEVELOPER'S PROJECT, before anything imports it.
85
+ *
86
+ * `paths.ts` snapshots its exports at import time (`export const RX_HOME = paths.rx`), so
87
+ * `setPaths()` called later updates an object nobody reads any more. The only hook that
88
+ * lands is the environment, read by `findHome()` on first import.
89
+ *
90
+ * Without this Studio fell back to walking up for `apps/unoverse`, so it worked perfectly
91
+ * in the monorepo it was written in and read the WRONG FOLDER everywhere else: a developer
92
+ * who had just created a project was shown the monorepo's. Silent, because both are valid
93
+ * directories and nothing errors.
94
+ */
95
+ const projectRoot = findProjectRootFromCwd();
96
+ process.env.UNOVERSE_HOME = projectRoot;
97
+ console.log(`\n Studio reading: ${join(projectRoot, "rx")}\n`);
98
+
99
+ const server = await createServer({
100
+ // The APP is the vite root; the developer's project is found by walking up from the
101
+ // working directory (findProjectRoot), so Studio works from anywhere inside it.
102
+ root: appRoot,
103
+ configFile: resolve(appRoot, "vite.config.ts"),
104
+ // NO PORT OPTION. Studio is always 4108 (vite.config.ts, with strictPort), so the
105
+ // address in your browser's history is always the right one. A --port flag existed
106
+ // briefly and only produced sessions on 4117, 4118 and 4119 that nobody could find again.
107
+ server: { open: true },
108
+ // A tool should be quiet unless something is wrong.
109
+ logLevel: "warn",
110
+ });
111
+
112
+ await server.listen();
113
+
114
+ const url = server.resolvedUrls?.local?.[0] ?? `http://localhost:${server.config.server.port}`;
115
+ console.log(`\n unoverse studio ${url}\n`);
116
+
117
+ // Ctrl-C should stop the server rather than leave a listener holding the port, which is
118
+ // the thing that makes the next start fail with a confusing "port in use".
119
+ for (const signal of ["SIGINT", "SIGTERM"]) {
120
+ process.on(signal, async () => {
121
+ await server.close();
122
+ process.exit(0);
123
+ });
124
+ }
package/index.html ADDED
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>unoverse studio</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The LOCAL catalog: the same disk loaders the universe server uses, pointed at the
3
+ * developer's own project folder instead of a running platform.
4
+ *
5
+ * This is the whole reason local Studio needs no backend. `definitions.ts` and
6
+ * `theme.ts` are pure filesystem readers: no Postgres, no Redis, no engine. So local
7
+ * Studio does not reimplement them (a second loader would drift within weeks, which is
8
+ * the exact problem this migration exists to fix). It imports the SAME modules and sets
9
+ * RX_PATH to the developer's folder before they load.
10
+ *
11
+ * See docs/architecture/LOCAL_STUDIO.md.
12
+ */
13
+ import { resolve } from "path";
14
+ import { existsSync } from "fs";
15
+
16
+ /** The server's loader modules, loaded lazily so RX_PATH is set before they read it. */
17
+ type Loaders = {
18
+ listDefinitions: (kind: string, org?: string) => unknown[];
19
+ listStates: (kind: string, name: string) => unknown[];
20
+ listTemplatesForWorkbench: (org?: string) => unknown[];
21
+ loadDefinition: (ref: string, kind: string) => unknown;
22
+ listOrgs: () => string[];
23
+ inputPropKeys: (props: unknown) => string[];
24
+ themeOrgs: () => string[];
25
+ listThemeNames: (org: string) => string[];
26
+ resolveTheme: (name: string) => unknown;
27
+ DEFAULT_ORG: string;
28
+ };
29
+
30
+ /**
31
+ * Resolve the developer's project root: the folder holding `rx/`. Walks up from the
32
+ * working directory so `unoverse studio` works from anywhere inside the project, the
33
+ * same way git and npm behave. Throws rather than guessing, because silently reading
34
+ * the wrong folder is worse than not starting.
35
+ */
36
+ export function findProjectRoot(from: string = process.cwd()): string {
37
+ let dir = resolve(from);
38
+ for (;;) {
39
+ // A developer's project (starter kit): rx/ sits at the root.
40
+ if (existsSync(resolve(dir, "rx"))) return dir;
41
+ // This monorepo, where Studio's author works: rx/ lives under apps/unoverse. Same
42
+ // shape underneath (rx/ + prompts/), just nested, so return that as the root.
43
+ if (existsSync(resolve(dir, "apps/unoverse/rx"))) return resolve(dir, "apps/unoverse");
44
+ const parent = resolve(dir, "..");
45
+ if (parent === dir) break;
46
+ dir = parent;
47
+ }
48
+ throw new Error(
49
+ `No rx/ folder found in ${from} or any parent. Run Studio from inside your project, ` +
50
+ `or create rx/<your-project>/ to start one.`,
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Load the server's disk loaders with RX_PATH pointed at this project.
56
+ *
57
+ * `baseSrc` is the path to @unoverse-platform/base's src, and `load` is Vite's
58
+ * ssrLoadModule (the dev server transpiles the TypeScript for us, so local Studio needs no
59
+ * build step of its own).
60
+ *
61
+ * THESE USED TO COME OUT OF apps/unoverse/server/src, which is what kept Studio from being
62
+ * publishable: nothing installed from npm can reach into another app's source. They live in
63
+ * `base` now, so Studio's entire platform dependency is one package.
64
+ */
65
+ export async function loadCatalog(
66
+ baseSrc: string,
67
+ load: (id: string) => Promise<Record<string, unknown>>,
68
+ projectRoot: string,
69
+ ): Promise<Loaders> {
70
+ /**
71
+ * POINT `base` AT THIS DEVELOPER'S PROJECT, before any loader is imported.
72
+ *
73
+ * This used to set `process.env.RX_PATH`, which paths.ts no longer reads: it was
74
+ * rewritten around UNOVERSE_HOME and `setPaths()`. The variable was simply ignored, and
75
+ * because paths.ts falls back to walking up for `apps/unoverse`, Studio kept working in
76
+ * THIS monorepo and read the wrong folder everywhere else. It served the monorepo's own
77
+ * projects to a developer who had just created one of their own.
78
+ *
79
+ * Silent because both answers are valid directories. Nothing errors; the wrong content
80
+ * simply appears, which is the failure mode this whole tool exists to avoid.
81
+ *
82
+ * ORDER MATTERS. paths.ts resolves its named exports ONCE at import ("a host calling
83
+ * setPaths() must do it before anything else imports this module"), so this load happens
84
+ * first and every later import sees the developer's home.
85
+ */
86
+ const paths = await load(resolve(baseSrc, "paths.ts"));
87
+ (paths.setPaths as (n: { home: string }) => void)({ home: projectRoot });
88
+
89
+ const defs = await load(resolve(baseSrc, "definitions/definitions.ts"));
90
+ const theme = await load(resolve(baseSrc, "definitions/theme.ts"));
91
+ const inputs = await load(resolve(baseSrc, "definitions/inputs.ts"));
92
+
93
+ return {
94
+ listDefinitions: defs.listDefinitions as Loaders["listDefinitions"],
95
+ listStates: defs.listStates as Loaders["listStates"],
96
+ listTemplatesForWorkbench: defs.listTemplatesForWorkbench as Loaders["listTemplatesForWorkbench"],
97
+ loadDefinition: defs.loadDefinition as Loaders["loadDefinition"],
98
+ listOrgs: defs.listOrgs as Loaders["listOrgs"],
99
+ inputPropKeys: inputs.inputPropKeys as Loaders["inputPropKeys"],
100
+ themeOrgs: theme.themeOrgs as Loaders["themeOrgs"],
101
+ listThemeNames: theme.listThemeNames as Loaders["listThemeNames"],
102
+ resolveTheme: theme.resolveTheme as Loaders["resolveTheme"],
103
+ DEFAULT_ORG: theme.DEFAULT_ORG as string,
104
+ };
105
+ }
package/local/keys.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Keys for local node testing: read from the project's own `.env`.
3
+ *
4
+ * Deliberately NOT a Studio-specific credential store. Developers already have a
5
+ * `.env`, it is already gitignored, and the platform already reads one. A second secret
6
+ * file would be a new concept to learn, a new thing to leak, and a new thing to explain.
7
+ *
8
+ * Studio therefore never WRITES a secret. It reads `.env` (and `.env.local`, which wins),
9
+ * tells the developer the exact variable each credential field wants, and reports whether
10
+ * it is set. Entering the value happens in the editor they are already in.
11
+ *
12
+ * Naming: the credential name loses its `Credential` suffix and the field is upper
13
+ * snake-cased, so `airtableCredential.personalAccessToken` reads from
14
+ * `AIRTABLE_PERSONAL_ACCESS_TOKEN`.
15
+ *
16
+ * See docs/architecture/LOCAL_STUDIO.md.
17
+ */
18
+ import { resolve } from "path";
19
+ import { existsSync, readFileSync } from "fs";
20
+
21
+ /** camelCase or PascalCase to UPPER_SNAKE. */
22
+ function upperSnake(s: string): string {
23
+ return s
24
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
25
+ .replace(/[-\s]+/g, "_")
26
+ .toUpperCase();
27
+ }
28
+
29
+ /**
30
+ * The env variable a credential field reads from.
31
+ *
32
+ * The credential's own name is treated as ONE word, so `openAICredential` gives
33
+ * `OPENAI_API_KEY` rather than `OPEN_AI_API_KEY`. That matters: the provider names
34
+ * developers already have in their .env are the ones we should be reading.
35
+ * `envVarCandidates` keeps the split form working too, so neither spelling is a trap.
36
+ */
37
+ export function envVarFor(credentialName: string, field: string): string {
38
+ const base = credentialName.replace(/Credentials?$/i, "");
39
+ return `${base.toUpperCase()}_${upperSnake(field)}`;
40
+ }
41
+
42
+ /** Accepted spellings, most conventional first. */
43
+ export function envVarCandidates(credentialName: string, field: string): string[] {
44
+ const base = credentialName.replace(/Credentials?$/i, "");
45
+ const compact = `${base.toUpperCase()}_${upperSnake(field)}`;
46
+ const split = `${upperSnake(base)}_${upperSnake(field)}`;
47
+ return compact === split ? [compact] : [compact, split];
48
+ }
49
+
50
+ /**
51
+ * Parse the project's env files. `.env.local` overrides `.env`, matching the convention
52
+ * every other tool uses. Deliberately tolerant: a malformed line is skipped, never fatal.
53
+ */
54
+ export function readEnv(projectRoot: string): Record<string, string> {
55
+ const out: Record<string, string> = {};
56
+ for (const name of [".env", ".env.local"]) {
57
+ // Look in the project AND its parent: in a monorepo the .env usually sits at the root.
58
+ for (const dir of [resolve(projectRoot, ".."), projectRoot]) {
59
+ const f = resolve(dir, name);
60
+ if (!existsSync(f)) continue;
61
+ for (const line of readFileSync(f, "utf8").split("\n")) {
62
+ const t = line.trim();
63
+ if (!t || t.startsWith("#")) continue;
64
+ const i = t.indexOf("=");
65
+ if (i <= 0) continue;
66
+ const key = t.slice(0, i).trim();
67
+ let value = t.slice(i + 1).trim();
68
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
69
+ value = value.slice(1, -1);
70
+ }
71
+ if (value) out[key] = value;
72
+ }
73
+ }
74
+ }
75
+ return out;
76
+ }
77
+
78
+ /**
79
+ * Build the credential object a node executor expects, from env.
80
+ * Shape mirrors the platform's: { <credentialName>: { <field>: value } }.
81
+ */
82
+ export function credentialsFromEnv(
83
+ projectRoot: string,
84
+ types: { name: string; properties?: { name: string }[] }[],
85
+ ): Record<string, Record<string, string>> {
86
+ const env = readEnv(projectRoot);
87
+ const out: Record<string, Record<string, string>> = {};
88
+ for (const t of types) {
89
+ const fields: Record<string, string> = {};
90
+ for (const p of t.properties ?? []) {
91
+ const v = envVarCandidates(t.name, p.name).map((n) => env[n]).find(Boolean);
92
+ if (v) fields[p.name] = v;
93
+ }
94
+ if (Object.keys(fields).length) out[t.name] = fields;
95
+ }
96
+ return out;
97
+ }
98
+
99
+ /**
100
+ * Which env variable each credential field wants, and whether it is currently set.
101
+ * Names only, never values: the browser has no reason to see a secret.
102
+ */
103
+ export function keyPresence(
104
+ projectRoot: string,
105
+ types: { name: string; properties?: { name: string }[] }[],
106
+ ): Record<string, { field: string; env: string; set: boolean }[]> {
107
+ const env = readEnv(projectRoot);
108
+ const out: Record<string, { field: string; env: string; set: boolean }[]> = {};
109
+ for (const t of types) {
110
+ out[t.name] = (t.properties ?? []).map((p) => {
111
+ const names = envVarCandidates(t.name, p.name);
112
+ // Report whichever spelling the developer actually used, else the conventional one.
113
+ const used = names.find((n) => env[n]);
114
+ return { field: p.name, env: used ?? names[0], set: Boolean(used) };
115
+ });
116
+ }
117
+ return out;
118
+ }
package/local/nodes.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The LOCAL node catalog and runner.
3
+ *
4
+ * Scope (decided 2026-07-25): the developer's OWN nodes only, the ones in their
5
+ * project's `nodes/` folder. Local Studio never reads the installed plugins directory,
6
+ * `installed_plugins`, or the marketplace. Acquiring a node is a universe's job; this
7
+ * is developing the nodes you are writing.
8
+ *
9
+ * As with the definitions (catalog.ts), this reuses the server's REAL registration
10
+ * path rather than scraping source: each package is loaded through `loadPlugin`, which
11
+ * calls its `setup(api)` exactly as production does. So what Studio lists is what the
12
+ * platform would register, including config schemas and credential types.
13
+ *
14
+ * The bootstrap comment is explicit that this path touches neither Redis nor the
15
+ * workflow service, which is what makes it usable with no backend.
16
+ *
17
+ * See docs/architecture/LOCAL_STUDIO.md.
18
+ */
19
+ import { resolve } from "path";
20
+ import { existsSync } from "fs";
21
+
22
+ export interface LocalNodes {
23
+ list: () => unknown[];
24
+ credentialTypes: () => unknown[];
25
+ /** Argument order mirrors the server's executeNode(nodeType, inputs, config, context). */
26
+ execute: (type: string, inputs: unknown, config: unknown, context: unknown) => Promise<unknown>;
27
+ }
28
+
29
+ /**
30
+ * Load the developer's node packages into the in-memory registry.
31
+ *
32
+ * `projectRoot` is the folder holding `nodes/`. Returns null when there is no nodes/
33
+ * folder at all, so Studio can simply omit the section rather than showing an error:
34
+ * plenty of developers only author design assets.
35
+ */
36
+ export async function loadLocalNodes(
37
+ baseSrc: string,
38
+ load: (id: string) => Promise<Record<string, unknown>>,
39
+ projectRoot: string,
40
+ ): Promise<LocalNodes | null> {
41
+ const nodesDir = resolve(projectRoot, "nodes");
42
+ if (!existsSync(nodesDir)) return null;
43
+
44
+ // Set BEFORE the modules load: paths.ts reads process.env at module load time.
45
+ process.env.NODES_PATH = nodesDir;
46
+
47
+ const discovery = await load(resolve(baseSrc, "plugins/discovery.ts"));
48
+ const loader = await load(resolve(baseSrc, "plugins/loader.ts"));
49
+ const platform = await load(resolve(baseSrc, "platform/index.ts"));
50
+ const registry = await load(resolve(baseSrc, "registry.ts"));
51
+ const executor = await load(resolve(baseSrc, "executor.ts"));
52
+
53
+ const api = (platform.createNodeServiceAPI as () => unknown)();
54
+ const packages = (discovery.discoverNodesHome as () => string[])();
55
+
56
+ for (const name of packages) {
57
+ try {
58
+ await (loader.loadPlugin as (n: string, a: unknown) => Promise<string[]>)(name, api);
59
+ } catch (err) {
60
+ // One broken package must never stop the others loading. Same posture as the
61
+ // server's bootstrap, and the same reason: a bad asset cannot take the tool down.
62
+ console.warn(`[studio] could not load node package ${name}: ${(err as Error).message}`);
63
+ }
64
+ }
65
+
66
+ return {
67
+ list: () => (registry.getAllNodes as () => unknown[])(),
68
+ credentialTypes: () => (registry.getAllCredentialTypes as () => unknown[])(),
69
+ execute: async (type, inputs, config, context) =>
70
+ (executor.executeNode as (t: string, i: unknown, c: unknown, ctx: unknown) => Promise<unknown>)(
71
+ type,
72
+ inputs,
73
+ config,
74
+ context,
75
+ ),
76
+ };
77
+ }