@ikas/code-components-mcp 1.4.0-beta.198 → 1.4.0-beta.199
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/data/framework.json +15 -46
- package/data/storefront-api.json +16 -2
- package/data/storefront-types.json +80 -4
- package/dist/index.js +46 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1603,7 +1603,9 @@ function squashIdentifier(text) {
|
|
|
1603
1603
|
/** A query word worth matching separator-insensitively: an identifier, not prose. */
|
|
1604
1604
|
function isIdentifierLike(word) {
|
|
1605
1605
|
return (word.length > 3 &&
|
|
1606
|
-
(/[_\-.]/.test(word) ||
|
|
1606
|
+
(/[_\-.]/.test(word) ||
|
|
1607
|
+
/[a-z][A-Z]/.test(word) ||
|
|
1608
|
+
/^[A-Z0-9_]+$/.test(word)));
|
|
1607
1609
|
}
|
|
1608
1610
|
function matchScore(text, query) {
|
|
1609
1611
|
const lower = text.toLowerCase();
|
|
@@ -4015,7 +4017,7 @@ server.tool("get_section_values", "Get the CURRENT prop values of one or MANY pl
|
|
|
4015
4017
|
// Tool: update_section_prop
|
|
4016
4018
|
server.tool("update_section_prop", 'Change a single prop value of a section placed on a page. This is the tool for FILLING/SETTING content (heading, text, image, link, etc.) on an existing section — it is pure data entry: it writes NO component code and needs NO rebuild. Do not author a new prop or component to set a value the section already supports; first check the section\'s existing `props` via `list_page_sections`. Target the specific placement by its `element_id` (from `list_page_sections`) and the prop by `prop_id` or `prop_name` (also from `list_page_sections`). `value` is the prop value object as stored by the editor — for scalar props it is wrapped as `{ "value": <scalar> }` (e.g. `{ "value": "Hello" }` for TEXT, `{ "value": true }` for BOOLEAN, `{ "value": 12 }` for NUMBER, `{ "value": "#FF0000" }` for COLOR, `{ "value": "<enum-key>" }` for ENUM). A COLOR prop can ALSO bind to a global theme color instead of a literal hex — pass `{ "patternValueId": "<id>" }` (the color id from `list_theme_globals` → `colors[].id`) and the prop tracks that global color live (resolved to `var(--<id>)` at render); the id is validated and an unknown one is REJECTED. Richer object prop types use their own object shape and are NOT `{ "value": ... }`-wrapped (full catalog in `get_editor_workflow`): IMAGE = `{ "id": "<asset-id>", "altText"?: "<alt>", "isVideo"?: false }` (the `id` MUST reference an already-uploaded asset — call `upload_image` with a `file_path`/`image_url` to get one, or reuse an existing `id` from another section\'s `propValues`); IMAGE_LIST = `{ "images": [ <image>, ... ] }`; VIDEO = `{ "video": { "id": "<asset-id>" }, "thumbnailImage"?: { "id": "..." }, "autoplay"?: false, "controls"?: true, "loop"?: false, "muted"?: false }` (NOT a bare `{ "id": ... }`); SVG = `{ "value": "<svg markup>" }` (value-wrapped, like a scalar); SVG_LIST = `{ "svgs": [ "<svg>", ... ] }`; TYPE (style props such as BorderRadiusStyleType/PaddingStyleType/GapStyleType) = the type-shaped object ITSELF, e.g. `{ "value": 8, "unit": "px" }` — get the exact field structure from the prop\'s `writeExample`/`typeName` in `get_component_props` or `get_type_definition("<typeName>")`; unknown fields, wrong field types, and invalid union values (e.g. `"unit": "banana"`) are REJECTED; entity props PRODUCT/CATEGORY/BRAND/BLOG/BLOG_CATEGORY/RAFFLE = `{ "<entity>Id": "...", "usePageData"?: false }` (e.g. `{ "categoryId": "..." }`) — get real ids from `search_products` (PRODUCT), `list_categories`, `list_brands`, `list_blogs`, `list_blog_categories`; PRODUCT_ATTRIBUTE = `{ "productId": "...", "variantId": "...", "attributeId": "..." }` — `variantId` is REQUIRED alongside `productId` (without it the value resolves with `product: null` and the component renders nothing), or bind to the page product with `{ "attributeId": "...", "usePageData": true }` on a product detail page; PRODUCT_ATTRIBUTE_LIST = the same with `"attributeIds": [ "..." ]`, and note the component receives a SINGLE wrapper object, never an array (read both with `getAttributeDetailValues()` / `getAttributeListValues()` — see `get_framework_guide("prop-types")`); list/collection props are typed objects keyed by a `<entity>ListType` discriminator: PRODUCT_LIST = `{ "id": "<unique>", "productListType": "STATIC", "productIds": [ { "productId": "...", "variantId": "..." } ], "initialLimit"?: 12 }` — note productIds is an array of {productId, variantId} PAIRS (from search_products), not bare ids; productListType ∈ ALL|STATIC|DISCOUNTED|RECOMMENDED|CATEGORY|SEARCH|LAST_VIEWED|RELATED_PRODUCTS|VIEWED_TOGETHER|PURCHASED_TOGETHER, and for a dynamic type use e.g. `{ "id": "<unique>", "productListType": "CATEGORY", "category": "<id from list_categories>" }` (the field is `category`, NOT `categoryId` — `categoryId` is only the standalone CATEGORY entity prop; no productIds here) or `"ALL"` (no ids). Unknown sub-fields inside a PRODUCT_LIST (e.g. `categoryId`) are REJECTED, not silently stored. CATEGORY_LIST = `{ "categoryListType": "STATIC", "categoryIds": ["..."] }` or `{ "categoryListType": "ALL" }`. BRAND_LIST = `{ "brandListType": "STATIC", "brandIds": ["..."] }` or ALL. BLOG_LIST = `{ "blogListType": "STATIC", "blogIds": ["..."] }`, `{ "blogListType": "CATEGORY", "categoryId": "..." }`, or ALL. BLOG_CATEGORY_LIST = `{ "blogCategoryListType": "STATIC", "blogCategoryIds": ["..."] }` or ALL. (Get the entity ids from search_products / list_*; you can also read an existing value with `get_section_values` to copy its exact shape.) LINK = a single link object `{ "id": "<unique-short-id>", "linkType": "PAGE"|"EXTERNAL"|"FILE", "label": "...", "openInNewTab"?: false, "subLinks": [] }` plus the target for its type — for PAGE add `"pageId"` AND `"pageType"` (both from `list_editor_pages`, e.g. pageType "INDEX"; for dynamic page types like PRODUCT/CATEGORY/BLOG also add the target `"itemId"`), for EXTERNAL add `"externalLink": "https://..."`. Examples: PAGE → `{ "id": "k3p9x", "linkType": "PAGE", "label": "Home", "pageId": "<page-id>", "pageType": "INDEX", "openInNewTab": false, "subLinks": [] }`; EXTERNAL → `{ "id": "m7q2z", "linkType": "EXTERNAL", "label": "Docs", "externalLink": "https://example.com", "openInNewTab": true, "subLinks": [] }`. To link to an ENTITY (category/brand/blog/blog-category) do NOT guess a slug — get the entity id from `list_categories`/`list_brands`/`list_blogs`/`list_blog_categories` and the page id from `get_page_by_type("<TYPE>")`, then build `{ "id": "<unique>", "linkType": "PAGE", "label": "...", "pageId": <pageId>, "pageType": "<TYPE>", "itemId": <entityId>, "subLinks": [] }`. LIST_OF_LINK = `{ "links": [ <link>, ... ] }` (each item is a link object as above). PRODUCT = `{ "productId": "...", "variantId"?: "..." }`. This SAME unwrapped shape applies whether the prop sits at section level OR nested inside a COMPONENT_LIST entry\'s `propValues` — there is no section-vs-nested difference. IMPORTANT: pass `value` as the parsed JSON object/array itself, NEVER as a JSON string (a stringified value is double-encoded and stored as a useless string). Values are validated server-side (deeply, including nested COMPONENT_LIST children): wrong shapes AND wrong semantics are REJECTED with an explanatory error instead of being silently stored. This includes: a `{ "value": [...] }` wrapper for a COMPONENT_LIST; a `{ "value": ... }`-wrapped IMAGE; `props` instead of `propValues`; a missing/duplicate entry `id`; a child referenced by the wrong key (a code component MUST use `codeComponentId`, a theme component `componentId`) or by an id that does not exist (child ids are opaque — take them from `get_section_template` / `list_imported_sections`, never invent them); and any `propValues` key that is not a real prop of that child component (the error lists the valid prop names). Auto-import: a referenced child code component that has been BUILT but not yet imported into the theme is imported automatically before the value is set (the response lists any such ids under `autoImported`); only children that are not built at all fail with a "no imported code component" error — build them first (`ikas-component build`/`dev`). For any prop type you are unsure about, inspect the existing value with `get_section_values` and match its exact shape. The change is applied with undo support.\n\n' +
|
|
4017
4019
|
"COMMON (global) sections — the theme's HEADER and FOOTER: these render on every page that carries a placement of them (ikas ready-made account/membership pages are born without one) and have ONE shared set of prop values, stored on the INDEX (Home) page's placement. Whichever page_id you write from, the value lands on that single record and changes the header/footer on ALL pages; the response carries a `common` block (`commonSectionType`, `canonicalPageId`, `canonicalElementId`) saying so, and the echoed `value` is read back from that canonical placement. Therefore: ALWAYS `get_section_values` first and change only the prop the user asked about — the props are already filled with the merchant's content even when you are on a page you have not inspected, so blind-filling \"empty-looking\" props overwrites the live header/footer everywhere. If the INDEX page has no placement of the section, there is no record to write to and the call fails telling you to add it to the INDEX page first.\n\n" +
|
|
4018
|
-
|
|
4020
|
+
"BIND TO A GLOBAL VARIABLE (Theme Setting): instead of a static `value`, pass `bind_global` = the global's `name` (its `variableName`, the `_<id>` form) from `list_theme_globals`. The prop becomes a LIVE dynamic value that always reflects that global's current value — editing the global in Theme Settings updates every bound prop. Use this to wire content to a project-wide setting (e.g. a shared announcement text, brand color, or logo image) rather than duplicating a literal. Pick a global whose type matches the prop (TEXT↔TEXT/RICH_TEXT, NUMBER↔NUMBER, BOOLEAN↔BOOLEAN, IMAGE↔IMAGE, COLOR↔COLOR, ENUM↔ENUM of the SAME enum type — the prop's `enumTypeId` must equal the setting's `enumTypeId`; a mismatch is REJECTED, …). Provide `value` OR `bind_global`, not both. To bind a prop of a COMPONENT_LIST CHILD, also pass `list_path` (the `{ propId, index }` steps down to the child) and set `prop_id` to the child's prop id. Works for every section type, including built-in / code-component sections that have no blueprint.\n\n" +
|
|
4019
4021
|
'COMPONENT_LIST / COMPONENT props: the value is NOT wrapped in `{ "value": ... }`. A COMPONENT_LIST value is `{ "components": [ <entry>, ... ] }`; a single COMPONENT value is one `<entry>`. Each entry = `{ "id": "<unique-id>", "codeComponentId": "<child-id>" | "componentId": "<child-id>", "propValues": { "<childPropName>": <wrapped-value>, ... } }`. Rules: (1) `id` is a unique short alphanumeric string identifying THIS entry — generate a fresh one per new entry and NEVER reuse an existing entry\'s id. (2) Use `codeComponentId` for code components, `componentId` for built-in theme components. (3) `propValues` is keyed by the CHILD component\'s prop NAMES, each using the same scalar wrappers as above (nesting is recursive — a child may itself hold a COMPONENT_LIST). (4) `update_section_prop` REPLACES the whole prop value — there is NO partial/deep merge. ALWAYS send the COMPLETE, fully-nested value for the prop: every existing entry (with its id), every nested `propValues`, and every nested COMPONENT_LIST at every depth. To change anything (even one deeply-nested scalar, or to add/remove/reorder a child), READ-MODIFY-WRITE: take the current `{ "components": [...] }` from `get_section_values`, edit it in place (preserving all other entries, ids, and nested structures), then send the ENTIRE updated object back. Sending only the changed part — a single entry, or a child without its siblings — wipes everything you omitted. (5) You can only add child component types the prop permits — call `get_component_props(parentComponentId)` to read the COMPONENT_LIST prop\'s `allowedComponentIds`, then `get_component_props(childId)` to learn that child\'s props (names, types, ENUM `options`/valid values) instead of guessing or reading ikas.config.json. The allowed set is wired at build/config time per the `get_section_template` setup recipe (`config update-prop`), not here.\n\n' +
|
|
4020
4022
|
'Example — a full COMPONENT_LIST `value` with two children, where the second child itself nests another COMPONENT_LIST (note: scalar leaves are `{ "value": ... }`-wrapped, COMPONENT_LIST values are `{ "components": [...] }` and are NOT wrapped, every entry has a unique `id`, and this is the COMPLETE value you would send even to change just one field): ' +
|
|
4021
4023
|
`{ "components": [ { "id": "a1b2c", "codeComponentId": "7ojrigep-Eml9n5sN3i", "propValues": { "heading": { "value": "Featured" }, "visible": { "value": true } } }, { "id": "d3e4f", "codeComponentId": "x2plk9zq-Qw8rt", "propValues": { "title": { "value": "Sub group" }, "cards": { "components": [ { "id": "g5h6i", "codeComponentId": "card01ab-Zz12", "propValues": { "label": { "value": "Card 1" } } }, { "id": "j7k8l", "codeComponentId": "card01ab-Zz12", "propValues": { "label": { "value": "Card 2" } } } ] } } } ] }`, {
|
|
@@ -4041,7 +4043,7 @@ server.tool("update_section_prop", 'Change a single prop value of a section plac
|
|
|
4041
4043
|
bind_global: z
|
|
4042
4044
|
.string()
|
|
4043
4045
|
.optional()
|
|
4044
|
-
.describe("Bind this prop to a project GLOBAL variable (Theme Setting) as a LIVE dynamic value instead of writing a static `value` — the prop then always reflects that global's current value (edited in Theme Settings). Pass the global's `name` (its `variableName`, the `_<id>` form) from `list_theme_globals`. Provide `value` OR `bind_global`, not both. Bindable globals include TEXT / RICH_TEXT / NUMBER / BOOLEAN / IMAGE / COLOR / BORDER / SHADOW settings; pick a global whose type matches the prop. (This is the general theme-global binding; a COLOR prop can alternatively take an inline `{ \"patternValueId\" }` for theme colors.)"),
|
|
4046
|
+
.describe("Bind this prop to a project GLOBAL variable (Theme Setting) as a LIVE dynamic value instead of writing a static `value` — the prop then always reflects that global's current value (edited in Theme Settings). Pass the global's `name` (its `variableName`, the `_<id>` form) from `list_theme_globals`. Provide `value` OR `bind_global`, not both. Bindable globals include TEXT / RICH_TEXT / NUMBER / BOOLEAN / IMAGE / COLOR / BORDER / SHADOW / ENUM settings; pick a global whose type matches the prop (an ENUM prop only binds an ENUM setting of the same enum type). (This is the general theme-global binding; a COLOR prop can alternatively take an inline `{ \"patternValueId\" }` for theme colors.)"),
|
|
4045
4047
|
list_path: z
|
|
4046
4048
|
.array(z.object({ propId: z.string(), index: z.number() }))
|
|
4047
4049
|
.optional()
|
|
@@ -4060,7 +4062,11 @@ server.tool("update_section_prop", 'Change a single prop value of a section plac
|
|
|
4060
4062
|
...(prop_id ? ["--prop-id", prop_id] : []),
|
|
4061
4063
|
...(prop_name ? ["--prop-name", prop_name] : []),
|
|
4062
4064
|
...(bind_global
|
|
4063
|
-
? [
|
|
4065
|
+
? [
|
|
4066
|
+
"--bind-global",
|
|
4067
|
+
bind_global,
|
|
4068
|
+
...(list_path ? ["--list-path", JSON.stringify(list_path)] : []),
|
|
4069
|
+
]
|
|
4064
4070
|
: [
|
|
4065
4071
|
"--value",
|
|
4066
4072
|
// The CLI JSON.parses --value. If `value` is already a JSON string, pass
|
|
@@ -4151,7 +4157,10 @@ server.tool("update_page_sections", "Fill MANY placed sections of a page in a SI
|
|
|
4151
4157
|
...(u.prop_id ? { propId: u.prop_id } : {}),
|
|
4152
4158
|
...(u.prop_name ? { propName: u.prop_name } : {}),
|
|
4153
4159
|
...(u.bind_global
|
|
4154
|
-
? {
|
|
4160
|
+
? {
|
|
4161
|
+
bindGlobal: u.bind_global,
|
|
4162
|
+
...(u.list_path ? { listPath: u.list_path } : {}),
|
|
4163
|
+
}
|
|
4155
4164
|
: { value: u.value }),
|
|
4156
4165
|
})),
|
|
4157
4166
|
}));
|
|
@@ -4360,7 +4369,7 @@ server.tool("create_page", 'Create a theme page of a given pageType in the conne
|
|
|
4360
4369
|
return callEditorAction(project_root, args);
|
|
4361
4370
|
});
|
|
4362
4371
|
// Tool: delete_page
|
|
4363
|
-
server.tool("delete_page",
|
|
4372
|
+
server.tool("delete_page", "Delete a theme page and every section placed on it. DESTRUCTIVE and effectively one-way from here (only the editor's own undo stack can bring it back) — ALWAYS get the user's explicit OK before calling it, and never call it to \"clean up\" a page you merely think is unused. Takes the page id from `list_editor_pages` / `get_page_by_type`; there is no name resolution. Refused for the pages a theme cannot run without — INDEX (home), CHECKOUT and NOT_FOUND (404) — and for an ikas-rendered ready-made account page, which must be turned off by its group with `enable_ready_made_pages` so the cleanup that flow does actually runs. Deleting the page currently open in the editor moves it to the home page first. Returns the deleted page's `{ pageId, pageType, name, slug }` plus `remainingPages`. Requires `ikas theme dev` running with the editor connected.", {
|
|
4364
4373
|
project_root: z
|
|
4365
4374
|
.string()
|
|
4366
4375
|
.describe("Absolute path to the code-component project."),
|
|
@@ -4385,7 +4394,7 @@ server.tool("delete_page", 'Delete a theme page and every section placed on it.
|
|
|
4385
4394
|
return callEditorAction(project_root, args);
|
|
4386
4395
|
});
|
|
4387
4396
|
// Tool: remove_page_section
|
|
4388
|
-
server.tool("remove_page_section",
|
|
4397
|
+
server.tool("remove_page_section", "Remove ONE section placement from a page — the delete counterpart of `add_section_to_page` / `move_page_section`. DESTRUCTIVE: it takes the merchant's content in that section with it, so get the user's explicit OK first. The checkout section on the CHECKOUT page is refused — the checkout page requires it, so never offer to remove it. Target the PLACEMENT by `element_id` from `list_page_sections`, never the `sectionId` or `componentId` — the same section placed twice has two independent placements and each is removed on its own (passing the wrong id is rejected with the elementId you meant). A common (header/footer) section on the home page is HIDDEN rather than deleted, because the INDEX placement is where a common section's prop values live for EVERY page; the reply says so with `hidden: true`, and `add_section_to_page` brings it back with its values intact. A page customized per routing/specification holds its own placements per state: pass `sf_routing_id`/`specification_id` to remove from that state, otherwise only the BASE layer is touched (the reply lists the states it left alone). Returns the removed placement plus the page's remaining `sections`. Requires `ikas theme dev` running with the editor connected.", {
|
|
4389
4398
|
project_root: z
|
|
4390
4399
|
.string()
|
|
4391
4400
|
.describe("Absolute path to the code-component project."),
|
|
@@ -4408,7 +4417,7 @@ server.tool("remove_page_section", 'Remove ONE section placement from a page —
|
|
|
4408
4417
|
.number()
|
|
4409
4418
|
.optional()
|
|
4410
4419
|
.describe("Dev server WebSocket port (default 5201)."),
|
|
4411
|
-
}, async ({ project_root, page_id, element_id, sf_routing_id, specification_id, confirm, port }) => {
|
|
4420
|
+
}, async ({ project_root, page_id, element_id, sf_routing_id, specification_id, confirm, port, }) => {
|
|
4412
4421
|
if (confirm !== true)
|
|
4413
4422
|
return destructiveConfirmRefusal();
|
|
4414
4423
|
const args = [
|
|
@@ -4596,10 +4605,10 @@ const EDITOR_WORKFLOW_GUIDE = [
|
|
|
4596
4605
|
"",
|
|
4597
4606
|
"## COMMON (global) sections: the header and the footer",
|
|
4598
4607
|
"The theme's header and footer are COMMON sections: they render on every page that carries a placement of them (pages created in the editor are seeded with one — EXCEPT ikas ready-made (built-in) account/membership pages, which are born without the theme header/footer) and their prop values are SHARED by the whole theme — there is exactly ONE record, kept on the INDEX (Home) page's placement. A placement of the same section on any other page stores NOTHING of its own.",
|
|
4599
|
-
|
|
4608
|
+
'- How to recognize one: list_page_sections / get_section_values / the write results return a `common` block for it — `isCommon: true`, `commonSectionType: "header"|"footer"`, `valuesArePageSpecific: false`, plus `canonicalPageId`/`canonicalElementId` (the INDEX placement that holds the values). A page containing one also gets a top-level `commonSectionsNote`.',
|
|
4600
4609
|
"- READS are redirected for you: get_section_values / list_page_sections report a common section's values FROM the INDEX placement, whatever page_id you pass. So what you see is what the page actually renders — no need to re-read from the INDEX page.",
|
|
4601
4610
|
"- WRITES are redirected too: update_section_prop / update_page_sections / add_sections_to_page store the value on that INDEX record, so the change applies to the header/footer on ALL pages. The echoed value is read back from the canonical placement, so it reflects what was really stored.",
|
|
4602
|
-
|
|
4611
|
+
'- THE TRAP to avoid: a header/footer is normally already filled with the merchant\'s content. Never conclude a prop is empty and never blind-fill "missing" props — you would overwrite live content on every page, including pages you never opened. Always get_section_values first and change ONLY the prop the user asked about.',
|
|
4603
4612
|
"- If the INDEX page has no placement of the section, there is no record: reads return nothing and writes are REJECTED (\"Index page does not have the common section!\"). Fix it by placing the section on the INDEX page (add_section_to_page with the INDEX page id — get it from list_editor_pages / get_page_by_type('INDEX')) and setting its props there; it then renders on the other pages too.",
|
|
4604
4613
|
"",
|
|
4605
4614
|
"## Building a whole page fast (placing + filling)",
|
|
@@ -4641,7 +4650,7 @@ server.tool("get_editor_workflow", "Read this FIRST when the user wants to place
|
|
|
4641
4650
|
};
|
|
4642
4651
|
});
|
|
4643
4652
|
// Tool: list_theme_globals
|
|
4644
|
-
server.tool("list_theme_globals", "List the theme's global settings from the connected editor: global variables (Theme Settings) and design tokens (colors, typography, breakpoints, keyframes, color schemes). Call this BEFORE generating sections/components so you reuse the theme's existing variables and tokens (read them in component code via `getThemeSetting`/`getThemeColors`/... from `@ikas/bp-storefront`). Includes items created manually in the editor UI. Requires `ikas theme dev` running with the editor connected. Two shape gotchas when consuming the result at runtime: (1) color schemes — `colorSchemes.schemes` holds only `{id,name}` slot LABELS; the actual colors live in `colorSchemes.values[].colorsByScheme` keyed by slot id, so iterate `values[].colorsByScheme` (iterating `schemes` renders empty). (2) An IMAGE setting's `value` is an image REFERENCE like `{id:'theme-images/<uuid>'}` with no `.url` — resolve it via `getDefaultSrc`/`getSrc`. (3) Each keyframe now includes its animation `settings` (type-dependent { property, value } entries — duration/iteration/timing/etc. for keyframe, transition for transition) and each point's resolved `styles`, so you can read back the full animation config, not just the `ref`. (4) Each typography entry may include `supportedFontWeights` — the exact numeric weights that text style's font actually ships (e.g. `[400]` for Modak); when present, only set `font_weight` to a value in this list (via `update_text_style`/`create_theme_global`), otherwise the write is rejected. If the field is absent the font's weight set couldn't be determined — fall back to 400 or rely on the validation error. Each typography entry also carries `isDefault` (true for the theme's default/global text style — the editor's \"Make Default Style\"); toggle it via `update_text_style`'s `is_default`. (5) Token NAMES are NOT unique — a theme can contain two tokens with the SAME name (e.g. colors pulled from two different design assets), so NEVER match a token by its human name. ALWAYS identify a token by its `id` (globally unique). IN COMPONENT CODE: prefer embedding the token's id-derived STABLE ref directly — color → cssVar `var(--<id>)`, typography → `className` `_<id>`, keyframe (animation-name) → `ref` `_<id>` — so you don't look anything up at runtime. If you DO need to pick a token out of a `getThemeColors()` / `getThemeTypography()` / `getThemeKeyframes()` / `getThemeColorSchemes()` array, match on `.id` (e.g. `getThemeColors().find(c => c.id === \"<id>\")`), NEVER on `.name`. EXCEPTION — global variables/settings: `getThemeSetting(name)` takes the token's `name`, but here `name` is the unique runtime key (`variableName`, the `_<id>` form returned in this list), which IS safe; do NOT pass a setting's human `displayName`. The update/delete tools also identify a token ONLY by `id` (they do NOT look up by name — on those tools `name` means the NEW name when renaming). Always take the `id` (or, for settings, the `name`/variableName) from THIS list. (6) GROUPING convention: a \"/\" in a token's NAME organizes it into a group in the editor UI — the text before the first \"/\" is the group, the rest is the item label. This applies to COLORS (e.g. \"Brand/Primary\"), TEXT STYLES (e.g. \"Heading/H1\"), and COLOR-SCHEME SLOTS (e.g. \"PrimaryButton/Background\"). When you create globals (via `create_theme_global`/`update_theme_color_scheme`), use \"/\"-prefixed names to keep related tokens grouped and tidy; reuse an existing group's exact prefix so they nest together.", {
|
|
4653
|
+
server.tool("list_theme_globals", "List the theme's global settings from the connected editor: global variables (Theme Settings) and design tokens (colors, typography, breakpoints, keyframes, color schemes). Call this BEFORE generating sections/components so you reuse the theme's existing variables and tokens (read them in component code via `getThemeSetting`/`getThemeColors`/... from `@ikas/bp-storefront`). Includes items created manually in the editor UI. Requires `ikas theme dev` running with the editor connected. Two shape gotchas when consuming the result at runtime: (1) color schemes — `colorSchemes.schemes` holds only `{id,name}` slot LABELS; the actual colors live in `colorSchemes.values[].colorsByScheme` keyed by slot id, so iterate `values[].colorsByScheme` (iterating `schemes` renders empty). (2) An IMAGE setting's `value` is an image REFERENCE like `{id:'theme-images/<uuid>'}` with no `.url` — resolve it via `getDefaultSrc`/`getSrc`. (3) Each keyframe now includes its animation `settings` (type-dependent { property, value } entries — duration/iteration/timing/etc. for keyframe, transition for transition) and each point's resolved `styles`, so you can read back the full animation config, not just the `ref`. (4) Each typography entry may include `supportedFontWeights` — the exact numeric weights that text style's font actually ships (e.g. `[400]` for Modak); when present, only set `font_weight` to a value in this list (via `update_text_style`/`create_theme_global`), otherwise the write is rejected. If the field is absent the font's weight set couldn't be determined — fall back to 400 or rely on the validation error. Each typography entry also carries `isDefault` (true for the theme's default/global text style — the editor's \"Make Default Style\"); toggle it via `update_text_style`'s `is_default`. (5) Token NAMES are NOT unique — a theme can contain two tokens with the SAME name (e.g. colors pulled from two different design assets), so NEVER match a token by its human name. ALWAYS identify a token by its `id` (globally unique). IN COMPONENT CODE: prefer embedding the token's id-derived STABLE ref directly — color → cssVar `var(--<id>)`, typography → `className` `_<id>`, keyframe (animation-name) → `ref` `_<id>` — so you don't look anything up at runtime. If you DO need to pick a token out of a `getThemeColors()` / `getThemeTypography()` / `getThemeKeyframes()` / `getThemeColorSchemes()` array, match on `.id` (e.g. `getThemeColors().find(c => c.id === \"<id>\")`), NEVER on `.name`. EXCEPTION — global variables/settings: `getThemeSetting(name)` takes the token's `name`, but here `name` is the unique runtime key (`variableName`, the `_<id>` form returned in this list), which IS safe; do NOT pass a setting's human `displayName`. The update/delete tools also identify a token ONLY by `id` (they do NOT look up by name — on those tools `name` means the NEW name when renaming). Always take the `id` (or, for settings, the `name`/variableName) from THIS list. (6) GROUPING convention: a \"/\" in a token's NAME organizes it into a group in the editor UI — the text before the first \"/\" is the group, the rest is the item label. This applies to COLORS (e.g. \"Brand/Primary\"), TEXT STYLES (e.g. \"Heading/H1\"), and COLOR-SCHEME SLOTS (e.g. \"PrimaryButton/Background\"). When you create globals (via `create_theme_global`/`update_theme_color_scheme`), use \"/\"-prefixed names to keep related tokens grouped and tidy; reuse an existing group's exact prefix so they nest together. (7) ENUM settings: a setting with `type: \"ENUM\"` selects one option of a PROJECT enum and carries `enumTypeId` (the enum's id — the same one an ENUM prop declares as `enumTypeId`) and `options` (`{ label: value }`); its `value` is always one of those option VALUES (never a label), or null when unset. The response's top-level `enumTypes` array lists the project enums a new ENUM setting may select from (`{ id, name, options }`) — use an `id` from it as `enum_type_id` in `create_theme_global`. An ENUM prop can be bound (`bind_global`) only to an ENUM setting of the SAME enum.", {
|
|
4645
4654
|
project_root: z
|
|
4646
4655
|
.string()
|
|
4647
4656
|
.describe("Absolute path to the code-component project (where `node_modules/.bin/ikas-component` lives)."),
|
|
@@ -4671,9 +4680,9 @@ const textStyleFontParams = {
|
|
|
4671
4680
|
font_weight: z
|
|
4672
4681
|
.string()
|
|
4673
4682
|
.optional()
|
|
4674
|
-
.describe(
|
|
4675
|
-
|
|
4676
|
-
|
|
4683
|
+
.describe("font-weight as a number (e.g. \"600\"); append 'i' for italic (e.g. \"600i\"). A PLAIN number with no 'i' " +
|
|
4684
|
+
"is upright and REMOVES italic if the text style was italic (italic is carried by the weight — re-send the " +
|
|
4685
|
+
"weight without 'i' to turn it off). Must be a weight the chosen " +
|
|
4677
4686
|
'font ships — if it isn\'t, the error lists the supported weights; when unsure use "400".'),
|
|
4678
4687
|
line_height: z.string().optional(),
|
|
4679
4688
|
letter_spacing: z.string().optional(),
|
|
@@ -4727,7 +4736,7 @@ function textStyleFontArgs(input) {
|
|
|
4727
4736
|
}
|
|
4728
4737
|
// Tool: create_theme_global
|
|
4729
4738
|
server.tool("create_theme_global", "Create a theme global setting in the connected editor. `kind` selects what to create:\n" +
|
|
4730
|
-
'- globalVariable: requires display_name + type (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); value optional. Pass `value` as a REAL JSON value, never a stringified JSON. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: real JSON boolean true/false (NOT the string "true"); IMAGE: an image REFERENCE object { id: "theme-images/<uuid>" } (NOT { url }) — the id MUST come from a REAL upload: call `upload_image` (file_path or image_url) and use the id it returns, or reuse an id already listed by `list_theme_globals`. NEVER invent an id — it is stored as given and renders as a broken image, so if you have no image, omit `value` and let the merchant set it from the "Theme Settings" panel; BORDER: { width: { value, unit }, style, color } (width.value must be a number); SHADOW: { x, y, blur, spread, color, position: "outside"|"inside" }. For object types (IMAGE/BORDER/SHADOW) pass an actual JSON object, not a JSON-encoded string. Every value is validated server-side and a wrong shape is REJECTED, not silently stored.\n' +
|
|
4739
|
+
'- globalVariable: requires display_name + type (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW|ENUM); value optional. Pass `value` as a REAL JSON value, never a stringified JSON. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: real JSON boolean true/false (NOT the string "true"); IMAGE: an image REFERENCE object { id: "theme-images/<uuid>" } (NOT { url }) — the id MUST come from a REAL upload: call `upload_image` (file_path or image_url) and use the id it returns, or reuse an id already listed by `list_theme_globals`. NEVER invent an id — it is stored as given and renders as a broken image, so if you have no image, omit `value` and let the merchant set it from the "Theme Settings" panel; BORDER: { width: { value, unit }, style, color } (width.value must be a number); SHADOW: { x, y, blur, spread, color, position: "outside"|"inside" }; ENUM: a setting that selects ONE option of a PROJECT enum — pass `enum_type_id` (an enum id from `list_theme_globals` → `enumTypes[]`; the same enum an ENUM prop declares as `enumTypeId`; built-in @ikas/* enums are REJECTED) and `value` = one of that enum\'s option VALUES (the strings in its `options`, e.g. "bottom"), NOT a label; omit `value` to leave it unset. Once created, an ENUM prop of the SAME enum can be bound to it with `update_section_prop` `bind_global`, and in component code `themeValue("_<id>")` from `./global-types` narrows to the enum\'s union type. For object types (IMAGE/BORDER/SHADOW) pass an actual JSON object, not a JSON-encoded string. Every value is validated server-side and a wrong shape is REJECTED, not silently stored.\n' +
|
|
4731
4740
|
'- color: requires name + value — a CONCRETE color (hex e.g. "#ff0000", rgb()/hsl() or a CSS color name). A color token cannot alias another color, so a "var(...)" reference is REJECTED here (to reuse another theme color, reference its cssVar in component code, or in a colorScheme slot). TIP: a "/" in the `name` GROUPS the color in the editor UI (e.g. "Brand/Primary", "Brand/Accent" appear under a "Brand" group); use this to organize related colors. To CHANGE an existing color later, use `update_theme_color` (edits in place, keeps the cssVar stable) — do NOT delete+recreate.\n' +
|
|
4732
4741
|
'- typography: requires name + at least one text-style property. These are exactly the fields the editor\'s "Update Text Style" modal exposes — nothing else (in particular there is NO color or text-align on a text style). VALUE FORMATS (all validated; a bad value is rejected with the offending value and the allowed set): font_weight is one of 100,200,300,400,500,600,700,800,900 (append \'i\' for italic, e.g. "600i") AND must be a weight the chosen font actually ships (many fonts provide only a subset, e.g. "Modak" only ships 400) — if unavailable the call fails and lists the supported weights, so when unsure use 400; stored as a numeric weight + a font-style entry; font_size is a number WITH a unit (e.g. "16px", "1.2rem", "80%"); line_height is "normal", a unitless number (e.g. "1.5") or a length; letter_spacing is "normal" or a length (e.g. "0.02em", "0.5px"); font_family is a real font the editor offers (a Google Fonts name with EXACT casing, e.g. "Inter") — an unknown/invented family is rejected; text_decoration is none|underline|line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis; white_space is normal|pre|pre-wrap|pre-line|wrap|nowrap|collapse; line_break is auto|loose|normal|strict|anywhere; word_break is normal|break-all|keep-all; text_wrap_style is auto|balance|pretty|stable. TIP: a "/" in the `name` GROUPS the text style in the editor UI (e.g. "Heading/H1", "Heading/H2" appear under a "Heading" group); use this to organize related styles. To CHANGE an existing text style later, use `update_text_style` (edits in place, keeps the `className` stable) — do NOT delete+recreate, which changes the id/className.\n' +
|
|
4733
4742
|
"- breakpoint: requires name + width (a positive INTEGER, in px — no decimals). To CHANGE an existing breakpoint later, use `update_theme_breakpoint` (edits in place, keeps the id).\n" +
|
|
@@ -4757,6 +4766,10 @@ server.tool("create_theme_global", "Create a theme global setting in the connect
|
|
|
4757
4766
|
.any()
|
|
4758
4767
|
.optional()
|
|
4759
4768
|
.describe("globalVariable default value (shape depends on type — see tool description), or color hex string."),
|
|
4769
|
+
enum_type_id: z
|
|
4770
|
+
.string()
|
|
4771
|
+
.optional()
|
|
4772
|
+
.describe("globalVariable of type ENUM only: id of the project enum the setting selects from (from `list_theme_globals` → `enumTypes[]`). Required for ENUM, invalid for other types."),
|
|
4760
4773
|
...textStyleFontParams,
|
|
4761
4774
|
width: z
|
|
4762
4775
|
.number()
|
|
@@ -4791,6 +4804,9 @@ server.tool("create_theme_global", "Create a theme global setting in the connect
|
|
|
4791
4804
|
...(input.value !== undefined
|
|
4792
4805
|
? ["--value", JSON.stringify(input.value)]
|
|
4793
4806
|
: []),
|
|
4807
|
+
...(input.enum_type_id
|
|
4808
|
+
? ["--enum-type-id", String(input.enum_type_id)]
|
|
4809
|
+
: []),
|
|
4794
4810
|
...p,
|
|
4795
4811
|
];
|
|
4796
4812
|
break;
|
|
@@ -4860,7 +4876,7 @@ server.tool("create_theme_global", "Create a theme global setting in the connect
|
|
|
4860
4876
|
return callEditorAction(input.project_root, args);
|
|
4861
4877
|
});
|
|
4862
4878
|
// Tool: update_theme_global
|
|
4863
|
-
server.tool("update_theme_global",
|
|
4879
|
+
server.tool("update_theme_global", "Update an existing global variable in the connected editor (e.g. fix its value or change its type). Identify it by `name` (the runtime key from `list_theme_globals`). Only the fields you pass are changed. Value shapes follow `create_theme_global` — pass `value` as REAL JSON (objects/booleans, never stringified); BOOLEAN is a real boolean, BORDER/SHADOW are real objects; IMAGE is an image REFERENCE object { id: \"theme-images/<uuid>\" } whose id MUST come from `upload_image` or an existing `list_theme_globals` entry — an invented id renders as a broken image; ENUM `value` is one of the enum's option VALUES (see the setting's `options` in `list_theme_globals`), and `enum_type_id` re-points the setting at another project enum. Changing `type` or `enum_type_id` of a global that props are bound to is REJECTED (the error names the places) — unbind those props first. Values are validated server-side; a wrong shape is REJECTED, not silently stored.", {
|
|
4864
4880
|
project_root: z
|
|
4865
4881
|
.string()
|
|
4866
4882
|
.describe("Absolute path to the code-component project."),
|
|
@@ -4871,16 +4887,20 @@ server.tool("update_theme_global", 'Update an existing global variable in the co
|
|
|
4871
4887
|
type: z
|
|
4872
4888
|
.string()
|
|
4873
4889
|
.optional()
|
|
4874
|
-
.describe("New value type (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW)."),
|
|
4890
|
+
.describe("New value type (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW|ENUM)."),
|
|
4875
4891
|
value: z
|
|
4876
4892
|
.any()
|
|
4877
4893
|
.optional()
|
|
4878
|
-
.describe('New value as REAL JSON (shape depends on type; e.g. BOOLEAN: true/false, NUMBER: 12, IMAGE: { id: "theme-images/<uuid>" } from upload_image — see create_theme_global). An invented IMAGE id renders as a broken image.'),
|
|
4894
|
+
.describe('New value as REAL JSON (shape depends on type; e.g. BOOLEAN: true/false, NUMBER: 12, IMAGE: { id: "theme-images/<uuid>" } from upload_image, ENUM: an option VALUE string — see create_theme_global). An invented IMAGE id renders as a broken image.'),
|
|
4895
|
+
enum_type_id: z
|
|
4896
|
+
.string()
|
|
4897
|
+
.optional()
|
|
4898
|
+
.describe('ENUM only: the project enum to select from (from `list_theme_globals` → `enumTypes[]`); may be passed alone to re-point an unbound ENUM global, or together with `type: "ENUM"`.'),
|
|
4879
4899
|
port: z
|
|
4880
4900
|
.number()
|
|
4881
4901
|
.optional()
|
|
4882
4902
|
.describe("Dev server WebSocket port (default 5201)."),
|
|
4883
|
-
}, async ({ project_root, name, display_name, type, value, port }) => {
|
|
4903
|
+
}, async ({ project_root, name, display_name, type, value, enum_type_id, port, }) => {
|
|
4884
4904
|
const args = [
|
|
4885
4905
|
"update-global-variable",
|
|
4886
4906
|
"--name",
|
|
@@ -4888,6 +4908,7 @@ server.tool("update_theme_global", 'Update an existing global variable in the co
|
|
|
4888
4908
|
...(display_name !== undefined ? ["--display-name", display_name] : []),
|
|
4889
4909
|
...(type !== undefined ? ["--type", type] : []),
|
|
4890
4910
|
...(value !== undefined ? ["--value", JSON.stringify(value)] : []),
|
|
4911
|
+
...(enum_type_id !== undefined ? ["--enum-type-id", enum_type_id] : []),
|
|
4891
4912
|
...(port ? ["--port", String(port)] : []),
|
|
4892
4913
|
];
|
|
4893
4914
|
return callEditorAction(project_root, args);
|
|
@@ -4965,7 +4986,7 @@ server.tool("update_text_style", "Update an existing theme typography token (tex
|
|
|
4965
4986
|
"line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis; white_space " +
|
|
4966
4987
|
"is normal|pre|pre-wrap|pre-line|wrap|nowrap|collapse; line_break is auto|loose|normal|strict|anywhere; word_break " +
|
|
4967
4988
|
"is normal|break-all|keep-all; text_wrap_style is auto|balance|pretty|stable. Set `is_default: true` to make " +
|
|
4968
|
-
|
|
4989
|
+
'this text style the theme\'s default/global style (the editor\'s right-click "Make Default Style" / "Global ' +
|
|
4969
4990
|
"Stil Yap\") — any other default in the group is cleared; `is_default: false` removes the flag. A text style's " +
|
|
4970
4991
|
"current default state is readable from `list_theme_globals` (each typography entry's `isDefault`). Requires " +
|
|
4971
4992
|
"`ikas theme dev` running with the editor connected.", {
|
|
@@ -4997,7 +5018,11 @@ server.tool("update_text_style", "Update an existing theme typography token (tex
|
|
|
4997
5018
|
id,
|
|
4998
5019
|
...(name !== undefined ? ["--name", name] : []),
|
|
4999
5020
|
...textStyleFontArgs(input),
|
|
5000
|
-
...(is_default === true
|
|
5021
|
+
...(is_default === true
|
|
5022
|
+
? ["--default"]
|
|
5023
|
+
: is_default === false
|
|
5024
|
+
? ["--no-default"]
|
|
5025
|
+
: []),
|
|
5001
5026
|
...(port ? ["--port", String(port)] : []),
|
|
5002
5027
|
];
|
|
5003
5028
|
return callEditorAction(project_root, args);
|