@symbo.ls/mcp-server 3.7.4 → 3.7.6
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/package.json +2 -6
- package/src/mcp-handler.js +855 -418
package/src/mcp-handler.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses pre-bundled skills data (generated by build.js) so it works
|
|
5
5
|
* in both Node.js and Cloudflare Worker environments without fs access.
|
|
6
|
+
*
|
|
7
|
+
* Full parity with bin/symbols-mcp.js — 18 tools, all audit checks (Rules 27-33).
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { skills } from './skills-data.js';
|
|
@@ -27,7 +29,7 @@ function listSkillFiles() {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
|
-
// Audit helpers (deterministic rule checking)
|
|
32
|
+
// Audit helpers (deterministic rule checking — Rules 27-33)
|
|
31
33
|
// ---------------------------------------------------------------------------
|
|
32
34
|
|
|
33
35
|
const V2_PATTERNS = [
|
|
@@ -57,8 +59,168 @@ const RULE_CHECKS = [
|
|
|
57
59
|
'Components must be plain objects, not functions that return objects',
|
|
58
60
|
],
|
|
59
61
|
[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
/\bextends\s*:\s*(?!['"])\w+/,
|
|
63
|
+
"FORBIDDEN: extends must be a quoted string name (extends: 'Name'), not a variable reference — register in components/ and use string lookup (Rule 10)",
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
/extends\s*:\s*['"]Flex['"]/,
|
|
67
|
+
"Replace extends: 'Flex' with flow: 'x' or flow: 'y' — do NOT just remove it, the element needs flow to stay flex (Rule 26)",
|
|
68
|
+
],
|
|
69
|
+
[/extends\s*:\s*['"]Box['"]/, "Remove extends: 'Box' — every element is already a Box (Rule 26)"],
|
|
70
|
+
[
|
|
71
|
+
/extends\s*:\s*['"]Text['"]/,
|
|
72
|
+
"Remove extends: 'Text' — any element with text: is already Text (Rule 26)",
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
/\bchildExtends\s*:\s*\{/,
|
|
76
|
+
'FORBIDDEN: childExtends must be a quoted string name, not an inline object — register as a named component (Rule 10)',
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
/(?:padding|margin|gap|width|height|fontSize|borderRadius|minWidth|maxWidth|minHeight|maxHeight|top|left|right|bottom|letterSpacing|lineHeight|borderWidth|outlineWidth)\s*:\s*['"]?\d+(?:\.\d+)?px/,
|
|
80
|
+
'FORBIDDEN: No raw px values — use design system tokens (A, B, C, etc.) instead of hardcoded pixels (Rule 28)',
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
/(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]#[0-9a-fA-F]/,
|
|
84
|
+
'Use design system color tokens (primary, secondary, white, gray.5) instead of hardcoded hex colors (Rule 27)',
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
/(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]rgb/,
|
|
88
|
+
'Use design system color tokens instead of hardcoded rgb/rgba values (Rule 27)',
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
/(?:color|background|backgroundColor|borderColor|fill|stroke)\s*:\s*['"]hsl/,
|
|
92
|
+
'Use design system color tokens instead of hardcoded hsl/hsla values (Rule 27)',
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
/<svg[\s>]/,
|
|
96
|
+
'FORBIDDEN: Use the Icon component for SVG icons — store SVGs in designSystem/icons, never inline (Rule 29)',
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
/tag\s*:\s*['"]svg['"]/,
|
|
100
|
+
"FORBIDDEN: Never use tag: 'svg' — store SVGs in designSystem/icons and use Icon component (Rule 29)",
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
/tag\s*:\s*['"]path['"]/,
|
|
104
|
+
"FORBIDDEN: Never use tag: 'path' — store SVG paths in designSystem/icons and use Icon component (Rule 29)",
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
/extends\s*:\s*['"]Svg['"]/,
|
|
108
|
+
'Use Icon component for icons, not Svg — Svg is only for decorative/structural SVGs (Rule 29)',
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
/\biconName\s*:/,
|
|
112
|
+
"FORBIDDEN: Use icon: not iconName: — the prop is icon: 'name' matching a key in designSystem/icons (Rule 29)",
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
/document\.createElement\b/,
|
|
116
|
+
'FORBIDDEN: No direct DOM manipulation — use DOMQL declarative object syntax instead (Rule 30)',
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
/\.querySelector\b/,
|
|
120
|
+
'FORBIDDEN: No DOM queries — reference elements by key name in the DOMQL object tree (Rule 30)',
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
/\.appendChild\b/,
|
|
124
|
+
'FORBIDDEN: No direct DOM manipulation — nest children as object keys or use children array (Rule 30)',
|
|
125
|
+
],
|
|
126
|
+
[
|
|
127
|
+
/\.removeChild\b/,
|
|
128
|
+
'FORBIDDEN: No direct DOM manipulation — use if: (el, s) => condition to show/hide (Rule 30)',
|
|
129
|
+
],
|
|
130
|
+
[
|
|
131
|
+
/\.insertBefore\b/,
|
|
132
|
+
'FORBIDDEN: No direct DOM manipulation — use children array ordering (Rule 30)',
|
|
133
|
+
],
|
|
134
|
+
[/\.innerHTML\s*=/, 'FORBIDDEN: No direct DOM manipulation — use text: or html: prop (Rule 30)'],
|
|
135
|
+
[
|
|
136
|
+
/\.classList\./,
|
|
137
|
+
"FORBIDDEN: No direct class manipulation — use isX + '.isX' pattern (Rule 19/30)",
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
/\.setAttribute\b/,
|
|
141
|
+
'FORBIDDEN: No direct DOM manipulation — set attributes at root level in DOMQL (Rule 30)',
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
/\.addEventListener\b/,
|
|
145
|
+
'FORBIDDEN: No direct event binding — use onX props: onClick, onInput, etc. (Rule 30)',
|
|
146
|
+
],
|
|
147
|
+
[
|
|
148
|
+
/\.style\.\w+\s*=/,
|
|
149
|
+
'FORBIDDEN: No direct style manipulation — use DOMQL CSS-in-props (Rule 30)',
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
/html\s*:\s*\(?.*\)?\s*=>\s*/,
|
|
153
|
+
'FORBIDDEN: Never use html: as a function returning markup — use DOMQL children, nesting, text:, and if: instead (Rule 31)',
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
/return\s*`<\w+/,
|
|
157
|
+
'FORBIDDEN: Never return HTML template literals — use DOMQL declarative children and nesting (Rule 31)',
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
/style\s*=\s*['"`]/,
|
|
161
|
+
'FORBIDDEN: No inline style= strings in html — use DOMQL CSS-in-props (Rule 31)',
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
/window\.innerWidth/,
|
|
165
|
+
'FORBIDDEN: No window.innerWidth checks — use @mobileL, @tabletS responsive breakpoints (Rule 31)',
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
/\.parentNode\b/,
|
|
169
|
+
'FORBIDDEN: No DOM traversal — use state and reactive props instead of walking the DOM tree (Rule 32)',
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
/\.childNodes\b/,
|
|
173
|
+
'FORBIDDEN: No DOM traversal — use state-driven children with if: props (Rule 32)',
|
|
174
|
+
],
|
|
175
|
+
[/\.textContent\b/, 'FORBIDDEN: No DOM property access — use state and text: prop (Rule 32)'],
|
|
176
|
+
[
|
|
177
|
+
/Array\.from\(\w+\.children\)/,
|
|
178
|
+
'FORBIDDEN: No DOM child iteration — use state arrays with children/childExtends and if: filtering (Rule 32)',
|
|
179
|
+
],
|
|
180
|
+
[
|
|
181
|
+
/\.style\.display\s*=/,
|
|
182
|
+
'FORBIDDEN: No style.display toggling — use show:/hide: to toggle visibility or if: to remove from DOM (Rule 32)',
|
|
183
|
+
],
|
|
184
|
+
[/\.style\.cssText\s*=/, 'FORBIDDEN: No direct cssText — use DOMQL CSS-in-props (Rule 32)'],
|
|
185
|
+
[
|
|
186
|
+
/\.dataset\./,
|
|
187
|
+
'FORBIDDEN: No dataset manipulation — use state and attr: for data-* attributes (Rule 32)',
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
/\.remove\(\)/,
|
|
191
|
+
'FORBIDDEN: No DOM node removal — use if: (el, s) => condition to conditionally render (Rule 32)',
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
/el\.node\.\w+\s*=/,
|
|
195
|
+
'FORBIDDEN: No direct el.node property assignment — use DOMQL props (placeholder:, value:, text:, etc.). Reading el.node is fine (Rule 39), writing is not (Rule 32)',
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
/document\.getElementById\b/,
|
|
199
|
+
"FORBIDDEN: No document.getElementById — use el.lookdown('key') to find DOMQL elements (Rule 40)",
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
/document\.querySelectorAll\b/,
|
|
203
|
+
"FORBIDDEN: No document.querySelectorAll — use el.lookdownAll('key') to find DOMQL elements (Rule 40)",
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
/el\.parent\.state\b/,
|
|
207
|
+
"FORBIDDEN: Never use el.parent.state — with childrenAs: 'state', use s.field directly (Rule 36)",
|
|
208
|
+
],
|
|
209
|
+
[
|
|
210
|
+
/el\.context\.designSystem\b/,
|
|
211
|
+
'FORBIDDEN: Never read designSystem from el.context in props — use token strings directly (Rule 38)',
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
/^const\s+\w+\s*=\s*(?:\(|function)/m,
|
|
215
|
+
"FORBIDDEN: No module-level helper functions — move to functions/ and call via el.call('fnName') (Rule 33)",
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
/^let\s+\w+\s*=/m,
|
|
219
|
+
'FORBIDDEN: No module-level variables — use el.scope for local state, functions/ for helpers (Rule 33)',
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
/^var\s+\w+\s*=/m,
|
|
223
|
+
'FORBIDDEN: No module-level variables — use el.scope for local state, functions/ for helpers (Rule 33)',
|
|
62
224
|
],
|
|
63
225
|
];
|
|
64
226
|
|
|
@@ -76,7 +238,7 @@ function auditCode(code) {
|
|
|
76
238
|
}
|
|
77
239
|
|
|
78
240
|
for (const [pattern, message] of RULE_CHECKS) {
|
|
79
|
-
const regex = new RegExp(pattern.source, pattern.flags);
|
|
241
|
+
const regex = new RegExp(pattern.source, pattern.flags || 'g');
|
|
80
242
|
let match;
|
|
81
243
|
while ((match = regex.exec(code)) !== null) {
|
|
82
244
|
const line = code.slice(0, match.index).split('\n').length;
|
|
@@ -97,6 +259,322 @@ function auditCode(code) {
|
|
|
97
259
|
};
|
|
98
260
|
}
|
|
99
261
|
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// JS-to-JSON conversion (mirrors frank pipeline)
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
const DATA_KEYS = [
|
|
267
|
+
'components',
|
|
268
|
+
'pages',
|
|
269
|
+
'snippets',
|
|
270
|
+
'functions',
|
|
271
|
+
'methods',
|
|
272
|
+
'designSystem',
|
|
273
|
+
'state',
|
|
274
|
+
'dependencies',
|
|
275
|
+
'files',
|
|
276
|
+
'config',
|
|
277
|
+
];
|
|
278
|
+
const CODE_SECTIONS = new Set(['components', 'pages', 'functions', 'methods', 'snippets']);
|
|
279
|
+
|
|
280
|
+
function findObjectEnd(code, start) {
|
|
281
|
+
if (start >= code.length) return -1;
|
|
282
|
+
let i = start;
|
|
283
|
+
while (i < code.length && ' \t\n\r'.includes(code[i])) i++;
|
|
284
|
+
if (i >= code.length || !'{['.includes(code[i])) return -1;
|
|
285
|
+
const opener = code[i];
|
|
286
|
+
const closer = opener === '{' ? '}' : ']';
|
|
287
|
+
let depth = 1;
|
|
288
|
+
i++;
|
|
289
|
+
let inString = null,
|
|
290
|
+
inTemplate = false,
|
|
291
|
+
escaped = false;
|
|
292
|
+
while (i < code.length && depth > 0) {
|
|
293
|
+
const ch = code[i];
|
|
294
|
+
if (escaped) {
|
|
295
|
+
escaped = false;
|
|
296
|
+
i++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (ch === '\\') {
|
|
300
|
+
escaped = true;
|
|
301
|
+
i++;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (inString) {
|
|
305
|
+
if (ch === inString) inString = null;
|
|
306
|
+
i++;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (inTemplate) {
|
|
310
|
+
if (ch === '`') inTemplate = false;
|
|
311
|
+
i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (ch === "'" || ch === '"') inString = ch;
|
|
315
|
+
else if (ch === '`') inTemplate = true;
|
|
316
|
+
else if (ch === opener) depth++;
|
|
317
|
+
else if (ch === closer) depth--;
|
|
318
|
+
i++;
|
|
319
|
+
}
|
|
320
|
+
return depth === 0 ? i : -1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parseJsToJson(sourceCode) {
|
|
324
|
+
const result = {};
|
|
325
|
+
const code = sourceCode.replace(/^\s*import\s+.*$/gm, '');
|
|
326
|
+
const stripped = code.trim();
|
|
327
|
+
if (stripped.startsWith('{')) {
|
|
328
|
+
try {
|
|
329
|
+
return JSON.parse(stripped);
|
|
330
|
+
} catch {}
|
|
331
|
+
}
|
|
332
|
+
const exportRe = /export\s+const\s+(\w+)\s*=\s*/g;
|
|
333
|
+
let m;
|
|
334
|
+
const matches = [];
|
|
335
|
+
while ((m = exportRe.exec(code)) !== null) matches.push(m);
|
|
336
|
+
for (const match of matches) {
|
|
337
|
+
const name = match[1];
|
|
338
|
+
const start = match.index + match[0].length;
|
|
339
|
+
const end = findObjectEnd(code, start);
|
|
340
|
+
if (end === -1) continue;
|
|
341
|
+
result[name] = code.slice(start, end).trim();
|
|
342
|
+
}
|
|
343
|
+
if (!matches.length) {
|
|
344
|
+
const defMatch = /export\s+default\s+/.exec(code);
|
|
345
|
+
if (defMatch) {
|
|
346
|
+
const start = defMatch.index + defMatch[0].length;
|
|
347
|
+
const end = findObjectEnd(code, start);
|
|
348
|
+
if (end !== -1) result['__default__'] = code.slice(start, end).trim();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function normalizeQuotes(s) {
|
|
355
|
+
const result = [];
|
|
356
|
+
let i = 0;
|
|
357
|
+
while (i < s.length) {
|
|
358
|
+
if (s[i] === "'") {
|
|
359
|
+
let j = i + 1;
|
|
360
|
+
while (j < s.length) {
|
|
361
|
+
if (s[j] === '\\' && j + 1 < s.length) {
|
|
362
|
+
j += 2;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (s[j] === "'") break;
|
|
366
|
+
j++;
|
|
367
|
+
}
|
|
368
|
+
const inner = s.slice(i + 1, j).replace(/"/g, '\\"');
|
|
369
|
+
result.push(`"${inner}"`);
|
|
370
|
+
i = j + 1;
|
|
371
|
+
} else if (s[i] === '"') {
|
|
372
|
+
let j = i + 1;
|
|
373
|
+
while (j < s.length) {
|
|
374
|
+
if (s[j] === '\\' && j + 1 < s.length) {
|
|
375
|
+
j += 2;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (s[j] === '"') break;
|
|
379
|
+
j++;
|
|
380
|
+
}
|
|
381
|
+
result.push(s.slice(i, j + 1));
|
|
382
|
+
i = j + 1;
|
|
383
|
+
} else {
|
|
384
|
+
result.push(s[i]);
|
|
385
|
+
i++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return result.join('');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isValuePosition(code, pos) {
|
|
392
|
+
let j = pos - 1;
|
|
393
|
+
while (j >= 0 && ' \t\n\r'.includes(code[j])) j--;
|
|
394
|
+
return j >= 0 && ':=,['.includes(code[j]);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function findFunctionEnd(code, start, isArrow) {
|
|
398
|
+
let i = start;
|
|
399
|
+
if (isArrow) {
|
|
400
|
+
const arrowPos = code.indexOf('=>', i);
|
|
401
|
+
if (arrowPos === -1) return -1;
|
|
402
|
+
i = arrowPos + 2;
|
|
403
|
+
while (i < code.length && ' \t\n\r'.includes(code[i])) i++;
|
|
404
|
+
if (i < code.length && code[i] === '{') return findObjectEnd(code, i);
|
|
405
|
+
let depth = 0;
|
|
406
|
+
while (i < code.length) {
|
|
407
|
+
const ch = code[i];
|
|
408
|
+
if ('({['.includes(ch)) depth++;
|
|
409
|
+
else if (')}]'.includes(ch)) {
|
|
410
|
+
if (depth === 0) return i;
|
|
411
|
+
depth--;
|
|
412
|
+
} else if (ch === ',' && depth === 0) return i;
|
|
413
|
+
i++;
|
|
414
|
+
}
|
|
415
|
+
return i;
|
|
416
|
+
} else {
|
|
417
|
+
const brace = code.indexOf('{', i);
|
|
418
|
+
if (brace === -1) return -1;
|
|
419
|
+
return findObjectEnd(code, brace);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function stringifyFunctionsInJs(code) {
|
|
424
|
+
const result = [];
|
|
425
|
+
let i = 0;
|
|
426
|
+
while (i < code.length) {
|
|
427
|
+
const rest = code.slice(i);
|
|
428
|
+
const arrowMatch = rest.match(/^(\([^)]*\)\s*=>|\w+\s*=>)\s*/);
|
|
429
|
+
const funcMatch = rest.match(/^function\s*\w*\s*\(/);
|
|
430
|
+
if (arrowMatch && isValuePosition(code, i)) {
|
|
431
|
+
const fnEnd = findFunctionEnd(code, i, true);
|
|
432
|
+
if (fnEnd > i) {
|
|
433
|
+
result.push(JSON.stringify(code.slice(i, fnEnd).trim()));
|
|
434
|
+
i = fnEnd;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (funcMatch && isValuePosition(code, i)) {
|
|
439
|
+
const fnEnd = findFunctionEnd(code, i, false);
|
|
440
|
+
if (fnEnd > i) {
|
|
441
|
+
result.push(JSON.stringify(code.slice(i, fnEnd).trim()));
|
|
442
|
+
i = fnEnd;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
result.push(code[i]);
|
|
447
|
+
i++;
|
|
448
|
+
}
|
|
449
|
+
return result.join('');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function jsObjToJson(rawJs) {
|
|
453
|
+
let s = rawJs.trim();
|
|
454
|
+
s = stringifyFunctionsInJs(s);
|
|
455
|
+
s = normalizeQuotes(s);
|
|
456
|
+
s = s.replace(/(?<=[\{,\n])\s*([a-zA-Z_$][\w$]*)\s*:/g, ' "$1":');
|
|
457
|
+
s = s.replace(/(?<=[\{,\n])\s*(@[\w$]+)\s*:/g, ' "$1":');
|
|
458
|
+
s = s.replace(/,\s*([}\]])/g, '$1');
|
|
459
|
+
try {
|
|
460
|
+
return JSON.parse(s);
|
|
461
|
+
} catch {
|
|
462
|
+
return s;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function encodeSchemaCode(codeStr) {
|
|
467
|
+
return codeStr.replace(/\n/g, '/////n').replace(/`/g, '/////tilde');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function buildSchemaItem(section, key, value) {
|
|
471
|
+
const codeStr = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
472
|
+
const item = {
|
|
473
|
+
title: key,
|
|
474
|
+
key,
|
|
475
|
+
type: section,
|
|
476
|
+
code: encodeSchemaCode(`export default ${codeStr}`),
|
|
477
|
+
};
|
|
478
|
+
if (section === 'components' || section === 'pages') {
|
|
479
|
+
Object.assign(item, {
|
|
480
|
+
settings: { gridOptions: {} },
|
|
481
|
+
props: {},
|
|
482
|
+
interactivity: [],
|
|
483
|
+
dataTypes: [],
|
|
484
|
+
error: null,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return item;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function buildChangesAndSchema(data) {
|
|
491
|
+
const changes = [],
|
|
492
|
+
granular = [],
|
|
493
|
+
orders = [];
|
|
494
|
+
for (const [sectionKey, sectionData] of Object.entries(data)) {
|
|
495
|
+
if (!DATA_KEYS.includes(sectionKey)) continue;
|
|
496
|
+
if (typeof sectionData !== 'object' || sectionData === null || Array.isArray(sectionData)) {
|
|
497
|
+
changes.push(['update', [sectionKey], sectionData]);
|
|
498
|
+
granular.push(['update', [sectionKey], sectionData]);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const sectionItemKeys = [];
|
|
502
|
+
for (const [itemKey, itemValue] of Object.entries(sectionData)) {
|
|
503
|
+
const itemPath = [sectionKey, itemKey];
|
|
504
|
+
changes.push(['update', itemPath, itemValue]);
|
|
505
|
+
sectionItemKeys.push(itemKey);
|
|
506
|
+
if (typeof itemValue === 'object' && itemValue !== null && !Array.isArray(itemValue)) {
|
|
507
|
+
const itemKeys = [];
|
|
508
|
+
for (const [propKey, propValue] of Object.entries(itemValue)) {
|
|
509
|
+
granular.push(['update', [...itemPath, propKey], propValue]);
|
|
510
|
+
itemKeys.push(propKey);
|
|
511
|
+
}
|
|
512
|
+
if (itemKeys.length) orders.push({ path: itemPath, keys: itemKeys });
|
|
513
|
+
} else {
|
|
514
|
+
granular.push(['update', itemPath, itemValue]);
|
|
515
|
+
}
|
|
516
|
+
if (CODE_SECTIONS.has(sectionKey)) {
|
|
517
|
+
const schemaItem = buildSchemaItem(sectionKey, itemKey, itemValue);
|
|
518
|
+
const schemaPath = ['schema', sectionKey, itemKey];
|
|
519
|
+
changes.push(['update', schemaPath, schemaItem]);
|
|
520
|
+
granular.push(['delete', [...schemaPath, 'code']]);
|
|
521
|
+
for (const [sk, sv] of Object.entries(schemaItem)) {
|
|
522
|
+
granular.push(['update', [...schemaPath, sk], sv]);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (sectionItemKeys.length) orders.push({ path: [sectionKey], keys: sectionItemKeys });
|
|
527
|
+
}
|
|
528
|
+
return { changes, granular, orders };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
// API helpers
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
|
|
535
|
+
const API_BASE = 'https://api.symbols.app';
|
|
536
|
+
|
|
537
|
+
const AUTH_HELP = `To authenticate, provide one of:
|
|
538
|
+
- **token**: JWT from \`smbls login\` (stored in ~/.smblsrc) or env var SYMBOLS_TOKEN
|
|
539
|
+
- **api_key**: API key (sk_live_...) from your project's integration settings
|
|
540
|
+
|
|
541
|
+
To get a token:
|
|
542
|
+
1. Run \`smbls login\` in your terminal, or
|
|
543
|
+
2. Use the \`login\` tool with your email and password`;
|
|
544
|
+
|
|
545
|
+
function authHeader(token, apiKey) {
|
|
546
|
+
if (apiKey) return { 'Authorization': `ApiKey ${apiKey}`, 'Content-Type': 'application/json' };
|
|
547
|
+
if (token) return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function apiRequest(method, path, { token, api_key, body } = {}) {
|
|
552
|
+
const headers = authHeader(token, api_key);
|
|
553
|
+
if (!headers) return { success: false, error: 'No credentials provided', message: AUTH_HELP };
|
|
554
|
+
const opts = { method, headers };
|
|
555
|
+
if (body) opts.body = JSON.stringify(body);
|
|
556
|
+
const resp = await fetch(`${API_BASE}${path}`, opts);
|
|
557
|
+
try {
|
|
558
|
+
return await resp.json();
|
|
559
|
+
} catch {
|
|
560
|
+
return { success: false, error: `HTTP ${resp.status}`, message: await resp.text() };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function resolveProjectId(project, token, apiKey) {
|
|
565
|
+
if (!project) return { id: '', error: 'Project identifier is required.' };
|
|
566
|
+
const isKey = project.startsWith('pr_') || !/^[0-9a-f]+$/.test(project);
|
|
567
|
+
if (isKey) {
|
|
568
|
+
const result = await apiRequest('GET', `/core/projects/key/${project}`, {
|
|
569
|
+
token,
|
|
570
|
+
api_key: apiKey,
|
|
571
|
+
});
|
|
572
|
+
if (result.success) return { id: result.data?._id || '', error: null };
|
|
573
|
+
return { id: '', error: `Project '${project}' not found: ${result.error || 'unknown error'}` };
|
|
574
|
+
}
|
|
575
|
+
return { id: project, error: null };
|
|
576
|
+
}
|
|
577
|
+
|
|
100
578
|
// ---------------------------------------------------------------------------
|
|
101
579
|
// Tools
|
|
102
580
|
// ---------------------------------------------------------------------------
|
|
@@ -110,34 +588,27 @@ const TOOLS = [
|
|
|
110
588
|
},
|
|
111
589
|
{
|
|
112
590
|
name: 'search_symbols_docs',
|
|
113
|
-
description:
|
|
591
|
+
description:
|
|
592
|
+
'Search the Symbols documentation knowledge base for relevant information including CLI commands, SDK services, syntax, components, and more.',
|
|
114
593
|
inputSchema: {
|
|
115
594
|
type: 'object',
|
|
116
595
|
properties: {
|
|
117
596
|
query: {
|
|
118
597
|
type: 'string',
|
|
119
|
-
description: 'Natural language search query about Symbols/DOMQL',
|
|
120
|
-
},
|
|
121
|
-
max_results: {
|
|
122
|
-
type: 'number',
|
|
123
|
-
description: 'Maximum number of results (1-5)',
|
|
124
|
-
default: 3,
|
|
598
|
+
description: 'Natural language search query about Symbols/DOMQL/CLI/SDK',
|
|
125
599
|
},
|
|
600
|
+
max_results: { type: 'number', description: 'Maximum number of results (1-5)', default: 3 },
|
|
126
601
|
},
|
|
127
602
|
required: ['query'],
|
|
128
603
|
},
|
|
129
604
|
},
|
|
130
605
|
{
|
|
131
606
|
name: 'generate_component',
|
|
132
|
-
description:
|
|
133
|
-
'Generate a Symbols.app DOMQL v3 component from a description. Returns rules, syntax, component catalog, cookbook examples, and default library reference as context.',
|
|
607
|
+
description: 'Generate a Symbols.app DOMQL v3 component from a description with full context.',
|
|
134
608
|
inputSchema: {
|
|
135
609
|
type: 'object',
|
|
136
610
|
properties: {
|
|
137
|
-
description: {
|
|
138
|
-
type: 'string',
|
|
139
|
-
description: 'What the component should do and look like',
|
|
140
|
-
},
|
|
611
|
+
description: { type: 'string', description: 'What the component should do and look like' },
|
|
141
612
|
component_name: {
|
|
142
613
|
type: 'string',
|
|
143
614
|
description: 'PascalCase name for the component',
|
|
@@ -149,49 +620,54 @@ const TOOLS = [
|
|
|
149
620
|
},
|
|
150
621
|
{
|
|
151
622
|
name: 'generate_page',
|
|
152
|
-
description:
|
|
153
|
-
'Generate a Symbols.app page with routing integration. Returns rules, project structure, patterns, snippets, and default library reference as context.',
|
|
623
|
+
description: 'Generate a Symbols.app page with routing integration and full context.',
|
|
154
624
|
inputSchema: {
|
|
155
625
|
type: 'object',
|
|
156
626
|
properties: {
|
|
157
|
-
description: {
|
|
158
|
-
|
|
159
|
-
description: 'What the page should contain and do',
|
|
160
|
-
},
|
|
161
|
-
page_name: {
|
|
162
|
-
type: 'string',
|
|
163
|
-
description: 'camelCase name for the page',
|
|
164
|
-
default: 'home',
|
|
165
|
-
},
|
|
627
|
+
description: { type: 'string', description: 'What the page should contain and do' },
|
|
628
|
+
page_name: { type: 'string', description: 'camelCase name for the page', default: 'home' },
|
|
166
629
|
},
|
|
167
630
|
required: ['description'],
|
|
168
631
|
},
|
|
169
632
|
},
|
|
170
633
|
{
|
|
171
634
|
name: 'convert_react',
|
|
172
|
-
description:
|
|
173
|
-
'Convert React/JSX code to Symbols.app DOMQL v3. Returns migration rules, syntax reference, and examples as context.',
|
|
635
|
+
description: 'Convert React/JSX code to Symbols.app DOMQL v3 with migration context.',
|
|
174
636
|
inputSchema: {
|
|
175
637
|
type: 'object',
|
|
176
638
|
properties: {
|
|
177
|
-
source_code: {
|
|
178
|
-
type: 'string',
|
|
179
|
-
description: 'The React/JSX source code to convert',
|
|
180
|
-
},
|
|
639
|
+
source_code: { type: 'string', description: 'The React/JSX source code to convert' },
|
|
181
640
|
},
|
|
182
641
|
required: ['source_code'],
|
|
183
642
|
},
|
|
184
643
|
},
|
|
185
644
|
{
|
|
186
645
|
name: 'convert_html',
|
|
646
|
+
description: 'Convert raw HTML/CSS to Symbols.app DOMQL v3 components with full context.',
|
|
647
|
+
inputSchema: {
|
|
648
|
+
type: 'object',
|
|
649
|
+
properties: {
|
|
650
|
+
source_code: { type: 'string', description: 'The HTML/CSS source code to convert' },
|
|
651
|
+
},
|
|
652
|
+
required: ['source_code'],
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: 'convert_to_json',
|
|
187
657
|
description:
|
|
188
|
-
'Convert
|
|
658
|
+
'Convert DOMQL v3 JavaScript source code to platform JSON format for save_to_project.',
|
|
189
659
|
inputSchema: {
|
|
190
660
|
type: 'object',
|
|
191
661
|
properties: {
|
|
192
662
|
source_code: {
|
|
193
663
|
type: 'string',
|
|
194
|
-
description: '
|
|
664
|
+
description: 'JavaScript source code with export const/default statements',
|
|
665
|
+
},
|
|
666
|
+
section: {
|
|
667
|
+
type: 'string',
|
|
668
|
+
description:
|
|
669
|
+
'Target section: components, pages, functions, snippets, designSystem, state',
|
|
670
|
+
default: 'components',
|
|
195
671
|
},
|
|
196
672
|
},
|
|
197
673
|
required: ['source_code'],
|
|
@@ -200,202 +676,149 @@ const TOOLS = [
|
|
|
200
676
|
{
|
|
201
677
|
name: 'audit_component',
|
|
202
678
|
description:
|
|
203
|
-
'Audit a Symbols/DOMQL component for v3 compliance
|
|
679
|
+
'Audit a Symbols/DOMQL component for v3 compliance — checks for v2 syntax, raw px values, hardcoded colors, direct DOM manipulation, and more.',
|
|
204
680
|
inputSchema: {
|
|
205
681
|
type: 'object',
|
|
206
682
|
properties: {
|
|
207
|
-
component_code: {
|
|
208
|
-
type: 'string',
|
|
209
|
-
description: 'The JavaScript component code to audit',
|
|
210
|
-
},
|
|
683
|
+
component_code: { type: 'string', description: 'The JavaScript component code to audit' },
|
|
211
684
|
},
|
|
212
685
|
required: ['component_code'],
|
|
213
686
|
},
|
|
214
687
|
},
|
|
215
688
|
{
|
|
216
|
-
name: '
|
|
689
|
+
name: 'detect_environment',
|
|
217
690
|
description:
|
|
218
|
-
'
|
|
691
|
+
'Detect project type (local, CDN, JSON runtime, or remote server) based on project indicators.',
|
|
219
692
|
inputSchema: {
|
|
220
693
|
type: 'object',
|
|
221
694
|
properties: {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
},
|
|
695
|
+
has_symbols_json: { type: 'boolean' },
|
|
696
|
+
has_symbols_dir: { type: 'boolean' },
|
|
697
|
+
has_package_json: { type: 'boolean' },
|
|
698
|
+
has_cdn_import: { type: 'boolean' },
|
|
699
|
+
has_iife_script: { type: 'boolean' },
|
|
700
|
+
has_json_data: { type: 'boolean' },
|
|
701
|
+
has_mermaid_config: { type: 'boolean' },
|
|
702
|
+
file_list: { type: 'string', default: '' },
|
|
230
703
|
},
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: 'get_cli_reference',
|
|
708
|
+
description: 'Returns the complete Symbols CLI (@symbo.ls/cli) command reference.',
|
|
709
|
+
inputSchema: { type: 'object', properties: {} },
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: 'get_sdk_reference',
|
|
713
|
+
description: 'Returns the complete Symbols SDK (@symbo.ls/sdk) API reference.',
|
|
714
|
+
inputSchema: { type: 'object', properties: {} },
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
name: 'login',
|
|
718
|
+
description: 'Log in to the Symbols platform and get an access token.',
|
|
719
|
+
inputSchema: {
|
|
720
|
+
type: 'object',
|
|
721
|
+
properties: { email: { type: 'string' }, password: { type: 'string' } },
|
|
231
722
|
required: ['email', 'password'],
|
|
232
723
|
},
|
|
233
724
|
},
|
|
234
725
|
{
|
|
235
|
-
name: '
|
|
236
|
-
description:
|
|
237
|
-
|
|
726
|
+
name: 'list_projects',
|
|
727
|
+
description: "List the user's Symbols projects (names, keys, IDs).",
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: 'object',
|
|
730
|
+
properties: { token: { type: 'string' }, api_key: { type: 'string' } },
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: 'create_project',
|
|
735
|
+
description: 'Create a new Symbols project on the platform.',
|
|
238
736
|
inputSchema: {
|
|
239
737
|
type: 'object',
|
|
240
738
|
properties: {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
},
|
|
259
|
-
branch: {
|
|
260
|
-
type: 'string',
|
|
261
|
-
description: 'Branch to publish from',
|
|
262
|
-
default: 'main',
|
|
263
|
-
},
|
|
739
|
+
name: { type: 'string' },
|
|
740
|
+
key: { type: 'string' },
|
|
741
|
+
token: { type: 'string' },
|
|
742
|
+
api_key: { type: 'string' },
|
|
743
|
+
visibility: { type: 'string', default: 'private' },
|
|
744
|
+
},
|
|
745
|
+
required: ['name'],
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: 'get_project',
|
|
750
|
+
description: "Get a project's current data (components, pages, design system, state).",
|
|
751
|
+
inputSchema: {
|
|
752
|
+
type: 'object',
|
|
753
|
+
properties: {
|
|
754
|
+
project: { type: 'string' },
|
|
755
|
+
token: { type: 'string' },
|
|
756
|
+
api_key: { type: 'string' },
|
|
757
|
+
branch: { type: 'string', default: 'main' },
|
|
264
758
|
},
|
|
265
759
|
required: ['project'],
|
|
266
760
|
},
|
|
267
761
|
},
|
|
268
762
|
{
|
|
269
|
-
name: '
|
|
763
|
+
name: 'save_to_project',
|
|
270
764
|
description:
|
|
271
|
-
'
|
|
765
|
+
'Save components/pages/data to a project — creates a new version with change tuples, granular changes, orders, and auto-generated schema entries.',
|
|
272
766
|
inputSchema: {
|
|
273
767
|
type: 'object',
|
|
274
768
|
properties: {
|
|
275
|
-
project: {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
'Deploy mode — "latest" (newest from branch), "published" (current published version), "version" (specific version), or "branch" (track a branch)',
|
|
297
|
-
default: 'published',
|
|
298
|
-
},
|
|
299
|
-
version: {
|
|
300
|
-
type: 'string',
|
|
301
|
-
description: 'Required when mode is "version" — the version string or ID to deploy',
|
|
302
|
-
default: '',
|
|
303
|
-
},
|
|
304
|
-
branch: {
|
|
305
|
-
type: 'string',
|
|
306
|
-
description: 'Branch to deploy from when mode is "latest" or "branch"',
|
|
307
|
-
default: 'main',
|
|
308
|
-
},
|
|
769
|
+
project: { type: 'string' },
|
|
770
|
+
changes: { type: 'string', description: 'JSON string with project data' },
|
|
771
|
+
token: { type: 'string' },
|
|
772
|
+
api_key: { type: 'string' },
|
|
773
|
+
message: { type: 'string' },
|
|
774
|
+
branch: { type: 'string', default: 'main' },
|
|
775
|
+
},
|
|
776
|
+
required: ['project', 'changes'],
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: 'publish',
|
|
781
|
+
description: 'Publish a version of a Symbols project (make it live).',
|
|
782
|
+
inputSchema: {
|
|
783
|
+
type: 'object',
|
|
784
|
+
properties: {
|
|
785
|
+
project: { type: 'string' },
|
|
786
|
+
token: { type: 'string' },
|
|
787
|
+
api_key: { type: 'string' },
|
|
788
|
+
version: { type: 'string' },
|
|
789
|
+
branch: { type: 'string', default: 'main' },
|
|
309
790
|
},
|
|
310
791
|
required: ['project'],
|
|
311
792
|
},
|
|
312
793
|
},
|
|
313
794
|
{
|
|
314
|
-
name: '
|
|
315
|
-
description:
|
|
316
|
-
'Detect what type of Symbols environment the user is working in (local project, CDN, JSON runtime, or remote server) based on project indicators, and return the appropriate setup guide and code format.',
|
|
795
|
+
name: 'push',
|
|
796
|
+
description: 'Deploy a Symbols project to an environment (production, staging, dev).',
|
|
317
797
|
inputSchema: {
|
|
318
798
|
type: 'object',
|
|
319
799
|
properties: {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
},
|
|
328
|
-
has_package_json: {
|
|
329
|
-
type: 'boolean',
|
|
330
|
-
description: 'Whether package.json exists with smbls dependency',
|
|
331
|
-
},
|
|
332
|
-
has_cdn_import: {
|
|
333
|
-
type: 'boolean',
|
|
334
|
-
description:
|
|
335
|
-
'Whether HTML files contain CDN imports (esm.sh/smbls, cdn.jsdelivr.net/npm/smbls, etc.)',
|
|
336
|
-
},
|
|
337
|
-
has_iife_script: {
|
|
338
|
-
type: 'boolean',
|
|
339
|
-
description: 'Whether HTML files use <script src="...smbls"> (IIFE global)',
|
|
340
|
-
},
|
|
341
|
-
has_json_data: {
|
|
342
|
-
type: 'boolean',
|
|
343
|
-
description: 'Whether the project uses frank-generated JSON data files',
|
|
344
|
-
},
|
|
345
|
-
has_mermaid_config: {
|
|
346
|
-
type: 'boolean',
|
|
347
|
-
description:
|
|
348
|
-
'Whether mermaid/wrangler config or GATEWAY_URL/JSON_PATH env vars are present',
|
|
349
|
-
},
|
|
350
|
-
file_list: {
|
|
351
|
-
type: 'string',
|
|
352
|
-
description:
|
|
353
|
-
'Comma-separated list of key files in the project root (e.g. "index.html,package.json,symbols.json")',
|
|
354
|
-
},
|
|
800
|
+
project: { type: 'string' },
|
|
801
|
+
token: { type: 'string' },
|
|
802
|
+
api_key: { type: 'string' },
|
|
803
|
+
environment: { type: 'string', default: 'production' },
|
|
804
|
+
mode: { type: 'string', default: 'published' },
|
|
805
|
+
version: { type: 'string' },
|
|
806
|
+
branch: { type: 'string', default: 'main' },
|
|
355
807
|
},
|
|
808
|
+
required: ['project'],
|
|
356
809
|
},
|
|
357
810
|
},
|
|
358
811
|
];
|
|
359
812
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
-
|
|
364
|
-
- **api_key**: API key (sk_live_...) from your project's integration settings
|
|
365
|
-
|
|
366
|
-
To get a token:
|
|
367
|
-
1. Run \`smbls login\` in your terminal, or
|
|
368
|
-
2. Use the \`login\` tool with your email and password
|
|
369
|
-
|
|
370
|
-
Your project can be identified by either:
|
|
371
|
-
- **project**: MongoDB ObjectId (from project settings or symbols.json)
|
|
372
|
-
- **project**: Project key (pr_xxxx, found in symbols.json or project URL)`;
|
|
373
|
-
|
|
374
|
-
function authHeader(token, apiKey) {
|
|
375
|
-
if (apiKey) return { 'Authorization': `ApiKey ${apiKey}`, 'Content-Type': 'application/json' };
|
|
376
|
-
if (token) return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
async function apiRequest(method, path, { token, api_key, body } = {}) {
|
|
381
|
-
const headers = authHeader(token, api_key);
|
|
382
|
-
if (!headers) return { success: false, error: 'No credentials provided', message: AUTH_HELP };
|
|
383
|
-
const resp = await fetch(`${API_BASE}${path}`, {
|
|
384
|
-
method,
|
|
385
|
-
headers,
|
|
386
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
387
|
-
});
|
|
388
|
-
try {
|
|
389
|
-
return await resp.json();
|
|
390
|
-
} catch {
|
|
391
|
-
return { success: false, error: `HTTP ${resp.status}`, message: await resp.text() };
|
|
392
|
-
}
|
|
393
|
-
}
|
|
813
|
+
const MANDATORY_REQUIREMENTS = `- **MANDATORY: ALL values MUST use design system tokens** — spacing (A, B, C, D), colors (primary, surface, white, gray.5), typography (fontSize: 'B'). ZERO px values, ZERO hex colors, ZERO rgb/hsl.
|
|
814
|
+
- Use Icon component for SVGs — store icons in designSystem/icons
|
|
815
|
+
- NO direct DOM manipulation — all structure via DOMQL declarative syntax
|
|
816
|
+
- NO variables outside component scope — use el.call('fn') or el.scope`;
|
|
394
817
|
|
|
395
818
|
async function callTool(name, args = {}) {
|
|
396
|
-
if (name === 'get_project_rules')
|
|
397
|
-
|
|
398
|
-
|
|
819
|
+
if (name === 'get_project_rules') return readSkill('RULES.md');
|
|
820
|
+
if (name === 'get_cli_reference') return readSkill('CLI.md');
|
|
821
|
+
if (name === 'get_sdk_reference') return readSkill('SDK.md');
|
|
399
822
|
|
|
400
823
|
if (name === 'search_symbols_docs') {
|
|
401
824
|
const query = args.query || '';
|
|
@@ -405,13 +828,10 @@ async function callTool(name, args = {}) {
|
|
|
405
828
|
.split(/\s+/)
|
|
406
829
|
.filter((w) => w.length > 2);
|
|
407
830
|
if (!keywords.length) keywords.push(query.toLowerCase());
|
|
408
|
-
|
|
409
831
|
const results = [];
|
|
410
832
|
for (const fname of listSkillFiles()) {
|
|
411
833
|
const content = readSkill(fname);
|
|
412
|
-
|
|
413
|
-
if (!keywords.some((kw) => contentLower.includes(kw))) continue;
|
|
414
|
-
|
|
834
|
+
if (!keywords.some((kw) => content.toLowerCase().includes(kw))) continue;
|
|
415
835
|
const lines = content.split('\n');
|
|
416
836
|
for (let i = 0; i < lines.length; i++) {
|
|
417
837
|
if (keywords.some((kw) => lines[i].toLowerCase().includes(kw))) {
|
|
@@ -424,41 +844,37 @@ async function callTool(name, args = {}) {
|
|
|
424
844
|
}
|
|
425
845
|
if (results.length >= maxResults) break;
|
|
426
846
|
}
|
|
427
|
-
|
|
428
|
-
return results.length
|
|
429
|
-
? JSON.stringify(results, null, 2)
|
|
430
|
-
: `No results found for '${query}'. Try a different search term.`;
|
|
847
|
+
return results.length ? JSON.stringify(results, null, 2) : `No results found for '${query}'.`;
|
|
431
848
|
}
|
|
432
849
|
|
|
433
850
|
if (name === 'generate_component') {
|
|
434
|
-
const desc = args.description || '';
|
|
435
851
|
const compName = args.component_name || 'MyComponent';
|
|
436
852
|
const context = readSkills(
|
|
437
853
|
'RULES.md',
|
|
854
|
+
'COMMON_MISTAKES.md',
|
|
438
855
|
'COMPONENTS.md',
|
|
439
856
|
'SYNTAX.md',
|
|
440
857
|
'COOKBOOK.md',
|
|
441
858
|
'DEFAULT_LIBRARY.md',
|
|
442
859
|
);
|
|
443
|
-
return `# Generate Component: ${compName}\n\n## Description\n${
|
|
860
|
+
return `# Generate Component: ${compName}\n\n## Description\n${args.description}\n\n## Requirements\n- Named export: \`export const ${compName} = { ... }\`\n- DOMQL v3 syntax only (extends, childExtends, flattened props, onX events)\n${MANDATORY_REQUIREMENTS}\n- NO imports between files — PascalCase keys auto-extend registered components\n- Include responsive breakpoints where appropriate (@tabletS, @mobileL)\n- Use the default library components (Button, Avatar, Icon, Field, etc.) via extends\n- Follow modern UI/UX: visual hierarchy, confident typography, minimal cognitive load\n\n## Context — Rules, Syntax & Examples\n\n${context}`;
|
|
444
861
|
}
|
|
445
862
|
|
|
446
863
|
if (name === 'generate_page') {
|
|
447
|
-
const desc = args.description || '';
|
|
448
864
|
const pageName = args.page_name || 'home';
|
|
449
865
|
const context = readSkills(
|
|
450
866
|
'RULES.md',
|
|
867
|
+
'COMMON_MISTAKES.md',
|
|
451
868
|
'PROJECT_STRUCTURE.md',
|
|
452
869
|
'PATTERNS.md',
|
|
453
870
|
'SNIPPETS.md',
|
|
454
871
|
'DEFAULT_LIBRARY.md',
|
|
455
872
|
'COMPONENTS.md',
|
|
456
873
|
);
|
|
457
|
-
return `# Generate Page: ${pageName}\n\n## Description\n${
|
|
874
|
+
return `# Generate Page: ${pageName}\n\n## Description\n${args.description}\n\n## Requirements\n- Export as: \`export const ${pageName} = { ... }\`\n- Page is a plain object composing components\n- Add to pages/index.js route map: \`'/${pageName}': ${pageName}\`\n- Use components by PascalCase key (Header, Footer, Hero, etc.)\n${MANDATORY_REQUIREMENTS}\n- Include responsive layout adjustments\n\n## Context — Rules, Structure, Patterns & Snippets\n\n${context}`;
|
|
458
875
|
}
|
|
459
876
|
|
|
460
877
|
if (name === 'convert_react') {
|
|
461
|
-
const source = args.source_code || '';
|
|
462
878
|
const context = readSkills(
|
|
463
879
|
'RULES.md',
|
|
464
880
|
'MIGRATION.md',
|
|
@@ -466,11 +882,10 @@ async function callTool(name, args = {}) {
|
|
|
466
882
|
'COMPONENTS.md',
|
|
467
883
|
'LEARNINGS.md',
|
|
468
884
|
);
|
|
469
|
-
return `# Convert React → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`jsx\n${
|
|
885
|
+
return `# Convert React → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`jsx\n${args.source_code}\n\`\`\`\n\n## Conversion Rules\n- Function/class components → plain object exports\n- JSX → nested object children (PascalCase keys auto-extend)\n- import/export between files → REMOVE (reference by key name)\n- useState → state: { key: val } + s.update({ key: newVal })\n- useEffect → onRender (mount), onStateUpdate (deps)\n- props → flattened directly on component (no props wrapper)\n- onClick={handler} → onClick: (event, el, state) => {}\n- className → use design tokens and theme directly\n- map() → children: (el, s) => s.items, childExtends, childProps\n- conditional rendering → if: (el, s) => boolean\n- CSS modules/styled → CSS-in-props with design tokens\n- React.Fragment → not needed, just nest children\n${MANDATORY_REQUIREMENTS}\n\n## Context — Migration Guide, Syntax & Rules\n\n${context}`;
|
|
470
886
|
}
|
|
471
887
|
|
|
472
888
|
if (name === 'convert_html') {
|
|
473
|
-
const source = args.source_code || '';
|
|
474
889
|
const context = readSkills(
|
|
475
890
|
'RULES.md',
|
|
476
891
|
'SYNTAX.md',
|
|
@@ -479,43 +894,97 @@ async function callTool(name, args = {}) {
|
|
|
479
894
|
'SNIPPETS.md',
|
|
480
895
|
'LEARNINGS.md',
|
|
481
896
|
);
|
|
482
|
-
return `# Convert HTML → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`html\n${
|
|
897
|
+
return `# Convert HTML → Symbols DOMQL v3\n\n## Source Code to Convert\n\`\`\`html\n${args.source_code}\n\`\`\`\n\n## Conversion Rules\n- <div> → Box, Flex, or Grid (based on layout purpose)\n- <span>, <p>, <h1>-<h6> → Text, P, H with tag property\n- <a> → Link (has built-in SPA router)\n- <button> → Button (has icon/text support)\n- <input> → Input, Radio, Checkbox (based on type)\n- <img> → Img\n- <form> → Form (extends Box with tag: 'form')\n- <ul>/<ol> + <li> → children array with childExtends\n- CSS classes → flatten as CSS-in-props on the component\n- CSS px values → design tokens (16px → 'A', 26px → 'B', 42px → 'C')\n- CSS colors → theme color tokens\n- media queries → @tabletS, @mobileL, @screenS breakpoints\n${MANDATORY_REQUIREMENTS}\n\n## Context — Syntax, Components & Design System\n\n${context}`;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (name === 'convert_to_json') {
|
|
901
|
+
const section = args.section || 'components';
|
|
902
|
+
const parsed = parseJsToJson(args.source_code);
|
|
903
|
+
if (!Object.keys(parsed).length) return 'Could not parse any exports from the source code.';
|
|
904
|
+
const result = {};
|
|
905
|
+
for (const [exportName, rawValue] of Object.entries(parsed)) {
|
|
906
|
+
const converted = typeof rawValue === 'string' ? jsObjToJson(rawValue) : rawValue;
|
|
907
|
+
if (exportName === '__default__') {
|
|
908
|
+
if (['designSystem', 'state', 'dependencies', 'config'].includes(section))
|
|
909
|
+
result[section] = converted;
|
|
910
|
+
else {
|
|
911
|
+
if (!result[section]) result[section] = {};
|
|
912
|
+
result[section]['default'] = converted;
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
if (!result[section]) result[section] = {};
|
|
916
|
+
result[section][exportName] = converted;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const output = JSON.stringify(result, null, 2);
|
|
920
|
+
const sections = Object.keys(result);
|
|
921
|
+
const items = [];
|
|
922
|
+
for (const sec of sections)
|
|
923
|
+
if (typeof result[sec] === 'object' && result[sec]) items.push(...Object.keys(result[sec]));
|
|
924
|
+
return `# Converted to Platform JSON\n\n**Section:** ${sections.join(', ')}\n**Items:** ${items.join(', ') || 'default export'}\n\n\`\`\`json\n${output}\n\`\`\`\n\nThis JSON is ready to use with \`save_to_project\`.`;
|
|
483
925
|
}
|
|
484
926
|
|
|
485
927
|
if (name === 'audit_component') {
|
|
486
|
-
const
|
|
487
|
-
const result = auditCode(code);
|
|
928
|
+
const result = auditCode(args.component_code || '');
|
|
488
929
|
const rulesContext = readSkill('AUDIT.md');
|
|
489
|
-
|
|
490
930
|
let output = `# Audit Report\n\n## Summary\n${result.summary}\nPassed: ${result.passed ? 'Yes' : 'No'}\n\n## Violations (Errors)\n`;
|
|
491
931
|
if (result.violations.length) {
|
|
492
|
-
for (const v of result.violations) {
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
} else {
|
|
496
|
-
output += 'No violations found.\n';
|
|
497
|
-
}
|
|
498
|
-
|
|
932
|
+
for (const v of result.violations) output += `- **Line ${v.line}**: ${v.message}\n`;
|
|
933
|
+
} else output += 'No violations found.\n';
|
|
499
934
|
output += '\n## Warnings\n';
|
|
500
935
|
if (result.warnings.length) {
|
|
501
|
-
for (const w of result.warnings) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
936
|
+
for (const w of result.warnings) output += `- **Line ${w.line}**: ${w.message}\n`;
|
|
937
|
+
} else output += 'No warnings.\n';
|
|
938
|
+
if (result.violations.length) {
|
|
939
|
+
output +=
|
|
940
|
+
'\n## MANDATORY ACTION\n\n**Every violation above MUST be fixed. There is NO concept of "known debt" or "accepted violations" in Symbols. Do NOT label any violation as "known debt" or defer it. Rewrite the code using proper DOMQL syntax.**\n\n';
|
|
506
941
|
}
|
|
507
|
-
|
|
508
942
|
output += `\n## Detailed Rules Reference\n\n${rulesContext}`;
|
|
509
943
|
return output;
|
|
510
944
|
}
|
|
511
945
|
|
|
946
|
+
if (name === 'detect_environment') {
|
|
947
|
+
let envType = 'unknown',
|
|
948
|
+
confidence = 'low';
|
|
949
|
+
if (args.has_mermaid_config) {
|
|
950
|
+
envType = 'remote_server';
|
|
951
|
+
confidence = 'high';
|
|
952
|
+
} else if (args.has_json_data) {
|
|
953
|
+
envType = 'json_runtime';
|
|
954
|
+
confidence = 'high';
|
|
955
|
+
} else if (args.has_symbols_json && args.has_symbols_dir) {
|
|
956
|
+
envType = 'local_project';
|
|
957
|
+
confidence = 'high';
|
|
958
|
+
} else if (args.has_symbols_dir || (args.has_package_json && args.has_symbols_json)) {
|
|
959
|
+
envType = 'local_project';
|
|
960
|
+
confidence = 'medium';
|
|
961
|
+
} else if (args.has_cdn_import || args.has_iife_script) {
|
|
962
|
+
envType = 'cdn';
|
|
963
|
+
confidence = 'high';
|
|
964
|
+
} else if (args.has_package_json) {
|
|
965
|
+
envType = 'local_project';
|
|
966
|
+
confidence = 'low';
|
|
967
|
+
} else if (args.file_list) {
|
|
968
|
+
const files = args.file_list.toLowerCase();
|
|
969
|
+
if (
|
|
970
|
+
files.includes('index.html') &&
|
|
971
|
+
!files.includes('package.json') &&
|
|
972
|
+
!files.includes('symbols.json')
|
|
973
|
+
) {
|
|
974
|
+
envType = 'cdn';
|
|
975
|
+
confidence = 'medium';
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const guide = readSkill('RUNNING_APPS.md');
|
|
979
|
+
return `# Environment Detection\n\n**Detected: ${envType}** (confidence: ${confidence})\n\n${guide}`;
|
|
980
|
+
}
|
|
981
|
+
|
|
512
982
|
if (name === 'login') {
|
|
513
|
-
|
|
514
|
-
if (!email || !password) return 'Error: email and password are required.';
|
|
983
|
+
if (!args.email || !args.password) return 'Error: email and password are required.';
|
|
515
984
|
const resp = await fetch(`${API_BASE}/core/auth/login`, {
|
|
516
985
|
method: 'POST',
|
|
517
986
|
headers: { 'Content-Type': 'application/json' },
|
|
518
|
-
body: JSON.stringify({ email, password }),
|
|
987
|
+
body: JSON.stringify({ email: args.email, password: args.password }),
|
|
519
988
|
});
|
|
520
989
|
let result;
|
|
521
990
|
try {
|
|
@@ -524,169 +993,161 @@ async function callTool(name, args = {}) {
|
|
|
524
993
|
return `Login failed: HTTP ${resp.status}`;
|
|
525
994
|
}
|
|
526
995
|
if (result.success) {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
const user = data.user || {};
|
|
530
|
-
return `Logged in as ${user.name || user.email || 'unknown'}.\nToken: ${tokens.accessToken || ''}\nExpires: ${(tokens.accessTokenExp || {}).expiresAt || 'unknown'}\n\nUse this token with the \`publish\` and \`push\` tools.`;
|
|
996
|
+
const { tokens = {}, user = {} } = result.data || {};
|
|
997
|
+
return `Logged in as ${user.name || user.email || 'unknown'}.\nToken: ${tokens.accessToken || ''}\nExpires: ${tokens.accessTokenExp?.expiresAt || 'unknown'}\n\nUse this token with project, save, publish and push tools.`;
|
|
531
998
|
}
|
|
532
999
|
return `Login failed: ${result.error || result.message || 'Unknown error'}`;
|
|
533
1000
|
}
|
|
534
1001
|
|
|
535
|
-
if (name === '
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const result = await apiRequest('POST', `/${project}/publish`, { token, api_key, body });
|
|
1002
|
+
if (name === 'list_projects') {
|
|
1003
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1004
|
+
const result = await apiRequest('GET', '/core/projects', {
|
|
1005
|
+
token: args.token,
|
|
1006
|
+
api_key: args.api_key,
|
|
1007
|
+
});
|
|
542
1008
|
if (result.success) {
|
|
543
|
-
const
|
|
544
|
-
return
|
|
1009
|
+
const projects = result.data || [];
|
|
1010
|
+
if (!projects.length) return 'No projects found. Use `create_project` to create one.';
|
|
1011
|
+
return (
|
|
1012
|
+
'# Your Projects\n\n' +
|
|
1013
|
+
projects
|
|
1014
|
+
.map(
|
|
1015
|
+
(p) =>
|
|
1016
|
+
`- **${p.name || 'Untitled'}** — key: \`${p.key || '—'}\`, id: \`${p._id || ''}\``,
|
|
1017
|
+
)
|
|
1018
|
+
.join('\n')
|
|
1019
|
+
);
|
|
545
1020
|
}
|
|
546
|
-
return `
|
|
1021
|
+
return `Failed to list projects: ${result.error || 'Unknown error'}`;
|
|
547
1022
|
}
|
|
548
1023
|
|
|
549
|
-
if (name === '
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!token && !api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
561
|
-
const body = { mode, branch };
|
|
562
|
-
if (version) body.version = version;
|
|
563
|
-
const result = await apiRequest('POST', `/${project}/environments/${environment}/publish`, {
|
|
564
|
-
token,
|
|
565
|
-
api_key,
|
|
1024
|
+
if (name === 'create_project') {
|
|
1025
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1026
|
+
const body = {
|
|
1027
|
+
name: args.name,
|
|
1028
|
+
visibility: args.visibility || 'private',
|
|
1029
|
+
language: 'javascript',
|
|
1030
|
+
};
|
|
1031
|
+
if (args.key) body.key = args.key;
|
|
1032
|
+
const result = await apiRequest('POST', '/core/projects', {
|
|
1033
|
+
token: args.token,
|
|
1034
|
+
api_key: args.api_key,
|
|
566
1035
|
body,
|
|
567
1036
|
});
|
|
568
1037
|
if (result.success) {
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
return `Pushed to ${data.key || environment} successfully.\nMode: ${config.mode || mode}\nVersion: ${config.version || 'latest'}\nBranch: ${config.branch || branch}`;
|
|
1038
|
+
const d = result.data || {};
|
|
1039
|
+
return `Project created.\nName: ${d.name}\nKey: \`${d.key}\`\nID: \`${d._id}\`\n\nUse with \`save_to_project\` to push components.`;
|
|
572
1040
|
}
|
|
573
|
-
return `
|
|
1041
|
+
return `Create failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
|
|
574
1042
|
}
|
|
575
1043
|
|
|
576
|
-
if (name === '
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
} else if (has_json_data) {
|
|
596
|
-
envType = 'json_runtime';
|
|
597
|
-
confidence = 'high';
|
|
598
|
-
} else if (has_symbols_json && has_symbols_dir) {
|
|
599
|
-
envType = 'local_project';
|
|
600
|
-
confidence = 'high';
|
|
601
|
-
} else if (has_symbols_dir || (has_package_json && has_symbols_json)) {
|
|
602
|
-
envType = 'local_project';
|
|
603
|
-
confidence = 'medium';
|
|
604
|
-
} else if (has_cdn_import || has_iife_script) {
|
|
605
|
-
envType = 'cdn';
|
|
606
|
-
confidence = 'high';
|
|
607
|
-
} else if (has_package_json) {
|
|
608
|
-
envType = 'local_project';
|
|
609
|
-
confidence = 'low';
|
|
610
|
-
} else if (file_list) {
|
|
611
|
-
const files = file_list.toLowerCase();
|
|
612
|
-
if (
|
|
613
|
-
files.includes('index.html') &&
|
|
614
|
-
!files.includes('package.json') &&
|
|
615
|
-
!files.includes('symbols.json')
|
|
616
|
-
) {
|
|
617
|
-
envType = 'cdn';
|
|
618
|
-
confidence = 'medium';
|
|
619
|
-
}
|
|
1044
|
+
if (name === 'get_project') {
|
|
1045
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1046
|
+
const branch = args.branch || 'main';
|
|
1047
|
+
const isKey = args.project.startsWith('pr_') || !/^[0-9a-f]+$/.test(args.project);
|
|
1048
|
+
const path = isKey
|
|
1049
|
+
? `/core/projects/key/${args.project}/data?branch=${branch}&version=latest`
|
|
1050
|
+
: `/core/projects/${args.project}/data?branch=${branch}&version=latest`;
|
|
1051
|
+
const result = await apiRequest('GET', path, { token: args.token, api_key: args.api_key });
|
|
1052
|
+
if (result.success) {
|
|
1053
|
+
const data = result.data || {};
|
|
1054
|
+
return `# Project Data (branch: ${branch})\n\n**Components (${Object.keys(data.components || {}).length}):** ${
|
|
1055
|
+
Object.keys(data.components || {})
|
|
1056
|
+
.slice(0, 20)
|
|
1057
|
+
.join(', ') || 'none'
|
|
1058
|
+
}\n**Pages (${Object.keys(data.pages || {}).length}):** ${
|
|
1059
|
+
Object.keys(data.pages || {})
|
|
1060
|
+
.slice(0, 20)
|
|
1061
|
+
.join(', ') || 'none'
|
|
1062
|
+
}\n\n\`\`\`json\n${JSON.stringify(data, null, 2).slice(0, 8000)}\n\`\`\``;
|
|
620
1063
|
}
|
|
1064
|
+
return `Failed to get project: ${result.error || 'Unknown error'}`;
|
|
1065
|
+
}
|
|
621
1066
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
let
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
output += `### Code Format\n`;
|
|
630
|
-
output += `- Components: \`export const Name = { extends: 'Flex', ... }\` in \`components/\`\n`;
|
|
631
|
-
output += `- Pages: \`export const pageName = { extends: 'Page', ... }\` in \`pages/\`\n`;
|
|
632
|
-
output += `- State: \`export default { key: value }\` in \`state.js\`\n`;
|
|
633
|
-
output += `- Functions: \`export const fn = function() {}\` in \`functions/\`\n`;
|
|
634
|
-
output += `- No imports between files (except pages/index.js)\n\n`;
|
|
635
|
-
output += `### Commands\n`;
|
|
636
|
-
output += `\`\`\`bash\nnpm start # dev server\nsmbls build # production build\nsmbls push # deploy to platform\nsmbls deploy # deploy to provider\n\`\`\`\n\n`;
|
|
637
|
-
const structureGuide = readSkill('PROJECT_STRUCTURE.md');
|
|
638
|
-
output += `### Full Project Structure Reference\n\n${structureGuide}`;
|
|
639
|
-
} else if (envType === 'cdn') {
|
|
640
|
-
output += `## Your Environment: CDN (Browser-Only)\n\n`;
|
|
641
|
-
output += `You're running Symbols directly in the browser via CDN import.\n\n`;
|
|
642
|
-
output += `### Code Format\n`;
|
|
643
|
-
output += `- Single HTML file with \`<script type="module">\`\n`;
|
|
644
|
-
output += `- Import: \`import { create } from 'https://esm.sh/smbls'\`\n`;
|
|
645
|
-
output += `- Define app as inline object tree\n`;
|
|
646
|
-
output += `- Mount: \`create(App, { designSystem, components, functions, state })\`\n`;
|
|
647
|
-
output += `- Components defined as JS variables (no file-based registry)\n\n`;
|
|
648
|
-
output += `### Limitations\n`;
|
|
649
|
-
output += `- No file-based routing (use tab/view switching)\n`;
|
|
650
|
-
output += `- No SSR\n`;
|
|
651
|
-
output += `- \`childExtends: 'Name'\` needs components passed to \`create()\`\n\n`;
|
|
652
|
-
const cdnGuide = readSkill('RUNNING_APPS.md');
|
|
653
|
-
output += `### Full CDN Reference\n\n${cdnGuide}`;
|
|
654
|
-
} else if (envType === 'json_runtime') {
|
|
655
|
-
output += `## Your Environment: JSON Runtime (Frank)\n\n`;
|
|
656
|
-
output += `You're running Symbols from serialized JSON project data.\n\n`;
|
|
657
|
-
output += `### Code Format\n`;
|
|
658
|
-
output += `- Project data is a JSON object with components, pages, designSystem, state, functions\n`;
|
|
659
|
-
output += `- Functions are serialized as strings\n`;
|
|
660
|
-
output += `- Convert with: \`smbls frank to-json ./symbols\` or \`toJSON({ entry: './symbols' })\`\n`;
|
|
661
|
-
output += `- Reverse with: \`smbls frank to-fs data.json -o ./output\` or \`toFS(data, './output')\`\n`;
|
|
662
|
-
output += `- Can be loaded by Mermaid server via JSON_PATH env var\n\n`;
|
|
663
|
-
output += `### Full Reference\n\n${fullGuide}`;
|
|
664
|
-
} else if (envType === 'remote_server') {
|
|
665
|
-
output += `## Your Environment: Remote Symbols Server (Mermaid)\n\n`;
|
|
666
|
-
output += `You're working with the Mermaid rendering server for hosted Symbols apps.\n\n`;
|
|
667
|
-
output += `### URL Patterns\n`;
|
|
668
|
-
output += `- Production: \`https://app.user.preview.symbols.app/\`\n`;
|
|
669
|
-
output += `- Development: \`https://app.user.preview.dev.symbols.app/\`\n`;
|
|
670
|
-
output += `- Staging: \`https://app.user.preview.staging.symbols.app/\`\n`;
|
|
671
|
-
output += `- Legacy: \`https://app.symbo.ls/\`\n`;
|
|
672
|
-
output += `- Custom domains supported\n\n`;
|
|
673
|
-
output += `### Deployment\n`;
|
|
674
|
-
output += `\`\`\`bash\nsmbls push # deploy to Symbols platform\n\`\`\`\n\n`;
|
|
675
|
-
output += `### Full Reference\n\n${fullGuide}`;
|
|
676
|
-
} else {
|
|
677
|
-
output += `## Could Not Determine Environment\n\n`;
|
|
678
|
-
output += `Provide more details about your project files to get specific guidance.\n\n`;
|
|
679
|
-
output += `### All 4 Ways to Run Symbols Apps\n\n${fullGuide}`;
|
|
1067
|
+
if (name === 'save_to_project') {
|
|
1068
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1069
|
+
let changesData;
|
|
1070
|
+
try {
|
|
1071
|
+
changesData = JSON.parse(args.changes);
|
|
1072
|
+
} catch (e) {
|
|
1073
|
+
return `Invalid JSON: ${e.message}`;
|
|
680
1074
|
}
|
|
1075
|
+
const { id: projectId, error: resolveErr } = await resolveProjectId(
|
|
1076
|
+
args.project,
|
|
1077
|
+
args.token,
|
|
1078
|
+
args.api_key,
|
|
1079
|
+
);
|
|
1080
|
+
if (resolveErr) return resolveErr;
|
|
1081
|
+
const { changes, granular, orders } = buildChangesAndSchema(changesData);
|
|
1082
|
+
if (!changes.length) return 'No valid changes found.';
|
|
1083
|
+
const branch = args.branch || 'main';
|
|
1084
|
+
const result = await apiRequest('POST', `/core/projects/${projectId}/changes`, {
|
|
1085
|
+
token: args.token,
|
|
1086
|
+
api_key: args.api_key,
|
|
1087
|
+
body: {
|
|
1088
|
+
changes,
|
|
1089
|
+
granularChanges: granular,
|
|
1090
|
+
orders,
|
|
1091
|
+
message: args.message || 'Updated via Symbols MCP',
|
|
1092
|
+
branch,
|
|
1093
|
+
type: 'patch',
|
|
1094
|
+
},
|
|
1095
|
+
});
|
|
1096
|
+
if (result.success) {
|
|
1097
|
+
const d = result.data || {};
|
|
1098
|
+
return `Saved to \`${args.project}\`.\nVersion: ${d.value || d.version || d.id || 'unknown'}\nBranch: ${branch}\nSections: ${Object.keys(changesData).join(', ')}\n\nUse \`publish\` then \`push\` to deploy.`;
|
|
1099
|
+
}
|
|
1100
|
+
return `Save failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
|
|
1101
|
+
}
|
|
681
1102
|
|
|
682
|
-
|
|
1103
|
+
if (name === 'publish') {
|
|
1104
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1105
|
+
const { id: projectId, error: resolveErr } = await resolveProjectId(
|
|
1106
|
+
args.project,
|
|
1107
|
+
args.token,
|
|
1108
|
+
args.api_key,
|
|
1109
|
+
);
|
|
1110
|
+
if (resolveErr) return resolveErr;
|
|
1111
|
+
const body = { branch: args.branch || 'main' };
|
|
1112
|
+
if (args.version) body.version = args.version;
|
|
1113
|
+
const result = await apiRequest('POST', `/core/projects/${projectId}/publish`, {
|
|
1114
|
+
token: args.token,
|
|
1115
|
+
api_key: args.api_key,
|
|
1116
|
+
body,
|
|
1117
|
+
});
|
|
1118
|
+
if (result.success)
|
|
1119
|
+
return `Published.\nVersion: ${result.data?.value || result.data?.id || 'unknown'}`;
|
|
1120
|
+
return `Publish failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (name === 'push') {
|
|
1124
|
+
if (!args.token && !args.api_key) return `Authentication required.\n\n${AUTH_HELP}`;
|
|
1125
|
+
const { id: projectId, error: resolveErr } = await resolveProjectId(
|
|
1126
|
+
args.project,
|
|
1127
|
+
args.token,
|
|
1128
|
+
args.api_key,
|
|
1129
|
+
);
|
|
1130
|
+
if (resolveErr) return resolveErr;
|
|
1131
|
+
const env = args.environment || 'production';
|
|
1132
|
+
const body = { mode: args.mode || 'published', branch: args.branch || 'main' };
|
|
1133
|
+
if (args.version) body.version = args.version;
|
|
1134
|
+
const result = await apiRequest(
|
|
1135
|
+
'POST',
|
|
1136
|
+
`/core/projects/${projectId}/environments/${env}/publish`,
|
|
1137
|
+
{ token: args.token, api_key: args.api_key, body },
|
|
1138
|
+
);
|
|
1139
|
+
if (result.success) {
|
|
1140
|
+
const config = result.data?.config || {};
|
|
1141
|
+
return `Pushed to ${result.data?.key || env}.\nMode: ${config.mode || body.mode}\nVersion: ${config.version || 'latest'}`;
|
|
1142
|
+
}
|
|
1143
|
+
return `Push failed: ${result.error || 'Unknown error'}\n${result.message || ''}`;
|
|
683
1144
|
}
|
|
684
1145
|
|
|
685
1146
|
throw new Error(`Unknown tool: ${name}`);
|
|
686
1147
|
}
|
|
687
1148
|
|
|
688
1149
|
// ---------------------------------------------------------------------------
|
|
689
|
-
// Resources
|
|
1150
|
+
// Resources
|
|
690
1151
|
// ---------------------------------------------------------------------------
|
|
691
1152
|
|
|
692
1153
|
const SKILL_RESOURCES = {
|
|
@@ -708,6 +1169,9 @@ const SKILL_RESOURCES = {
|
|
|
708
1169
|
'symbols://skills/default-components': 'DEFAULT_COMPONENTS.md',
|
|
709
1170
|
'symbols://skills/learnings': 'LEARNINGS.md',
|
|
710
1171
|
'symbols://skills/running-apps': 'RUNNING_APPS.md',
|
|
1172
|
+
'symbols://skills/cli': 'CLI.md',
|
|
1173
|
+
'symbols://skills/sdk': 'SDK.md',
|
|
1174
|
+
'symbols://skills/common-mistakes': 'COMMON_MISTAKES.md',
|
|
711
1175
|
'symbols://skills/brand-identity': 'BRAND_IDENTITY.md',
|
|
712
1176
|
'symbols://skills/design-critique': 'DESIGN_CRITIQUE.md',
|
|
713
1177
|
'symbols://skills/design-system-architect': 'DESIGN_SYSTEM_ARCHITECT.md',
|
|
@@ -718,21 +1182,14 @@ const SKILL_RESOURCES = {
|
|
|
718
1182
|
};
|
|
719
1183
|
|
|
720
1184
|
function listResources() {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
resources.push({ uri, name: filename.replace('.md', ''), mimeType: 'text/markdown' });
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return resources;
|
|
1185
|
+
return Object.entries(SKILL_RESOURCES)
|
|
1186
|
+
.filter(([, f]) => skills[f])
|
|
1187
|
+
.map(([uri, f]) => ({ uri, name: f.replace('.md', ''), mimeType: 'text/markdown' }));
|
|
728
1188
|
}
|
|
729
1189
|
|
|
730
1190
|
function readResource(uri) {
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
return readSkill(filename);
|
|
734
|
-
}
|
|
735
|
-
return null;
|
|
1191
|
+
const f = SKILL_RESOURCES[uri];
|
|
1192
|
+
return f && skills[f] ? readSkill(f) : null;
|
|
736
1193
|
}
|
|
737
1194
|
|
|
738
1195
|
// ---------------------------------------------------------------------------
|
|
@@ -742,41 +1199,35 @@ function readResource(uri) {
|
|
|
742
1199
|
const PROMPTS = [
|
|
743
1200
|
{
|
|
744
1201
|
name: 'symbols_component_prompt',
|
|
745
|
-
description: '
|
|
1202
|
+
description: 'Generate a Symbols.app component.',
|
|
746
1203
|
arguments: [
|
|
747
|
-
{ name: 'description',
|
|
748
|
-
{ name: 'component_name',
|
|
1204
|
+
{ name: 'description', required: true },
|
|
1205
|
+
{ name: 'component_name', required: false },
|
|
749
1206
|
],
|
|
750
1207
|
},
|
|
751
1208
|
{
|
|
752
1209
|
name: 'symbols_migration_prompt',
|
|
753
|
-
description: '
|
|
754
|
-
arguments: [
|
|
755
|
-
{
|
|
756
|
-
name: 'source_framework',
|
|
757
|
-
description: 'Source framework (React, Vue, etc.)',
|
|
758
|
-
required: false,
|
|
759
|
-
},
|
|
760
|
-
],
|
|
1210
|
+
description: 'Migrate code to Symbols.app.',
|
|
1211
|
+
arguments: [{ name: 'source_framework', required: false }],
|
|
761
1212
|
},
|
|
762
1213
|
{
|
|
763
1214
|
name: 'symbols_project_prompt',
|
|
764
|
-
description: '
|
|
765
|
-
arguments: [{ name: 'description',
|
|
1215
|
+
description: 'Scaffold a complete Symbols project.',
|
|
1216
|
+
arguments: [{ name: 'description', required: true }],
|
|
766
1217
|
},
|
|
767
1218
|
{
|
|
768
1219
|
name: 'symbols_review_prompt',
|
|
769
|
-
description: '
|
|
1220
|
+
description: 'Review Symbols/DOMQL code for v3 compliance.',
|
|
770
1221
|
arguments: [],
|
|
771
1222
|
},
|
|
772
1223
|
{
|
|
773
1224
|
name: 'symbols_convert_html_prompt',
|
|
774
|
-
description: '
|
|
1225
|
+
description: 'Convert HTML to Symbols.app components.',
|
|
775
1226
|
arguments: [],
|
|
776
1227
|
},
|
|
777
1228
|
{
|
|
778
1229
|
name: 'symbols_design_review_prompt',
|
|
779
|
-
description: '
|
|
1230
|
+
description: 'Visual/design audit against the design system.',
|
|
780
1231
|
arguments: [],
|
|
781
1232
|
},
|
|
782
1233
|
];
|
|
@@ -785,12 +1236,11 @@ const PROMPTS = [
|
|
|
785
1236
|
// MCP JSON-RPC handler
|
|
786
1237
|
// ---------------------------------------------------------------------------
|
|
787
1238
|
|
|
788
|
-
const SERVER_INFO = { name: 'Symbols MCP', version: '1.
|
|
1239
|
+
const SERVER_INFO = { name: 'Symbols MCP', version: '1.0.15' };
|
|
789
1240
|
|
|
790
1241
|
export async function handleJsonRpc(req) {
|
|
791
1242
|
const { method, params, id } = req;
|
|
792
|
-
|
|
793
|
-
if (method === 'initialize') {
|
|
1243
|
+
if (method === 'initialize')
|
|
794
1244
|
return {
|
|
795
1245
|
jsonrpc: '2.0',
|
|
796
1246
|
id,
|
|
@@ -800,15 +1250,9 @@ export async function handleJsonRpc(req) {
|
|
|
800
1250
|
serverInfo: SERVER_INFO,
|
|
801
1251
|
},
|
|
802
1252
|
};
|
|
803
|
-
}
|
|
804
|
-
|
|
805
1253
|
if (method === 'notifications/initialized') return null;
|
|
806
1254
|
if (method === 'ping') return { jsonrpc: '2.0', id, result: {} };
|
|
807
|
-
|
|
808
|
-
if (method === 'tools/list') {
|
|
809
|
-
return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
|
|
810
|
-
}
|
|
811
|
-
|
|
1255
|
+
if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
|
|
812
1256
|
if (method === 'tools/call') {
|
|
813
1257
|
const { name, arguments: args = {} } = params || {};
|
|
814
1258
|
try {
|
|
@@ -822,32 +1266,25 @@ export async function handleJsonRpc(req) {
|
|
|
822
1266
|
};
|
|
823
1267
|
}
|
|
824
1268
|
}
|
|
825
|
-
|
|
826
|
-
if (method === 'resources/list') {
|
|
1269
|
+
if (method === 'resources/list')
|
|
827
1270
|
return { jsonrpc: '2.0', id, result: { resources: listResources() } };
|
|
828
|
-
}
|
|
829
|
-
|
|
830
1271
|
if (method === 'resources/read') {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1272
|
+
const content = readResource(params?.uri);
|
|
1273
|
+
if (content === null)
|
|
1274
|
+
return {
|
|
1275
|
+
jsonrpc: '2.0',
|
|
1276
|
+
id,
|
|
1277
|
+
error: { code: -32602, message: `Resource not found: ${params?.uri}` },
|
|
1278
|
+
};
|
|
836
1279
|
return {
|
|
837
1280
|
jsonrpc: '2.0',
|
|
838
1281
|
id,
|
|
839
|
-
result: { contents: [{ uri, mimeType: 'text/markdown', text: content }] },
|
|
1282
|
+
result: { contents: [{ uri: params.uri, mimeType: 'text/markdown', text: content }] },
|
|
840
1283
|
};
|
|
841
1284
|
}
|
|
842
|
-
|
|
843
|
-
if (
|
|
844
|
-
return { jsonrpc: '2.0', id, result: { prompts: PROMPTS } };
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
if (id !== undefined) {
|
|
1285
|
+
if (method === 'prompts/list') return { jsonrpc: '2.0', id, result: { prompts: PROMPTS } };
|
|
1286
|
+
if (id !== undefined)
|
|
848
1287
|
return { jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } };
|
|
849
|
-
}
|
|
850
|
-
|
|
851
1288
|
return null;
|
|
852
1289
|
}
|
|
853
1290
|
|