@ompo-design/mcp-server 0.1.9 → 0.1.11
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 +182 -43
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
package/dist/apply-plan.js
CHANGED
|
@@ -2,8 +2,154 @@ const OMPO_GLOSSARY = {
|
|
|
2
2
|
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
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\`. 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
4
|
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: copy the source file from \`backgroundImageSource\` into the project (e.g. public/images/), then set \`background-image\` to a project-relative url(), with \`background-size: cover\`, \`background-position: center\`, and \`background-repeat: no-repeat\`. Do not commit file:// URLs to source
|
|
5
|
+
imageFill: `Ompo image background fill: copy the source file from \`backgroundImageSource\` into the project (e.g. public/images/), then set \`background-image\` to a project-relative url(), with \`background-size: cover\`, \`background-position: center\`, and \`background-repeat: no-repeat\`. Do not commit file:// URLs to source.`,
|
|
6
|
+
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
|
+
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.`,
|
|
8
|
+
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
|
+
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.`
|
|
6
10
|
};
|
|
11
|
+
function describeAnchor(anchor) {
|
|
12
|
+
const parts = [anchor.tagName];
|
|
13
|
+
if (anchor.id)
|
|
14
|
+
parts.push(`#${anchor.id}`);
|
|
15
|
+
if (anchor.className) {
|
|
16
|
+
const classes = anchor.className.split(/\s+/).filter(Boolean).slice(0, 3).join('.');
|
|
17
|
+
if (classes)
|
|
18
|
+
parts.push(`.${classes}`);
|
|
19
|
+
}
|
|
20
|
+
if (anchor.insertKind === 'icon' && anchor.iconId) {
|
|
21
|
+
parts.push(`icon:${anchor.iconId}`);
|
|
22
|
+
}
|
|
23
|
+
if (anchor.textSnippet)
|
|
24
|
+
parts.push(`"${anchor.textSnippet}"`);
|
|
25
|
+
return parts.join(' ');
|
|
26
|
+
}
|
|
27
|
+
function buildDomInsertPlan(operation) {
|
|
28
|
+
const inserted = operation.insertedElement;
|
|
29
|
+
const isIcon = operation.insertKind === 'icon';
|
|
30
|
+
const isText = operation.insertKind === 'text';
|
|
31
|
+
const steps = [];
|
|
32
|
+
if (operation.parent) {
|
|
33
|
+
steps.push(`Locate destination parent: ${describeAnchor(operation.parent)}.`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
steps.push(`Locate parent "${operation.parentSelector}" in source.`);
|
|
37
|
+
}
|
|
38
|
+
if (inserted) {
|
|
39
|
+
steps.push(`New element anchor: ${describeAnchor(inserted)} (selector: "${inserted.selector}").`);
|
|
40
|
+
}
|
|
41
|
+
else if (operation.selector) {
|
|
42
|
+
steps.push(`Inserted element selector: "${operation.selector}".`);
|
|
43
|
+
}
|
|
44
|
+
if (isText) {
|
|
45
|
+
steps.push(OMPO_GLOSSARY.insertText);
|
|
46
|
+
}
|
|
47
|
+
else if (isIcon) {
|
|
48
|
+
steps.push(OMPO_GLOSSARY.insertIcon);
|
|
49
|
+
if (operation.iconId) {
|
|
50
|
+
steps.push(`Iconify id: ${operation.iconId}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
steps.push(`Insert at child index ${operation.index}.`);
|
|
54
|
+
steps.push('Sanitize and adapt the HTML to the project’s component conventions before writing.');
|
|
55
|
+
const summary = isIcon
|
|
56
|
+
? `Insert Iconify icon${operation.iconId ? ` (${operation.iconId})` : ''}`
|
|
57
|
+
: isText
|
|
58
|
+
? 'Insert text block'
|
|
59
|
+
: 'Insert new element into the page';
|
|
60
|
+
return {
|
|
61
|
+
kind: 'dom.insert',
|
|
62
|
+
summary,
|
|
63
|
+
steps,
|
|
64
|
+
payload: {
|
|
65
|
+
parentSelector: operation.parentSelector,
|
|
66
|
+
insertIndex: operation.index,
|
|
67
|
+
html: operation.html,
|
|
68
|
+
selector: operation.selector,
|
|
69
|
+
insertKind: operation.insertKind,
|
|
70
|
+
iconId: operation.iconId,
|
|
71
|
+
insertedElement: operation.insertedElement,
|
|
72
|
+
parent: operation.parent
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function formatChildOrder(children, movedSelector) {
|
|
77
|
+
return children.map((child, index) => {
|
|
78
|
+
const marker = child.selector === movedSelector ? ' ← moved element' : '';
|
|
79
|
+
return `${index + 1}. ${describeAnchor(child)}${marker}`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function buildDomMovePlan(operation) {
|
|
83
|
+
const reordered = operation.fromParentSelector === operation.toParentSelector;
|
|
84
|
+
const moved = operation.movedElement;
|
|
85
|
+
const steps = [];
|
|
86
|
+
if (moved) {
|
|
87
|
+
steps.push(`Find the moved element in source: ${describeAnchor(moved)}.`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
steps.push(`Locate element "${operation.selector}" in source using tag, id, class, or visible text.`);
|
|
91
|
+
}
|
|
92
|
+
if (operation.toParent) {
|
|
93
|
+
steps.push(`Destination parent: ${describeAnchor(operation.toParent)}.`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
steps.push(`Destination parent selector: "${operation.toParentSelector}".`);
|
|
97
|
+
}
|
|
98
|
+
if (operation.insertBefore) {
|
|
99
|
+
steps.push(`Place the moved element immediately before: ${describeAnchor(operation.insertBefore)}.`);
|
|
100
|
+
}
|
|
101
|
+
else if (operation.destinationChildren && operation.destinationChildren.length > 0) {
|
|
102
|
+
steps.push('Place the moved element as the last child of the destination parent.');
|
|
103
|
+
}
|
|
104
|
+
if (operation.destinationChildren && operation.destinationChildren.length > 0) {
|
|
105
|
+
steps.push('Match this sibling order in source (structural snapshot after the move):');
|
|
106
|
+
steps.push(...formatChildOrder(operation.destinationChildren, operation.selector));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
steps.push(reordered
|
|
110
|
+
? `Reorder inside the parent from index ${operation.fromIndex} to index ${operation.index}.`
|
|
111
|
+
: `Move from "${operation.fromParentSelector}" (index ${operation.fromIndex}) to "${operation.toParentSelector}" at index ${operation.index}.`);
|
|
112
|
+
}
|
|
113
|
+
if (operation.fromChildrenBefore && operation.fromChildrenBefore.length > 0) {
|
|
114
|
+
steps.push('Previous sibling order before the move:');
|
|
115
|
+
steps.push(...formatChildOrder(operation.fromChildrenBefore, operation.selector));
|
|
116
|
+
}
|
|
117
|
+
steps.push('Update JSX/HTML/component structure — this is a markup or children-order change, not CSS.', 'If children come from a mapped array (React .map, v-for, etc.), reorder that array or its data source.', 'Preserve element content and styling that Ompo did not change.');
|
|
118
|
+
const payload = {
|
|
119
|
+
selector: operation.selector,
|
|
120
|
+
fromParentSelector: operation.fromParentSelector,
|
|
121
|
+
fromIndex: operation.fromIndex,
|
|
122
|
+
toParentSelector: operation.toParentSelector,
|
|
123
|
+
insertIndex: operation.index,
|
|
124
|
+
reordered
|
|
125
|
+
};
|
|
126
|
+
if (moved)
|
|
127
|
+
payload.movedElement = moved;
|
|
128
|
+
if (operation.fromParent)
|
|
129
|
+
payload.fromParent = operation.fromParent;
|
|
130
|
+
if (operation.toParent)
|
|
131
|
+
payload.toParent = operation.toParent;
|
|
132
|
+
if (operation.insertBefore)
|
|
133
|
+
payload.insertBefore = operation.insertBefore;
|
|
134
|
+
if (operation.destinationChildren) {
|
|
135
|
+
payload.destinationChildren = operation.destinationChildren;
|
|
136
|
+
}
|
|
137
|
+
if (operation.fromChildrenBefore) {
|
|
138
|
+
payload.fromChildrenBefore = operation.fromChildrenBefore;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
kind: 'dom.move',
|
|
142
|
+
summary: moved
|
|
143
|
+
? reordered
|
|
144
|
+
? `Reorder ${describeAnchor(moved)} within its parent`
|
|
145
|
+
: `Move ${describeAnchor(moved)} to ${operation.toParent ? describeAnchor(operation.toParent) : operation.toParentSelector}`
|
|
146
|
+
: reordered
|
|
147
|
+
? `Reorder element within parent "${operation.toParentSelector}"`
|
|
148
|
+
: `Move element to parent "${operation.toParentSelector}"`,
|
|
149
|
+
steps,
|
|
150
|
+
payload
|
|
151
|
+
};
|
|
152
|
+
}
|
|
7
153
|
function formatChangedValue(property, value) {
|
|
8
154
|
const text = String(value);
|
|
9
155
|
if (text !== '')
|
|
@@ -133,6 +279,14 @@ function styleSuggestionForProperty(property, value, changed) {
|
|
|
133
279
|
};
|
|
134
280
|
}
|
|
135
281
|
break;
|
|
282
|
+
case 'iconStrokeWidth':
|
|
283
|
+
case 'iconStrokeColor':
|
|
284
|
+
return {
|
|
285
|
+
property,
|
|
286
|
+
value: formatChangedValue(property, value),
|
|
287
|
+
strategy: 'inline-style',
|
|
288
|
+
notes: OMPO_GLOSSARY.iconStroke
|
|
289
|
+
};
|
|
136
290
|
}
|
|
137
291
|
return {
|
|
138
292
|
property,
|
|
@@ -184,47 +338,9 @@ function buildDomStructurePlan(operation) {
|
|
|
184
338
|
}
|
|
185
339
|
};
|
|
186
340
|
case 'dom.insert':
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
steps: [
|
|
191
|
-
`Locate parent "${operation.parentSelector}" in source.`,
|
|
192
|
-
`Insert the provided HTML at child index ${operation.index}.`,
|
|
193
|
-
'Sanitize and adapt the HTML to the project’s component conventions before writing.'
|
|
194
|
-
],
|
|
195
|
-
payload: {
|
|
196
|
-
parentSelector: operation.parentSelector,
|
|
197
|
-
insertIndex: operation.index,
|
|
198
|
-
html: operation.html,
|
|
199
|
-
selector: operation.selector
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
case 'dom.move': {
|
|
203
|
-
const reordered = operation.fromParentSelector === operation.toParentSelector;
|
|
204
|
-
return {
|
|
205
|
-
kind: 'dom.move',
|
|
206
|
-
summary: reordered
|
|
207
|
-
? `Reorder element within parent "${operation.toParentSelector}"`
|
|
208
|
-
: `Move element to a new parent "${operation.toParentSelector}"`,
|
|
209
|
-
steps: [
|
|
210
|
-
`Locate element "${operation.selector}" in source using selector, tag, id, class, or nearby text.`,
|
|
211
|
-
reordered
|
|
212
|
-
? `Reorder it inside "${operation.toParentSelector}" from child index ${operation.fromIndex} to index ${operation.index}.`
|
|
213
|
-
: `Move it from "${operation.fromParentSelector}" (index ${operation.fromIndex}) to "${operation.toParentSelector}" at index ${operation.index}.`,
|
|
214
|
-
'Update JSX/HTML/component structure in source — this is a markup change, not a CSS tweak.',
|
|
215
|
-
'Preserve the element’s content and non-Ompo styling.',
|
|
216
|
-
'If the project uses mapped lists or arrays, update item order in data when that drives rendering.'
|
|
217
|
-
],
|
|
218
|
-
payload: {
|
|
219
|
-
selector: operation.selector,
|
|
220
|
-
fromParentSelector: operation.fromParentSelector,
|
|
221
|
-
fromIndex: operation.fromIndex,
|
|
222
|
-
toParentSelector: operation.toParentSelector,
|
|
223
|
-
insertIndex: operation.index,
|
|
224
|
-
reordered
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
341
|
+
return buildDomInsertPlan(operation);
|
|
342
|
+
case 'dom.move':
|
|
343
|
+
return buildDomMovePlan(operation);
|
|
228
344
|
case 'dom.delete':
|
|
229
345
|
return {
|
|
230
346
|
kind: 'dom.delete',
|
|
@@ -295,6 +411,14 @@ export function buildApplyPlan(bundle) {
|
|
|
295
411
|
if (domPlan)
|
|
296
412
|
domStructurePlans.push(domPlan);
|
|
297
413
|
domChanges.push(operation);
|
|
414
|
+
if (operation.kind === 'dom.move') {
|
|
415
|
+
if (!operation.movedElement && operation.selector.includes('nth-of-type')) {
|
|
416
|
+
warnings.push(`DOM move "${operation.selector}" uses nth-of-type selectors. Re-export from Ompo after rearranging to get richer anchors, or match by visible text and parent structure.`);
|
|
417
|
+
}
|
|
418
|
+
else if (operation.movedElement && !operation.movedElement.id && !operation.movedElement.textSnippet) {
|
|
419
|
+
warnings.push(`DOM move target "${describeAnchor(operation.movedElement)}" has no id or text. Use destinationChildren order and parent context to locate it in source.`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
298
422
|
if (operation.kind === 'dom.flexWrap') {
|
|
299
423
|
for (const child of operation.children) {
|
|
300
424
|
if (!child.id && !child.className && child.selector.includes('nth-of-type')) {
|
|
@@ -318,8 +442,11 @@ export function buildApplyPlan(bundle) {
|
|
|
318
442
|
'When widthMode or heightMode is "fill", apply the grouped flex properties together (flexGrow, flexBasis, alignSelf, removed width/height) — do not set a fixed px width/height on the filled axis.',
|
|
319
443
|
'Gap belongs on the flex/grid parent. Fill belongs on the child inside a flex parent.',
|
|
320
444
|
'DOM moves are in domStructurePlans and domChanges — reorder or reparent elements in source markup/components, not just CSS.',
|
|
445
|
+
'Read ompoGlossary.domMove before applying dom.move operations.',
|
|
446
|
+
'For dom.move: destinationChildren is the structural snapshot of the final sibling order — match it in JSX/HTML or reorder mapped arrays.',
|
|
321
447
|
'For dom.flexWrap: create a new wrapper, move matched children, then apply wrapperStyles.',
|
|
322
|
-
'
|
|
448
|
+
'For dom.insert with insertKind text or icon: read ompoGlossary.insertText / insertIcon; use insertedElement anchors and iconId when applying.',
|
|
449
|
+
'Use child anchors (tag, id, class, textSnippet, insertKind, iconId) to find elements in source when selectors are unstable.',
|
|
323
450
|
'Prefer the smallest possible diff for each file.'
|
|
324
451
|
],
|
|
325
452
|
ompoGlossary: OMPO_GLOSSARY
|
|
@@ -329,6 +456,9 @@ export function explainEdit(bundle) {
|
|
|
329
456
|
const styleCount = bundle.operations.filter((operation) => operation.kind === 'style').length;
|
|
330
457
|
const textCount = bundle.operations.filter((operation) => operation.kind === 'text').length;
|
|
331
458
|
const flexWrapCount = bundle.operations.filter((operation) => operation.kind === 'dom.flexWrap').length;
|
|
459
|
+
const moveCount = bundle.operations.filter((operation) => operation.kind === 'dom.move').length;
|
|
460
|
+
const textInsertCount = bundle.operations.filter((operation) => operation.kind === 'dom.insert' && operation.insertKind === 'text').length;
|
|
461
|
+
const iconInsertCount = bundle.operations.filter((operation) => operation.kind === 'dom.insert' && operation.insertKind === 'icon').length;
|
|
332
462
|
const domCount = bundle.operations.length - styleCount - textCount;
|
|
333
463
|
const scopeLabel = bundle.scope.mode === 'subtree' && bundle.scope.rootLabel
|
|
334
464
|
? `${bundle.scope.rootLabel} and its children`
|
|
@@ -342,6 +472,15 @@ export function explainEdit(bundle) {
|
|
|
342
472
|
if (flexWrapCount > 0) {
|
|
343
473
|
lines.push(`${flexWrapCount} flex-wrap operation${flexWrapCount === 1 ? '' : 's'}`);
|
|
344
474
|
}
|
|
475
|
+
if (moveCount > 0) {
|
|
476
|
+
lines.push(`${moveCount} DOM move${moveCount === 1 ? '' : 's'} — apply via domStructurePlans (markup/children order, not CSS)`);
|
|
477
|
+
}
|
|
478
|
+
if (textInsertCount > 0) {
|
|
479
|
+
lines.push(`${textInsertCount} text insert${textInsertCount === 1 ? '' : 's'} — apply via domStructurePlans (markup at parent/index)`);
|
|
480
|
+
}
|
|
481
|
+
if (iconInsertCount > 0) {
|
|
482
|
+
lines.push(`${iconInsertCount} icon insert${iconInsertCount === 1 ? '' : 's'} — map iconId to project icons when possible`);
|
|
483
|
+
}
|
|
345
484
|
lines.push(`Source preview: ${bundle.source.url}`);
|
|
346
485
|
return lines.join('\n');
|
|
347
486
|
}
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getOmpoEditsStorePath } from './edits-path.js';
|
|
|
7
7
|
import { readOmpoMcpSession } from './session.js';
|
|
8
8
|
import { readMcpTokenBalance } from './tokens.js';
|
|
9
9
|
const PACKAGE_NAME = '@ompo-design/mcp-server';
|
|
10
|
-
const PACKAGE_VERSION = '0.1.
|
|
10
|
+
const PACKAGE_VERSION = '0.1.11';
|
|
11
11
|
const SERVER_NAME = 'ompo';
|
|
12
12
|
function resolveExecutable(name) {
|
|
13
13
|
try {
|
|
@@ -187,7 +187,7 @@ async function runDoctor() {
|
|
|
187
187
|
console.log('Edits: none yet (click Send in Ompo first)');
|
|
188
188
|
}
|
|
189
189
|
console.log('');
|
|
190
|
-
console.log('If tools skip token usage, quit and reopen Claude Code to reload MCP v0.1.
|
|
190
|
+
console.log('If tools skip token usage, quit and reopen Claude Code to reload MCP v0.1.11+');
|
|
191
191
|
console.log('Global install: npx @ompo-design/mcp-server setup-global');
|
|
192
192
|
}
|
|
193
193
|
function printProjectNextSteps(projectRoot) {
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { getOmpoEditsStorePath } from './edits-path.js';
|
|
|
10
10
|
import { McpTokenError } from './tokens.js';
|
|
11
11
|
const server = new McpServer({
|
|
12
12
|
name: 'ompo-mcp-server',
|
|
13
|
-
version: '0.1.
|
|
13
|
+
version: '0.1.11'
|
|
14
14
|
});
|
|
15
15
|
function requireEditsStore() {
|
|
16
16
|
const storePath = getOmpoEditsStorePath();
|
package/dist/types.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export type DomInsertOperation = {
|
|
|
15
15
|
index: number;
|
|
16
16
|
html: string;
|
|
17
17
|
selector?: string;
|
|
18
|
+
insertKind?: 'text' | 'icon';
|
|
19
|
+
iconId?: string;
|
|
20
|
+
insertedElement?: ElementAnchor;
|
|
21
|
+
parent?: ElementAnchor;
|
|
18
22
|
};
|
|
19
23
|
export type DomMoveOperation = {
|
|
20
24
|
kind: 'dom.move';
|
|
@@ -23,6 +27,12 @@ export type DomMoveOperation = {
|
|
|
23
27
|
fromIndex: number;
|
|
24
28
|
toParentSelector: string;
|
|
25
29
|
index: number;
|
|
30
|
+
movedElement?: ElementAnchor;
|
|
31
|
+
fromParent?: ElementAnchor;
|
|
32
|
+
toParent?: ElementAnchor;
|
|
33
|
+
insertBefore?: ElementAnchor;
|
|
34
|
+
destinationChildren?: ElementAnchor[];
|
|
35
|
+
fromChildrenBefore?: ElementAnchor[];
|
|
26
36
|
};
|
|
27
37
|
export type DomDeleteOperation = {
|
|
28
38
|
kind: 'dom.delete';
|
|
@@ -34,6 +44,8 @@ export type ElementAnchor = {
|
|
|
34
44
|
id?: string;
|
|
35
45
|
className?: string;
|
|
36
46
|
textSnippet?: string;
|
|
47
|
+
insertKind?: 'text' | 'icon';
|
|
48
|
+
iconId?: string;
|
|
37
49
|
};
|
|
38
50
|
export type DomFlexWrapOperation = {
|
|
39
51
|
kind: 'dom.flexWrap';
|