@unoverse-platform/studio 0.1.96 → 0.1.98
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/bin/studio.mjs +3 -3
- package/local/authoring.ts +1 -1
- package/local/catalog.ts +22 -2
- package/local/keys.ts +1 -1
- package/local/nodes.ts +29 -42
- package/local/plugin.ts +75 -13
- package/local/sample-identity/brand.yaml +24 -0
- package/local/sample-identity/organisation.yaml +14 -0
- package/local/sample-identity/purpose.yaml +14 -0
- package/local/sample-identity/story.yaml +18 -0
- package/local/scaffold.mjs +51 -22
- package/local/source.ts +4 -9
- package/package.json +1 -1
- package/screens/AgentsView.tsx +173 -0
- package/screens/ComponentsView.tsx +2 -2
- package/screens/EmptyLibrary.tsx +1 -1
- package/screens/IdentityHeader.tsx +1 -1
- package/screens/IdentityView.tsx +206 -0
- package/screens/PromptBlocksView.tsx +1 -1
- package/screens/SourceView.tsx +1 -1
- package/screens/TemplatesLibraryView.tsx +91 -6
- package/screens/TemplatesView.tsx +24 -9
- package/screens/index.ts +5 -2
- package/src/App.tsx +14 -4
- package/src/NodeKeys.tsx +1 -1
- package/src/host.ts +3 -16
- package/vendor/base/src/agent-mcp/discovery.ts +76 -18
- package/vendor/base/src/agent-mcp/handoff.ts +11 -31
- package/vendor/base/src/agent-mcp/index.ts +5 -5
- package/vendor/base/src/agent-mcp/invoke.ts +37 -19
- package/vendor/base/src/agent-mcp/lean.ts +19 -0
- package/vendor/base/src/agent-mcp/memoryContext.ts +15 -9
- package/vendor/base/src/agent-mcp/memoryIngest.ts +8 -3
- package/vendor/base/src/agent-mcp/rowComponents.ts +184 -72
- package/vendor/base/src/agent-mcp/tools.ts +136 -7
- package/vendor/base/src/agent-mcp/types.ts +47 -10
- package/vendor/base/src/definitions/load/compile/component.ts +59 -4
- package/vendor/base/src/definitions/load/compile/sections.ts +7 -1
- package/vendor/base/src/definitions/load/compile/template.ts +19 -15
- package/vendor/base/src/definitions/load/expand/inherit.ts +5 -1
- package/vendor/base/src/definitions/load/expand/referenced.ts +21 -2
- package/vendor/base/src/definitions/load/expand/refs.ts +14 -0
- package/vendor/base/src/definitions/load/expand/walk.ts +88 -38
- package/vendor/base/src/definitions/load/load.ts +37 -28
- package/vendor/base/src/definitions/model.ts +8 -12
- package/vendor/base/src/definitions/registry/agents.ts +111 -0
- package/vendor/base/src/definitions/registry/catalog.ts +28 -12
- package/vendor/base/src/definitions/registry/datasetSql.ts +149 -0
- package/vendor/base/src/definitions/registry/homes.ts +69 -5
- package/vendor/base/src/definitions/registry/objects.ts +413 -0
- package/vendor/base/src/definitions/registry/orgDocuments.ts +107 -0
- package/vendor/base/src/definitions/registry/orgPack.ts +50 -0
- package/vendor/base/src/definitions/registry/pipelines.ts +197 -0
- package/vendor/base/src/definitions/theme.ts +1 -2
- package/vendor/base/src/items/collect.ts +33 -25
- package/vendor/base/src/items/publish.ts +31 -7
- package/vendor/base/src/lint/context.mjs +17 -0
- package/vendor/base/src/lint/design/file.mjs +199 -2
- package/vendor/base/src/lint/design/index.mjs +66 -73
- package/vendor/base/src/lint/design/vocabulary.mjs +6 -3
- package/vendor/base/src/lint/design/walk.mjs +14 -4
- package/vendor/base/src/lint/index.mjs +15 -9
- package/vendor/base/src/lint/nodes/blocks.mjs +6 -2
- package/vendor/base/src/lint/nodes/index.mjs +6 -1
- package/vendor/base/src/lint/nodes/node.mjs +157 -7
- package/vendor/base/src/lint/objects/index.d.mts +32 -0
- package/vendor/base/src/lint/objects/index.mjs +324 -0
- package/vendor/base/src/lint/objects/rules.d.mts +44 -0
- package/vendor/base/src/lint/objects/rules.mjs +446 -0
- package/vendor/base/src/lint/prompts/index.d.mts +34 -0
- package/vendor/base/src/lint/prompts/index.mjs +198 -0
- package/vendor/base/src/manifests/compose.ts +81 -10
- package/vendor/base/src/manifests/executor/callback.ts +15 -4
- package/vendor/base/src/manifests/executor/context.ts +12 -1
- package/vendor/base/src/manifests/executor/memory.ts +83 -3
- package/vendor/base/src/manifests/executor/session.ts +50 -0
- package/vendor/base/src/manifests/executor/toolBridge.ts +114 -49
- package/vendor/base/src/manifests/index.ts +1 -1
- package/vendor/base/src/manifests/runtime/auth/aws.ts +14 -1
- package/vendor/base/src/manifests/runtime/channels/final.ts +46 -34
- package/vendor/base/src/manifests/runtime/channels/service.ts +30 -32
- package/vendor/base/src/manifests/runtime/channels/workflow.ts +6 -11
- package/vendor/base/src/manifests/runtime/context.ts +18 -6
- package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +159 -3
- package/vendor/base/src/manifests/runtime/duplex/session.ts +304 -9
- package/vendor/base/src/manifests/runtime/events.ts +39 -3
- package/vendor/base/src/manifests/runtime/http/concurrency.ts +44 -0
- package/vendor/base/src/manifests/runtime/http/request.ts +36 -7
- package/vendor/base/src/manifests/runtime/index.ts +3 -2
- package/vendor/base/src/manifests/runtime/loops/repeat.ts +86 -0
- package/vendor/base/src/manifests/runtime/tools/toolloop.ts +30 -3
- package/vendor/base/src/manifests/runtime/usage.ts +10 -0
- package/vendor/base/src/manifests/source.ts +1 -1
- package/vendor/base/src/paths.ts +16 -8
- package/vendor/base/src/platform/audioManager.ts +32 -0
- package/vendor/base/src/platform/executionContext.ts +14 -0
- package/vendor/base/src/platform/liveInput.ts +52 -0
- package/vendor/base/src/plugins/README.md +1 -1
- package/vendor/base/src/plugins/discovery.ts +1 -1
- package/vendor/base/src/template/SafeExpression.ts +6 -0
- package/vendor/base/src/workflow/GetMemoryService.ts +12 -11
- package/vendor/base/src/workflow/PlatformMemoryCore.ts +23 -10
- package/vendor/sdk/appEngine.tsx +31 -12
- package/vendor/sdk/lib/UnoverseComponent.tsx +8 -4
- package/vendor/sdk/lib/app.tsx +70 -95
- package/vendor/sdk/lib/connection.tsx +47 -57
- package/vendor/sdk/lib/core/actions.ts +6 -0
- package/vendor/sdk/lib/core/apps.ts +66 -81
- package/vendor/sdk/lib/core/client.ts +5 -14
- package/vendor/sdk/lib/core/connection.ts +30 -0
- package/vendor/sdk/lib/core/index.ts +2 -2
- package/vendor/sdk/lib/core/store.ts +90 -15
- package/vendor/sdk/lib/core/types.ts +9 -8
- package/vendor/sdk/lib/primitives.tsx +113 -4
- package/vendor/sdk/lib/realtime/useAudioPlayback.ts +8 -1
- package/vendor/sdk/lib/render.tsx +3 -3
- package/vendor/sdk/lib/voice.tsx +27 -13
- package/vite.config.ts +1 -1
package/bin/studio.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* unoverse-studio — the authoring tool, run from a developer's own project.
|
|
4
4
|
*
|
|
5
5
|
* Studio is a TOOL, not a starter kit. A developer's repository holds only their assets
|
|
6
|
-
* (
|
|
6
|
+
* (design/); this is installed alongside and reads them. That is why there
|
|
7
7
|
* is no second repository to keep in step, and why their project contains no Docker, no
|
|
8
8
|
* ansible and no platform source.
|
|
9
9
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* to avoid. So the tool starts the same dev server `npm run dev` does.
|
|
15
15
|
*
|
|
16
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.
|
|
17
|
+
* universe, which is a different job. See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
18
18
|
*/
|
|
19
19
|
import { createServer } from "vite";
|
|
20
20
|
import { dirname, resolve, join, basename } from "path";
|
|
@@ -143,7 +143,7 @@ async function scaffoldIfEmpty() {
|
|
|
143
143
|
// publishes, so a flat rx/ could hold exactly one project and would read `components`
|
|
144
144
|
// and `apps` as orgs.
|
|
145
145
|
createFirstProject(home, name);
|
|
146
|
-
console.log(`\n Created design/${name}
|
|
146
|
+
console.log(`\n Created design/${name}/ in ${cwd}\n`);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
await scaffoldIfEmpty();
|
package/local/authoring.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* completion list is not a hint, it is the whole legal set, and it knows the developer's
|
|
17
17
|
* real tokens in a way a JSON Schema in a text editor cannot.
|
|
18
18
|
*
|
|
19
|
-
* See docs/architecture/LOCAL_STUDIO.md §Authoring in Studio.
|
|
19
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md §Authoring in Studio.
|
|
20
20
|
*/
|
|
21
21
|
import { designHome } from "./design-home.mjs";
|
|
22
22
|
import { resolve } from "path";
|
package/local/catalog.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the exact problem this migration exists to fix). It imports the SAME modules and sets
|
|
9
9
|
* RX_PATH to the developer's folder before they load.
|
|
10
10
|
*
|
|
11
|
-
* See docs/architecture/LOCAL_STUDIO.md.
|
|
11
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
12
12
|
*/
|
|
13
13
|
import { hasDesignHome } from "./design-home.mjs";
|
|
14
14
|
import { resolve } from "path";
|
|
@@ -24,6 +24,10 @@ type Loaders = {
|
|
|
24
24
|
inputPropKeys: (props: unknown) => string[];
|
|
25
25
|
themeOrgs: () => string[];
|
|
26
26
|
listThemeNames: (org: string) => string[];
|
|
27
|
+
/** The identity documents of one org or every org: `<org>/<doc>` items with manifest and briefed props. */
|
|
28
|
+
listIdentity: (org?: string) => unknown[];
|
|
29
|
+
/** The Agents of one org or every org: `<org>/<name>` items with their meta and door. */
|
|
30
|
+
listAgents: (org?: string) => unknown[];
|
|
27
31
|
resolveTheme: (name: string) => unknown;
|
|
28
32
|
DEFAULT_ORG: string;
|
|
29
33
|
/** A project's on-disk home. The writable set is built from these (see source.ts). */
|
|
@@ -44,7 +48,7 @@ export function findProjectRoot(from: string = process.cwd()): string {
|
|
|
44
48
|
// A developer's project (starter kit): rx/ sits at the root.
|
|
45
49
|
if (hasDesignHome(dir)) return dir;
|
|
46
50
|
// This monorepo, where Studio's author works: rx/ lives under apps/unoverse. Same
|
|
47
|
-
// shape underneath (
|
|
51
|
+
// shape underneath (design/), just nested, so return that as the root.
|
|
48
52
|
if (hasDesignHome(resolve(dir, "apps/unoverse"))) return resolve(dir, "apps/unoverse");
|
|
49
53
|
const parent = resolve(dir, "..");
|
|
50
54
|
if (parent === dir) break;
|
|
@@ -94,6 +98,9 @@ export async function loadCatalog(
|
|
|
94
98
|
const defs = await load(resolve(baseSrc, "definitions/definitions.ts"));
|
|
95
99
|
const theme = await load(resolve(baseSrc, "definitions/theme.ts"));
|
|
96
100
|
const inputs = await load(resolve(baseSrc, "definitions/inputs.ts"));
|
|
101
|
+
const homes = await load(resolve(baseSrc, "definitions/registry/homes.ts"));
|
|
102
|
+
const identity = await load(resolve(baseSrc, "definitions/registry/orgDocuments.ts"));
|
|
103
|
+
const agents = await load(resolve(baseSrc, "definitions/registry/agents.ts"));
|
|
97
104
|
|
|
98
105
|
return {
|
|
99
106
|
listDefinitions: defs.listDefinitions as Loaders["listDefinitions"],
|
|
@@ -104,6 +111,19 @@ export async function loadCatalog(
|
|
|
104
111
|
inputPropKeys: inputs.inputPropKeys as Loaders["inputPropKeys"],
|
|
105
112
|
themeOrgs: theme.themeOrgs as Loaders["themeOrgs"],
|
|
106
113
|
listThemeNames: theme.listThemeNames as Loaders["listThemeNames"],
|
|
114
|
+
listIdentity: ((org?: string) => {
|
|
115
|
+
const orgs: string[] = org && org !== "all" ? [org] : (homes.listOrgs as () => string[])();
|
|
116
|
+
const out: unknown[] = [];
|
|
117
|
+
for (const o of orgs) {
|
|
118
|
+
const root = (homes.projectDir as (n: string) => string)(o);
|
|
119
|
+
for (const doc of identity.ORG_DOCUMENTS as readonly string[]) {
|
|
120
|
+
const read = (identity.readOrgDocument as (r: string, d: string) => { manifest: unknown; props: unknown; path: string } | null)(root, doc);
|
|
121
|
+
if (read) out.push({ name: `${o}/${doc}`, org: o, doc, manifest: read.manifest, props: read.props, path: read.path });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}) as Loaders["listIdentity"],
|
|
126
|
+
listAgents: agents.listAgents as Loaders["listAgents"],
|
|
107
127
|
resolveTheme: theme.resolveTheme as Loaders["resolveTheme"],
|
|
108
128
|
DEFAULT_ORG: theme.DEFAULT_ORG as string,
|
|
109
129
|
projectDir: defs.projectDir as Loaders["projectDir"],
|
package/local/keys.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* snake-cased, so `airtableCredential.personalAccessToken` reads from
|
|
14
14
|
* `AIRTABLE_PERSONAL_ACCESS_TOKEN`.
|
|
15
15
|
*
|
|
16
|
-
* See docs/architecture/LOCAL_STUDIO.md.
|
|
16
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
17
17
|
*/
|
|
18
18
|
import { resolve } from "path";
|
|
19
19
|
import { existsSync, readFileSync } from "fs";
|
package/local/nodes.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The LOCAL node catalog and runner.
|
|
3
3
|
*
|
|
4
|
-
* Scope (decided 2026-07-25): the developer's OWN nodes only, the ones in their
|
|
5
|
-
*
|
|
4
|
+
* Scope (decided 2026-07-25): the developer's OWN nodes only, the ones in each of their
|
|
5
|
+
* projects' `design/<project>/nodes/` folder (org-scoped since 2026-09-08, like skills;
|
|
6
|
+
* the workspace-root `nodes/` is gone). Local Studio never reads the installed plugins directory,
|
|
6
7
|
* `installed_plugins`, or the marketplace. Acquiring a node is a universe's job; this
|
|
7
8
|
* is developing the nodes you are writing.
|
|
8
9
|
*
|
|
9
10
|
* As with the definitions (catalog.ts), this reuses the server's REAL registration
|
|
10
|
-
* path rather than scraping source:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* path rather than scraping source: every package goes through `loadManifests` over a
|
|
12
|
+
* disk source, exactly as the server boots. So what Studio lists is what the platform
|
|
13
|
+
* would register, including config schemas and credential types.
|
|
13
14
|
*
|
|
14
15
|
* The bootstrap comment is explicit that this path touches neither Redis nor the
|
|
15
16
|
* workflow service, which is what makes it usable with no backend.
|
|
16
17
|
*
|
|
17
|
-
* See docs/architecture/LOCAL_STUDIO.md.
|
|
18
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
18
19
|
*/
|
|
19
20
|
import { resolve } from "path";
|
|
20
21
|
import { existsSync } from "fs";
|
|
@@ -29,57 +30,43 @@ export interface LocalNodes {
|
|
|
29
30
|
/**
|
|
30
31
|
* Load the developer's node packages into the in-memory registry.
|
|
31
32
|
*
|
|
32
|
-
* `
|
|
33
|
-
*
|
|
33
|
+
* `nodesDirs` is every project's `design/<project>/nodes/`. Returns null when none of
|
|
34
|
+
* them exists, so Studio can simply omit the section rather than showing an error:
|
|
34
35
|
* plenty of developers only author design assets.
|
|
36
|
+
*
|
|
37
|
+
* YAML manifest nodes (package.yaml + nodes/<Node>/*.yaml) are the ONLY authoring
|
|
38
|
+
* format (DECLARATIVE_NODES.md §10, nothing stays code), so this is one pass over one
|
|
39
|
+
* disk source per project: local Studio reads the developer's folders, never a
|
|
40
|
+
* database, and loadManifests registers into the same registry getAllNodes() reads.
|
|
41
|
+
* The legacy code-package loader that ran ahead of this pass read a `NODES_PATH`
|
|
42
|
+
* variable paths.ts had stopped honouring, so it loaded nothing and is gone.
|
|
35
43
|
*/
|
|
36
44
|
export async function loadLocalNodes(
|
|
37
45
|
baseSrc: string,
|
|
38
46
|
load: (id: string) => Promise<Record<string, unknown>>,
|
|
39
|
-
|
|
47
|
+
nodesDirs: string[],
|
|
40
48
|
): Promise<LocalNodes | null> {
|
|
41
|
-
const
|
|
42
|
-
if (!
|
|
43
|
-
|
|
44
|
-
// Set BEFORE the modules load: paths.ts reads process.env at module load time.
|
|
45
|
-
process.env.NODES_PATH = nodesDir;
|
|
49
|
+
const present = nodesDirs.filter((d) => existsSync(d));
|
|
50
|
+
if (!present.length) return null;
|
|
46
51
|
|
|
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
52
|
const registry = await load(resolve(baseSrc, "registry.ts"));
|
|
51
53
|
const executor = await load(resolve(baseSrc, "executor.ts"));
|
|
54
|
+
const manifests = await load(resolve(baseSrc, "manifests/index.ts"));
|
|
55
|
+
const msource = await load(resolve(baseSrc, "manifests/source.ts"));
|
|
52
56
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (const name of packages) {
|
|
57
|
+
for (const dir of present) {
|
|
58
|
+
// One broken project must never stop the others loading. Same posture as the
|
|
59
|
+
// server's bootstrap, and the same reason: a bad asset cannot take the tool down.
|
|
57
60
|
try {
|
|
58
|
-
await (
|
|
61
|
+
const r = await (manifests.loadManifests as (s: unknown) => Promise<{ nodes: number; errors: string[] }>)(
|
|
62
|
+
(msource.diskSource as (d: string) => unknown)(dir),
|
|
63
|
+
);
|
|
64
|
+
for (const e of r.errors) console.warn(`[studio] manifest node: ${e}`);
|
|
59
65
|
} catch (err) {
|
|
60
|
-
|
|
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}`);
|
|
66
|
+
console.warn(`[studio] nodes in ${dir} did not load: ${(err as Error).message}`);
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
// YAML manifest nodes (package.yaml + nodes/<Node>/*.yaml) — the DEFAULT authoring
|
|
67
|
-
// format, and what the scaffolded sample nodes are. The discovery above only finds
|
|
68
|
-
// legacy CODE packages (a package.json with an entry point), so without this pass a
|
|
69
|
-
// pure-YAML nodes/ folder — every new developer's — listed NOTHING at all. Explicit
|
|
70
|
-
// disk source: local Studio reads the developer's folder, never a database, and
|
|
71
|
-
// loadManifests registers into the same registry getAllNodes() reads.
|
|
72
|
-
try {
|
|
73
|
-
const manifests = await load(resolve(baseSrc, "manifests/index.ts"));
|
|
74
|
-
const msource = await load(resolve(baseSrc, "manifests/source.ts"));
|
|
75
|
-
const r = await (manifests.loadManifests as (s: unknown) => Promise<{ nodes: number; errors: string[] }>)(
|
|
76
|
-
(msource.diskSource as (d: string) => unknown)(nodesDir),
|
|
77
|
-
);
|
|
78
|
-
for (const e of r.errors) console.warn(`[studio] manifest node: ${e}`);
|
|
79
|
-
} catch (err) {
|
|
80
|
-
console.warn(`[studio] manifest nodes did not load: ${(err as Error).message}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
70
|
return {
|
|
84
71
|
list: () => (registry.getAllNodes as () => unknown[])(),
|
|
85
72
|
credentialTypes: () => (registry.getAllCredentialTypes as () => unknown[])(),
|
package/local/plugin.ts
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Routes mirror the server's shapes exactly, because the screens parse those shapes:
|
|
10
10
|
* GET /dev/orgs GET /dev/themes?org= GET /dev/components?org=
|
|
11
|
-
* GET /dev/apps?org= GET /dev/atoms
|
|
11
|
+
* GET /dev/apps?org= GET /dev/atoms GET /dev/identity?org= GET /dev/agents?org=
|
|
12
12
|
* GET /local/theme?ref= GET /local/definition?uri=
|
|
13
13
|
* GET /skills /skills/:name GET /prompt-blocks
|
|
14
14
|
* GET|POST /registry/state (no database locally, see below)
|
|
15
15
|
*
|
|
16
|
-
* See docs/architecture/LOCAL_STUDIO.md.
|
|
16
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
17
17
|
*/
|
|
18
18
|
import { designHome } from "./design-home.mjs";
|
|
19
19
|
import { createRequire } from "node:module";
|
|
@@ -92,7 +92,12 @@ function json(res: import("http").ServerResponse, body: unknown, status = 200):
|
|
|
92
92
|
interface MarkdownEntry {
|
|
93
93
|
/** Stable key the screens select on. Derived from the filename, like the server's. */
|
|
94
94
|
id: string;
|
|
95
|
+
/** The LOOKUP name, org-qualified for an org's own skill or block. Round-trips verbatim. */
|
|
95
96
|
name: string;
|
|
97
|
+
/** What a person reads. `name` carries the org, and a navigator printing it shows a path. */
|
|
98
|
+
title?: string;
|
|
99
|
+
/** The owning org. ABSENT means the universal tier, exactly as the server tags it. */
|
|
100
|
+
org?: string;
|
|
96
101
|
description: string;
|
|
97
102
|
category?: string;
|
|
98
103
|
version?: string;
|
|
@@ -136,6 +141,7 @@ function readMarkdownLibrary(dir: string): MarkdownEntry[] {
|
|
|
136
141
|
out.push({
|
|
137
142
|
id,
|
|
138
143
|
name: meta.name || id,
|
|
144
|
+
title: meta.title || meta.name || id,
|
|
139
145
|
description: meta.description ?? "",
|
|
140
146
|
category: meta.category,
|
|
141
147
|
version: meta.version,
|
|
@@ -155,6 +161,37 @@ function readMarkdownLibrary(dir: string): MarkdownEntry[] {
|
|
|
155
161
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
/**
|
|
165
|
+
* A MARKDOWN LIBRARY HAS TWO TIERS, and this host only ever read one.
|
|
166
|
+
*
|
|
167
|
+
* A skill or a prompt block lives in one of two places, and the NAME's shape is what
|
|
168
|
+
* carries which:
|
|
169
|
+
*
|
|
170
|
+
* UNIVERSAL `design/marketplace/<folder>/`, named bare. Published to the marketplace.
|
|
171
|
+
* ORG `design/<org>/<folder>/`, named `<org>/<name>`. Private to that org.
|
|
172
|
+
*
|
|
173
|
+
* The universe server resolves both (the items loaders). This host read the universal
|
|
174
|
+
* home alone, so a developer's OWN skill was missing from local Studio while sitting
|
|
175
|
+
* correctly in their universe, and the two surfaces disagreed about the same folder.
|
|
176
|
+
*
|
|
177
|
+
* Scoping is the server's `?org=`: an org sees the universal tier plus its own and never
|
|
178
|
+
* another org's, the rule RX_ORG_MODEL.md settled for a project's assets.
|
|
179
|
+
*/
|
|
180
|
+
function readLibrary(projectRoot: string, folder: "skills" | "blocks", orgs: string[], org?: string): MarkdownEntry[] {
|
|
181
|
+
const out = readMarkdownLibrary(resolve(designHome(projectRoot), "marketplace", folder));
|
|
182
|
+
for (const o of orgs) {
|
|
183
|
+
if (org && org !== "all" && o !== org) continue;
|
|
184
|
+
for (const e of readMarkdownLibrary(resolve(designHome(projectRoot), o, folder))) {
|
|
185
|
+
// A SKILL's lookup name carries its org, because that is the name every reader
|
|
186
|
+
// addresses it by and two orgs may hold one bare name. A BLOCK's does not: the
|
|
187
|
+
// server tags a block with its org and leaves the name alone, and the shape here
|
|
188
|
+
// has to be the server's or the screens read two different contracts.
|
|
189
|
+
out.push(folder === "skills" ? { ...e, name: `${o}/${e.id}`, org: o } : { ...e, org: o });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
193
|
+
}
|
|
194
|
+
|
|
158
195
|
/**
|
|
159
196
|
* Files this server has just written, so the watcher can tell Studio's own save apart from
|
|
160
197
|
* the developer's other editor. The window only has to outlast the filesystem event, which
|
|
@@ -205,7 +242,12 @@ export function localStudioHost(): Plugin {
|
|
|
205
242
|
// Node packages load lazily: only when the Nodes section is first opened, so a
|
|
206
243
|
// developer authoring only design assets never pays for it at startup.
|
|
207
244
|
let nodesOnce: Promise<LocalNodes | null> | null = null;
|
|
208
|
-
|
|
245
|
+
// Every project's own nodes: design/<project>/nodes/, org-scoped like its skills.
|
|
246
|
+
const nodesDirs = async () => {
|
|
247
|
+
const c = await catalog;
|
|
248
|
+
return c.listOrgs().map((o) => resolve(c.projectDir(o), "nodes"));
|
|
249
|
+
};
|
|
250
|
+
const nodes = () => (nodesOnce ??= nodesDirs().then((dirs) => loadLocalNodes(baseSrc, ssr, dirs)));
|
|
209
251
|
|
|
210
252
|
server.config.logger.info(`\n Studio reading: ${designHome(projectRoot)}\n`);
|
|
211
253
|
|
|
@@ -220,7 +262,7 @@ export function localStudioHost(): Plugin {
|
|
|
220
262
|
*
|
|
221
263
|
* Adding the paths puts them in the same watcher, so the existing hook fires.
|
|
222
264
|
*/
|
|
223
|
-
for (const dir of [designHome(projectRoot)
|
|
265
|
+
for (const dir of [designHome(projectRoot)]) {
|
|
224
266
|
const full = resolve(projectRoot, dir);
|
|
225
267
|
if (existsSync(full)) server.watcher.add(full);
|
|
226
268
|
}
|
|
@@ -255,7 +297,7 @@ export function localStudioHost(): Plugin {
|
|
|
255
297
|
return json(res, {
|
|
256
298
|
projectRoot,
|
|
257
299
|
rx: designHome(projectRoot),
|
|
258
|
-
nodes:
|
|
300
|
+
nodes: await nodesDirs(),
|
|
259
301
|
envFiles: [resolve(projectRoot, "..", ".env"), resolve(projectRoot, ".env")],
|
|
260
302
|
});
|
|
261
303
|
|
|
@@ -360,6 +402,14 @@ export function localStudioHost(): Plugin {
|
|
|
360
402
|
case path === "/dev/apps":
|
|
361
403
|
return json(res, { apps: c.listAppsForWorkbench(org) });
|
|
362
404
|
|
|
405
|
+
// The org's identity documents (design/<org>/identity/<doc>.yaml), one org or all.
|
|
406
|
+
case path === "/dev/identity":
|
|
407
|
+
return json(res, { items: c.listIdentity(org) });
|
|
408
|
+
|
|
409
|
+
// Agents: the one file authored for the map, design/<org>/agents/<name>.yaml.
|
|
410
|
+
case path === "/dev/agents":
|
|
411
|
+
return json(res, { items: c.listAgents(org) });
|
|
412
|
+
|
|
363
413
|
case path === "/dev/atoms":
|
|
364
414
|
return json(res, { atoms: c.listDefinitions("atom") });
|
|
365
415
|
|
|
@@ -433,7 +483,14 @@ export function localStudioHost(): Plugin {
|
|
|
433
483
|
if (!p || typeof b.text !== "string") return json(res, { error: "path and text are required" }, 400);
|
|
434
484
|
|
|
435
485
|
type LintModule = { lintDefinitions(designRoot: string): { problems: { level?: string; file?: string }[] } };
|
|
486
|
+
type ObjectsLintModule = { lintObjects(designRoot: string): { problems: { level?: string; file?: string }[] } };
|
|
487
|
+
type PromptsLintModule = { lintPrompts(designRoot: string): { problems: { level?: string; file?: string }[] } };
|
|
436
488
|
const lintMod = (await ssr(`${baseSrc}/lint/design/index.mjs`)) as unknown as LintModule;
|
|
489
|
+
// OBJECTS AND PIPELINES TOO (objects README 9a, 2026-09-10): the same third linter
|
|
490
|
+
// the publish gate runs, so a save here meets the sentence publish would print.
|
|
491
|
+
const objectsMod = (await ssr(`${baseSrc}/lint/objects/index.mjs`)) as unknown as ObjectsLintModule;
|
|
492
|
+
// SKILLS AND PROMPT BLOCKS TOO (2026-09-18): the fourth linter, same gate.
|
|
493
|
+
const promptsMod = (await ssr(`${baseSrc}/lint/prompts/index.mjs`)) as unknown as PromptsLintModule;
|
|
437
494
|
|
|
438
495
|
// Marked BEFORE the write, so the filesystem event can never beat the mark.
|
|
439
496
|
recentSelfWrites.set(resolve(p), Date.now());
|
|
@@ -442,7 +499,7 @@ export function localStudioHost(): Plugin {
|
|
|
442
499
|
b.text as string,
|
|
443
500
|
writableRoots(projectRoot, c),
|
|
444
501
|
projectRoot,
|
|
445
|
-
(designRoot) => lintMod.lintDefinitions(designRoot),
|
|
502
|
+
(designRoot) => ({ problems: [...lintMod.lintDefinitions(designRoot).problems, ...objectsMod.lintObjects(designRoot).problems, ...promptsMod.lintPrompts(designRoot).problems] }),
|
|
446
503
|
);
|
|
447
504
|
return json(res, result, result.saved ? 200 : result.error === "lint errors" ? 422 : 403);
|
|
448
505
|
}
|
|
@@ -466,9 +523,14 @@ export function localStudioHost(): Plugin {
|
|
|
466
523
|
options?: { overlay?: Record<string, string> },
|
|
467
524
|
): { problems: { level?: string; file?: string; line?: number; msg?: string }[] };
|
|
468
525
|
};
|
|
526
|
+
type ObjectsLintModule = { lintObjects(designRoot: string, options?: { overlay?: Record<string, string> }): { problems: { level?: string; file?: string; line?: number; msg?: string }[] } };
|
|
527
|
+
type PromptsLintModule = { lintPrompts(designRoot: string, options?: { overlay?: Record<string, string> }): { problems: { level?: string; file?: string; line?: number; msg?: string }[] } };
|
|
469
528
|
const lintMod = (await ssr(`${baseSrc}/lint/design/index.mjs`)) as unknown as LintModule;
|
|
529
|
+
const objectsMod = (await ssr(`${baseSrc}/lint/objects/index.mjs`)) as unknown as ObjectsLintModule;
|
|
530
|
+
const promptsMod = (await ssr(`${baseSrc}/lint/prompts/index.mjs`)) as unknown as PromptsLintModule;
|
|
470
531
|
const abs = resolve(p);
|
|
471
|
-
const
|
|
532
|
+
const overlay = { [abs]: b.text as string };
|
|
533
|
+
const problems = [...lintMod.lintDefinitions(designHome(projectRoot), { overlay }).problems, ...objectsMod.lintObjects(designHome(projectRoot), { overlay }).problems, ...promptsMod.lintPrompts(designHome(projectRoot), { overlay }).problems];
|
|
472
534
|
return json(res, {
|
|
473
535
|
findings: problems.filter((f) => f.file && resolve(f.file) === abs),
|
|
474
536
|
elsewhere: problems.filter((f) => !f.file || resolve(f.file) !== abs).length,
|
|
@@ -507,17 +569,17 @@ export function localStudioHost(): Plugin {
|
|
|
507
569
|
return json(res, selection ?? { empty: true });
|
|
508
570
|
|
|
509
571
|
case path === "/skills":
|
|
510
|
-
return json(res, { skills:
|
|
572
|
+
return json(res, { skills: readLibrary(projectRoot, "skills", c.listOrgs(), org) });
|
|
511
573
|
|
|
512
574
|
case path.startsWith("/skills/"): {
|
|
513
575
|
const name = decodeURIComponent(path.slice("/skills/".length).replace(/\/file$/, ""));
|
|
514
|
-
const found =
|
|
576
|
+
const found = readLibrary(projectRoot, "skills", c.listOrgs()).find((s) => s.name === name || s.id === name);
|
|
515
577
|
// SkillsView reads `d.skill`, matching the server's shape. Not the bare object.
|
|
516
578
|
return found ? json(res, { skill: found }) : json(res, { error: "skill not found" }, 404);
|
|
517
579
|
}
|
|
518
580
|
|
|
519
581
|
case path === "/prompt-blocks":
|
|
520
|
-
return json(res, { blocks:
|
|
582
|
+
return json(res, { blocks: readLibrary(projectRoot, "blocks", c.listOrgs(), org) });
|
|
521
583
|
|
|
522
584
|
// Registry toggles are a DEPLOYED concept: they persist to installed_plugins
|
|
523
585
|
// and decide what a running universe exposes. Local Studio has no database and
|
|
@@ -528,11 +590,11 @@ export function localStudioHost(): Plugin {
|
|
|
528
590
|
if (req.method === "POST") return json(res, { ok: true });
|
|
529
591
|
return json(res, { entries: [] });
|
|
530
592
|
|
|
531
|
-
// ---- The developer's OWN nodes (
|
|
593
|
+
// ---- The developer's OWN nodes (design/<project>/nodes/). No marketplace. ----
|
|
532
594
|
|
|
533
595
|
case path === "/nodes": {
|
|
534
596
|
const n = await nodes();
|
|
535
|
-
if (!n) return json(res, { nodes: [], credentialTypes: [], reason: "no nodes/ folder in this
|
|
597
|
+
if (!n) return json(res, { nodes: [], credentialTypes: [], reason: "no design/<project>/nodes/ folder in this workspace" });
|
|
536
598
|
return json(res, { nodes: n.list(), credentialTypes: n.credentialTypes() });
|
|
537
599
|
}
|
|
538
600
|
|
|
@@ -597,7 +659,7 @@ export function localStudioHost(): Plugin {
|
|
|
597
659
|
* else, which means the developer's other editor, still reloads.
|
|
598
660
|
*/
|
|
599
661
|
handleHotUpdate({ server, file }) {
|
|
600
|
-
const watched = [designHome(projectRoot)
|
|
662
|
+
const watched = [designHome(projectRoot)];
|
|
601
663
|
if (!watched.some((w) => file.startsWith(w))) return;
|
|
602
664
|
const self = recentSelfWrites.get(resolve(file));
|
|
603
665
|
if (self !== undefined && Date.now() - self < SELF_WRITE_WINDOW_MS) return [];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The org's brand: its own words, and only those. The briefs, the limits and the shape are the
|
|
2
|
+
# brand object's (design/marketplace/objects/brand/); this file carries the answers as defaults.
|
|
3
|
+
unoverse: "1.0"
|
|
4
|
+
type: identity
|
|
5
|
+
extends: brand
|
|
6
|
+
props:
|
|
7
|
+
name:
|
|
8
|
+
default: ""
|
|
9
|
+
standsFor:
|
|
10
|
+
default: ""
|
|
11
|
+
promise:
|
|
12
|
+
default: ""
|
|
13
|
+
represents:
|
|
14
|
+
default: ""
|
|
15
|
+
positioning:
|
|
16
|
+
default: ""
|
|
17
|
+
differentiators:
|
|
18
|
+
default: []
|
|
19
|
+
brands:
|
|
20
|
+
default: []
|
|
21
|
+
rules:
|
|
22
|
+
default: []
|
|
23
|
+
examples:
|
|
24
|
+
default: []
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# The org's organisation: its own words, and only those. The briefs, the limits and the shape are the
|
|
2
|
+
# organisation object's (design/marketplace/objects/organisation/); this file carries the answers as defaults.
|
|
3
|
+
unoverse: "1.0"
|
|
4
|
+
type: identity
|
|
5
|
+
extends: organisation
|
|
6
|
+
props:
|
|
7
|
+
name:
|
|
8
|
+
default: ""
|
|
9
|
+
what:
|
|
10
|
+
default: ""
|
|
11
|
+
units:
|
|
12
|
+
default: []
|
|
13
|
+
vocabulary:
|
|
14
|
+
default: ""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# The org's purpose: its own words, and only those. The briefs, the limits and the shape are the
|
|
2
|
+
# purpose object's (design/marketplace/objects/purpose/); this file carries the answers as defaults.
|
|
3
|
+
unoverse: "1.0"
|
|
4
|
+
type: identity
|
|
5
|
+
extends: purpose
|
|
6
|
+
props:
|
|
7
|
+
statement:
|
|
8
|
+
default: ""
|
|
9
|
+
values:
|
|
10
|
+
default: []
|
|
11
|
+
ambition:
|
|
12
|
+
default: ""
|
|
13
|
+
goals:
|
|
14
|
+
default: []
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# The org's story: its own words, and only those. The briefs, the limits and the shape are the
|
|
2
|
+
# story object's (design/marketplace/objects/story/); this file carries the answers as defaults.
|
|
3
|
+
unoverse: "1.0"
|
|
4
|
+
type: identity
|
|
5
|
+
extends: story
|
|
6
|
+
props:
|
|
7
|
+
history:
|
|
8
|
+
default: ""
|
|
9
|
+
milestones:
|
|
10
|
+
default: []
|
|
11
|
+
present:
|
|
12
|
+
default: ""
|
|
13
|
+
vision:
|
|
14
|
+
default: ""
|
|
15
|
+
people:
|
|
16
|
+
default: ""
|
|
17
|
+
meaning:
|
|
18
|
+
default: ""
|
package/local/scaffold.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* alike and disagree about what a starting component contains. So this file is plain ESM
|
|
12
12
|
* rather than TypeScript, because the CLI runs unbuilt and cannot transpile.
|
|
13
13
|
*
|
|
14
|
-
* See docs/architecture/LOCAL_STUDIO.md.
|
|
14
|
+
* See docs/architecture/authoring/LOCAL_STUDIO.md.
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync, mkdirSync, writeFileSync, cpSync, readdirSync, readFileSync } from "node:fs";
|
|
17
17
|
import { designHome } from "./design-home.mjs";
|
|
@@ -61,7 +61,7 @@ const WELCOME = [
|
|
|
61
61
|
].join("\n");
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Create `design/<name>/` under `home`, with its
|
|
64
|
+
* Create `design/<name>/` under `home`, with its authoring folders, one component and the identity.
|
|
65
65
|
*
|
|
66
66
|
* Refuses an existing name rather than merging into it: "create" that silently adopts a
|
|
67
67
|
* folder is how a developer ends up publishing someone else's work under their own project.
|
|
@@ -77,7 +77,7 @@ export function createProject(home, name) {
|
|
|
77
77
|
// beside components/apps/styles so a skill is org-scoped exactly like a component —
|
|
78
78
|
// two orgs may each ship an `hr-education-coach` without colliding on one flat name
|
|
79
79
|
// (items/collect.ts qualifies a skill's row `<org>/<name>`).
|
|
80
|
-
for (const d of ["components/welcome", "apps", "styles", "skills", "blocks"]) mkdirSync(join(root, d), { recursive: true });
|
|
80
|
+
for (const d of ["components/welcome", "apps", "styles", "skills", "blocks", "identity"]) mkdirSync(join(root, d), { recursive: true });
|
|
81
81
|
writeFileSync(join(root, "components/welcome/welcome.yaml"), WELCOME);
|
|
82
82
|
|
|
83
83
|
// Worked example styles (sample-styles: brand ingredients + the override rules), so
|
|
@@ -90,6 +90,15 @@ export function createProject(home, name) {
|
|
|
90
90
|
cpSync(sampleStyles, join(root, "styles"), { recursive: true });
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// THE IDENTITY (sample-identity: organisation, brand, purpose, story), four instances of
|
|
94
|
+
// the marketplace's identity objects with their defaults empty, so the org's own words
|
|
95
|
+
// have a place to land from day one. Generated by base's instanceText (a guard test holds
|
|
96
|
+
// them equal to what createOrgPack writes). Same rule: copied only into an empty folder.
|
|
97
|
+
const sampleIdentity = join(dirname(fileURLToPath(import.meta.url)), "sample-identity");
|
|
98
|
+
if (existsSync(sampleIdentity) && readdirSync(join(root, "identity")).length === 0) {
|
|
99
|
+
cpSync(sampleIdentity, join(root, "identity"), { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
// A worked example APP (a generic chat: focus overlay + the chat region),
|
|
94
103
|
// so apps/ is never an empty folder and the first app is a rename away. Same
|
|
95
104
|
// rule as the styles: copied only into an empty folder, then the developer's.
|
|
@@ -115,7 +124,10 @@ export function createProject(home, name) {
|
|
|
115
124
|
* makes that safe.
|
|
116
125
|
*/
|
|
117
126
|
const SAMPLES = [
|
|
118
|
-
|
|
127
|
+
// ORG-SCOPED since 2026-09-08, like the skill and block below: a project's nodes live at
|
|
128
|
+
// design/<project>/nodes/, the same rule as every other kind it authors. The id keeps
|
|
129
|
+
// the old flat string for the same reason theirs do.
|
|
130
|
+
{ id: "nodes/samples", dir: "sample-nodes/samples", to: (p) => `design/${p}/nodes/samples` },
|
|
119
131
|
// ORG-SCOPED since 2026-08-24: a sample skill/block lands in the FIRST project's own
|
|
120
132
|
// folder, because that is where authored skills and blocks now live. `to` is a
|
|
121
133
|
// function of the project for exactly that reason — the id stays the old flat string
|
|
@@ -180,10 +192,10 @@ export function topUpSamples(home, project) {
|
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
/**
|
|
183
|
-
* The FIRST project, plus
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
195
|
+
* The FIRST project, plus what a workspace needs exactly once: editor settings and the
|
|
196
|
+
* worked-example samples. Nothing lives beside the design tree any more: skills, blocks
|
|
197
|
+
* and (since 2026-09-08) nodes are all per-project, made by `createProject` or landed by
|
|
198
|
+
* topUpSamples inside the first project.
|
|
187
199
|
*/
|
|
188
200
|
|
|
189
201
|
/**
|
|
@@ -195,17 +207,34 @@ export function topUpSamples(home, project) {
|
|
|
195
207
|
*/
|
|
196
208
|
const SCHEMAS = "https://docs.unoverse.ai/schemas/nodes";
|
|
197
209
|
const NODE_SCHEMA_MAP = {
|
|
198
|
-
[`${SCHEMAS}/package.schema.json`]: ["nodes/*/package.yaml"],
|
|
199
|
-
[`${SCHEMAS}/credential.schema.json`]: ["nodes/*/credentials/*.yaml"],
|
|
200
|
-
[`${SCHEMAS}/node.schema.json`]: ["nodes/*/nodes/*/node.yaml"],
|
|
201
|
-
[`${SCHEMAS}/interface.schema.json`]: ["nodes/*/nodes/*/interface.yaml"],
|
|
202
|
-
[`${SCHEMAS}/config.schema.json`]: ["nodes/*/nodes/*/config.yaml"],
|
|
203
|
-
[`${SCHEMAS}/test.schema.json`]: ["nodes/*/nodes/*/test.yaml"],
|
|
204
|
-
[`${SCHEMAS}/run.schema.json`]: ["nodes/*/nodes/*/api/run.yaml"],
|
|
205
|
-
[`${SCHEMAS}/events.schema.json`]: ["nodes/*/nodes/*/api/events.yaml"],
|
|
206
|
-
[`${SCHEMAS}/service.schema.json`]: ["nodes/*/nodes/*/api/service.yaml"],
|
|
207
|
-
[`${SCHEMAS}/toolExchange.schema.json`]: ["nodes/*/nodes/*/api/toolExchange.yaml"],
|
|
208
|
-
[`${SCHEMAS}/audio.schema.json`]: ["nodes/*/nodes/*/api/audio.yaml"],
|
|
210
|
+
[`${SCHEMAS}/package.schema.json`]: ["design/*/nodes/*/package.yaml"],
|
|
211
|
+
[`${SCHEMAS}/credential.schema.json`]: ["design/*/nodes/*/credentials/*.yaml"],
|
|
212
|
+
[`${SCHEMAS}/node.schema.json`]: ["design/*/nodes/*/nodes/*/node.yaml"],
|
|
213
|
+
[`${SCHEMAS}/interface.schema.json`]: ["design/*/nodes/*/nodes/*/interface.yaml"],
|
|
214
|
+
[`${SCHEMAS}/config.schema.json`]: ["design/*/nodes/*/nodes/*/config.yaml"],
|
|
215
|
+
[`${SCHEMAS}/test.schema.json`]: ["design/*/nodes/*/nodes/*/test.yaml"],
|
|
216
|
+
[`${SCHEMAS}/run.schema.json`]: ["design/*/nodes/*/nodes/*/api/run.yaml"],
|
|
217
|
+
[`${SCHEMAS}/events.schema.json`]: ["design/*/nodes/*/nodes/*/api/events.yaml"],
|
|
218
|
+
[`${SCHEMAS}/service.schema.json`]: ["design/*/nodes/*/nodes/*/api/service.yaml"],
|
|
219
|
+
[`${SCHEMAS}/toolExchange.schema.json`]: ["design/*/nodes/*/nodes/*/api/toolExchange.yaml"],
|
|
220
|
+
[`${SCHEMAS}/audio.schema.json`]: ["design/*/nodes/*/nodes/*/api/audio.yaml"],
|
|
221
|
+
// OBJECTS AND PIPELINES (objects README 9a, 2026-09-10): folders in the node grammar. The
|
|
222
|
+
// envelope is named for its folder, so its glob excludes the sibling files BY NAME, and
|
|
223
|
+
// api/service.yaml among them: in the YAML extension's matcher a `*` segment can cross
|
|
224
|
+
// `api/`, so without `service` in the exclusion the envelope schema claimed the functions
|
|
225
|
+
// file and red-underlined every key in it (seen live 2026-09-10, redhat.vscode-yaml 1.24.0).
|
|
226
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/object.schema.json`]: ["design/*/objects/*/!(props|test|service|make|dataset).yaml"],
|
|
227
|
+
// `dataset` joined the exclusion 2026-09-18: an object that owns a table declares its columns
|
|
228
|
+
// in dataset.yaml, and the envelope schema knows nothing of columns.
|
|
229
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/dataset.schema.json`]: ["design/*/objects/*/dataset.yaml"],
|
|
230
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/props.schema.json`]: ["design/*/objects/*/props.yaml"],
|
|
231
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/service.schema.json`]: ["design/*/objects/*/api/service.yaml"],
|
|
232
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/make.schema.json`]: ["design/*/objects/*/api/make.yaml"],
|
|
233
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/objects")}/test.schema.json`]: ["design/*/objects/*/test.yaml"],
|
|
234
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/pipelines")}/pipeline.schema.json`]: ["design/*/pipelines/*/pipeline.yaml"],
|
|
235
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/pipelines")}/config.schema.json`]: ["design/*/pipelines/*/config.yaml"],
|
|
236
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/pipelines")}/interface.schema.json`]: ["design/*/pipelines/*/interface.yaml"],
|
|
237
|
+
[`${SCHEMAS.replace(/\/nodes$/, "/pipelines")}/test.schema.json`]: ["design/*/pipelines/*/test.yaml"],
|
|
209
238
|
};
|
|
210
239
|
export function writeEditorSettings(home) {
|
|
211
240
|
const dir = join(home, ".vscode");
|
|
@@ -218,9 +247,9 @@ export function writeEditorSettings(home) {
|
|
|
218
247
|
|
|
219
248
|
export function createFirstProject(home, name) {
|
|
220
249
|
const root = createProject(home, name);
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
250
|
+
// NOTHING beside the design tree. skills/ and blocks/ moved INTO the org on 2026-08-24
|
|
251
|
+
// and nodes/ followed on 2026-09-08 (the sample package lands at design/<org>/nodes/),
|
|
252
|
+
// so every authored kind sits in one place.
|
|
224
253
|
writeEditorSettings(home);
|
|
225
254
|
|
|
226
255
|
topUpSamples(home, name);
|