@ompo-design/mcp-server 0.1.12 → 0.1.13
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/dist/apply-plan.js +187 -12
- package/dist/edit-billing.js +1 -1
- package/dist/iconify.d.ts +11 -0
- package/dist/iconify.js +42 -0
- package/dist/index.js +40 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +6 -2
- package/dist/types.d.ts +4 -2
- package/package.json +1 -1
package/dist/apply-plan.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
const OMPO_GLOSSARY = {
|
|
2
|
+
layoutNone: `Ompo layout "None" means the element is a normal block container (\`display: block\`), not a flex or grid layout parent. Do not add flex-direction, grid-template-columns, or gap unless a separate edit sets \`display: flex\` or \`display: grid\` on this selector.`,
|
|
3
|
+
layoutFlex: `Ompo layout "Flex" maps to \`display: flex\` (or inline-flex) on this selector. Apply flexDirection, justifyContent, alignItems, and gap together on the same container. Row/column toggles in Ompo map to flex-direction.`,
|
|
4
|
+
layoutGrid: `Ompo layout "Grid" maps to \`display: grid\` on this selector. Apply gridTemplateColumns (often \`repeat(n, 1fr)\`), optional gridTemplateRows / gridAutoRows, gridAutoFlow (row vs column), justifyContent, alignItems, and gap together on the grid parent. Prefer grid over flex for multi-column card/bento layouts.`,
|
|
2
5
|
gap: `Ompo "Gap" is the space between children inside a flex or grid container. Apply \`gap\` on the parent layout element (not on children). The parent needs \`display: flex\` or \`display: grid\`. Example: gap "16px" → CSS \`gap: 16px\` or Tailwind \`gap-4\`.`,
|
|
3
|
-
fill: `Ompo "Fill" means an element should expand to consume free space inside its parent. Fill is NOT a CSS property — read \`widthMode: fill\` / \`heightMode: fill\` together with \`ensureParentFlex\`, \`flexGrow\`, \`flexShrink\`, \`flexBasis\`, and \`alignSelf\` on the same selector. Parent must be \`display: flex
|
|
6
|
+
fill: `Ompo "Fill" means an element should expand to consume free space inside its parent. Fill is NOT a CSS property — read \`widthMode: fill\` / \`heightMode: fill\` together with \`ensureParentFlex\`, \`flexGrow\`, \`flexShrink\`, \`flexBasis\`, and \`alignSelf\` on the same selector. Parent must be \`display: flex\` (Fill does not apply inside grid parents). Main axis (direction of flex): use \`flex: 1 1 0\` (grow/shrink/basis). Cross axis: use \`align-self: stretch\` and remove fixed width/height on that axis. If \`ensureParentFlex\` is present, make the parent a flex container with that direction first.`,
|
|
4
7
|
fit: `Ompo "Fit" means size to content. Map \`widthMode: fit\` → \`width: fit-content\`, \`heightMode: fit\` → \`height: fit-content\` (or the project's equivalent).`,
|
|
5
|
-
imageFill: `Ompo image background fill:
|
|
8
|
+
imageFill: `Ompo image background fill: when \`backgroundImageSource\` is an https URL, download that asset into the project (e.g. public/images/) and set \`background-image\` to a project-relative url(). For legacy local paths, copy from the user's machine instead. Always use \`background-size: cover\`, \`background-position: center\`, and \`background-repeat: no-repeat\`. Do not commit file:// URLs to source.`,
|
|
6
9
|
domMove: `Ompo DOM move: reorder or reparent elements in source markup/components. Use movedElement anchors (tag, id, class, textSnippet) to find nodes. destinationChildren is the authoritative sibling order after the move — match this order in JSX/HTML or reorder mapped arrays. CSS alone cannot satisfy a dom.move.`,
|
|
7
10
|
insertText: `Ompo text insert: the user added a new text block (usually <p>). insertedElement carries insertKind "text", a stable data-ompo-insert-id selector, and textSnippet for the current copy. Apply as markup or a text component at parent/index; check separate text operations for edits after insert.`,
|
|
11
|
+
insertSection: `Ompo section insert: the user added a full-width page section (<div>, insertKind "section"). Default size is width 100% and height 300px with fill #AEC8F8. Append at the page root / bottom of main content unless parent/index says otherwise.`,
|
|
12
|
+
insertFrame: `Ompo frame insert: the user added a small div (insertKind "frame"). Default size is 150×80px with fill #AEC8F8. Use for nested containers, cards, or tiles inside a section.`,
|
|
8
13
|
insertIcon: `Ompo icon insert: the user added an Iconify icon (span.ompo-insert-icon) or editable SVG. iconId is the Iconify name (e.g. lucide:home) when known. Prefer the project's icon library or @iconify/react with that id instead of inlining raw SVG when possible. Size, fill color, and stroke changes appear as style operations on the same stable data-ompo-insert-id or data-ompo-icon-id selector.`,
|
|
9
14
|
replaceIcon: `Ompo icon replace: the user swapped an existing icon/SVG to a different Iconify icon. Update the project's icon component or @iconify/react usage to the new iconId — do not only patch inline SVG unless the project already inlines icons.`,
|
|
10
|
-
iconStroke: `Ompo icon/SVG stroke: iconStrokeWidth sets stroke-width on the inner SVG; iconStrokeColor sets stroke (often currentColor). Applies to inserted icons and native SVG elements selected in Ompo
|
|
15
|
+
iconStroke: `Ompo icon/SVG stroke: iconStrokeWidth sets stroke-width on the inner SVG; iconStrokeColor sets stroke (often currentColor). Applies to inserted icons and native SVG elements selected in Ompo.`,
|
|
16
|
+
zIndex: `Ompo "Z Index" maps to CSS \`z-index\` on the selected element. Use the integer from the edit, or \`auto\` to clear an authored value. Higher values stack above lower values within the same stacking context. If stacking does not change in the browser, the element may also need a non-static \`position\` (e.g. relative) in source — Ompo may set inline z-index without changing position.`
|
|
11
17
|
};
|
|
12
18
|
function describeAnchor(anchor) {
|
|
13
19
|
const parts = [anchor.tagName];
|
|
@@ -29,6 +35,8 @@ function buildDomInsertPlan(operation) {
|
|
|
29
35
|
const inserted = operation.insertedElement;
|
|
30
36
|
const isIcon = operation.insertKind === 'icon';
|
|
31
37
|
const isText = operation.insertKind === 'text';
|
|
38
|
+
const isSection = operation.insertKind === 'section';
|
|
39
|
+
const isFrame = operation.insertKind === 'frame';
|
|
32
40
|
const steps = [];
|
|
33
41
|
if (operation.parent) {
|
|
34
42
|
steps.push(`Locate destination parent: ${describeAnchor(operation.parent)}.`);
|
|
@@ -45,11 +53,20 @@ function buildDomInsertPlan(operation) {
|
|
|
45
53
|
if (isText) {
|
|
46
54
|
steps.push(OMPO_GLOSSARY.insertText);
|
|
47
55
|
}
|
|
56
|
+
else if (isSection) {
|
|
57
|
+
steps.push(OMPO_GLOSSARY.insertSection);
|
|
58
|
+
}
|
|
59
|
+
else if (isFrame) {
|
|
60
|
+
steps.push(OMPO_GLOSSARY.insertFrame);
|
|
61
|
+
}
|
|
48
62
|
else if (isIcon) {
|
|
49
63
|
steps.push(OMPO_GLOSSARY.insertIcon);
|
|
50
64
|
if (operation.iconId) {
|
|
51
65
|
steps.push(`Iconify id: ${operation.iconId}`);
|
|
52
66
|
}
|
|
67
|
+
if (operation.iconSvg) {
|
|
68
|
+
steps.push('iconSvg is included in the plan payload for inline SVG or reference.');
|
|
69
|
+
}
|
|
53
70
|
}
|
|
54
71
|
steps.push(`Insert at child index ${operation.index}.`);
|
|
55
72
|
steps.push('Sanitize and adapt the HTML to the project’s component conventions before writing.');
|
|
@@ -57,7 +74,11 @@ function buildDomInsertPlan(operation) {
|
|
|
57
74
|
? `Insert Iconify icon${operation.iconId ? ` (${operation.iconId})` : ''}`
|
|
58
75
|
: isText
|
|
59
76
|
? 'Insert text block'
|
|
60
|
-
:
|
|
77
|
+
: isSection
|
|
78
|
+
? 'Insert page section'
|
|
79
|
+
: isFrame
|
|
80
|
+
? 'Insert frame div'
|
|
81
|
+
: 'Insert new element into the page';
|
|
61
82
|
return {
|
|
62
83
|
kind: 'dom.insert',
|
|
63
84
|
summary,
|
|
@@ -69,6 +90,7 @@ function buildDomInsertPlan(operation) {
|
|
|
69
90
|
selector: operation.selector,
|
|
70
91
|
insertKind: operation.insertKind,
|
|
71
92
|
iconId: operation.iconId,
|
|
93
|
+
iconSvg: operation.iconSvg,
|
|
72
94
|
insertedElement: operation.insertedElement,
|
|
73
95
|
parent: operation.parent
|
|
74
96
|
}
|
|
@@ -84,6 +106,9 @@ function buildIconReplacePlan(operation) {
|
|
|
84
106
|
}
|
|
85
107
|
steps.push(OMPO_GLOSSARY.replaceIcon);
|
|
86
108
|
steps.push(`Set Iconify id to ${operation.iconId}.`);
|
|
109
|
+
if (operation.iconSvg) {
|
|
110
|
+
steps.push('Use iconSvg from the payload when the project inlines SVG markup.');
|
|
111
|
+
}
|
|
87
112
|
if (operation.previousIconId) {
|
|
88
113
|
steps.push(`Previous icon id: ${operation.previousIconId}`);
|
|
89
114
|
}
|
|
@@ -94,6 +119,7 @@ function buildIconReplacePlan(operation) {
|
|
|
94
119
|
payload: {
|
|
95
120
|
selector: operation.selector,
|
|
96
121
|
iconId: operation.iconId,
|
|
122
|
+
iconSvg: operation.iconSvg,
|
|
97
123
|
previousIconId: operation.previousIconId,
|
|
98
124
|
element: operation.element
|
|
99
125
|
}
|
|
@@ -188,10 +214,120 @@ function formatChangedValue(property, value) {
|
|
|
188
214
|
}
|
|
189
215
|
return '(empty / remove)';
|
|
190
216
|
}
|
|
217
|
+
function layoutModeFromChanged(changed) {
|
|
218
|
+
const display = typeof changed.display === 'string' ? changed.display : null;
|
|
219
|
+
if (display === 'flex' || display === 'inline-flex')
|
|
220
|
+
return 'flex';
|
|
221
|
+
if (display === 'grid' || display === 'inline-grid')
|
|
222
|
+
return 'grid';
|
|
223
|
+
if (display === 'block' || display === 'none')
|
|
224
|
+
return 'block';
|
|
225
|
+
if (changed.gridTemplateColumns !== undefined || changed.gridAutoFlow !== undefined) {
|
|
226
|
+
return 'grid';
|
|
227
|
+
}
|
|
228
|
+
if (changed.flexDirection !== undefined)
|
|
229
|
+
return 'flex';
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function imageFillSourceNotes(source) {
|
|
233
|
+
const trimmed = source.trim();
|
|
234
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
235
|
+
return `${OMPO_GLOSSARY.imageFill} Download ${trimmed}, save into the project (e.g. public/images/), and reference the copied asset in CSS.`;
|
|
236
|
+
}
|
|
237
|
+
return `${OMPO_GLOSSARY.imageFill} Read this file from the user's machine (${trimmed}), copy it into the repo, and reference the copied asset in CSS.`;
|
|
238
|
+
}
|
|
191
239
|
function styleSuggestionForProperty(property, value, changed) {
|
|
192
240
|
const parentFlex = changed.ensureParentFlex;
|
|
193
241
|
const parentFlexLabel = parentFlex === 'row' || parentFlex === 'column' ? parentFlex : 'row or column';
|
|
242
|
+
const layoutMode = layoutModeFromChanged(changed);
|
|
194
243
|
switch (property) {
|
|
244
|
+
case 'display': {
|
|
245
|
+
const displayValue = String(value);
|
|
246
|
+
if (displayValue === 'grid' || displayValue === 'inline-grid') {
|
|
247
|
+
return {
|
|
248
|
+
property,
|
|
249
|
+
value: displayValue,
|
|
250
|
+
strategy: 'css-rule',
|
|
251
|
+
notes: OMPO_GLOSSARY.layoutGrid
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (displayValue === 'flex' || displayValue === 'inline-flex') {
|
|
255
|
+
return {
|
|
256
|
+
property,
|
|
257
|
+
value: displayValue,
|
|
258
|
+
strategy: 'css-rule',
|
|
259
|
+
notes: OMPO_GLOSSARY.layoutFlex
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
property,
|
|
264
|
+
value: displayValue,
|
|
265
|
+
strategy: 'css-rule',
|
|
266
|
+
notes: OMPO_GLOSSARY.layoutNone
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
case 'gridTemplateColumns':
|
|
270
|
+
return {
|
|
271
|
+
property,
|
|
272
|
+
value: formatChangedValue(property, value),
|
|
273
|
+
strategy: 'css-rule',
|
|
274
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Column template for the grid parent — e.g. repeat(3, 1fr) for three equal columns. Tailwind: grid-cols-3 or arbitrary grid-template-columns.`
|
|
275
|
+
};
|
|
276
|
+
case 'gridTemplateRows':
|
|
277
|
+
return {
|
|
278
|
+
property,
|
|
279
|
+
value: formatChangedValue(property, value),
|
|
280
|
+
strategy: 'css-rule',
|
|
281
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Explicit row template. Use when Ompo authored fixed row tracks; otherwise grid-auto-rows may define implicit row sizing.`
|
|
282
|
+
};
|
|
283
|
+
case 'gridAutoRows':
|
|
284
|
+
return {
|
|
285
|
+
property,
|
|
286
|
+
value: formatChangedValue(property, value),
|
|
287
|
+
strategy: 'css-rule',
|
|
288
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Size for implicit grid rows (auto-generated tracks). Common: auto, minmax(180px, auto).`
|
|
289
|
+
};
|
|
290
|
+
case 'gridAutoColumns':
|
|
291
|
+
return {
|
|
292
|
+
property,
|
|
293
|
+
value: formatChangedValue(property, value),
|
|
294
|
+
strategy: 'css-rule',
|
|
295
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Size for implicit grid columns when grid-auto-flow creates new columns.`
|
|
296
|
+
};
|
|
297
|
+
case 'gridAutoFlow':
|
|
298
|
+
return {
|
|
299
|
+
property,
|
|
300
|
+
value: formatChangedValue(property, value),
|
|
301
|
+
strategy: 'css-rule',
|
|
302
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Ompo row/column direction toggle on grid maps to grid-auto-flow (row | column). Apply on the grid container.`
|
|
303
|
+
};
|
|
304
|
+
case 'gridColumn':
|
|
305
|
+
case 'gridRow':
|
|
306
|
+
return {
|
|
307
|
+
property,
|
|
308
|
+
value: formatChangedValue(property, value),
|
|
309
|
+
strategy: 'css-rule',
|
|
310
|
+
notes: `${OMPO_GLOSSARY.layoutGrid} Grid item placement on this child selector (span / line placement).`
|
|
311
|
+
};
|
|
312
|
+
case 'flexDirection':
|
|
313
|
+
return {
|
|
314
|
+
property,
|
|
315
|
+
value: formatChangedValue(property, value),
|
|
316
|
+
strategy: 'css-rule',
|
|
317
|
+
notes: layoutMode === 'grid'
|
|
318
|
+
? `${OMPO_GLOSSARY.layoutGrid} flex-direction does not apply when display is grid — use gridAutoFlow instead if present in the same edit.`
|
|
319
|
+
: `${OMPO_GLOSSARY.layoutFlex} Main axis direction for the flex container.`
|
|
320
|
+
};
|
|
321
|
+
case 'justifyContent':
|
|
322
|
+
case 'alignItems':
|
|
323
|
+
return {
|
|
324
|
+
property,
|
|
325
|
+
value: formatChangedValue(property, value),
|
|
326
|
+
strategy: 'css-rule',
|
|
327
|
+
notes: layoutMode === 'grid'
|
|
328
|
+
? `${OMPO_GLOSSARY.layoutGrid} Alignment on the grid container (${property}). Maps from Ompo’s alignment pinner / space-between toggle.`
|
|
329
|
+
: `${OMPO_GLOSSARY.layoutFlex} Alignment on the flex container (${property}). Maps from Ompo’s alignment pinner / space-between toggle.`
|
|
330
|
+
};
|
|
195
331
|
case 'gap':
|
|
196
332
|
return {
|
|
197
333
|
property,
|
|
@@ -281,7 +417,7 @@ function styleSuggestionForProperty(property, value, changed) {
|
|
|
281
417
|
property,
|
|
282
418
|
value: formatChangedValue(property, value),
|
|
283
419
|
strategy: 'component-markup',
|
|
284
|
-
notes:
|
|
420
|
+
notes: imageFillSourceNotes(String(value))
|
|
285
421
|
};
|
|
286
422
|
case 'backgroundImage':
|
|
287
423
|
if (changed.backgroundImageSource) {
|
|
@@ -289,7 +425,7 @@ function styleSuggestionForProperty(property, value, changed) {
|
|
|
289
425
|
property,
|
|
290
426
|
value: formatChangedValue(property, value),
|
|
291
427
|
strategy: 'css-rule',
|
|
292
|
-
notes:
|
|
428
|
+
notes: imageFillSourceNotes(String(changed.backgroundImageSource))
|
|
293
429
|
};
|
|
294
430
|
}
|
|
295
431
|
break;
|
|
@@ -313,6 +449,13 @@ function styleSuggestionForProperty(property, value, changed) {
|
|
|
313
449
|
strategy: 'inline-style',
|
|
314
450
|
notes: OMPO_GLOSSARY.iconStroke
|
|
315
451
|
};
|
|
452
|
+
case 'zIndex':
|
|
453
|
+
return {
|
|
454
|
+
property,
|
|
455
|
+
value: formatChangedValue(property, value),
|
|
456
|
+
strategy: 'css-rule',
|
|
457
|
+
notes: `${OMPO_GLOSSARY.zIndex} Apply as CSS \`z-index\` on this selector.`
|
|
458
|
+
};
|
|
316
459
|
}
|
|
317
460
|
return {
|
|
318
461
|
property,
|
|
@@ -327,6 +470,30 @@ function styleSuggestions(operation) {
|
|
|
327
470
|
}
|
|
328
471
|
function summarizeLayoutIntent(changed) {
|
|
329
472
|
const parts = [];
|
|
473
|
+
const layoutMode = layoutModeFromChanged(changed);
|
|
474
|
+
if (changed.display !== undefined) {
|
|
475
|
+
if (layoutMode === 'grid') {
|
|
476
|
+
parts.push('Layout: Grid container');
|
|
477
|
+
}
|
|
478
|
+
else if (layoutMode === 'flex') {
|
|
479
|
+
parts.push('Layout: Flex container');
|
|
480
|
+
}
|
|
481
|
+
else if (layoutMode === 'block') {
|
|
482
|
+
parts.push('Layout: None (block flow, not flex/grid)');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (changed.gridTemplateColumns !== undefined) {
|
|
486
|
+
parts.push(`Grid columns: ${changed.gridTemplateColumns}`);
|
|
487
|
+
}
|
|
488
|
+
if (changed.gridTemplateRows !== undefined) {
|
|
489
|
+
parts.push(`Grid rows: ${changed.gridTemplateRows}`);
|
|
490
|
+
}
|
|
491
|
+
if (changed.gridAutoFlow !== undefined) {
|
|
492
|
+
parts.push(`Grid auto-flow: ${changed.gridAutoFlow}`);
|
|
493
|
+
}
|
|
494
|
+
if (changed.flexDirection !== undefined && layoutMode !== 'grid') {
|
|
495
|
+
parts.push(`Flex direction: ${changed.flexDirection}`);
|
|
496
|
+
}
|
|
330
497
|
if (changed.gap !== undefined) {
|
|
331
498
|
parts.push(`Set container gap to ${changed.gap}`);
|
|
332
499
|
}
|
|
@@ -339,6 +506,9 @@ function summarizeLayoutIntent(changed) {
|
|
|
339
506
|
if (changed.ensureParentFlex) {
|
|
340
507
|
parts.push(`Parent needs display:flex with flex-direction:${changed.ensureParentFlex}`);
|
|
341
508
|
}
|
|
509
|
+
if (changed.zIndex !== undefined) {
|
|
510
|
+
parts.push(`Set z-index to ${changed.zIndex}`);
|
|
511
|
+
}
|
|
342
512
|
return parts.length > 0 ? parts.join('. ') : null;
|
|
343
513
|
}
|
|
344
514
|
function buildDomStructurePlan(operation) {
|
|
@@ -351,7 +521,8 @@ function buildDomStructurePlan(operation) {
|
|
|
351
521
|
`Locate the parent element matching "${operation.parentSelector}" in source.`,
|
|
352
522
|
`Insert a new <div> wrapper at child index ${operation.index} inside that parent.`,
|
|
353
523
|
`Move the matched child elements into the new wrapper. Use the child anchors (tag, id, class, textSnippet) to find the correct nodes in source.`,
|
|
354
|
-
`Apply only the wrapper styles listed in wrapperStyles to the new container.`,
|
|
524
|
+
`Apply only the wrapper styles listed in wrapperStyles to the new container (typically display:flex and flex-direction).`,
|
|
525
|
+
'For multi-column card layouts, prefer display:grid with grid-template-columns if wrapperStyles do not already specify layout.',
|
|
355
526
|
'Do not change unrelated siblings or parent styling.'
|
|
356
527
|
],
|
|
357
528
|
payload: {
|
|
@@ -466,15 +637,19 @@ export function buildApplyPlan(bundle) {
|
|
|
466
637
|
instructions: [
|
|
467
638
|
'Only apply properties and operations listed in this plan.',
|
|
468
639
|
'Leave all untouched styling and markup unchanged.',
|
|
469
|
-
'Read ompoGlossary.fill and
|
|
470
|
-
'
|
|
640
|
+
'Read ompoGlossary.layoutNone, layoutFlex, layoutGrid, fill, and gap before applying layout changes.',
|
|
641
|
+
'Ompo layout types: None → display:block; Flex → display:flex + flexDirection; Grid → display:grid + gridTemplateColumns (+ gridAutoFlow for row/column).',
|
|
642
|
+
'When display is grid, apply gridTemplateColumns / gridAutoFlow / gap / justifyContent / alignItems on the grid parent — do not convert grid containers to flex.',
|
|
643
|
+
'When display is block (layout None), do not add flex or grid properties unless this edit explicitly sets display:flex or display:grid.',
|
|
644
|
+
'When widthMode or heightMode is "fill", apply the grouped flex properties together (flexGrow, flexBasis, alignSelf, removed width/height) — Fill requires a flex parent, not grid.',
|
|
471
645
|
'Gap belongs on the flex/grid parent. Fill belongs on the child inside a flex parent.',
|
|
472
646
|
'DOM moves are in domStructurePlans and domChanges — reorder or reparent elements in source markup/components, not just CSS.',
|
|
473
647
|
'Read ompoGlossary.domMove before applying dom.move operations.',
|
|
474
648
|
'For dom.move: destinationChildren is the structural snapshot of the final sibling order — match it in JSX/HTML or reorder mapped arrays.',
|
|
475
649
|
'For dom.flexWrap: create a new wrapper, move matched children, then apply wrapperStyles.',
|
|
476
650
|
'For dom.insert with insertKind text or icon: read ompoGlossary.insertText / insertIcon; use insertedElement anchors and iconId when applying.',
|
|
477
|
-
'For dom.iconReplace: read ompoGlossary.replaceIcon; update the project icon to the new iconId.',
|
|
651
|
+
'For dom.iconReplace: read ompoGlossary.replaceIcon; update the project icon to the new iconId. Use iconSvg from domStructurePlans when inlining SVG.',
|
|
652
|
+
'For zIndex: read ompoGlossary.zIndex; map to CSS z-index on the matching selector.',
|
|
478
653
|
'Use child anchors (tag, id, class, textSnippet, insertKind, iconId) to find elements in source when selectors are unstable.',
|
|
479
654
|
'Prefer the smallest possible diff for each file.'
|
|
480
655
|
],
|
|
@@ -509,10 +684,10 @@ export function explainEdit(bundle) {
|
|
|
509
684
|
lines.push(`${textInsertCount} text insert${textInsertCount === 1 ? '' : 's'} — apply via domStructurePlans (markup at parent/index)`);
|
|
510
685
|
}
|
|
511
686
|
if (iconInsertCount > 0) {
|
|
512
|
-
lines.push(`${iconInsertCount} icon insert${iconInsertCount === 1 ? '' : 's'} —
|
|
687
|
+
lines.push(`${iconInsertCount} icon insert${iconInsertCount === 1 ? '' : 's'} — apply iconId (and iconSvg when present) via domStructurePlans`);
|
|
513
688
|
}
|
|
514
689
|
if (iconReplaceCount > 0) {
|
|
515
|
-
lines.push(`${iconReplaceCount} icon replace${iconReplaceCount === 1 ? '' : 's'} — update iconId via domStructurePlans`);
|
|
690
|
+
lines.push(`${iconReplaceCount} icon replace${iconReplaceCount === 1 ? '' : 's'} — update iconId/iconSvg via domStructurePlans`);
|
|
516
691
|
}
|
|
517
692
|
lines.push(`Source preview: ${bundle.source.url}`);
|
|
518
693
|
return lines.join('\n');
|
package/dist/edit-billing.js
CHANGED
|
@@ -64,7 +64,7 @@ export async function consumeMcpTokenForEdit(toolName, editId) {
|
|
|
64
64
|
const balance = await readMcpTokenBalance();
|
|
65
65
|
return balance ?? 0;
|
|
66
66
|
}
|
|
67
|
-
const tokensRemaining = await consumeMcpToken(toolName);
|
|
67
|
+
const tokensRemaining = await consumeMcpToken(toolName, editId);
|
|
68
68
|
markEditBilled(editId);
|
|
69
69
|
return tokensRemaining;
|
|
70
70
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type IconifySearchResult = {
|
|
2
|
+
icons: string[];
|
|
3
|
+
total: number;
|
|
4
|
+
limit: number;
|
|
5
|
+
start: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function searchIconifyIcons(query: string, options?: {
|
|
8
|
+
limit?: number;
|
|
9
|
+
start?: number;
|
|
10
|
+
}): Promise<IconifySearchResult>;
|
|
11
|
+
export declare function fetchIconifySvg(iconId: string): Promise<string>;
|
package/dist/iconify.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const ICONIFY_API_BASE = 'https://api.iconify.design';
|
|
2
|
+
function splitIconifyId(iconId) {
|
|
3
|
+
const separator = iconId.indexOf(':');
|
|
4
|
+
if (separator <= 0)
|
|
5
|
+
return ['', ''];
|
|
6
|
+
return [iconId.slice(0, separator), iconId.slice(separator + 1)];
|
|
7
|
+
}
|
|
8
|
+
export async function searchIconifyIcons(query, options) {
|
|
9
|
+
const trimmed = query.trim();
|
|
10
|
+
const limit = Math.min(Math.max(options?.limit ?? 32, 1), 999);
|
|
11
|
+
const start = Math.max(options?.start ?? 0, 0);
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
return { icons: [], total: 0, limit, start };
|
|
14
|
+
}
|
|
15
|
+
const url = new URL(`${ICONIFY_API_BASE}/search`);
|
|
16
|
+
url.searchParams.set('query', trimmed);
|
|
17
|
+
url.searchParams.set('limit', String(limit));
|
|
18
|
+
url.searchParams.set('start', String(start));
|
|
19
|
+
const response = await fetch(url);
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(`Iconify search failed with ${response.status}`);
|
|
22
|
+
}
|
|
23
|
+
const payload = (await response.json());
|
|
24
|
+
return {
|
|
25
|
+
icons: payload.icons ?? [],
|
|
26
|
+
total: payload.total ?? payload.icons?.length ?? 0,
|
|
27
|
+
limit: payload.limit ?? limit,
|
|
28
|
+
start: payload.start ?? start
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function fetchIconifySvg(iconId) {
|
|
32
|
+
const [prefix, name] = splitIconifyId(iconId);
|
|
33
|
+
if (!prefix || !name) {
|
|
34
|
+
throw new Error(`Invalid Iconify icon id: ${iconId}`);
|
|
35
|
+
}
|
|
36
|
+
const url = `${ICONIFY_API_BASE}/${prefix}/${name}.svg`;
|
|
37
|
+
const response = await fetch(url);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Iconify SVG fetch failed with ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
return response.text();
|
|
42
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,11 @@ import { runCli } from './cli.js';
|
|
|
7
7
|
import { consumeMcpTokenForEdit, requireMcpSessionBalance } from './edit-billing.js';
|
|
8
8
|
import { editsStoreReady, listEdits, readEditBundle, recordEditApplied, recordEditPull } from './edit-store.js';
|
|
9
9
|
import { getOmpoEditsStorePath } from './edits-path.js';
|
|
10
|
+
import { fetchIconifySvg, searchIconifyIcons } from './iconify.js';
|
|
10
11
|
import { McpTokenError } from './tokens.js';
|
|
11
12
|
const server = new McpServer({
|
|
12
13
|
name: 'ompo-mcp-server',
|
|
13
|
-
version: '0.1.
|
|
14
|
+
version: '0.1.13'
|
|
14
15
|
});
|
|
15
16
|
function requireEditsStore() {
|
|
16
17
|
const storePath = getOmpoEditsStorePath();
|
|
@@ -129,6 +130,44 @@ server.tool('apply_edit', 'Build an apply plan for an Ompo edit. The agent shoul
|
|
|
129
130
|
return tokenErrorResult(error);
|
|
130
131
|
}
|
|
131
132
|
});
|
|
133
|
+
server.tool('search_icons', 'Search Iconify icons by keyword (same catalog Ompo uses in the editor)', {
|
|
134
|
+
query: z.string().describe('Search terms, e.g. "home", "menu", "arrow right"'),
|
|
135
|
+
limit: z.number().int().min(1).max(64).optional().describe('Max results (default 32)'),
|
|
136
|
+
start: z.number().int().min(0).optional().describe('Pagination offset')
|
|
137
|
+
}, async ({ query, limit, start }) => {
|
|
138
|
+
try {
|
|
139
|
+
const result = await searchIconifyIcons(query, { limit, start });
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: JSON.stringify(result, null, 2)
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return tokenErrorResult(error);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
server.tool('fetch_icon', 'Fetch Iconify SVG markup for an icon id (e.g. lucide:home)', {
|
|
154
|
+
iconId: z.string().describe('Iconify id in prefix:name format, e.g. lucide:home')
|
|
155
|
+
}, async ({ iconId }) => {
|
|
156
|
+
try {
|
|
157
|
+
const iconSvg = await fetchIconifySvg(iconId);
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: JSON.stringify({ iconId, iconSvg }, null, 2)
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return tokenErrorResult(error);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
132
171
|
async function main() {
|
|
133
172
|
if (runCli(process.argv.slice(2)))
|
|
134
173
|
return;
|
package/dist/tokens.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export declare class McpTokenError extends Error {
|
|
|
3
3
|
constructor(code: McpTokenError['code'], message: string);
|
|
4
4
|
}
|
|
5
5
|
export declare function readMcpTokenBalance(): Promise<number | null>;
|
|
6
|
-
export declare function consumeMcpToken(toolName: string): Promise<number>;
|
|
6
|
+
export declare function consumeMcpToken(toolName: string, editId?: string): Promise<number>;
|
package/dist/tokens.js
CHANGED
|
@@ -16,6 +16,9 @@ function mapRpcError(message) {
|
|
|
16
16
|
if (normalized.includes('INSUFFICIENT_TOKENS')) {
|
|
17
17
|
return new McpTokenError('INSUFFICIENT_TOKENS', 'You are out of MCP tokens. Add more tokens to your account before using Ompo MCP tools.');
|
|
18
18
|
}
|
|
19
|
+
if (normalized.includes('COULD NOT CHOOSE THE BEST CANDIDATE FUNCTION')) {
|
|
20
|
+
return new McpTokenError('TOKEN_SERVICE_UNAVAILABLE', 'MCP token service is misconfigured on the server. Update Ompo or contact support.');
|
|
21
|
+
}
|
|
19
22
|
return new McpTokenError('TOKEN_SERVICE_UNAVAILABLE', 'Could not verify MCP token balance. Check your connection and try again.');
|
|
20
23
|
}
|
|
21
24
|
async function createAuthedSupabase(session) {
|
|
@@ -70,14 +73,15 @@ export async function readMcpTokenBalance() {
|
|
|
70
73
|
throw mapRpcError(error.message);
|
|
71
74
|
return data?.tokens_available ?? null;
|
|
72
75
|
}
|
|
73
|
-
export async function consumeMcpToken(toolName) {
|
|
76
|
+
export async function consumeMcpToken(toolName, editId) {
|
|
74
77
|
const session = readOmpoMcpSession();
|
|
75
78
|
if (!session) {
|
|
76
79
|
throw new McpTokenError('NOT_SIGNED_IN', 'Sign in to Ompo and keep the app open so your MCP session stays active.');
|
|
77
80
|
}
|
|
78
81
|
const supabase = await createAuthedSupabase(session);
|
|
79
82
|
const { data, error } = await supabase.rpc('consume_mcp_token', {
|
|
80
|
-
p_tool_name: toolName
|
|
83
|
+
p_tool_name: toolName,
|
|
84
|
+
p_edit_id: editId ?? null
|
|
81
85
|
});
|
|
82
86
|
if (error) {
|
|
83
87
|
throw mapRpcError(error.message);
|
package/dist/types.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ export type DomInsertOperation = {
|
|
|
15
15
|
index: number;
|
|
16
16
|
html: string;
|
|
17
17
|
selector?: string;
|
|
18
|
-
insertKind?: 'text' | 'icon';
|
|
18
|
+
insertKind?: 'text' | 'icon' | 'frame' | 'section';
|
|
19
19
|
iconId?: string;
|
|
20
|
+
iconSvg?: string;
|
|
20
21
|
insertedElement?: ElementAnchor;
|
|
21
22
|
parent?: ElementAnchor;
|
|
22
23
|
};
|
|
@@ -42,6 +43,7 @@ export type DomIconReplaceOperation = {
|
|
|
42
43
|
kind: 'dom.iconReplace';
|
|
43
44
|
selector: string;
|
|
44
45
|
iconId: string;
|
|
46
|
+
iconSvg?: string;
|
|
45
47
|
previousIconId?: string;
|
|
46
48
|
element?: ElementAnchor;
|
|
47
49
|
};
|
|
@@ -51,7 +53,7 @@ export type ElementAnchor = {
|
|
|
51
53
|
id?: string;
|
|
52
54
|
className?: string;
|
|
53
55
|
textSnippet?: string;
|
|
54
|
-
insertKind?: 'text' | 'icon';
|
|
56
|
+
insertKind?: 'text' | 'icon' | 'frame' | 'section';
|
|
55
57
|
iconId?: string;
|
|
56
58
|
};
|
|
57
59
|
export type DomFlexWrapOperation = {
|