@plitzi/sdk-mcp 0.37.7 → 0.37.9
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/CHANGELOG.md +267 -0
- package/README.md +5 -0
- package/dist/modules/mcp/helpers/components.d.ts +14 -0
- package/dist/modules/mcp/helpers/components.js +32 -0
- package/dist/modules/mcp/helpers/guide.js +48 -1
- package/dist/modules/mcp/helpers/index.d.ts +1 -0
- package/dist/modules/mcp/helpers/space.d.ts +5 -0
- package/dist/modules/mcp/helpers/space.js +2 -1
- package/dist/modules/mcp/helpers/uris.d.ts +2 -0
- package/dist/modules/mcp/helpers/uris.js +3 -1
- package/dist/modules/mcp/resources/primer.js +7 -1
- package/dist/modules/mcp/resources/schema.js +16 -10
- package/dist/modules/mcp/server.js +1 -1
- package/dist/modules/mcp/tests/components.test.d.ts +1 -0
- package/dist/modules/mcp/tools/apply/dispatch.js +26 -1
- package/dist/modules/mcp/tools/apply/index.d.ts +11 -0
- package/dist/modules/mcp/tools/operations/index.d.ts +44 -0
- package/dist/modules/mcp/tools/operations/index.js +2 -0
- package/dist/modules/mcp/tools/operations/schema/components/deleteComponent.d.ts +9 -0
- package/dist/modules/mcp/tools/operations/schema/components/deleteComponent.js +21 -0
- package/dist/modules/mcp/tools/operations/schema/components/index.d.ts +2 -0
- package/dist/modules/mcp/tools/operations/schema/components/upsertComponent.d.ts +14 -0
- package/dist/modules/mcp/tools/operations/schema/components/upsertComponent.js +147 -0
- package/dist/modules/mcp/tools/operations/schema/index.d.ts +1 -0
- package/dist/modules/mcp/tools/operations/schema/operations.d.ts +13 -0
- package/dist/modules/mcp/tools/operations/schema/operations.js +5 -1
- package/dist/modules/mcp/tools/operations/schema/write.js +3 -1
- package/dist/modules/mcp/tools/preview.d.ts +11 -0
- package/dist/modules/mcp/tools/render.d.ts +11 -0
- package/dist/modules/mcp/tools/screenshot.d.ts +11 -0
- package/dist/modules/mcp/tools/shared/validator/batch.js +1 -1
- package/dist/modules/mcp/tools/shared/validator/index.js +17 -9
- package/dist/modules/mcp/tools/validate.d.ts +11 -0
- package/dist/modules/mcp/types/aiSchema.d.ts +20 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,272 @@
|
|
|
1
1
|
# @plitzi/sdk-mcp
|
|
2
2
|
|
|
3
|
+
## 0.37.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3ae61a4: ## Components replace segments
|
|
8
|
+
|
|
9
|
+
- **What a component is:** a reusable block written once and placed anywhere as an instance. An edit to the component
|
|
10
|
+
is an edit to every instance.
|
|
11
|
+
- **Where it lives:** in the space document, as `schema.components`, one tree per component and never in
|
|
12
|
+
`schema.flat`. It is published, rolled back, copied by templates and exported with the space.
|
|
13
|
+
- **Props and slots:**
|
|
14
|
+
- A component declares props (`type`, `description`, `required`, `default`, `options`). An instance hands them in
|
|
15
|
+
as its own attributes, so templates and bindings reach them. Inside, they are read as `{{ props.<name> }}`.
|
|
16
|
+
- Slots are elements an instance fills; each child names its slot in `attributes.slot`.
|
|
17
|
+
- **A prop an instance leaves out prints nothing.** It is its `default`, or `null`. `props` is a settled source:
|
|
18
|
+
- `processTwig` takes `{ settled }` in place of `keepEmptyTokens: true`.
|
|
19
|
+
- Every other empty token is still kept for a later pass.
|
|
20
|
+
- `COMPONENT_PROPS_SOURCE` names the source.
|
|
21
|
+
- **Fixed: a flow in a list row or a component instance acts on its own copy.** A step's target resolves in the
|
|
22
|
+
replica the flow fired in first, then outwards. It used to reach whichever copy registered last.
|
|
23
|
+
- **Fixed: an instance's own `visible` hides it in preview.** The same goes for an element reference.
|
|
24
|
+
- **Fixed: a list row keeps its state with its record.** Rows are keyed by each record's unique `id`, so filtering no
|
|
25
|
+
longer moves one row's state onto another.
|
|
26
|
+
- **Closed scope:** inside, a component reads only its props and the globals. The validator, `lintSpace` and
|
|
27
|
+
`authorSpace` each refuse a read of the page around an instance. Components nest, and a cycle is refused.
|
|
28
|
+
- **Where to use them:**
|
|
29
|
+
- In code (`@plitzi/sdk-authoring`): `SpaceSpec.components` and `component(id, { props, children })`.
|
|
30
|
+
`specFromSpace` and `specToSource` read and write both.
|
|
31
|
+
- In the builder:
|
|
32
|
+
- a Components panel lists them, and an open component becomes the canvas;
|
|
33
|
+
- an element becomes a component with **Save as component**;
|
|
34
|
+
- in an instance's settings, an instance gets its props and slots, or is **detached** back into a copy.
|
|
35
|
+
- In the MCP: `upsertComponent` and `deleteComponent`. Element ops work inside a component through `pageRef`.
|
|
36
|
+
- **Schema helpers:** `@plitzi/sdk-schema` gains `addComponent`, `updateComponent`, `removeComponent`,
|
|
37
|
+
`detachInstance`, `renameElement`, `treeOf`, `flatMapOf` and `documentIds`.
|
|
38
|
+
- **GraphQL and live events:** `SpaceAddComponent`, `SpaceUpdateComponent`, `SpaceRemoveComponent` and
|
|
39
|
+
`SpaceDetachInstance`, each with a live event. History records a declaration change as a `component` entry.
|
|
40
|
+
- **Breaking: segments are removed.** This covers:
|
|
41
|
+
- `@plitzi/sdk-shared`'s segment types, queries, mutations, context and `SEGMENT_*` events;
|
|
42
|
+
- `referenceType: 'segment'`;
|
|
43
|
+
- the `Segments` builder module;
|
|
44
|
+
- `Space.segments` and the `Segment`/`Segments` queries;
|
|
45
|
+
- `CommonState.prevSchema`.
|
|
46
|
+
- **Breaking: `ElementLayout` and `LayoutBody` change shape.**
|
|
47
|
+
- `ElementLayout` is `{ slots, rootId, type }`; it was `{ containerId }`.
|
|
48
|
+
- `LayoutBody` takes `bodies` keyed by slot.
|
|
49
|
+
- `reference`'s `referenceContainer` attribute is removed.
|
|
50
|
+
- Guide: `docs/en/components.md`.
|
|
51
|
+
|
|
52
|
+
## Functions ask for the time they need
|
|
53
|
+
- **What changes:** a task can ask for more CPU or wall time than the default with `limits`, in milliseconds. Example:
|
|
54
|
+
`limits: { cpuMs: 1000, wallMs: 20_000 }`. `defineFunctions({ limits })` asks it for every task and route at once,
|
|
55
|
+
and a task's own limits win over those.
|
|
56
|
+
- **What a run is given:** what it asked for, or the default (100 ms of CPU, 10 s) when it asked for nothing. Never
|
|
57
|
+
above the space's plan or the deployment's ceiling.
|
|
58
|
+
- **Deployment ceilings:** `functions.limits` sets them. `DEFAULT_FUNCTION_CEILINGS` covers each unset one, at 2 s of
|
|
59
|
+
CPU and 30 s.
|
|
60
|
+
- **Asking for more than the ceiling** is a problem when the functions are saved; it is never quietly cut down.
|
|
61
|
+
- **Manifest:** carries what each task asked for, and the builder shows it beside the task.
|
|
62
|
+
|
|
63
|
+
## Functions answer under `/fn`, not `/api`
|
|
64
|
+
|
|
65
|
+
A space's routes are served at `/fn/<path>` (`FUNCTION_ROUTES_PREFIX`). `/api` is a slug a site wants for a page of its
|
|
66
|
+
own. A page under `/fn` is refused instead (`page-route-reserved`).
|
|
67
|
+
|
|
68
|
+
## The builder's Functions panel
|
|
69
|
+
|
|
70
|
+
Rebuilt around the code. The panel reads `defineFunctions` as it is typed and writes into it, so the code stays the one
|
|
71
|
+
place a function is declared.
|
|
72
|
+
|
|
73
|
+
- **Layout:** the tasks, routes and files on the left, the code in the middle, the selected task on the right.
|
|
74
|
+
- **Live list:** tasks and routes are listed as the code declares them, including tasks imported from another file.
|
|
75
|
+
A task you have written but not saved says so. Tasks built by calling something are counted, and listed once saved.
|
|
76
|
+
- **Code and panel follow each other:** clicking a task or a route opens its file at its line. Putting the cursor inside
|
|
77
|
+
a task's code selects that task.
|
|
78
|
+
- **New task:** + in Tasks asks for its namespace, action and title. The task is written into `defineFunctions` with a
|
|
79
|
+
`run` to start from, in the file's own quotes, and the editor opens on it.
|
|
80
|
+
- **Time limit:** each task gets a slider from 100 ms to 1 s of CPU per run, with presets. The value is written into
|
|
81
|
+
the task as `limits: { cpuMs }`. "Use default" takes it out. `DEFAULT_FUNCTION_TIME_LIMITS` in
|
|
82
|
+
`@plitzi/sdk-shared/actions` is the default both the panel and the server use.
|
|
83
|
+
- **Test:** fills a task's params the way its step does, from their defaults: a select, a switch or text. JSON stays a
|
|
84
|
+
toggle away. With unsaved changes, the button reads "Save & run": it saves, then runs. If the save fails, it says why.
|
|
85
|
+
- **Header:** shows Saved, Unsaved (and in how many files) or the number of problems the last save found. ⌘S saves.
|
|
86
|
+
**Discard** asks first, then puts every file back to what was last saved, or back to nothing for functions never
|
|
87
|
+
saved. Removing the functions is a quiet button beside Save.
|
|
88
|
+
- **Problems:** clicking one opens its file at its line.
|
|
89
|
+
- **Editor:** each file has its own editor and undo history. Opening another file no longer marks the one you left as
|
|
90
|
+
changed. Before this, it could also write the newly opened file's text into the one you left. The cause was in
|
|
91
|
+
`@plitzi/plitzi-ui`'s CodeMirror, fixed in 1.6.25, which every package now depends on. That release also sets code
|
|
92
|
+
editors (several lines) in a monospaced face again. Long lines scroll inside the editor, and the line numbers stay
|
|
93
|
+
in place.
|
|
94
|
+
|
|
95
|
+
## A space's own functions are their own category of steps
|
|
96
|
+
|
|
97
|
+
In the action editor's step picker, the space's own functions are listed under **Functions**, apart from the
|
|
98
|
+
platform's **Tasks**. The other headings now read Callbacks, Global callbacks and Utilities. The saved step is still a
|
|
99
|
+
`task` node.
|
|
100
|
+
|
|
101
|
+
- `@plitzi/sdk-server`: every registered task has an `origin`, `'deployment'` (shipped with the server or a native
|
|
102
|
+
function) or `'space'` (from the space's functions). `describeCatalog`, `/_action/catalog` and the builder's
|
|
103
|
+
`SpaceActionTasks` carry it (`ActionTaskDescriptor.origin`).
|
|
104
|
+
- `@plitzi/sdk-shared`: an `InteractionCallback` may name the `group` the picker lists it under.
|
|
105
|
+
|
|
106
|
+
## Fixed: preview no longer breaks a builder that is embedded in a page
|
|
107
|
+
|
|
108
|
+
When the builder is mounted inside a Plitzi page (the platform's `/spaces/:id/update`), going to preview with a page
|
|
109
|
+
that has SEO turned on left the builder unstyled. The previewed page wrote its title and description through a head
|
|
110
|
+
manager of the builder's own, and that manager rewrote the host page's head, removing the builder's own stylesheet.
|
|
111
|
+
|
|
112
|
+
- `@plitzi/sdk-shared`: a new render setting, `ownsHead`, says whether the page may write the document head. It is on
|
|
113
|
+
by default (`DEFAULT_RENDER_SETTINGS`).
|
|
114
|
+
- `@plitzi/sdk-elements`: `Page` writes its SEO only where `ownsHead` is on.
|
|
115
|
+
- Builder: sets `ownsHead: false` for its canvas. A page drawn there is in a frame, and the document head is the
|
|
116
|
+
editor's. The canvas's own `HelmetProvider` is gone.
|
|
117
|
+
|
|
118
|
+
## Fixed: the builder's plan usage panel shows the space being edited
|
|
119
|
+
- **The 404 is gone.** Opening Plan usage said "The account breakdown could not be read (The server answered 404.)".
|
|
120
|
+
The panel asked `/account/usage`, which went away when accounts became workspaces.
|
|
121
|
+
- **Only this space.** The panel listed every space of the workspace. It now reads `/spaces/:spaceId/usage`, which
|
|
122
|
+
now also answers the space's own `pages` (the heaviest ten), `pagesTotal` and `periodEndsAt`. The plan's ceilings
|
|
123
|
+
stay on top. Anyone who can edit the space can read it, including a guest of another workspace.
|
|
124
|
+
- **It scrolls.** A long breakdown scrolls inside the modal, where it used to overflow.
|
|
125
|
+
- **`getKeyDecoded(webKey, true)` reads the token's subject.** The function, in `@plitzi/sdk-shared`, looked for
|
|
126
|
+
`data.spaceId`, which space tokens no longer carry, so every space decoded as 0. The builder asked about space 0, and
|
|
127
|
+
the builder and the SDK kept every space's persisted state under the same key on a host. Pages served without a
|
|
128
|
+
`webKey` (SSR) still decode as 0, which is what their painted-state cookie is named after.
|
|
129
|
+
|
|
130
|
+
## Fixed: a remote plugin in the builder reads the canvas it sits in
|
|
131
|
+
|
|
132
|
+
The builder carries its own copy of the Plitzi runtime. A remote plugin imports `@plitzi/plitzi-sdk`, which the page's
|
|
133
|
+
import map resolves to the SDK's copy. Each copy made its own React contexts, so on the builder's canvas a plugin read
|
|
134
|
+
none of the canvas's providers. In the builder embedded in Plitzi's site, it read the site around the builder instead:
|
|
135
|
+
the site's element as its own, the site's live mode (so it stayed interactive while being edited), and the site's store
|
|
136
|
+
and interactions.
|
|
137
|
+
|
|
138
|
+
- `@plitzi/sdk-shared` gains `sharedContext(name, default)`: a context made once per page and handed to every copy of
|
|
139
|
+
the runtime that asks for it.
|
|
140
|
+
- The runtime's contexts now go through it, so a provider from any copy reaches a consumer from any copy:
|
|
141
|
+
- `@plitzi/sdk-shared`: service, component, schema, network, theme scope, dev tools, builder.
|
|
142
|
+
- `@plitzi/sdk-elements`: element, element parent, layout body.
|
|
143
|
+
- interactions, plugins, event bridge, auth, variables and style.
|
|
144
|
+
- The store's contexts need `@plitzi/nexus` 1.4.0, which does the same. Every `@plitzi/*` package now asks for
|
|
145
|
+
`^1.4.0`, which is published.
|
|
146
|
+
|
|
147
|
+
## The source of plugins and runtimes is kept on the space
|
|
148
|
+
|
|
149
|
+
What a plugin or a runtime is built from now goes up with it, so a space can be taken back out as a project
|
|
150
|
+
(`plitzi create --from`, below).
|
|
151
|
+
|
|
152
|
+
- `@plitzi/cli`:
|
|
153
|
+
- `plitzi pack plugin` writes the plugin's source beside its zip: every file of the project its elements import,
|
|
154
|
+
`import type` included, and the packages they need. `--source-root` names the project those paths are relative to.
|
|
155
|
+
- `plitzi upload plugin` and `plitzi runtime push` keep that source on the space, in its private bucket.
|
|
156
|
+
- `plitzi pack source` writes it to a file.
|
|
157
|
+
- A source that cannot be kept (a file outside the project, an undeclared package, a credential in the code) never
|
|
158
|
+
stops the upload or the push: they say why, and the artifact is kept built only.
|
|
159
|
+
- `@plitzi/sdk-shared/source`: the snapshot's format, the paths it may hold and the credential check, shared by the CLI
|
|
160
|
+
and the platform.
|
|
161
|
+
|
|
162
|
+
## `plitzi create --from` and `plitzi pull`: a space on Plitzi as a project of your own
|
|
163
|
+
|
|
164
|
+
The way back from everything the CLI puts on Plitzi. `plitzi create my-board --from pizarra` writes a server project
|
|
165
|
+
holding what the space is made of, and runs it with nothing of Plitzi's: neither its servers nor its CDN. `plitzi pull`
|
|
166
|
+
keeps it in step with the space. See `docs/en/projects-from-spaces.md`.
|
|
167
|
+
|
|
168
|
+
- **What lands in the project:**
|
|
169
|
+
- its pages as authoring code;
|
|
170
|
+
- its server actions as `defineAction` code — JSON, with the reason said, for one that would not read back exactly;
|
|
171
|
+
- its plugins and runtime as the source they were uploaded from, and its functions;
|
|
172
|
+
- its files, downloaded into `public/`, with every CDN address rewritten to the project's.
|
|
173
|
+
- **`src/main.ts`** serves all of it, the runtime in the same process.
|
|
174
|
+
- **`.env`** gets a key made for the project's actions to sign with, and the names of the variables and credentials
|
|
175
|
+
the space had. Their values stay on Plitzi.
|
|
176
|
+
- **Who may run it:** the person must be signed in and able to change the space (owner, administrator or writer).
|
|
177
|
+
- **Plugins** are rebuilt against the project's SDK. One uploaded before sources were kept runs as it was built, from
|
|
178
|
+
`vendor/plugins/`, and the report says to upload it again.
|
|
179
|
+
- **The end of `create`** says what came across differently, including a space whose visitors sign in with Plitzi.
|
|
180
|
+
- `--source cloud` keeps the pages on Plitzi and runs the rest locally.
|
|
181
|
+
- **Any version:** `--environment` and `--revision` take out a published snapshot — its latest, or one revision pinned
|
|
182
|
+
— instead of the draft, with the source its plugins and runtime were built from then. A cloud project serves that
|
|
183
|
+
revision pinned.
|
|
184
|
+
- **`plitzi pull`** writes what changed on the space, keeps what changed in the project, and writes nothing when a
|
|
185
|
+
file changed on both — naming them, with `--force` to take the space's copy. It never touches `.env`, only adds to
|
|
186
|
+
`package.json`, and keeps `plitzi functions push` working from the project. It follows the version the project was
|
|
187
|
+
made from, and `--environment`/`--revision` move it.
|
|
188
|
+
- `@plitzi/sdk-authoring`:
|
|
189
|
+
- `actionSpecFromEntry` reads an action document back into its `defineAction` declaration, only when the round trip
|
|
190
|
+
is exact, and `actionToSource` writes it as a module.
|
|
191
|
+
- `defineAction` takes `limits`.
|
|
192
|
+
- `specToSource` takes `importExtension: '.ts'`, for split files that Node imports as they are.
|
|
193
|
+
|
|
194
|
+
## The builder shows what a snapshot holds
|
|
195
|
+
|
|
196
|
+
**Make Snapshot** lists what it will freeze — pages, layouts and elements, server actions, connectors, functions, the
|
|
197
|
+
runtime and the plugins, each with whether its source is kept — and what no snapshot freezes: the space's files, its
|
|
198
|
+
variables and credentials. A space's components are part of its document, so they are frozen with its pages. **Publish Snapshot** lists what the chosen environment's snapshot holds.
|
|
199
|
+
|
|
200
|
+
## Fixed: a space read from Plitzi kept its server elements
|
|
201
|
+
- **What happened:** a page server reading its space from Plitzi (`createCloudAdapters`) never ran an element's
|
|
202
|
+
`render` action, and a browser-rendered space ignored `loadStrategy`. The space's GraphQL answered neither
|
|
203
|
+
`runtime` nor `loadStrategy` of an element, nor the space's `rsc` settings.
|
|
204
|
+
- **Now:** the platform answers them, and the SDK, the builder and the cloud adapters ask for them.
|
|
205
|
+
|
|
206
|
+
## A plugin the deployment registers is not looked for elsewhere
|
|
207
|
+
|
|
208
|
+
`@plitzi/sdk-server` used to fetch the manifest of every plugin the space lists on its CDN, even one the deployment
|
|
209
|
+
registers itself, and logged a warning when the CDN was out of reach. It now asks only for the ones it does not have.
|
|
210
|
+
|
|
211
|
+
## Fixed: inline code in Markdown carries nothing of the syntax tree
|
|
212
|
+
|
|
213
|
+
A `markdown` element wrote every inline `` `code` `` as `<code node="[object Object]">`: `react-markdown` hands its
|
|
214
|
+
renderers the syntax-tree node as a prop, and the inline branch spread it onto the tag. Fixed in
|
|
215
|
+
`@plitzi/plitzi-ui` 1.6.26 (with a test), which every package now asks for.
|
|
216
|
+
|
|
217
|
+
## Dev tools hear about the render run the page stopped waiting for
|
|
218
|
+
|
|
219
|
+
When a server element's action ran past the section's budget, the page was answered without it. The run ended a moment
|
|
220
|
+
later, after the page's runs had been sent, so the dev tools never showed the one run that needed debugging. It is now
|
|
221
|
+
told the moment the page stops waiting, as `aborted`, with the reason.
|
|
222
|
+
|
|
223
|
+
## Fixed: a tab you come back to no longer loses its session
|
|
224
|
+
- **What happened:** a page left in another tab past its access token's life signed its visitor out on return. A
|
|
225
|
+
reload put them right back in.
|
|
226
|
+
- **Why:** the browser drops the cookie carrying the access token the moment the token expires, and the background
|
|
227
|
+
tab's renewal timer had not run. The first check on return was told `missing`, which the client took as no
|
|
228
|
+
session at all.
|
|
229
|
+
- **Now:** a `missing` refusal is renewed instead whenever the browser can still renew, meaning it holds a refresh
|
|
230
|
+
token or a session hint whose renewal window is open. The session ends only if that renewal fails.
|
|
231
|
+
- **Requests made in that moment:** `reportAuthFailure` now answers whether it renewed the session. A read refused
|
|
232
|
+
as the tab came back is asked again, once, once the session is renewed. This covers an api container's read and a
|
|
233
|
+
server section's refresh, so they no longer show a 401 that only a reload cleared.
|
|
234
|
+
|
|
235
|
+
- Updated dependencies [3ae61a4]
|
|
236
|
+
- @plitzi/sdk-authoring@0.37.9
|
|
237
|
+
- @plitzi/sdk-elements@0.37.9
|
|
238
|
+
- @plitzi/sdk-interactions@0.37.9
|
|
239
|
+
- @plitzi/sdk-schema@0.37.9
|
|
240
|
+
- @plitzi/sdk-shared@0.37.9
|
|
241
|
+
- @plitzi/sdk-style@0.37.9
|
|
242
|
+
- @plitzi/plitzi-sdk@0.37.9
|
|
243
|
+
- @plitzi/sdk-server@0.37.9
|
|
244
|
+
|
|
245
|
+
## 0.37.8
|
|
246
|
+
|
|
247
|
+
### Patch Changes
|
|
248
|
+
|
|
249
|
+
- 4d10e0a: ## Fixed: an api container could not read a file from a CDN
|
|
250
|
+
|
|
251
|
+
- **What happened:** the api container sent `Content-Type: application/json` on every request, including a GET with no
|
|
252
|
+
body to describe. That made every read a non-simple cross-origin request, so the browser asked the host's
|
|
253
|
+
permission first.
|
|
254
|
+
- **Who it hit:** hosts that allow plain cross-origin reads (GET and HEAD from any origin, the usual CORS of a public
|
|
255
|
+
bucket, the space's own CDN among them) refused that preflight. The file never loaded. Tremor's world outlines on
|
|
256
|
+
`cdn-dev.plitzi.com` are one example.
|
|
257
|
+
- **What changes:** a read now sends `Accept: application/json`, and `Content-Type` goes only with a body, unless the
|
|
258
|
+
author set their own. Headers the author adds still make the request non-simple, as they must.
|
|
259
|
+
|
|
260
|
+
- Updated dependencies [4d10e0a]
|
|
261
|
+
- @plitzi/sdk-authoring@0.37.8
|
|
262
|
+
- @plitzi/sdk-elements@0.37.8
|
|
263
|
+
- @plitzi/sdk-interactions@0.37.8
|
|
264
|
+
- @plitzi/sdk-schema@0.37.8
|
|
265
|
+
- @plitzi/sdk-shared@0.37.8
|
|
266
|
+
- @plitzi/sdk-style@0.37.8
|
|
267
|
+
- @plitzi/plitzi-sdk@0.37.8
|
|
268
|
+
- @plitzi/sdk-server@0.37.8
|
|
269
|
+
|
|
3
270
|
## 0.37.7
|
|
4
271
|
|
|
5
272
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -73,6 +73,10 @@ The server is stateless: it resolves the space per request and reads and writes
|
|
|
73
73
|
| `saveStyle(spaceId, env, style)` | for writes | Persist a mutated style document. |
|
|
74
74
|
| `getChanges(spaceId, env, query)` | no | The space's change history, for the `plitzi://changes` resources. Writes arrive with an `SSRWriteContext` (`userId`, one `batch` per tool call) so a consumer can record them. |
|
|
75
75
|
| `getOfflineData(spaceId, env, rev)` | for preview | Read side of draft-preview. Only the preview endpoint calls it. |
|
|
76
|
+
| `getComponentCatalog(spaceId, env)` | no | The element types the space's plugins add, for the catalog resources. |
|
|
77
|
+
| `getConnectors` / `saveConnector` / `deleteConnector` | for connectors | The space's connector manifests, read and written by the connector operations. |
|
|
78
|
+
| `getActions` / `getActionTasks` / `saveAction` / `deleteAction` | for actions | The space's server actions, and the catalog of tasks a step may run — the deployment's and the space's own functions, each with its `origin`. |
|
|
79
|
+
| `getFunctions` / `saveFunctions` / `tryFunction` | for functions | The space's functions: read for `plitzi://functions/{env}`, saved (built and checked, refused from an older copy) by `upsertFunctionFile` / `deleteFunctionFile`, and run by `plitzi_try_function`. |
|
|
76
80
|
|
|
77
81
|
Schema and style are read as **separate documents** on purpose: `getOfflineData` is SSR-shaped and strips
|
|
78
82
|
`style.platform`, which the style resources need.
|
|
@@ -92,6 +96,7 @@ resolves with `canWrite: false` reads everything and is refused at every write t
|
|
|
92
96
|
| `plitzi_preview` | read | Render a draft to HTML through an SSR server |
|
|
93
97
|
| `plitzi_screenshot` | read | Render a draft to a PNG (desktop, mobile or both) through the screenshot service |
|
|
94
98
|
| `plitzi_render` | read | Render a self-contained UI widget, offline, with no space |
|
|
99
|
+
| `plitzi_try_function` | write | Run one task of the space's functions against the draft, in the sandbox: its value, logs and error |
|
|
95
100
|
|
|
96
101
|
Reads follow a filesystem model: list cheap, read one item in detail on demand. Agents are told never to
|
|
97
102
|
hand-build a URI — every write and search response hands back the URI to use next.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Space } from './space';
|
|
2
|
+
import { AIComponentSummary } from '../types';
|
|
3
|
+
import { Schema, SpaceComponent } from '@plitzi/sdk-shared';
|
|
4
|
+
/** A component by the name it is placed by, unless a page or a layout answers to it first. */
|
|
5
|
+
export declare const findComponentByRef: (schema: Schema, ref: string) => SpaceComponent | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* The space as an op addressed to a component sees it: the component's tree as the only one, its root as the one root.
|
|
8
|
+
*
|
|
9
|
+
* `flat` IS the component's own object, so whatever the op writes lands in the component. Every other tree is out of
|
|
10
|
+
* sight — a component is closed, nothing in it may name them — but not out of mind: `document` is the whole space,
|
|
11
|
+
* which is what a new name has to be free of.
|
|
12
|
+
*/
|
|
13
|
+
export declare const componentView: (space: Space, component: SpaceComponent) => Space;
|
|
14
|
+
export declare const componentSummariesToAI: (schema: Schema) => AIComponentSummary[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { componentNamed, instancesOf } from "@plitzi/sdk-schema/helpers/components";
|
|
2
|
+
//#region src/modules/mcp/helpers/components.ts
|
|
3
|
+
/** A component by the name it is placed by, unless a page or a layout answers to it first. */
|
|
4
|
+
var findComponentByRef = (schema, ref) => Object.hasOwn(schema.flat, ref) ? void 0 : componentNamed(schema, ref);
|
|
5
|
+
/**
|
|
6
|
+
* The space as an op addressed to a component sees it: the component's tree as the only one, its root as the one root.
|
|
7
|
+
*
|
|
8
|
+
* `flat` IS the component's own object, so whatever the op writes lands in the component. Every other tree is out of
|
|
9
|
+
* sight — a component is closed, nothing in it may name them — but not out of mind: `document` is the whole space,
|
|
10
|
+
* which is what a new name has to be free of.
|
|
11
|
+
*/
|
|
12
|
+
var componentView = (space, component) => ({
|
|
13
|
+
...space,
|
|
14
|
+
schema: {
|
|
15
|
+
...space.schema,
|
|
16
|
+
flat: component.flat,
|
|
17
|
+
pages: [component.rootId],
|
|
18
|
+
components: {}
|
|
19
|
+
},
|
|
20
|
+
document: space.document ?? space.schema
|
|
21
|
+
});
|
|
22
|
+
var componentSummariesToAI = (schema) => Object.values(schema.components).map((component) => ({
|
|
23
|
+
ref: component.id,
|
|
24
|
+
label: component.label ?? component.id,
|
|
25
|
+
props: component.props ?? {},
|
|
26
|
+
slots: component.slots ?? [],
|
|
27
|
+
rootRef: component.rootId,
|
|
28
|
+
instances: instancesOf(schema, component.id).length,
|
|
29
|
+
elementCount: Object.keys(component.flat).length
|
|
30
|
+
}));
|
|
31
|
+
//#endregion
|
|
32
|
+
export { componentSummariesToAI, componentView, findComponentByRef };
|
|
@@ -113,6 +113,8 @@ Never download a whole tree you do not need.
|
|
|
113
113
|
- \`plitzi://schema/{env}/pages\` — page **summaries** (ref, label, elementCount, folder). No element trees.
|
|
114
114
|
- \`plitzi://schema/{env}/layouts\` — the shared **layout shells** (header/sidebar/footer) and the pages rendered
|
|
115
115
|
inside each. Read one like a page. See *Shared layouts* below.
|
|
116
|
+
- \`plitzi://schema/{env}/components\` — the space's **components** (reusable subtrees): their props, slots, root and
|
|
117
|
+
how many places render each. Read one like a page. See *Components* below.
|
|
116
118
|
- \`plitzi://folders/{env}\` — page **folders** (the sidebar tree): ref, name, slug, parentId. \`/{ref}\` for one.
|
|
117
119
|
- \`plitzi://schema/{env}/pages/{ref}\` — one page as a **skeleton tree**: each node is \`ref/type/label\` **plus the
|
|
118
120
|
style classes it attaches** (\`base\`, and \`slots\` for non-base slots) — names only, no CSS. So you can map every
|
|
@@ -645,7 +647,11 @@ any other (\`<namespace>.<action>\`), listed in \`plitzi://actions/{env}/tasks\`
|
|
|
645
647
|
\`ctx.user\` (who asked, never their session). A secret is NAMED, never read: \`ctx.fetch(url, { credential: "stripe",
|
|
646
648
|
headers: { authorization: "Bearer {{ credential.apiKey }}" } })\`.
|
|
647
649
|
- **Stateless:** nothing a function keeps in memory outlives the call — a module-level cache or counter starts empty
|
|
648
|
-
every time, on whichever instance runs it. What must last goes in \`ctx.kv\`. A call gets 100 ms of CPU and 10 s
|
|
650
|
+
every time, on whichever instance runs it. What must last goes in \`ctx.kv\`. A call gets 100 ms of CPU and 10 s;
|
|
651
|
+
a task that needs more says so — \`limits: { cpuMs: 1000, wallMs: 20000 }\`, or \`defineFunctions({ limits })\` for
|
|
652
|
+
all — up to the plan's ceiling. Asking above it is refused at save, never quietly cut.
|
|
653
|
+
- **Routes** answer HTTP on the space's own host under \`/fn/\`: \`routes: { "GET /boards/:board": (request, ctx) =>
|
|
654
|
+
Response.json(…) }\` answers \`/fn/boards/kanban\` (\`ctx.params.board\`). No page may live under \`/fn\`.
|
|
649
655
|
- **Saving builds and checks** the files: the batch is refused with each problem where it is (file and line). A
|
|
650
656
|
namespace the platform uses (\`kv\`, \`http\`, \`flow\`…) is refused.
|
|
651
657
|
- **Try it** with \`plitzi_try_function { task, params }\` before a page relies on it: the value, the logs, the error.
|
|
@@ -712,6 +718,47 @@ vocabulary:
|
|
|
712
718
|
link, an upgrade prompt in the sidebar — it belongs in the LAYOUT, not in the page you happen to have open. Check
|
|
713
719
|
the page's \`layout\` first; if the element you are looking for is not in the page's own tree, it is in the shell.
|
|
714
720
|
|
|
721
|
+
## Components — a subtree written once, placed anywhere
|
|
722
|
+
A **component** is a reusable subtree: a product card, a pricing tier, a testimonial — written once and placed on
|
|
723
|
+
any page as an **instance**. It is part of the space, so it is published, versioned and copied with it. Its tree is
|
|
724
|
+
not inside any page: it is a root of its own, like a layout, and **editing it changes every instance** — say so
|
|
725
|
+
before you touch one.
|
|
726
|
+
|
|
727
|
+
It is **closed**. Inside, a binding or a template reads **\`props\`** — what each instance hands in — and the globals
|
|
728
|
+
(\`state\`, \`auth\`, \`navigation\`, \`theme\`, \`variables\`, \`computed\`), never the page an instance sits on. A
|
|
729
|
+
component that needs a list row's record gets it as a prop: the INSTANCE binds it (\`item: "{{ list_rows.item }}"\`),
|
|
730
|
+
the component reads \`{{ props.item.name }}\`.
|
|
731
|
+
|
|
732
|
+
**Reading them.** \`plitzi://schema/{env}/components\` lists them: \`ref\`, \`label\`, \`props\`, \`slots\`, \`rootRef\`,
|
|
733
|
+
\`instances\`. Read one like a page: \`plitzi://schema/{env}/pages/{componentRef}\` (and \`/styles\`).
|
|
734
|
+
|
|
735
|
+
**Writing them.**
|
|
736
|
+
- \`upsertComponent { ref, label?, props?, slots? }\` declares one — with nothing in it but its root, \`{ref}-root\`, a
|
|
737
|
+
container — or updates the declaration of one that exists (\`props\` is REPLACED whole). Fill it with every
|
|
738
|
+
element/binding/interaction op you already know, with **\`pageRef: "<componentRef>"\`**:
|
|
739
|
+
\`\`\`json
|
|
740
|
+
{ "type": "upsertElement", "pageRef": "product-card", "parentRef": "product-card-root",
|
|
741
|
+
"element": { "ref": "product-card-title", "type": "heading" } }
|
|
742
|
+
\`\`\`
|
|
743
|
+
- \`upsertComponent { ref, fromRef, instanceRef }\` makes a component OF an element that already exists: the element
|
|
744
|
+
and everything inside it become the component, and an instance named \`instanceRef\` takes its place, so the page
|
|
745
|
+
renders what it did. The way to turn a repeated block into one.
|
|
746
|
+
- A **prop** is \`{ type, description, required?, default?, options? }\`, \`type\` one of \`text\`, \`textarea\`,
|
|
747
|
+
\`number\`, \`boolean\`, \`select\` (with \`options\`), \`scalar\`, \`json\`. Its name is read as \`{{ props.<name> }}\`, so
|
|
748
|
+
letters, digits and \`_\` — and never one an instance already has (\`referenceId\`, \`referenceType\`, \`slot\`…).
|
|
749
|
+
- A **slot** is an element of the component's tree (usually an empty container) an instance fills with children.
|
|
750
|
+
- \`deleteComponent { ref }\` removes one nothing places; while instances remain it is refused, naming them.
|
|
751
|
+
|
|
752
|
+
**Placing one** is an element: a \`reference\` whose props say which component and hand in its props, one attribute each:
|
|
753
|
+
\`\`\`json
|
|
754
|
+
{ "type": "upsertElement", "pageRef": "shop", "parentRef": "grid",
|
|
755
|
+
"element": { "ref": "lamp-card", "type": "reference",
|
|
756
|
+
"props": { "referenceType": "component", "referenceId": "product-card", "title": "Lamp" },
|
|
757
|
+
"children": [ { "ref": "lamp-buy", "type": "button", "props": { "content": "Buy", "slot": "product-card-actions" } } ] } }
|
|
758
|
+
\`\`\`
|
|
759
|
+
A required prop left out, a prop the component does not declare, a value of the wrong kind or a child naming a slot
|
|
760
|
+
the component does not have is reported by \`plitzi_validate\` — fix it before applying.
|
|
761
|
+
|
|
715
762
|
## Pages & folders
|
|
716
763
|
- **Always set a \`slug\` when creating a page** (\`upsertPage\`) — it is the page's URL path and good practice for a
|
|
717
764
|
clean, stable route (e.g. \`"pricing"\` or \`"posts/:postId"\`). Omit it and the page ref is used as the slug,
|
|
@@ -17,6 +17,11 @@ export interface Space {
|
|
|
17
17
|
* the deployment offers no functions: then there is nothing to read and every function op is refused. */
|
|
18
18
|
functions?: FunctionsDraft;
|
|
19
19
|
catalog?: ComponentCatalog;
|
|
20
|
+
/**
|
|
21
|
+
* The whole document, when `schema` is a view of one tree of it — a component's, which an op addressed to a
|
|
22
|
+
* component edits as if it were the space's only tree. What a new name has to be free of is still all of it.
|
|
23
|
+
*/
|
|
24
|
+
document?: Schema;
|
|
20
25
|
}
|
|
21
26
|
export declare const cloneSpace: (space: Space) => Space;
|
|
22
27
|
export declare const emptySpace: () => Space;
|
|
@@ -9,6 +9,8 @@ export declare const pageUri: (env: Env, ref: string) => string;
|
|
|
9
9
|
/** The shared layout shells. One of them is read as a page is — `pages/{ref}` resolves either root. */
|
|
10
10
|
export declare const layoutsUri: (env: Env) => string;
|
|
11
11
|
export declare const elementUri: (env: Env, ref: string) => string;
|
|
12
|
+
/** The components. One of them is read as a page is — `pages/{ref}` resolves it — and edited with `pageRef: <ref>`. */
|
|
13
|
+
export declare const componentsUri: (env: Env) => string;
|
|
12
14
|
export declare const schemaVarsUri: (env: Env) => string;
|
|
13
15
|
export declare const settingsUri: (env: Env) => string;
|
|
14
16
|
export declare const interactionsUri: (env: Env) => string;
|
|
@@ -9,6 +9,8 @@ var pageUri = (env, ref) => `plitzi://schema/${env}/pages/${ref}`;
|
|
|
9
9
|
/** The shared layout shells. One of them is read as a page is — `pages/{ref}` resolves either root. */
|
|
10
10
|
var layoutsUri = (env) => `plitzi://schema/${env}/layouts`;
|
|
11
11
|
var elementUri = (env, ref) => `plitzi://schema/${env}/elements/${ref}`;
|
|
12
|
+
/** The components. One of them is read as a page is — `pages/{ref}` resolves it — and edited with `pageRef: <ref>`. */
|
|
13
|
+
var componentsUri = (env) => `plitzi://schema/${env}/components`;
|
|
12
14
|
var schemaVarsUri = (env) => `plitzi://schema-variables/${env}`;
|
|
13
15
|
var settingsUri = (env) => `plitzi://settings/${env}`;
|
|
14
16
|
var interactionsUri = (env) => `plitzi://interactions/${env}`;
|
|
@@ -59,4 +61,4 @@ var itemTemplates = (env) => [
|
|
|
59
61
|
* family and extracts its ref in one step, without repeating the prefix string for both the startsWith and slice. */
|
|
60
62
|
var afterPrefix = (uri, prefix) => uri.startsWith(prefix) ? uri.slice(prefix.length) : void 0;
|
|
61
63
|
//#endregion
|
|
62
|
-
export { actionTasksUri, actionUri, actionsUri, afterPrefix, aliasedRoots, changesUri, connectorPresetsUri, connectorUri, connectorsUri, cssPropertiesUri, dataSourcesUri, defUri, defsUri, elementUri, folderUri, foldersUri, fontUri, fontsUri, functionFileUri, functionsUri, globalUri, globalsUri, guideUri, idUri, idsUri, interactionsUri, itemTemplates, layoutsUri, pageUri, pagesUri, primerUri, schemaVarsUri, settingsUri, styleVarUri, styleVarsUri, typesUri };
|
|
64
|
+
export { actionTasksUri, actionUri, actionsUri, afterPrefix, aliasedRoots, changesUri, componentsUri, connectorPresetsUri, connectorUri, connectorsUri, cssPropertiesUri, dataSourcesUri, defUri, defsUri, elementUri, folderUri, foldersUri, fontUri, fontsUri, functionFileUri, functionsUri, globalUri, globalsUri, guideUri, idUri, idsUri, interactionsUri, itemTemplates, layoutsUri, pageUri, pagesUri, primerUri, schemaVarsUri, settingsUri, styleVarUri, styleVarsUri, typesUri };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildDataSourceCatalog, buildInteractionCatalog } from "../catalogs/observed.js";
|
|
2
2
|
import { buildTypeRegistry } from "../catalogs/registry.js";
|
|
3
3
|
import { cssProperties, cssShorthands } from "../catalogs/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { componentSummariesToAI } from "../helpers/components.js";
|
|
5
|
+
import { componentsUri, connectorsUri, dataSourcesUri, defsUri, foldersUri, fontsUri, interactionsUri, layoutsUri, pagesUri, primerUri, schemaVarsUri, settingsUri, styleVarsUri, typesUri } from "../helpers/uris.js";
|
|
5
6
|
import { guideQuickstart } from "../helpers/guide.js";
|
|
6
7
|
import { envelope } from "./envelope.js";
|
|
7
8
|
import { connectorSummaries } from "./connectors.js";
|
|
@@ -83,6 +84,11 @@ var readPrimerResource = (space, env, uri) => {
|
|
|
83
84
|
value: layoutSummariesToAI(space.schema),
|
|
84
85
|
read: layoutsUri(env)
|
|
85
86
|
},
|
|
87
|
+
{
|
|
88
|
+
key: "components",
|
|
89
|
+
value: componentSummariesToAI(space.schema),
|
|
90
|
+
read: componentsUri(env)
|
|
91
|
+
},
|
|
86
92
|
{
|
|
87
93
|
key: "folders",
|
|
88
94
|
value: foldersToAI(space.schema),
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { buildDataSourceCatalog, buildInteractionCatalog } from "../catalogs/observed.js";
|
|
2
|
+
import { componentSummariesToAI, componentView, findComponentByRef } from "../helpers/components.js";
|
|
2
3
|
import { findElementByRef, findRootByRef } from "../helpers/space.js";
|
|
3
|
-
import { afterPrefix, dataSourcesUri, elementUri, folderUri, foldersUri, interactionsUri, layoutsUri, pageUri, pagesUri, schemaVarsUri, settingsUri } from "../helpers/uris.js";
|
|
4
|
+
import { afterPrefix, componentsUri, dataSourcesUri, elementUri, folderUri, foldersUri, interactionsUri, layoutsUri, pageUri, pagesUri, schemaVarsUri, settingsUri } from "../helpers/uris.js";
|
|
4
5
|
import { envelope } from "./envelope.js";
|
|
5
6
|
import { elementView, folderRefToAI, foldersToAI, layoutSummariesToAI, pageSkeletonToAI, pageStylesToAI, pageSummariesToAI, schemaVariablesToAI, settingsToAI } from "../tools/operations/schema/translator.js";
|
|
7
|
+
import { treeOf } from "@plitzi/sdk-schema/helpers/components";
|
|
6
8
|
//#region src/modules/mcp/resources/schema.ts
|
|
7
9
|
/** Element-schema reads: page listings/skeletons/styles, folders, single elements and schema variables. Returns
|
|
8
10
|
* undefined when the URI belongs to another domain, null when the shape is ours but the ref does not resolve. */
|
|
9
11
|
var readSchemaResource = (space, env, uri) => {
|
|
10
12
|
if (uri === pagesUri(env)) return envelope(pageSummariesToAI(space.schema));
|
|
11
13
|
if (uri === layoutsUri(env)) return envelope(layoutSummariesToAI(space.schema));
|
|
14
|
+
if (uri === componentsUri(env)) return envelope(componentSummariesToAI(space.schema));
|
|
12
15
|
if (uri === foldersUri(env)) return envelope(foldersToAI(space.schema));
|
|
13
16
|
const folderRef = afterPrefix(uri, folderUri(env, ""));
|
|
14
17
|
if (folderRef !== void 0) {
|
|
@@ -17,19 +20,22 @@ var readSchemaResource = (space, env, uri) => {
|
|
|
17
20
|
}
|
|
18
21
|
const pageItem = afterPrefix(uri, pageUri(env, ""));
|
|
19
22
|
if (pageItem !== void 0) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
23
|
+
const styles = pageItem.endsWith("/styles");
|
|
24
|
+
const ref = styles ? pageItem.slice(0, -7) : pageItem;
|
|
25
|
+
const component = findComponentByRef(space.schema, ref);
|
|
26
|
+
const target = component ? componentView(space, component) : space;
|
|
27
|
+
const page = findRootByRef(target.schema, component ? component.rootId : ref);
|
|
28
|
+
if (!page) return null;
|
|
29
|
+
return envelope(styles ? pageStylesToAI(target.schema, target.style, page) : pageSkeletonToAI(target.schema, page, target.style));
|
|
27
30
|
}
|
|
28
31
|
const elementRef = afterPrefix(uri, elementUri(env, ""));
|
|
29
32
|
if (elementRef !== void 0) {
|
|
30
|
-
const
|
|
33
|
+
const componentId = treeOf(space.schema, elementRef)?.componentId;
|
|
34
|
+
const component = componentId ? findComponentByRef(space.schema, componentId) : void 0;
|
|
35
|
+
const target = component ? componentView(space, component) : space;
|
|
36
|
+
const el = findElementByRef(target.schema, elementRef);
|
|
31
37
|
if (!el) return null;
|
|
32
|
-
const view = elementView(
|
|
38
|
+
const view = elementView(target.schema, el, target.style);
|
|
33
39
|
return {
|
|
34
40
|
stateVersion: view.version,
|
|
35
41
|
data: view.detail
|
|
@@ -69,7 +69,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
69
69
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
70
70
|
const server = new McpServer({
|
|
71
71
|
name: "plitzi-mcp",
|
|
72
|
-
version: "0.37.
|
|
72
|
+
version: "0.37.9"
|
|
73
73
|
}, { instructions: hasSpace ? serverInstructions : widgetsOnlyInstructions });
|
|
74
74
|
const { getChanges } = adapters;
|
|
75
75
|
registerResources(server, getSpace, MCP_ENV, log, hasSpace, getChanges ? (query) => getChanges(requireSpaceId(), MCP_ENV, query) : void 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { componentView, findComponentByRef } from "../../helpers/components.js";
|
|
2
|
+
import { pageUri } from "../../helpers/uris.js";
|
|
1
3
|
import { fail } from "../../helpers/opResult.js";
|
|
2
4
|
import { deleteAction } from "../operations/actions/deleteAction.js";
|
|
3
5
|
import { patchAction } from "../operations/actions/patchAction.js";
|
|
@@ -9,6 +11,8 @@ import { deleteFunctionFile, upsertFunctionFile } from "../operations/functions/
|
|
|
9
11
|
import { deleteBinding } from "../operations/schema/bindings/deleteBinding.js";
|
|
10
12
|
import { patchBinding } from "../operations/schema/bindings/patchBinding.js";
|
|
11
13
|
import { upsertBinding } from "../operations/schema/bindings/upsertBinding.js";
|
|
14
|
+
import { deleteComponent } from "../operations/schema/components/deleteComponent.js";
|
|
15
|
+
import { upsertComponent } from "../operations/schema/components/upsertComponent.js";
|
|
12
16
|
import { deleteElement } from "../operations/schema/elements/deleteElement.js";
|
|
13
17
|
import { moveElement } from "../operations/schema/elements/moveElement.js";
|
|
14
18
|
import { patchElement } from "../operations/schema/elements/patchElement.js";
|
|
@@ -41,6 +45,25 @@ import { deleteStyleVariable } from "../operations/style/variables/deleteStyleVa
|
|
|
41
45
|
import { upsertStyleVariable } from "../operations/style/variables/upsertStyleVariable.js";
|
|
42
46
|
import { isStyleOp } from "../operations/index.js";
|
|
43
47
|
//#region src/modules/mcp/tools/apply/dispatch.ts
|
|
48
|
+
var isRooted = (op) => "pageRef" in op;
|
|
49
|
+
/**
|
|
50
|
+
* An element op names its root with `pageRef`, and a component is one: the op runs on the component's tree as if it
|
|
51
|
+
* were the space's only one — rooted at the component's root — so every element op works inside a component with no
|
|
52
|
+
* code of its own. What it made stale is the component, read as `pages/<component>`.
|
|
53
|
+
*/
|
|
54
|
+
var executeRooted = (space, env, op) => {
|
|
55
|
+
const component = isRooted(op) ? findComponentByRef(space.schema, op.pageRef) : void 0;
|
|
56
|
+
if (!component || !isRooted(op)) return executeOp(space, env, op);
|
|
57
|
+
const retargeted = {
|
|
58
|
+
...op,
|
|
59
|
+
pageRef: component.rootId
|
|
60
|
+
};
|
|
61
|
+
const result = executeOp(componentView(space, component), env, retargeted);
|
|
62
|
+
return {
|
|
63
|
+
...result,
|
|
64
|
+
staleResources: result.staleResources.map((uri) => uri === pageUri(env, component.rootId) ? pageUri(env, component.id) : uri)
|
|
65
|
+
};
|
|
66
|
+
};
|
|
44
67
|
var executeOp = (space, env, op) => {
|
|
45
68
|
switch (op.type) {
|
|
46
69
|
case "upsertElement": return upsertElement(space, env, op);
|
|
@@ -62,6 +85,8 @@ var executeOp = (space, env, op) => {
|
|
|
62
85
|
case "patchInteractionNode": return patchInteractionNode(space, env, op);
|
|
63
86
|
case "deleteInteraction": return deleteInteraction(space, env, op);
|
|
64
87
|
case "patchSettings": return patchSettings(space, env, op);
|
|
88
|
+
case "upsertComponent": return upsertComponent(space, env, op);
|
|
89
|
+
case "deleteComponent": return deleteComponent(space, env, op);
|
|
65
90
|
case "upsertDefinition": return upsertDefinition(space, env, op);
|
|
66
91
|
case "upsertDefinitions": return upsertDefinitions(space, env, op);
|
|
67
92
|
case "patchDefinition": return patchDefinition(space, env, op);
|
|
@@ -113,7 +138,7 @@ var applyOperations = (space, env, ops) => {
|
|
|
113
138
|
const droppedActions = /* @__PURE__ */ new Set();
|
|
114
139
|
for (let i = 0; i < ops.length; i++) {
|
|
115
140
|
const op = ops[i];
|
|
116
|
-
const result =
|
|
141
|
+
const result = executeRooted(space, env, op);
|
|
117
142
|
if (result.errors) {
|
|
118
143
|
outcome.errors.push(...result.errors.map((e) => ({
|
|
119
144
|
...e,
|
|
@@ -269,6 +269,17 @@ export declare const applyShape: {
|
|
|
269
269
|
}>>;
|
|
270
270
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
271
271
|
}, z.core.$strip>>>>;
|
|
272
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
273
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
274
|
+
ref: z.ZodString;
|
|
275
|
+
label: z.ZodOptional<z.ZodString>;
|
|
276
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
277
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
278
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
279
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
280
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
281
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
282
|
+
ref: z.ZodString;
|
|
272
283
|
}, z.core.$strip>, z.ZodObject<{
|
|
273
284
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
274
285
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -265,6 +265,17 @@ export declare const documentOperation: z.ZodDiscriminatedUnion<readonly [z.ZodO
|
|
|
265
265
|
}>>;
|
|
266
266
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
267
267
|
}, z.core.$strip>>>>;
|
|
268
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
269
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
270
|
+
ref: z.ZodString;
|
|
271
|
+
label: z.ZodOptional<z.ZodString>;
|
|
272
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
273
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
274
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
275
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
276
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
277
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
278
|
+
ref: z.ZodString;
|
|
268
279
|
}, z.core.$strip>, z.ZodObject<{
|
|
269
280
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
270
281
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -1022,6 +1033,17 @@ export declare const operation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1022
1033
|
}>>;
|
|
1023
1034
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
1024
1035
|
}, z.core.$strip>>>>;
|
|
1036
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1037
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
1038
|
+
ref: z.ZodString;
|
|
1039
|
+
label: z.ZodOptional<z.ZodString>;
|
|
1040
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1041
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1042
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
1043
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
1044
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1045
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
1046
|
+
ref: z.ZodString;
|
|
1025
1047
|
}, z.core.$strip>, z.ZodObject<{
|
|
1026
1048
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
1027
1049
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -2051,6 +2073,17 @@ export declare const operations: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
2051
2073
|
}>>;
|
|
2052
2074
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
2053
2075
|
}, z.core.$strip>>>>;
|
|
2076
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2077
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
2078
|
+
ref: z.ZodString;
|
|
2079
|
+
label: z.ZodOptional<z.ZodString>;
|
|
2080
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2081
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2082
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
2083
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
2084
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2085
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
2086
|
+
ref: z.ZodString;
|
|
2054
2087
|
}, z.core.$strip>, z.ZodObject<{
|
|
2055
2088
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
2056
2089
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -3075,6 +3108,17 @@ export declare const documentOperations: z.ZodArray<z.ZodDiscriminatedUnion<read
|
|
|
3075
3108
|
}>>;
|
|
3076
3109
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
3077
3110
|
}, z.core.$strip>>>>;
|
|
3111
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3112
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
3113
|
+
ref: z.ZodString;
|
|
3114
|
+
label: z.ZodOptional<z.ZodString>;
|
|
3115
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
3116
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
3117
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
3118
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
3119
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
3120
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
3121
|
+
ref: z.ZodString;
|
|
3078
3122
|
}, z.core.$strip>, z.ZodObject<{
|
|
3079
3123
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
3080
3124
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -28,6 +28,8 @@ var documentOps = [
|
|
|
28
28
|
elementOps.patchInteractionNode,
|
|
29
29
|
elementOps.deleteInteraction,
|
|
30
30
|
elementOps.patchSettings,
|
|
31
|
+
elementOps.upsertComponent,
|
|
32
|
+
elementOps.deleteComponent,
|
|
31
33
|
styleOps.upsertDefinition,
|
|
32
34
|
styleOps.upsertDefinitions,
|
|
33
35
|
styleOps.patchDefinition,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OpResult, Space } from '../../../../helpers';
|
|
3
|
+
import { Env } from '../../../../types';
|
|
4
|
+
export declare const deleteComponentOp: z.ZodObject<{
|
|
5
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
6
|
+
ref: z.ZodString;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export type DeleteComponent = z.infer<typeof deleteComponentOp>;
|
|
9
|
+
export declare const deleteComponent: (space: Space, env: Env, op: DeleteComponent) => OpResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { componentsUri } from "../../../../helpers/uris.js";
|
|
2
|
+
import { empty, fail } from "../../../../helpers/opResult.js";
|
|
3
|
+
import { componentNamed, instancesOf, removeComponent } from "@plitzi/sdk-schema/helpers/components";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/modules/mcp/tools/operations/schema/components/deleteComponent.ts
|
|
6
|
+
var deleteComponentOp = z.object({
|
|
7
|
+
type: z.literal("deleteComponent"),
|
|
8
|
+
ref: z.string()
|
|
9
|
+
}).describe("Remove a component nothing places any more.");
|
|
10
|
+
var deleteComponent = (space, env, op) => {
|
|
11
|
+
if (!componentNamed(space.schema, op.ref)) return fail("ref", `Component "${op.ref}" not found`, `Read plitzi://schema/${env}/components for valid refs`);
|
|
12
|
+
const placed = instancesOf(space.schema, op.ref);
|
|
13
|
+
if (placed.length > 0 || !removeComponent(space.schema, op.ref)) return fail("ref", `Component "${op.ref}" is still placed by ${placed.map((instance) => `"${instance.id}"`).join(", ")}`, "detachInstance or deleteElement each of them first");
|
|
14
|
+
return {
|
|
15
|
+
...empty(),
|
|
16
|
+
deleted: 1,
|
|
17
|
+
staleResources: [componentsUri(env)]
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { deleteComponent, deleteComponentOp };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OpResult, Space } from '../../../../helpers';
|
|
3
|
+
import { Env } from '../../../../types';
|
|
4
|
+
export declare const upsertComponentOp: z.ZodObject<{
|
|
5
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
6
|
+
ref: z.ZodString;
|
|
7
|
+
label: z.ZodOptional<z.ZodString>;
|
|
8
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
9
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
10
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
11
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type UpsertComponent = z.infer<typeof upsertComponentOp>;
|
|
14
|
+
export declare const upsertComponent: (space: Space, env: Env, op: UpsertComponent) => OpResult;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { invalidateIndex } from "../../../../helpers/space.js";
|
|
2
|
+
import { componentsUri, pageUri } from "../../../../helpers/uris.js";
|
|
3
|
+
import { empty, fail } from "../../../../helpers/opResult.js";
|
|
4
|
+
import { ID_REF_HINT, guardNewRef } from "../write.js";
|
|
5
|
+
import { addComponent, componentNamed, propNameProblem, treeOf, updateComponent } from "@plitzi/sdk-schema/helpers/components";
|
|
6
|
+
import { descendants } from "@plitzi/sdk-schema/helpers/elementTree";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { isValidElementId } from "@plitzi/sdk-schema/helpers/elementId";
|
|
9
|
+
//#region src/modules/mcp/tools/operations/schema/components/upsertComponent.ts
|
|
10
|
+
/**
|
|
11
|
+
* A prop as a component declares it. Checked when the op runs rather than spelled out in the op's schema: the op union
|
|
12
|
+
* rides on every request of every conversation, and the guide — read once — says what a prop is.
|
|
13
|
+
*/
|
|
14
|
+
var componentProp = z.object({
|
|
15
|
+
type: z.enum([
|
|
16
|
+
"text",
|
|
17
|
+
"textarea",
|
|
18
|
+
"number",
|
|
19
|
+
"boolean",
|
|
20
|
+
"select",
|
|
21
|
+
"scalar",
|
|
22
|
+
"json"
|
|
23
|
+
]),
|
|
24
|
+
description: z.string(),
|
|
25
|
+
required: z.boolean().optional(),
|
|
26
|
+
default: z.union([
|
|
27
|
+
z.string(),
|
|
28
|
+
z.number(),
|
|
29
|
+
z.boolean()
|
|
30
|
+
]).optional(),
|
|
31
|
+
options: z.array(z.string()).optional(),
|
|
32
|
+
label: z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
var upsertComponentOp = z.object({
|
|
35
|
+
type: z.literal("upsertComponent"),
|
|
36
|
+
ref: z.string(),
|
|
37
|
+
label: z.string().optional(),
|
|
38
|
+
props: z.record(z.string(), z.unknown()).optional().describe("{ name: { type, description, required?, default?, options? } }"),
|
|
39
|
+
slots: z.array(z.string()).optional(),
|
|
40
|
+
fromRef: z.string().optional().describe("New only: the element it is made of"),
|
|
41
|
+
instanceRef: z.string().optional().describe("With fromRef: the instance left in its place")
|
|
42
|
+
}).describe("Declare or update a component; edit inside it with pageRef: \"<ref>\". See \"Components\" in plitzi://guide.");
|
|
43
|
+
/** The declaration an op writes, with nothing it leaves out — an update keeps what it does not name. */
|
|
44
|
+
var declarationOf = (op, props) => ({
|
|
45
|
+
...op.label === void 0 ? {} : { label: op.label },
|
|
46
|
+
...props === void 0 ? {} : { props },
|
|
47
|
+
...op.slots === void 0 ? {} : { slots: op.slots }
|
|
48
|
+
});
|
|
49
|
+
/** The props an op declares, each held to what a prop is — or why one is not, by name. */
|
|
50
|
+
var parseProps = (props) => {
|
|
51
|
+
if (props === void 0) return {};
|
|
52
|
+
const parsed = {};
|
|
53
|
+
for (const [name, value] of Object.entries(props)) {
|
|
54
|
+
const result = componentProp.safeParse(value);
|
|
55
|
+
if (!result.success) return { error: `prop "${name}" is not a prop: ${result.error.issues.map((issue) => `${issue.path.join(".") || "it"} ${issue.message}`).join("; ")}` };
|
|
56
|
+
parsed[name] = result.data;
|
|
57
|
+
}
|
|
58
|
+
return { props: parsed };
|
|
59
|
+
};
|
|
60
|
+
/** Why a declaration cannot stand, in words, or undefined: the same two rules `sdk-schema` holds every writer to. */
|
|
61
|
+
var declarationProblem = (declaration, flat) => {
|
|
62
|
+
for (const name of Object.keys(declaration.props ?? {})) {
|
|
63
|
+
const problem = propNameProblem(name);
|
|
64
|
+
if (problem) return problem;
|
|
65
|
+
}
|
|
66
|
+
const missing = (declaration.slots ?? []).find((slot) => !Object.hasOwn(flat, slot));
|
|
67
|
+
return missing ? `slot "${missing}" is not an element of the component` : void 0;
|
|
68
|
+
};
|
|
69
|
+
var upsertComponent = (space, env, op) => {
|
|
70
|
+
const { schema } = space;
|
|
71
|
+
const stale = [componentsUri(env), pageUri(env, op.ref)];
|
|
72
|
+
const props = parseProps(op.props);
|
|
73
|
+
if ("error" in props) return fail("props", props.error, "A prop is { type: \"text\" | \"textarea\" | \"number\" | \"boolean\" | \"select\" | \"scalar\" | \"json\", description, required?, default?, options? }");
|
|
74
|
+
const declaration = declarationOf(op, props.props);
|
|
75
|
+
const existing = componentNamed(schema, op.ref);
|
|
76
|
+
if (existing) {
|
|
77
|
+
if (op.fromRef) return fail("fromRef", `Component "${op.ref}" already exists`, "fromRef makes a NEW component; pick another ref");
|
|
78
|
+
const problem = declarationProblem({
|
|
79
|
+
...existing,
|
|
80
|
+
...declaration
|
|
81
|
+
}, existing.flat);
|
|
82
|
+
if (problem || !updateComponent(schema, op.ref, {
|
|
83
|
+
...existing,
|
|
84
|
+
...declaration
|
|
85
|
+
})) return fail("props", problem ?? `Component "${op.ref}" refused the declaration`, "Name props as {{ props.<name> }} reads them, and slots as elements of the tree");
|
|
86
|
+
return {
|
|
87
|
+
...empty(),
|
|
88
|
+
updated: 1,
|
|
89
|
+
staleResources: stale
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (!isValidElementId(op.ref)) return fail("ref", `"${op.ref}" is not a name a component can have`, ID_REF_HINT);
|
|
93
|
+
if (op.fromRef) {
|
|
94
|
+
const tree = treeOf(schema, op.fromRef);
|
|
95
|
+
const element = tree?.flat[op.fromRef];
|
|
96
|
+
if (!tree || !element?.definition.parentId) return fail("fromRef", `"${op.fromRef}" is not an element inside a page, a layout or a component`, "A page or a layout is a root, not a subtree — name an element inside one");
|
|
97
|
+
if (!op.instanceRef) return fail("instanceRef", "fromRef needs the name of the instance that takes the element’s place", "e.g. instanceRef: \"hero-card\"");
|
|
98
|
+
const guard = guardNewRef(space, op.instanceRef, "instanceRef");
|
|
99
|
+
if (guard) return guard;
|
|
100
|
+
const component = {
|
|
101
|
+
...declaration,
|
|
102
|
+
id: op.ref,
|
|
103
|
+
rootId: "",
|
|
104
|
+
flat: {}
|
|
105
|
+
};
|
|
106
|
+
const problem = declarationProblem(declaration, Object.fromEntries([op.fromRef, ...descendants(tree.flat, op.fromRef)].map((id) => [id, tree.flat[id]])));
|
|
107
|
+
if (problem || !addComponent(schema, component, {
|
|
108
|
+
elementId: op.fromRef,
|
|
109
|
+
instanceId: op.instanceRef
|
|
110
|
+
})) return fail("props", problem ?? `"${op.fromRef}" could not be made a component`, "Slots name elements inside fromRef");
|
|
111
|
+
invalidateIndex(schema);
|
|
112
|
+
return {
|
|
113
|
+
...empty(),
|
|
114
|
+
created: 1,
|
|
115
|
+
staleResources: stale,
|
|
116
|
+
elementRefs: [op.instanceRef]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const rootRef = `${op.ref}-root`;
|
|
120
|
+
const guard = guardNewRef(space, rootRef, "ref");
|
|
121
|
+
if (guard) return guard;
|
|
122
|
+
const component = {
|
|
123
|
+
...declaration,
|
|
124
|
+
id: op.ref,
|
|
125
|
+
rootId: rootRef,
|
|
126
|
+
flat: { [rootRef]: {
|
|
127
|
+
id: rootRef,
|
|
128
|
+
attributes: { subType: "div" },
|
|
129
|
+
definition: {
|
|
130
|
+
label: op.label ?? op.ref,
|
|
131
|
+
type: "container",
|
|
132
|
+
rootId: rootRef,
|
|
133
|
+
items: [],
|
|
134
|
+
styleSelectors: { base: "" }
|
|
135
|
+
}
|
|
136
|
+
} }
|
|
137
|
+
};
|
|
138
|
+
const problem = declarationProblem(declaration, component.flat);
|
|
139
|
+
if (problem || !addComponent(schema, component)) return fail("props", problem ?? `Component "${op.ref}" could not be declared`, "A new component has one element yet — its root; add the rest with upsertElement { pageRef: \"<ref>\" } first, then name slots");
|
|
140
|
+
return {
|
|
141
|
+
...empty(),
|
|
142
|
+
created: 1,
|
|
143
|
+
staleResources: stale
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
export { upsertComponent, upsertComponentOp };
|
|
@@ -283,4 +283,17 @@ export declare const elementOps: {
|
|
|
283
283
|
presence: import('zod').ZodOptional<import('zod').ZodBoolean>;
|
|
284
284
|
}, import('zod/v4/core').$strip>>>>;
|
|
285
285
|
}, import('zod/v4/core').$strip>;
|
|
286
|
+
upsertComponent: import('zod').ZodObject<{
|
|
287
|
+
type: import('zod').ZodLiteral<"upsertComponent">;
|
|
288
|
+
ref: import('zod').ZodString;
|
|
289
|
+
label: import('zod').ZodOptional<import('zod').ZodString>;
|
|
290
|
+
props: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
|
|
291
|
+
slots: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
|
|
292
|
+
fromRef: import('zod').ZodOptional<import('zod').ZodString>;
|
|
293
|
+
instanceRef: import('zod').ZodOptional<import('zod').ZodString>;
|
|
294
|
+
}, import('zod/v4/core').$strip>;
|
|
295
|
+
deleteComponent: import('zod').ZodObject<{
|
|
296
|
+
type: import('zod').ZodLiteral<"deleteComponent">;
|
|
297
|
+
ref: import('zod').ZodString;
|
|
298
|
+
}, import('zod/v4/core').$strip>;
|
|
286
299
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { deleteBindingOp } from "./bindings/deleteBinding.js";
|
|
2
2
|
import { patchBindingOp } from "./bindings/patchBinding.js";
|
|
3
3
|
import { upsertBindingOp } from "./bindings/upsertBinding.js";
|
|
4
|
+
import { deleteComponentOp } from "./components/deleteComponent.js";
|
|
5
|
+
import { upsertComponentOp } from "./components/upsertComponent.js";
|
|
4
6
|
import { deleteElementOp } from "./elements/deleteElement.js";
|
|
5
7
|
import { moveElementOp } from "./elements/moveElement.js";
|
|
6
8
|
import { patchElementOp } from "./elements/patchElement.js";
|
|
@@ -39,7 +41,9 @@ var elementOps = {
|
|
|
39
41
|
upsertInteractionFlow: upsertInteractionFlowOp,
|
|
40
42
|
patchInteractionNode: patchInteractionNodeOp,
|
|
41
43
|
deleteInteraction: deleteInteractionOp,
|
|
42
|
-
patchSettings: patchSettingsOp
|
|
44
|
+
patchSettings: patchSettingsOp,
|
|
45
|
+
upsertComponent: upsertComponentOp,
|
|
46
|
+
deleteComponent: deleteComponentOp
|
|
43
47
|
};
|
|
44
48
|
//#endregion
|
|
45
49
|
export { elementOps };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findRootByRef, indexAddElement, resolveRef } from "../../../helpers/space.js";
|
|
2
2
|
import { folderUri, foldersUri, layoutsUri, pageUri, pagesUri, schemaVarsUri, settingsUri } from "../../../helpers/uris.js";
|
|
3
3
|
import { fail } from "../../../helpers/opResult.js";
|
|
4
|
+
import { documentIds } from "@plitzi/sdk-schema/helpers/components";
|
|
4
5
|
import { elementIdConflict, isValidElementId } from "@plitzi/sdk-schema/helpers/elementId";
|
|
5
6
|
import FlatMap from "@plitzi/sdk-schema/helpers/FlatMap";
|
|
6
7
|
//#region src/modules/mcp/tools/operations/schema/write.ts
|
|
@@ -12,7 +13,8 @@ var collectInputRefs = (input) => [input.ref, ...(input.children ?? []).flatMap(
|
|
|
12
13
|
* it. Both rules are sdk-schema's `elementIdConflict` — never restated here, so the MCP cannot drift from what
|
|
13
14
|
* the builder and the schema validator enforce. Null when the name is usable. */
|
|
14
15
|
var guardNewRef = (space, ref, field) => {
|
|
15
|
-
const
|
|
16
|
+
const elsewhere = documentIds(space.document ?? space.schema).has(ref) ? `"${ref}" is already used by another element here` : null;
|
|
17
|
+
const conflict = elementIdConflict(space.schema.flat, ref) ?? elsewhere;
|
|
16
18
|
if (!conflict) return null;
|
|
17
19
|
return isValidElementId(ref) ? fail(field, conflict, TAKEN_HINT) : fail(field, conflict, ID_REF_HINT);
|
|
18
20
|
};
|
|
@@ -265,6 +265,17 @@ export declare const previewShape: {
|
|
|
265
265
|
}>>;
|
|
266
266
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
267
267
|
}, z.core.$strip>>>>;
|
|
268
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
269
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
270
|
+
ref: z.ZodString;
|
|
271
|
+
label: z.ZodOptional<z.ZodString>;
|
|
272
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
273
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
274
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
275
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
276
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
277
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
278
|
+
ref: z.ZodString;
|
|
268
279
|
}, z.core.$strip>, z.ZodObject<{
|
|
269
280
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
270
281
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -267,6 +267,17 @@ export declare const renderShape: {
|
|
|
267
267
|
}>>;
|
|
268
268
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
269
269
|
}, z.core.$strip>>>>;
|
|
270
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
271
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
272
|
+
ref: z.ZodString;
|
|
273
|
+
label: z.ZodOptional<z.ZodString>;
|
|
274
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
275
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
276
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
277
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
278
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
279
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
280
|
+
ref: z.ZodString;
|
|
270
281
|
}, z.core.$strip>, z.ZodObject<{
|
|
271
282
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
272
283
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -265,6 +265,17 @@ export declare const screenshotShape: {
|
|
|
265
265
|
}>>;
|
|
266
266
|
presence: z.ZodOptional<z.ZodBoolean>;
|
|
267
267
|
}, z.core.$strip>>>>;
|
|
268
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
269
|
+
type: z.ZodLiteral<"upsertComponent">;
|
|
270
|
+
ref: z.ZodString;
|
|
271
|
+
label: z.ZodOptional<z.ZodString>;
|
|
272
|
+
props: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
273
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
274
|
+
fromRef: z.ZodOptional<z.ZodString>;
|
|
275
|
+
instanceRef: z.ZodOptional<z.ZodString>;
|
|
276
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
277
|
+
type: z.ZodLiteral<"deleteComponent">;
|
|
278
|
+
ref: z.ZodString;
|
|
268
279
|
}, z.core.$strip>, z.ZodObject<{
|
|
269
280
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
270
281
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -8,7 +8,7 @@ var batchDeclaredVars = (ops) => {
|
|
|
8
8
|
};
|
|
9
9
|
var batchDeclaredPages = (ops) => {
|
|
10
10
|
const refs = /* @__PURE__ */ new Set();
|
|
11
|
-
for (const op of ops) if (op.type === "upsertPage" || op.type === "upsertLayout") refs.add(op.ref);
|
|
11
|
+
for (const op of ops) if (op.type === "upsertPage" || op.type === "upsertLayout" || op.type === "upsertComponent") refs.add(op.ref);
|
|
12
12
|
return refs;
|
|
13
13
|
};
|
|
14
14
|
var batchDeclaredFolders = (ops) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { observedDataSources, observedInteractionActions } from "../../../catalogs/observed.js";
|
|
2
2
|
import { buildTypeRegistry } from "../../../catalogs/registry.js";
|
|
3
3
|
import { elementTypeNames } from "../../../catalogs/index.js";
|
|
4
|
+
import { componentView, findComponentByRef } from "../../../helpers/components.js";
|
|
4
5
|
import { findElementByRef, findFolderByRef, findPageByRef, findRootByRef, folderAncestorIds, getLayoutElements, getPageElements, pageFoldersOf, resolveRef, routeParamNames } from "../../../helpers/space.js";
|
|
5
6
|
import { MAX_OPS } from "../../operations/index.js";
|
|
6
7
|
import { batchDeclaredElements, batchDeclaredFolders, batchDeclaredPages, batchDeclaredVariants, batchDeclaredVars } from "./batch.js";
|
|
@@ -11,6 +12,7 @@ import { checkSlotCss } from "./css.js";
|
|
|
11
12
|
import { checkRef } from "./refs.js";
|
|
12
13
|
import { checkElementInput, checkRawMarkup, checkTypeProps, checkVariantApplication } from "./elements.js";
|
|
13
14
|
import { checkInteractionNode } from "./interactions.js";
|
|
15
|
+
import { treeOf } from "@plitzi/sdk-schema/helpers/components";
|
|
14
16
|
import { isValidElementId } from "@plitzi/sdk-schema/helpers/elementId";
|
|
15
17
|
//#region src/modules/mcp/tools/shared/validator/index.ts
|
|
16
18
|
var STYLE_CATEGORIES = [
|
|
@@ -50,8 +52,8 @@ var buildValidationCtx = (space, ops, mode = "space") => {
|
|
|
50
52
|
knownTypes: /* @__PURE__ */ new Set([...Object.keys(registry.types), ...elementTypeNames]),
|
|
51
53
|
typeProps: new Map(Object.entries(registry.types).map(([type, info]) => [type, new Set(Object.keys(info.props))])),
|
|
52
54
|
typeMeta: buildTypeMeta(space.catalog),
|
|
53
|
-
elementType: (ref) => (
|
|
54
|
-
elementExists: (ref) => batchElements.has(ref) ||
|
|
55
|
+
elementType: (ref) => treeOf(space.schema, ref)?.flat[ref]?.definition.type,
|
|
56
|
+
elementExists: (ref) => batchElements.has(ref) || treeOf(space.schema, ref) !== void 0,
|
|
55
57
|
schemaVars: /* @__PURE__ */ new Set([
|
|
56
58
|
...space.schema.variables.map((v) => v.name),
|
|
57
59
|
...routeParamNames(space.schema),
|
|
@@ -81,13 +83,19 @@ var validateOperations = (space, ops, mode = "space") => {
|
|
|
81
83
|
});
|
|
82
84
|
ops.forEach((op, i) => {
|
|
83
85
|
const base = `operations[${i}]`;
|
|
86
|
+
const component = "pageRef" in op ? findComponentByRef(space.schema, op.pageRef) : void 0;
|
|
87
|
+
const scoped = component ? componentView(space, component).schema : space.schema;
|
|
84
88
|
if ((op.type === "upsertElement" || op.type === "patchElement" || op.type === "deleteElement" || op.type === "moveElement" || op.type === "upsertBinding" || op.type === "patchBinding" || op.type === "deleteBinding" || op.type === "upsertInteractionFlow" || op.type === "patchInteractionNode" || op.type === "deleteInteraction") && op.pageRef) {
|
|
85
|
-
if (!findRootByRef(space.schema, op.pageRef) && !batchPages.has(op.pageRef)) {
|
|
86
|
-
const validRefs = [
|
|
89
|
+
if (!component && !findRootByRef(space.schema, op.pageRef) && !batchPages.has(op.pageRef)) {
|
|
90
|
+
const validRefs = [
|
|
91
|
+
...getPageElements(space.schema).map((page) => page.id),
|
|
92
|
+
...getLayoutElements(space.schema).map((layout) => layout.id),
|
|
93
|
+
...Object.keys(space.schema.components)
|
|
94
|
+
];
|
|
87
95
|
ctx.errors.push({
|
|
88
96
|
path: `${base}.pageRef`,
|
|
89
|
-
message: `Page or
|
|
90
|
-
hint: "Use an existing page or
|
|
97
|
+
message: `Page, layout or component "${op.pageRef}" does not exist`,
|
|
98
|
+
hint: "Use an existing page, layout or component ref, or create it earlier in the same batch",
|
|
91
99
|
validValues: validRefs
|
|
92
100
|
});
|
|
93
101
|
}
|
|
@@ -100,8 +108,8 @@ var validateOperations = (space, ops, mode = "space") => {
|
|
|
100
108
|
break;
|
|
101
109
|
case "patchElement": {
|
|
102
110
|
checkRef(op.ref, `${base}.ref`, ctx);
|
|
103
|
-
const page = findRootByRef(
|
|
104
|
-
const target = page ? resolveRef(
|
|
111
|
+
const page = findRootByRef(scoped, component ? component.rootId : op.pageRef);
|
|
112
|
+
const target = page ? resolveRef(scoped, page, op.ref) : void 0;
|
|
105
113
|
if (op.props && target && target.id !== page?.id) {
|
|
106
114
|
checkRawMarkup(target.definition.type, op.props, base, ctx);
|
|
107
115
|
checkTypeProps(target.definition.type, op.props, base, ctx);
|
|
@@ -230,7 +238,7 @@ var validateOperations = (space, ops, mode = "space") => {
|
|
|
230
238
|
break;
|
|
231
239
|
case "patchInteractionNode": {
|
|
232
240
|
checkRef(op.ref, `${base}.ref`, ctx);
|
|
233
|
-
const existing = findElementByRef(
|
|
241
|
+
const existing = findElementByRef(scoped, op.ref)?.definition.interactions?.[op.nodeId];
|
|
234
242
|
if (existing) {
|
|
235
243
|
const merged = {
|
|
236
244
|
id: existing.id,
|
|
@@ -266,6 +266,17 @@ export declare const validateShape: {
|
|
|
266
266
|
}>>;
|
|
267
267
|
presence: import('zod').ZodOptional<import('zod').ZodBoolean>;
|
|
268
268
|
}, import('zod/v4/core').$strip>>>>;
|
|
269
|
+
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
270
|
+
type: import('zod').ZodLiteral<"upsertComponent">;
|
|
271
|
+
ref: import('zod').ZodString;
|
|
272
|
+
label: import('zod').ZodOptional<import('zod').ZodString>;
|
|
273
|
+
props: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
|
|
274
|
+
slots: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
|
|
275
|
+
fromRef: import('zod').ZodOptional<import('zod').ZodString>;
|
|
276
|
+
instanceRef: import('zod').ZodOptional<import('zod').ZodString>;
|
|
277
|
+
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
278
|
+
type: import('zod').ZodLiteral<"deleteComponent">;
|
|
279
|
+
ref: import('zod').ZodString;
|
|
269
280
|
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
270
281
|
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
271
282
|
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
@@ -49,6 +49,26 @@ export interface AILayoutSummary {
|
|
|
49
49
|
slots: string[];
|
|
50
50
|
elementCount: number;
|
|
51
51
|
}
|
|
52
|
+
/** A component of the space: a reusable subtree, placed on a page as an instance and edited as a root of its own. */
|
|
53
|
+
export interface AIComponentSummary {
|
|
54
|
+
ref: string;
|
|
55
|
+
label: string;
|
|
56
|
+
/** What an instance hands in, each an attribute of the instance and read inside as `{{ props.<name> }}`. */
|
|
57
|
+
props: Record<string, {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
default?: unknown;
|
|
62
|
+
options?: string[];
|
|
63
|
+
}>;
|
|
64
|
+
/** Elements of its tree an instance fills with its children, each child naming the one it fills (`slot`). */
|
|
65
|
+
slots: string[];
|
|
66
|
+
/** The root of its tree: `pageRef` for every element op inside it is the component's ref. */
|
|
67
|
+
rootRef: string;
|
|
68
|
+
/** How many places render it. */
|
|
69
|
+
instances: number;
|
|
70
|
+
elementCount: number;
|
|
71
|
+
}
|
|
52
72
|
/** A page folder in the sidebar tree. `ref` is the folder's id; pages reference it
|
|
53
73
|
* by that id via their `folder`, and nested folders via `parentId`. */
|
|
54
74
|
export interface AIFolder {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-mcp",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.9",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
33
|
-
"@plitzi/plitzi-sdk": "0.37.
|
|
34
|
-
"@plitzi/sdk-authoring": "0.37.
|
|
35
|
-
"@plitzi/sdk-elements": "0.37.
|
|
36
|
-
"@plitzi/sdk-interactions": "0.37.
|
|
37
|
-
"@plitzi/sdk-schema": "0.37.
|
|
38
|
-
"@plitzi/sdk-server": "0.37.
|
|
39
|
-
"@plitzi/sdk-shared": "0.37.
|
|
40
|
-
"@plitzi/sdk-style": "0.37.
|
|
33
|
+
"@plitzi/plitzi-sdk": "0.37.9",
|
|
34
|
+
"@plitzi/sdk-authoring": "0.37.9",
|
|
35
|
+
"@plitzi/sdk-elements": "0.37.9",
|
|
36
|
+
"@plitzi/sdk-interactions": "0.37.9",
|
|
37
|
+
"@plitzi/sdk-schema": "0.37.9",
|
|
38
|
+
"@plitzi/sdk-server": "0.37.9",
|
|
39
|
+
"@plitzi/sdk-shared": "0.37.9",
|
|
40
|
+
"@plitzi/sdk-style": "0.37.9",
|
|
41
41
|
"ejs": "^6.0.1",
|
|
42
42
|
"esbuild": "^0.28.2",
|
|
43
43
|
"zod": "^4.6.5"
|