@dryui/mcp 0.1.3 → 0.2.0

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/index.js CHANGED
@@ -3243,8 +3243,8 @@ var require_utils = __commonJS((exports, module) => {
3243
3243
  }
3244
3244
  return output.join("");
3245
3245
  }
3246
- function normalizeComponentEncoding(component, esc2) {
3247
- const func = esc2 !== true ? escape : unescape;
3246
+ function normalizeComponentEncoding(component, esc3) {
3247
+ const func = esc3 !== true ? escape : unescape;
3248
3248
  if (component.scheme !== undefined) {
3249
3249
  component.scheme = func(component.scheme);
3250
3250
  }
@@ -8146,7 +8146,8 @@ function detectProject(spec, inputPath) {
8146
8146
  const themeImportFiles = rootLayout && importsAppCss(rootLayout) ? [rootLayout, appCss] : [rootLayout];
8147
8147
  const defaultImported = hasAnyImport(themeImportFiles, spec.themeImports.default);
8148
8148
  const darkImported = hasAnyImport(themeImportFiles, spec.themeImports.dark);
8149
- const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!(appHtml && hasThemeAuto(appHtml)));
8149
+ const themeAuto = appHtml ? hasThemeAuto(appHtml) : false;
8150
+ const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!themeAuto);
8150
8151
  const warnings = [];
8151
8152
  if (!packageJsonPath)
8152
8153
  warnings.push("No package.json found above the provided path.");
@@ -8172,7 +8173,7 @@ function detectProject(spec, inputPath) {
8172
8173
  theme: {
8173
8174
  defaultImported,
8174
8175
  darkImported,
8175
- themeAuto: hasThemeAuto(appHtml)
8176
+ themeAuto
8176
8177
  },
8177
8178
  warnings
8178
8179
  };
@@ -8243,7 +8244,7 @@ function planInstall(spec, inputPath) {
8243
8244
  title: "Set html theme mode to auto",
8244
8245
  description: 'In src/app.html, find the opening <html> tag (e.g. <html lang="en">) and add class="theme-auto" to it, preserving any existing attributes. Result should be like <html lang="en" class="theme-auto">. Do NOT add a second <html> element.',
8245
8246
  path: detection.files.appHtml,
8246
- snippet: '<html lang="en" class="theme-auto">'
8247
+ snippet: 'class="theme-auto"'
8247
8248
  });
8248
8249
  }
8249
8250
  if (steps.length === 0) {
@@ -8773,6 +8774,582 @@ function formatSimple(name, def) {
8773
8774
  `);
8774
8775
  }
8775
8776
 
8777
+ // src/composition-search.ts
8778
+ var STOP_WORDS = new Set([
8779
+ "a",
8780
+ "an",
8781
+ "the",
8782
+ "and",
8783
+ "or",
8784
+ "but",
8785
+ "in",
8786
+ "on",
8787
+ "at",
8788
+ "to",
8789
+ "for",
8790
+ "of",
8791
+ "with",
8792
+ "by",
8793
+ "from",
8794
+ "is",
8795
+ "it",
8796
+ "that",
8797
+ "this",
8798
+ "as",
8799
+ "be",
8800
+ "are",
8801
+ "was",
8802
+ "were",
8803
+ "been",
8804
+ "has",
8805
+ "have",
8806
+ "had",
8807
+ "do",
8808
+ "does",
8809
+ "did",
8810
+ "will",
8811
+ "would",
8812
+ "could",
8813
+ "should",
8814
+ "may",
8815
+ "might",
8816
+ "can",
8817
+ "i",
8818
+ "me",
8819
+ "my",
8820
+ "we",
8821
+ "our",
8822
+ "you",
8823
+ "your",
8824
+ "need",
8825
+ "want",
8826
+ "like",
8827
+ "create",
8828
+ "make",
8829
+ "build",
8830
+ "add",
8831
+ "using",
8832
+ "use",
8833
+ "show",
8834
+ "display"
8835
+ ]);
8836
+ function tokenize(text) {
8837
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter((w) => w.length > 1 && !STOP_WORDS.has(w));
8838
+ }
8839
+ function scoreText(tokens, text) {
8840
+ const lower = text.toLowerCase();
8841
+ return tokens.reduce((score, t) => score + (lower.includes(t) ? 1 : 0), 0);
8842
+ }
8843
+ function searchComposition(composition, query) {
8844
+ const q = query.toLowerCase();
8845
+ const tokens = tokenize(query);
8846
+ const exactComponentMatches = [];
8847
+ const exactRecipeMatches = [];
8848
+ for (const comp of Object.values(composition.components)) {
8849
+ if (comp.component.toLowerCase().includes(q) || comp.useWhen.toLowerCase().includes(q) || comp.alternatives.some((a) => a.component.toLowerCase().includes(q) || a.useWhen.toLowerCase().includes(q)) || comp.antiPatterns.some((ap) => ap.pattern.toLowerCase().includes(q))) {
8850
+ exactComponentMatches.push(comp);
8851
+ }
8852
+ }
8853
+ for (const recipe of Object.values(composition.recipes)) {
8854
+ if (recipe.name.toLowerCase().includes(q) || recipe.description.toLowerCase().includes(q) || recipe.tags.some((t) => t.toLowerCase().includes(q)) || recipe.components.some((c) => c.toLowerCase().includes(q))) {
8855
+ exactRecipeMatches.push(recipe);
8856
+ }
8857
+ }
8858
+ if (exactComponentMatches.length || exactRecipeMatches.length) {
8859
+ return { componentMatches: exactComponentMatches, recipeMatches: exactRecipeMatches };
8860
+ }
8861
+ if (!tokens.length) {
8862
+ return { componentMatches: [], recipeMatches: [] };
8863
+ }
8864
+ const scoredComponents = [];
8865
+ for (const comp of Object.values(composition.components)) {
8866
+ const corpus = [
8867
+ comp.component,
8868
+ comp.useWhen,
8869
+ ...comp.alternatives.flatMap((a) => [a.component, a.useWhen]),
8870
+ ...comp.antiPatterns.map((ap) => ap.pattern),
8871
+ ...comp.combinesWith
8872
+ ].join(" ");
8873
+ const score = scoreText(tokens, corpus);
8874
+ if (score > 0)
8875
+ scoredComponents.push({ comp, score });
8876
+ }
8877
+ const scoredRecipes = [];
8878
+ for (const recipe of Object.values(composition.recipes)) {
8879
+ const corpus = [recipe.name, recipe.description, ...recipe.tags, ...recipe.components].join(" ");
8880
+ const score = scoreText(tokens, corpus);
8881
+ if (score > 0)
8882
+ scoredRecipes.push({ recipe, score });
8883
+ }
8884
+ scoredComponents.sort((a, b) => b.score - a.score);
8885
+ scoredRecipes.sort((a, b) => b.score - a.score);
8886
+ const minScore = Math.max(1, Math.floor(tokens.length * 0.3));
8887
+ return {
8888
+ componentMatches: scoredComponents.filter((s) => s.score >= minScore).slice(0, 10).map((s) => s.comp),
8889
+ recipeMatches: scoredRecipes.filter((s) => s.score >= minScore).slice(0, 5).map((s) => s.recipe)
8890
+ };
8891
+ }
8892
+ function formatCompositionResult(results) {
8893
+ const lines = [];
8894
+ for (const comp of results.componentMatches) {
8895
+ lines.push(`── ${comp.component} ──────────────────────────────`);
8896
+ lines.push(`[DEV GUIDANCE — do not render as page content]`);
8897
+ lines.push(`Use: ${comp.component} — ${comp.useWhen}`);
8898
+ lines.push("");
8899
+ for (const alt of comp.alternatives) {
8900
+ lines.push(` ${alt.rank}. ${alt.component} (${alt.useWhen})`);
8901
+ lines.push(alt.snippet.split(`
8902
+ `).map((l) => ` ${l}`).join(`
8903
+ `));
8904
+ lines.push("");
8905
+ }
8906
+ for (const ap of comp.antiPatterns) {
8907
+ lines.push(`⚠ Anti-pattern: ${ap.pattern}`);
8908
+ lines.push(` Why: ${ap.reason}`);
8909
+ lines.push(` Use ${ap.fix} instead`);
8910
+ lines.push("");
8911
+ }
8912
+ if (comp.combinesWith.length) {
8913
+ lines.push(`Combines with: ${comp.combinesWith.join(", ")}`);
8914
+ }
8915
+ lines.push("");
8916
+ }
8917
+ for (const recipe of results.recipeMatches) {
8918
+ lines.push(`── Recipe: ${recipe.name} ──────────────────────────────`);
8919
+ lines.push(`[DEV GUIDANCE — do not render as page content]`);
8920
+ lines.push(recipe.description);
8921
+ lines.push(`Components: ${recipe.components.join(", ")}`);
8922
+ lines.push("");
8923
+ lines.push(`[CODE — use this in your Svelte file]`);
8924
+ lines.push(recipe.snippet);
8925
+ lines.push("");
8926
+ }
8927
+ return lines.join(`
8928
+ `);
8929
+ }
8930
+
8931
+ // src/toon.ts
8932
+ function esc(value) {
8933
+ if (value.includes(",") || value.includes(`
8934
+ `)) {
8935
+ return `"${value.replace(/"/g, '""')}"`;
8936
+ }
8937
+ return value;
8938
+ }
8939
+ function header(resource, count, fields) {
8940
+ return `${resource}[${count}]{${fields.join(",")}}:`;
8941
+ }
8942
+ function row(...values) {
8943
+ return " " + values.map((v) => esc(String(v))).join(",");
8944
+ }
8945
+ function truncate(text, maxLen, hint) {
8946
+ if (text.length <= maxLen)
8947
+ return text;
8948
+ return `(truncated, ${text.length} chars -- ${hint})`;
8949
+ }
8950
+ function buildContextualHelp(ctx) {
8951
+ const hints = [];
8952
+ switch (ctx.command) {
8953
+ case "info":
8954
+ if (ctx.componentName) {
8955
+ hints.push(`compose "${ctx.componentName.toLowerCase()}" -- see composition patterns`);
8956
+ hints.push(`add ${ctx.componentName} -- get starter snippet`);
8957
+ }
8958
+ break;
8959
+ case "list":
8960
+ hints.push("info <Component> -- see full API reference");
8961
+ break;
8962
+ case "compose":
8963
+ if (ctx.componentName) {
8964
+ hints.push(`info ${ctx.componentName} -- full API reference`);
8965
+ hints.push(`add ${ctx.componentName} -- starter snippet`);
8966
+ }
8967
+ break;
8968
+ case "review":
8969
+ if (ctx.hasErrors) {
8970
+ hints.push("info <Component> -- check API for errored component");
8971
+ hints.push("diagnose <file.css> -- check theme if theme warnings present");
8972
+ } else if (ctx.isEmpty) {
8973
+ hints.push("lint . -- check entire workspace");
8974
+ }
8975
+ break;
8976
+ case "diagnose":
8977
+ if (ctx.hasErrors) {
8978
+ hints.push('compose "app shell" -- get correct theme setup');
8979
+ } else if (ctx.isEmpty) {
8980
+ hints.push("review <file.svelte> -- validate component usage");
8981
+ }
8982
+ break;
8983
+ case "doctor":
8984
+ case "lint":
8985
+ if (ctx.hasFindings) {
8986
+ hints.push("lint --max-severity error -- focus on errors only");
8987
+ hints.push("review <file.svelte> -- check specific file");
8988
+ } else if (ctx.isEmpty) {
8989
+ hints.push("detect -- verify project setup");
8990
+ }
8991
+ break;
8992
+ case "detect":
8993
+ if (ctx.status === "partial" || ctx.status === "unsupported") {
8994
+ hints.push("install -- see step-by-step setup plan");
8995
+ } else if (ctx.status === "ready") {
8996
+ hints.push("list -- see available components");
8997
+ hints.push('compose "app shell" -- get root layout template');
8998
+ }
8999
+ break;
9000
+ case "install":
9001
+ hints.push("detect -- verify project after completing steps");
9002
+ break;
9003
+ }
9004
+ return hints;
9005
+ }
9006
+ function formatHelp(hints) {
9007
+ if (hints.length === 0)
9008
+ return "";
9009
+ const lines = [`next[${hints.length}]:`];
9010
+ for (const hint of hints) {
9011
+ lines.push(" " + hint);
9012
+ }
9013
+ return lines.join(`
9014
+ `);
9015
+ }
9016
+ function toonComponent(name, def, opts) {
9017
+ const full = opts?.full ?? false;
9018
+ const lines = [];
9019
+ lines.push(`${name} -- ${def.description}`, `category: ${def.category} | tags: ${def.tags.join(",")}`, `import: import { ${name} } from '${def.import}'`, `compound: ${def.compound}`);
9020
+ if (def.compound && def.structure?.tree.length) {
9021
+ lines.push("", header("structure", def.structure.tree.length, ["node"]));
9022
+ for (const node of def.structure.tree) {
9023
+ lines.push(" " + node);
9024
+ }
9025
+ if (def.structure.note)
9026
+ lines.push(` note: ${def.structure.note}`);
9027
+ }
9028
+ if (def.compound && def.parts) {
9029
+ const partEntries = Object.entries(def.parts);
9030
+ lines.push("", header("parts", partEntries.length, ["part"]));
9031
+ for (const [partName, partDef] of partEntries) {
9032
+ lines.push(` ${name}.${partName}`);
9033
+ const props = Object.entries(partDef.props ?? {});
9034
+ if (props.length > 0) {
9035
+ for (const [propName, propDef] of props) {
9036
+ const flags = [propDef.type];
9037
+ if (propDef.required)
9038
+ flags.push("required");
9039
+ if (propDef.default !== undefined)
9040
+ flags.push(`default:${propDef.default}`);
9041
+ if (propDef.acceptedValues?.length)
9042
+ flags.push(`values:${propDef.acceptedValues.join("|")}`);
9043
+ lines.push(` ${propName}: ${flags.join(" | ")}`);
9044
+ }
9045
+ }
9046
+ }
9047
+ } else if (def.props) {
9048
+ const propEntries = Object.entries(def.props);
9049
+ if (propEntries.length > 0) {
9050
+ lines.push("", header("props", propEntries.length, ["name", "type", "required", "default"]));
9051
+ for (const [propName, propDef] of propEntries) {
9052
+ lines.push(row(propName, propDef.type, propDef.required ? "yes" : "no", propDef.default ?? "-"));
9053
+ }
9054
+ }
9055
+ }
9056
+ const cssEntries = Object.entries(def.cssVars);
9057
+ if (cssEntries.length > 0) {
9058
+ lines.push("", header("css-vars", cssEntries.length, ["name", "description"]));
9059
+ for (const [varName, desc] of cssEntries) {
9060
+ lines.push(row(varName, desc));
9061
+ }
9062
+ }
9063
+ if (def.dataAttributes.length > 0) {
9064
+ lines.push("", header("data-attrs", def.dataAttributes.length, ["name", "values"]));
9065
+ for (const attr of def.dataAttributes) {
9066
+ lines.push(row(attr.name, attr.values?.join("|") ?? "-"));
9067
+ }
9068
+ }
9069
+ if (def.example) {
9070
+ const example = full ? def.example : truncate(def.example, 400, `use add ${name} for full snippet`);
9071
+ lines.push("", "example:", example);
9072
+ }
9073
+ const help = buildContextualHelp({ command: "info", componentName: name });
9074
+ if (help.length > 0) {
9075
+ lines.push("", formatHelp(help));
9076
+ }
9077
+ return lines.join(`
9078
+ `);
9079
+ }
9080
+ function toonComponentList(components, category) {
9081
+ const entries = Object.entries(components);
9082
+ const filtered = category ? entries.filter(([, def]) => def.category.toLowerCase() === category.toLowerCase()) : entries;
9083
+ if (filtered.length === 0) {
9084
+ const cats = [...new Set(entries.map(([, d]) => d.category))].sort();
9085
+ return `components[0]: no matches
9086
+ available categories: ${cats.join(", ")}`;
9087
+ }
9088
+ const groups = {};
9089
+ for (const entry of filtered) {
9090
+ const cat = entry[1].category;
9091
+ (groups[cat] ??= []).push(entry);
9092
+ }
9093
+ const lines = [header("components", filtered.length, ["name", "category", "compound", "description"])];
9094
+ const sortedCats = Object.keys(groups).sort();
9095
+ for (const cat of sortedCats) {
9096
+ const items = groups[cat] ?? [];
9097
+ for (const [name, def] of items) {
9098
+ lines.push(row(name, cat, def.compound, def.description));
9099
+ }
9100
+ }
9101
+ const help = buildContextualHelp({ command: "list" });
9102
+ if (help.length > 0) {
9103
+ lines.push("", formatHelp(help));
9104
+ }
9105
+ return lines.join(`
9106
+ `);
9107
+ }
9108
+ function toonComposition(results, opts) {
9109
+ const full = opts?.full ?? false;
9110
+ const lines = [];
9111
+ const totalMatches = results.componentMatches.length + results.recipeMatches.length;
9112
+ if (totalMatches === 0) {
9113
+ return "matches[0]: none";
9114
+ }
9115
+ for (const comp of results.componentMatches) {
9116
+ lines.push(`-- ${comp.component}: ${comp.useWhen}`);
9117
+ for (const alt of comp.alternatives) {
9118
+ const snippet = full ? alt.snippet : truncate(alt.snippet, 500, `use info ${alt.component} for full snippet`);
9119
+ lines.push(` ${alt.rank}. ${alt.component} (${alt.useWhen})`);
9120
+ lines.push(snippet.split(`
9121
+ `).map((l) => " " + l).join(`
9122
+ `));
9123
+ }
9124
+ for (const ap of comp.antiPatterns) {
9125
+ lines.push(` anti-pattern: ${ap.pattern}`);
9126
+ lines.push(` reason: ${ap.reason} | fix: ${ap.fix}`);
9127
+ }
9128
+ if (comp.combinesWith.length) {
9129
+ lines.push(` combines-with: ${comp.combinesWith.join(",")}`);
9130
+ }
9131
+ lines.push("");
9132
+ }
9133
+ for (const recipe of results.recipeMatches) {
9134
+ const snippet = full ? recipe.snippet : truncate(recipe.snippet, 500, `use compose "${recipe.name}" --full for complete code`);
9135
+ lines.push(`-- recipe: ${recipe.name}`);
9136
+ lines.push(` ${recipe.description}`);
9137
+ lines.push(` components: ${recipe.components.join(",")}`);
9138
+ lines.push(" code:");
9139
+ lines.push(snippet.split(`
9140
+ `).map((l) => " " + l).join(`
9141
+ `));
9142
+ lines.push("");
9143
+ }
9144
+ const firstComponent = results.componentMatches[0]?.alternatives[0]?.component ?? results.recipeMatches[0]?.components[0] ?? undefined;
9145
+ const ctx = { command: "compose" };
9146
+ if (firstComponent)
9147
+ ctx.componentName = firstComponent;
9148
+ const help = buildContextualHelp(ctx);
9149
+ if (help.length > 0) {
9150
+ lines.push(formatHelp(help));
9151
+ }
9152
+ return lines.join(`
9153
+ `).trimEnd();
9154
+ }
9155
+ function toonReviewResult(result) {
9156
+ const lines = [];
9157
+ const hasBlockers = result.issues.some((i) => i.severity === "error");
9158
+ const autoFixable = result.issues.filter((i) => i.fix !== null).length;
9159
+ if (result.issues.length === 0) {
9160
+ lines.push("issues[0]: clean");
9161
+ lines.push(`hasBlockers: false | autoFixable: 0`);
9162
+ } else {
9163
+ lines.push(header("issues", result.issues.length, ["severity", "line", "code", "message"]));
9164
+ for (const issue of result.issues) {
9165
+ lines.push(row(issue.severity, issue.line, issue.code, issue.message));
9166
+ if (issue.fix) {
9167
+ lines.push(` fix: ${issue.fix}`);
9168
+ }
9169
+ }
9170
+ lines.push(`hasBlockers: ${hasBlockers} | autoFixable: ${autoFixable}`);
9171
+ }
9172
+ lines.push(result.summary);
9173
+ const help = buildContextualHelp({
9174
+ command: "review",
9175
+ hasErrors: hasBlockers,
9176
+ isEmpty: result.issues.length === 0
9177
+ });
9178
+ if (help.length > 0) {
9179
+ lines.push("", formatHelp(help));
9180
+ }
9181
+ return lines.join(`
9182
+ `);
9183
+ }
9184
+ function toonDiagnoseResult(result) {
9185
+ const lines = [];
9186
+ const { variables } = result;
9187
+ const missingCount = result.issues.filter((i) => i.code === "missing-token").length;
9188
+ const totalRequired = variables.required + missingCount;
9189
+ const coverage = totalRequired > 0 ? Math.round(variables.required / totalRequired * 100) : 100;
9190
+ lines.push(`tokens: ${variables.found} found, ${variables.required} required, ${variables.extra} extra | coverage: ${coverage}%`);
9191
+ if (result.issues.length === 0) {
9192
+ lines.push("issues[0]: clean");
9193
+ } else {
9194
+ lines.push(header("issues", result.issues.length, ["severity", "code", "variable", "message"]));
9195
+ for (const issue of result.issues) {
9196
+ lines.push(row(issue.severity, issue.code, issue.variable, issue.message));
9197
+ if (issue.fix) {
9198
+ lines.push(` fix: ${issue.fix}`);
9199
+ }
9200
+ }
9201
+ }
9202
+ lines.push(result.summary);
9203
+ const hasErrors = result.issues.some((i) => i.severity === "error");
9204
+ const help = buildContextualHelp({
9205
+ command: "diagnose",
9206
+ hasErrors,
9207
+ isEmpty: result.issues.length === 0
9208
+ });
9209
+ if (help.length > 0) {
9210
+ lines.push("", formatHelp(help));
9211
+ }
9212
+ return lines.join(`
9213
+ `);
9214
+ }
9215
+ var MAX_FINDINGS_DEFAULT = 50;
9216
+ function toonWorkspaceReport(report, opts) {
9217
+ const full = opts?.full ?? false;
9218
+ const title = opts?.title ?? "workspace";
9219
+ const command = opts?.command ?? (title.includes("lint") ? "lint" : "doctor");
9220
+ const lines = [];
9221
+ lines.push(`${title} | root: ${report.root}`);
9222
+ lines.push(`scanned: ${report.scannedFiles} files | errors: ${report.summary.error} | warnings: ${report.summary.warning} | info: ${report.summary.info}`);
9223
+ if (report.summary.byRule && Object.keys(report.summary.byRule).length > 0) {
9224
+ const sorted = Object.entries(report.summary.byRule).sort(([, a], [, b]) => b - a);
9225
+ const topRule = sorted[0];
9226
+ if (topRule) {
9227
+ lines.push(`top-rule: ${topRule[0]} (${topRule[1]} occurrences)`);
9228
+ }
9229
+ }
9230
+ if (report.findings.length === 0) {
9231
+ lines.push("findings[0]: clean");
9232
+ } else {
9233
+ const findings = full ? report.findings : report.findings.slice(0, MAX_FINDINGS_DEFAULT);
9234
+ const truncated = !full && report.findings.length > MAX_FINDINGS_DEFAULT;
9235
+ lines.push(header("findings", findings.length, ["severity", "rule", "file", "line", "message"]));
9236
+ for (const f of findings) {
9237
+ lines.push(row(f.severity, f.ruleId, f.file, f.line ?? "-", f.message));
9238
+ if (f.suggestedFixes.length > 0) {
9239
+ for (const fix of f.suggestedFixes) {
9240
+ lines.push(` fix: ${fix.description}${fix.replacement ? ` -> ${fix.replacement}` : ""}`);
9241
+ }
9242
+ }
9243
+ }
9244
+ if (truncated) {
9245
+ lines.push(` (showing ${MAX_FINDINGS_DEFAULT} of ${report.findings.length} -- use --full to see all)`);
9246
+ }
9247
+ }
9248
+ if (report.warnings.length > 0) {
9249
+ lines.push("", header("warnings", report.warnings.length, ["message"]));
9250
+ for (const w of report.warnings) {
9251
+ lines.push(" " + w);
9252
+ }
9253
+ }
9254
+ const help = buildContextualHelp({
9255
+ command,
9256
+ hasFindings: report.findings.length > 0,
9257
+ isEmpty: report.findings.length === 0
9258
+ });
9259
+ if (help.length > 0) {
9260
+ lines.push("", formatHelp(help));
9261
+ }
9262
+ return lines.join(`
9263
+ `);
9264
+ }
9265
+ function toonProjectDetection(detection) {
9266
+ const lines = [];
9267
+ lines.push(`project: ${detection.status} | framework: ${detection.framework} | pkg-manager: ${detection.packageManager}`);
9268
+ lines.push(`root: ${detection.root ?? "(not found)"}`);
9269
+ lines.push(`deps: ui=${detection.dependencies.ui}, primitives=${detection.dependencies.primitives}`);
9270
+ lines.push(`theme: default=${detection.theme.defaultImported}, dark=${detection.theme.darkImported}, auto=${detection.theme.themeAuto}`);
9271
+ if (detection.warnings.length > 0) {
9272
+ lines.push(header("warnings", detection.warnings.length, ["message"]));
9273
+ for (const w of detection.warnings) {
9274
+ lines.push(" " + w);
9275
+ }
9276
+ }
9277
+ const help = buildContextualHelp({ command: "detect", status: detection.status });
9278
+ if (help.length > 0) {
9279
+ lines.push("", formatHelp(help));
9280
+ }
9281
+ return lines.join(`
9282
+ `);
9283
+ }
9284
+ function toonStep(step, index) {
9285
+ const parts = [`${index + 1}. [${step.status}] ${step.title}: ${step.description}`];
9286
+ if (step.command)
9287
+ parts.push(` cmd: ${step.command}`);
9288
+ if (step.path)
9289
+ parts.push(` file: ${step.path}`);
9290
+ return parts.join(`
9291
+ `);
9292
+ }
9293
+ function toonInstallPlan(plan) {
9294
+ const lines = [];
9295
+ lines.push(toonProjectDetection(plan.detection));
9296
+ lines.push("");
9297
+ if (plan.steps.length === 0) {
9298
+ lines.push("steps[0]: none needed");
9299
+ } else {
9300
+ lines.push(header("steps", plan.steps.length, ["status", "title", "description"]));
9301
+ for (const [i, step] of plan.steps.entries()) {
9302
+ lines.push(toonStep(step, i));
9303
+ }
9304
+ }
9305
+ const help = buildContextualHelp({ command: "install" });
9306
+ if (help.length > 0) {
9307
+ lines.push("", formatHelp(help));
9308
+ }
9309
+ return lines.join(`
9310
+ `);
9311
+ }
9312
+ function toonAddPlan(plan) {
9313
+ const lines = [];
9314
+ lines.push(`add: ${plan.name} | target: ${plan.target ?? "(choose)"}`);
9315
+ if (plan.importStatement) {
9316
+ lines.push(`import: ${plan.importStatement}`);
9317
+ }
9318
+ lines.push("");
9319
+ if (plan.installPlan.steps.length > 0) {
9320
+ lines.push(header("install-steps", plan.installPlan.steps.length, ["status", "title"]));
9321
+ for (const [i, step] of plan.installPlan.steps.entries()) {
9322
+ lines.push(toonStep(step, i));
9323
+ }
9324
+ lines.push("");
9325
+ }
9326
+ if (plan.steps.length > 0) {
9327
+ lines.push(header("add-steps", plan.steps.length, ["status", "title"]));
9328
+ for (const [i, step] of plan.steps.entries()) {
9329
+ lines.push(toonStep(step, i));
9330
+ }
9331
+ }
9332
+ if (plan.warnings.length > 0) {
9333
+ lines.push("", header("warnings", plan.warnings.length, ["message"]));
9334
+ for (const w of plan.warnings) {
9335
+ lines.push(" " + w);
9336
+ }
9337
+ }
9338
+ return lines.join(`
9339
+ `);
9340
+ }
9341
+ function toonError(code, message, suggestions) {
9342
+ const lines = [`error[1]{code,message}: ${esc(code)},${esc(message)}`];
9343
+ if (suggestions?.length) {
9344
+ lines.push(header("suggestions", suggestions.length, ["value"]));
9345
+ for (const s of suggestions) {
9346
+ lines.push(" " + s);
9347
+ }
9348
+ }
9349
+ return lines.join(`
9350
+ `);
9351
+ }
9352
+
8776
9353
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v3/helpers/util.js
8777
9354
  var util;
8778
9355
  (function(util2) {
@@ -9639,10 +10216,10 @@ function isValidJWT(jwt, alg) {
9639
10216
  if (!jwtRegex.test(jwt))
9640
10217
  return false;
9641
10218
  try {
9642
- const [header] = jwt.split(".");
9643
- if (!header)
10219
+ const [header2] = jwt.split(".");
10220
+ if (!header2)
9644
10221
  return false;
9645
- const base64 = header.replace(/-/g, "+").replace(/_/g, "/").padEnd(header.length + (4 - header.length % 4) % 4, "=");
10222
+ const base64 = header2.replace(/-/g, "+").replace(/_/g, "/").padEnd(header2.length + (4 - header2.length % 4) % 4, "=");
9646
10223
  const decoded = JSON.parse(atob(base64));
9647
10224
  if (typeof decoded !== "object" || decoded === null)
9648
10225
  return false;
@@ -12981,7 +13558,7 @@ __export(exports_util, {
12981
13558
  finalizeIssue: () => finalizeIssue,
12982
13559
  extend: () => extend,
12983
13560
  escapeRegex: () => escapeRegex,
12984
- esc: () => esc,
13561
+ esc: () => esc2,
12985
13562
  defineLazy: () => defineLazy,
12986
13563
  createTransparentProxy: () => createTransparentProxy,
12987
13564
  cloneDef: () => cloneDef,
@@ -13132,7 +13709,7 @@ function randomString(length = 10) {
13132
13709
  }
13133
13710
  return str;
13134
13711
  }
13135
- function esc(str) {
13712
+ function esc2(str) {
13136
13713
  return JSON.stringify(str);
13137
13714
  }
13138
13715
  function slugify(input) {
@@ -14961,10 +15538,10 @@ function isValidJWT2(token, algorithm = null) {
14961
15538
  const tokensParts = token.split(".");
14962
15539
  if (tokensParts.length !== 3)
14963
15540
  return false;
14964
- const [header] = tokensParts;
14965
- if (!header)
15541
+ const [header2] = tokensParts;
15542
+ if (!header2)
14966
15543
  return false;
14967
- const parsedHeader = JSON.parse(atob(header));
15544
+ const parsedHeader = JSON.parse(atob(header2));
14968
15545
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
14969
15546
  return false;
14970
15547
  if (!parsedHeader.alg)
@@ -15358,7 +15935,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
15358
15935
  const doc = new Doc(["shape", "payload", "ctx"]);
15359
15936
  const normalized = _normalized.value;
15360
15937
  const parseStr = (key) => {
15361
- const k = esc(key);
15938
+ const k = esc2(key);
15362
15939
  return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
15363
15940
  };
15364
15941
  doc.write(`const input = payload.value;`);
@@ -15370,7 +15947,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
15370
15947
  doc.write(`const newResult = {};`);
15371
15948
  for (const key of normalized.keys) {
15372
15949
  const id = ids[key];
15373
- const k = esc(key);
15950
+ const k = esc2(key);
15374
15951
  const schema = shape[key];
15375
15952
  const isOptionalOut = schema?._zod?.optout === "optional";
15376
15953
  doc.write(`const ${id} = ${parseStr(key)};`);
@@ -30688,11 +31265,14 @@ function toolError(e) {
30688
31265
  var SERVER_INSTRUCTIONS = [
30689
31266
  "DryUI is a zero-dependency Svelte 5 component library. Follow these rules:",
30690
31267
  "",
31268
+ "OUTPUT FORMAT: All tool responses use TOON (token-optimized) format. Each response ends with",
31269
+ " `next[]` suggesting logical next commands. Empty results show `issues[0]: clean` or similar.",
31270
+ "",
30691
31271
  '1. SET UP THE APP SHELL FIRST: Run `compose "app shell"` to get the root layout template.',
30692
31272
  ' Your +layout.svelte must import the theme CSS, and app.html needs <html class="theme-auto">.',
30693
31273
  "",
30694
- "2. PAGE LAYOUT: Use PageHeader.Root + Container + Stack for page structure.",
30695
- " This gives you a header, constrained content width, and vertical rhythm.",
31274
+ "2. PAGE LAYOUT: Use CSS grid for layout — not flexbox. Use Container for constrained content",
31275
+ " width. Use `@container` queries for responsive sizing (never `@media` for layout).",
30696
31276
  "",
30697
31277
  "3. CORRECT CSS TOKENS: Background is --dry-color-bg-base (not --dry-color-bg).",
30698
31278
  ' Text is --dry-color-text-strong (not --dry-color-text). See compose "app shell" for the full reset.',
@@ -30704,10 +31284,9 @@ var SERVER_INSTRUCTIONS = [
30704
31284
  " the compose tool are instructions for YOU, not content for the page. Never render them as",
30705
31285
  " visible text in the UI. Only use the code snippets from compose output.",
30706
31286
  "",
30707
- "6. USE DRYUI LAYOUT COMPONENTS, NOT CUSTOM CSS: Grid instead of `display: grid`, Flex instead",
30708
- " of `display: flex`, Stack instead of vertical flex. Use Field.Root + Label for form fields,",
30709
- " Button instead of raw `<button>`. Never write custom CSS for layout, spacing, or form",
30710
- " structure when a DryUI component exists."
31287
+ "6. USE DRYUI COMPONENTS FOR UI ELEMENTS: Use Field.Root + Label for form fields, Button instead",
31288
+ " of raw `<button>`, Separator instead of `<hr>`. Always call `info` or `compose` to check",
31289
+ " if a DryUI component exists before writing raw HTML."
30711
31290
  ].join(`
30712
31291
  `);
30713
31292
  var server = new McpServer({ name: "@dryui/mcp", version: version2 }, {
@@ -30729,7 +31308,7 @@ var REVIEW_DESC = [
30729
31308
  `- **Suggestions:** Manual flex/grid where DryUI layout components apply, hardcoded colors, raw <hr> instead of <Separator>
30730
31309
 
30731
31310
  `,
30732
- `Output: JSON with issues array and summary. When projectCss is provided, theme diagnosis results are included.
31311
+ `Output: TOON format with issues, hasBlockers/autoFixable aggregates, and next-step suggestions. When projectCss is provided, theme diagnosis results are appended.
30733
31312
 
30734
31313
  `,
30735
31314
  `Example:
@@ -30753,25 +31332,7 @@ var REVIEW_DESC = [
30753
31332
  `Output:
30754
31333
 
30755
31334
  `,
30756
- `\`\`\`json
30757
- {
30758
- "issues": [
30759
- {
30760
- "severity": "error",
30761
- "code": "bare-compound",
30762
- "message": "Card is a compound component — use <Card.Root> instead of <Card>",
30763
- "line": 5
30764
- },
30765
- {
30766
- "severity": "suggestion",
30767
- "code": "prefer-separator",
30768
- "message": "Use <Separator /> instead of raw <hr>",
30769
- "line": 11
30770
- }
30771
- ],
30772
- "summary": "1 error, 0 warnings, 1 suggestion"
30773
- }
30774
- \`\`\``
31335
+ "```\nissues[2]{severity,line,code,message}:\n error,5,bare-compound,Card is a compound component — use <Card.Root> instead of <Card>\n suggestion,11,prefer-separator,Use <Separator /> instead of raw <hr>\nhasBlockers: true | autoFixable: 0\n1 error, 0 warnings, 1 suggestion\n```"
30775
31336
  ].join(`
30776
31337
  `);
30777
31338
  var DIAGNOSE_DESC = [
@@ -30797,7 +31358,7 @@ var DIAGNOSE_DESC = [
30797
31358
  `- To validate a custom theme before shipping
30798
31359
 
30799
31360
  `,
30800
- `Output: JSON with variables summary, issues array (severity + code + variable + message + fix), and summary string.
31361
+ `Output: TOON format with token coverage percentage, issues, and next-step suggestions.
30801
31362
 
30802
31363
  `,
30803
31364
  `Example:
@@ -30824,18 +31385,15 @@ server.tool("review", REVIEW_DESC, {
30824
31385
  const spec = getSpec();
30825
31386
  const parts = [];
30826
31387
  const result = reviewComponent(code, spec, filename);
30827
- parts.push({ type: "text", text: JSON.stringify(result, null, 2) });
31388
+ parts.push({ type: "text", text: toonReviewResult(result) });
30828
31389
  if (projectCss) {
30829
31390
  const diagnosis = diagnoseTheme(projectCss, spec);
30830
31391
  if (diagnosis.issues.length > 0) {
30831
31392
  parts.push({
30832
31393
  type: "text",
30833
31394
  text: `
30834
-
30835
31395
  --- THEME DIAGNOSIS ---
30836
- The project CSS has issues that will affect how DryUI components render:
30837
-
30838
- ` + JSON.stringify(diagnosis, null, 2)
31396
+ ` + toonDiagnoseResult(diagnosis)
30839
31397
  });
30840
31398
  }
30841
31399
  }
@@ -30863,7 +31421,7 @@ server.tool("diagnose", DIAGNOSE_DESC, {
30863
31421
  };
30864
31422
  }
30865
31423
  const result = diagnoseTheme(content, getSpec());
30866
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31424
+ return { content: [{ type: "text", text: toonDiagnoseResult(result) }] };
30867
31425
  } catch (e) {
30868
31426
  return toolError(e);
30869
31427
  }
@@ -30874,7 +31432,7 @@ var DETECT_PROJECT_DESC = [
30874
31432
  `,
30875
31433
  `Input: Optional cwd or project path.
30876
31434
  `,
30877
- `Output: JSON describing framework, package manager, DryUI dependencies, theme imports, theme-auto status, key file paths, and warnings.
31435
+ `Output: TOON format with project status, framework, deps, theme state, and next-step suggestions.
30878
31436
 
30879
31437
  `,
30880
31438
  "Use this before planning installation or project-aware component adoption."
@@ -30884,7 +31442,7 @@ server.tool("detect_project", DETECT_PROJECT_DESC, {
30884
31442
  }, async ({ cwd }) => {
30885
31443
  try {
30886
31444
  const result = detectProject(getSpec(), cwd);
30887
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31445
+ return { content: [{ type: "text", text: toonProjectDetection(result) }] };
30888
31446
  } catch (e) {
30889
31447
  return toolError(e);
30890
31448
  }
@@ -30895,7 +31453,7 @@ var PLAN_INSTALL_DESC = [
30895
31453
  `,
30896
31454
  `Input: Optional cwd or project path.
30897
31455
  `,
30898
- `Output: JSON with project detection plus ordered install steps. The result never mutates files.
31456
+ `Output: TOON format with project detection plus ordered install steps. The result never mutates files.
30899
31457
 
30900
31458
  `,
30901
31459
  "Use this to prepare a repo for DryUI before inserting components."
@@ -30905,7 +31463,7 @@ server.tool("plan_install", PLAN_INSTALL_DESC, {
30905
31463
  }, async ({ cwd }) => {
30906
31464
  try {
30907
31465
  const result = planInstall(getSpec(), cwd);
30908
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31466
+ return { content: [{ type: "text", text: toonInstallPlan(result) }] };
30909
31467
  } catch (e) {
30910
31468
  return toolError(e);
30911
31469
  }
@@ -30916,7 +31474,7 @@ var PLAN_ADD_DESC = [
30916
31474
  `,
30917
31475
  `Input: Component/composed-output name plus optional cwd, target file path, subpath flag, and withTheme flag.
30918
31476
  `,
30919
- `Output: JSON with install prerequisites, chosen import/source, target suggestions, and next steps. The result never mutates files.
31477
+ `Output: TOON format with install prerequisites, chosen import/source, target suggestions, and next steps. The result never mutates files.
30920
31478
 
30921
31479
  `,
30922
31480
  "Use this when an agent needs project-aware adoption guidance without editing the repo directly."
@@ -30936,7 +31494,7 @@ server.tool("plan_add", PLAN_ADD_DESC, {
30936
31494
  ...withTheme !== undefined ? { withTheme } : {}
30937
31495
  };
30938
31496
  const result = planAdd(getSpec(), name, options);
30939
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31497
+ return { content: [{ type: "text", text: toonAddPlan(result) }] };
30940
31498
  } catch (e) {
30941
31499
  return toolError(e);
30942
31500
  }
@@ -30949,7 +31507,7 @@ var DOCTOR_DESC = [
30949
31507
  `,
30950
31508
  `Input: Optional cwd/path plus include/exclude globs, maxSeverity, and changed-only filtering.
30951
31509
  `,
30952
- `Output: JSON WorkspaceReport with detected projects, ordered findings, summary counts, and scan warnings.
31510
+ `Output: TOON format with top-rule aggregate, findings (capped at 50, use --full for all), and next-step suggestions.
30953
31511
 
30954
31512
  `,
30955
31513
  "Use this for repo-wide health summaries across Svelte files, theme CSS, and project setup."
@@ -30969,7 +31527,7 @@ server.tool("doctor", DOCTOR_DESC, {
30969
31527
  ...maxSeverity ? { maxSeverity } : {},
30970
31528
  ...changed !== undefined ? { changed } : {}
30971
31529
  });
30972
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31530
+ return { content: [{ type: "text", text: toonWorkspaceReport(result, { title: "doctor", command: "doctor" }) }] };
30973
31531
  } catch (e) {
30974
31532
  return toolError(e);
30975
31533
  }
@@ -30980,7 +31538,7 @@ var LINT_DESC = [
30980
31538
  `,
30981
31539
  `Input: Optional cwd/path plus include/exclude globs, maxSeverity, and changed-only filtering.
30982
31540
  `,
30983
- `Output: JSON WorkspaceReport with sorted findings that can be consumed by scripts or agents.
31541
+ `Output: TOON format with sorted findings (capped at 50) and next-step suggestions.
30984
31542
 
30985
31543
  `,
30986
31544
  "Use this when you need machine-oriented DryUI audit output for CI or automation."
@@ -31000,7 +31558,7 @@ server.tool("lint", LINT_DESC, {
31000
31558
  ...maxSeverity ? { maxSeverity } : {},
31001
31559
  ...changed !== undefined ? { changed } : {}
31002
31560
  });
31003
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
31561
+ return { content: [{ type: "text", text: toonWorkspaceReport(result, { title: "lint", command: "lint" }) }] };
31004
31562
  } catch (e) {
31005
31563
  return toolError(e);
31006
31564
  }
@@ -31014,10 +31572,7 @@ var INFO_DESC = [
31014
31572
  `,
31015
31573
  `Input: Component name (case-insensitive, e.g. "Button", "card").
31016
31574
  `,
31017
- "Output: Formatted text showing name, description, category, tags, import statement, ",
31018
- "whether it is compound or simple, root and subpath imports when available, all props ",
31019
- "(with type, accepted values when known, required, bindable, default, and notes), native/rest-prop forwarding, ",
31020
- `compound structure guidance, CSS variables, data attributes, and example code.
31575
+ `Output: TOON format with props, structure, CSS vars, data attributes, truncated example, and next-step suggestions.
31021
31576
 
31022
31577
  `,
31023
31578
  "Use this tool to understand what the component supports before generating code."
@@ -31044,8 +31599,7 @@ Available components:
31044
31599
  };
31045
31600
  }
31046
31601
  const { name: canonical, def } = result;
31047
- const output = def.compound ? formatCompound(canonical, def) : formatSimple(canonical, def);
31048
- return { content: [{ type: "text", text: output }] };
31602
+ return { content: [{ type: "text", text: toonComponent(canonical, def) }] };
31049
31603
  } catch (e) {
31050
31604
  return toolError(e);
31051
31605
  }
@@ -31056,7 +31610,7 @@ var LIST_DESC = [
31056
31610
  `,
31057
31611
  `Input: Optional category name to filter results.
31058
31612
  `,
31059
- `Output: Components grouped by category.
31613
+ `Output: TOON format with components listed as name, category, compound, description.
31060
31614
 
31061
31615
  `,
31062
31616
  "Use this tool to discover which components are available before looking up details with the info tool."
@@ -31066,42 +31620,7 @@ server.tool("list", LIST_DESC, {
31066
31620
  }, async ({ category }) => {
31067
31621
  try {
31068
31622
  const components = getComponents();
31069
- const entries = Object.entries(components);
31070
- const groups = {};
31071
- for (const [name, def] of entries) {
31072
- const cat = def.category;
31073
- if (category && cat.toLowerCase() !== category.toLowerCase())
31074
- continue;
31075
- (groups[cat] ??= []).push({ name, description: def.description });
31076
- }
31077
- if (Object.keys(groups).length === 0) {
31078
- const allCats = [...new Set(entries.map(([, d]) => d.category))].sort();
31079
- return {
31080
- content: [
31081
- {
31082
- type: "text",
31083
- text: `No components found for category: "${category}"
31084
-
31085
- Available categories:
31086
- ${allCats.join(", ")}`
31087
- }
31088
- ],
31089
- isError: true
31090
- };
31091
- }
31092
- const lines = [];
31093
- const sortedCats = Object.keys(groups).sort();
31094
- for (const cat of sortedCats) {
31095
- const items = groups[cat] ?? [];
31096
- lines.push(`${cat.toUpperCase()}`);
31097
- const maxNameLen = Math.max(...items.map((c) => c.name.length));
31098
- for (const item of items) {
31099
- lines.push(` ${pad(item.name, maxNameLen + 2)}${item.description}`);
31100
- }
31101
- lines.push("");
31102
- }
31103
- return { content: [{ type: "text", text: lines.join(`
31104
- `).trimEnd() }] };
31623
+ return { content: [{ type: "text", text: toonComponentList(components, category) }] };
31105
31624
  } catch (e) {
31106
31625
  return toolError(e);
31107
31626
  }
@@ -31110,7 +31629,7 @@ var COMPOSE_DESC = [
31110
31629
  `Look up composition guidance for building UIs with DryUI.
31111
31630
 
31112
31631
  `,
31113
- `Returns ranked component alternatives with code snippets, anti-patterns to avoid, and full composition recipes.
31632
+ `Returns ranked component alternatives with truncated code snippets, anti-patterns to avoid, and composition recipes in TOON format.
31114
31633
  `,
31115
31634
  `Call this BEFORE writing any DryUI layout to ensure correct component selection.
31116
31635
 
@@ -31126,177 +31645,12 @@ var COMPOSE_DESC = [
31126
31645
  `,
31127
31646
  "Output: Ranked alternatives with snippets, anti-patterns, and matching recipes."
31128
31647
  ].join("");
31129
- var STOP_WORDS = new Set([
31130
- "a",
31131
- "an",
31132
- "the",
31133
- "and",
31134
- "or",
31135
- "but",
31136
- "in",
31137
- "on",
31138
- "at",
31139
- "to",
31140
- "for",
31141
- "of",
31142
- "with",
31143
- "by",
31144
- "from",
31145
- "is",
31146
- "it",
31147
- "that",
31148
- "this",
31149
- "as",
31150
- "be",
31151
- "are",
31152
- "was",
31153
- "were",
31154
- "been",
31155
- "has",
31156
- "have",
31157
- "had",
31158
- "do",
31159
- "does",
31160
- "did",
31161
- "will",
31162
- "would",
31163
- "could",
31164
- "should",
31165
- "may",
31166
- "might",
31167
- "can",
31168
- "i",
31169
- "me",
31170
- "my",
31171
- "we",
31172
- "our",
31173
- "you",
31174
- "your",
31175
- "need",
31176
- "want",
31177
- "like",
31178
- "create",
31179
- "make",
31180
- "build",
31181
- "add",
31182
- "using",
31183
- "use",
31184
- "show",
31185
- "display"
31186
- ]);
31187
- function tokenize(text) {
31188
- return text.toLowerCase().replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter((w) => w.length > 1 && !STOP_WORDS.has(w));
31189
- }
31190
- function scoreText(tokens, text) {
31191
- const lower = text.toLowerCase();
31192
- return tokens.reduce((score, t) => score + (lower.includes(t) ? 1 : 0), 0);
31193
- }
31194
31648
  function findComposition(query) {
31195
31649
  const compositionData = getSpec().composition;
31196
31650
  if (!compositionData)
31197
- return {
31198
- componentMatches: [],
31199
- recipeMatches: []
31200
- };
31201
- const q = query.toLowerCase();
31202
- const tokens = tokenize(query);
31203
- const exactComponentMatches = [];
31204
- const exactRecipeMatches = [];
31205
- for (const comp of Object.values(compositionData.components)) {
31206
- if (comp.component.toLowerCase().includes(q) || comp.useWhen.toLowerCase().includes(q) || comp.alternatives.some((a) => a.component.toLowerCase().includes(q) || a.useWhen.toLowerCase().includes(q)) || comp.antiPatterns.some((ap) => ap.pattern.toLowerCase().includes(q))) {
31207
- exactComponentMatches.push(comp);
31208
- }
31209
- }
31210
- for (const recipe of Object.values(compositionData.recipes)) {
31211
- if (recipe.name.toLowerCase().includes(q) || recipe.description.toLowerCase().includes(q) || recipe.tags.some((t) => t.toLowerCase().includes(q)) || recipe.components.some((c) => c.toLowerCase().includes(q))) {
31212
- exactRecipeMatches.push(recipe);
31213
- }
31214
- }
31215
- if (exactComponentMatches.length || exactRecipeMatches.length) {
31216
- return { componentMatches: exactComponentMatches, recipeMatches: exactRecipeMatches };
31217
- }
31218
- if (!tokens.length)
31219
- return {
31220
- componentMatches: [],
31221
- recipeMatches: []
31222
- };
31223
- const scoredComponents = [];
31224
- for (const comp of Object.values(compositionData.components)) {
31225
- const corpus = [
31226
- comp.component,
31227
- comp.useWhen,
31228
- ...comp.alternatives.flatMap((a) => [a.component, a.useWhen]),
31229
- ...comp.antiPatterns.map((ap) => ap.pattern),
31230
- ...comp.combinesWith
31231
- ].join(" ");
31232
- const score = scoreText(tokens, corpus);
31233
- if (score > 0)
31234
- scoredComponents.push({ comp, score });
31235
- }
31236
- const scoredRecipes = [];
31237
- for (const recipe of Object.values(compositionData.recipes)) {
31238
- const corpus = [recipe.name, recipe.description, ...recipe.tags, ...recipe.components].join(" ");
31239
- const score = scoreText(tokens, corpus);
31240
- if (score > 0)
31241
- scoredRecipes.push({ recipe, score });
31242
- }
31243
- scoredComponents.sort((a, b) => b.score - a.score);
31244
- scoredRecipes.sort((a, b) => b.score - a.score);
31245
- const minScore = Math.max(1, Math.floor(tokens.length * 0.3));
31246
- return {
31247
- componentMatches: scoredComponents.filter((s) => s.score >= minScore).slice(0, 10).map((s) => s.comp),
31248
- recipeMatches: scoredRecipes.filter((s) => s.score >= minScore).slice(0, 5).map((s) => s.recipe)
31249
- };
31250
- }
31251
- function formatCompositionResult(results) {
31252
- const lines = [];
31253
- for (const comp of results.componentMatches) {
31254
- lines.push(`── ${comp.component} ──────────────────────────────`);
31255
- lines.push(`[DEV GUIDANCE — do not render as page content]`);
31256
- lines.push(`Use: ${comp.component} — ${comp.useWhen}`);
31257
- lines.push("");
31258
- for (const alt of comp.alternatives) {
31259
- lines.push(` ${alt.rank}. ${alt.component} (${alt.useWhen})`);
31260
- lines.push(alt.snippet.split(`
31261
- `).map((l) => ` ${l}`).join(`
31262
- `));
31263
- lines.push("");
31264
- }
31265
- for (const ap of comp.antiPatterns) {
31266
- lines.push(`⚠ Anti-pattern: ${ap.pattern}`);
31267
- lines.push(` Why: ${ap.reason}`);
31268
- lines.push(` Use ${ap.fix} instead`);
31269
- lines.push("");
31270
- }
31271
- if (comp.combinesWith.length) {
31272
- lines.push(`Combines with: ${comp.combinesWith.join(", ")}`);
31273
- }
31274
- lines.push("");
31275
- }
31276
- for (const recipe of results.recipeMatches) {
31277
- lines.push(`── Recipe: ${recipe.name} ──────────────────────────────`);
31278
- lines.push(`[DEV GUIDANCE — do not render as page content]`);
31279
- lines.push(recipe.description);
31280
- lines.push(`Components: ${recipe.components.join(", ")}`);
31281
- lines.push("");
31282
- lines.push(`[CODE — use this in your Svelte file]`);
31283
- lines.push(recipe.snippet);
31284
- lines.push("");
31285
- }
31286
- return lines.join(`
31287
- `);
31651
+ return { componentMatches: [], recipeMatches: [] };
31652
+ return searchComposition(compositionData, query);
31288
31653
  }
31289
- var COMPOSE_PREAMBLE = [
31290
- "⚠ SETUP: Your root +layout.svelte must import '@dryui/ui/themes/default.css'",
31291
- ` and '@dryui/ui/themes/dark.css'. app.html needs <html class="theme-auto">.`,
31292
- ' If not set up yet, run: compose "app shell"',
31293
- "",
31294
- "⚠ THIS OUTPUT IS DEVELOPER GUIDANCE — do NOT render recipe names, descriptions,",
31295
- " or component lists as visible page content. Only use the code snippets.",
31296
- "",
31297
- ""
31298
- ].join(`
31299
- `);
31300
31654
  server.tool("compose", COMPOSE_DESC, {
31301
31655
  query: exports_external.string().describe('Short keywords: component name, pattern name, or UI concept (e.g. "search form", "date input", "hotel card", "dashboard")')
31302
31656
  }, async ({ query }) => {
@@ -31307,12 +31661,8 @@ server.tool("compose", COMPOSE_DESC, {
31307
31661
  content: [
31308
31662
  {
31309
31663
  type: "text",
31310
- text: `No composition guidance found for "${query}".
31311
-
31312
- Try:
31313
- - A component name (e.g. "DatePicker", "Avatar")
31314
- - A UI concept (e.g. "date input", "image placeholder")
31315
- - A pattern name (e.g. "search-form", "dashboard-page")`
31664
+ text: `matches[0]: none for "${query}"
31665
+ Try: component name (DatePicker, Avatar), UI concept (date input), or pattern (search-form)`
31316
31666
  }
31317
31667
  ]
31318
31668
  };
@@ -31321,7 +31671,7 @@ Try:
31321
31671
  content: [
31322
31672
  {
31323
31673
  type: "text",
31324
- text: COMPOSE_PREAMBLE + formatCompositionResult(results)
31674
+ text: toonComposition(results)
31325
31675
  }
31326
31676
  ]
31327
31677
  };
@@ -31439,7 +31789,7 @@ server.registerPrompt("dryui-install", {
31439
31789
  role: "user",
31440
31790
  content: { type: "text", text: "Plan the DryUI installation for this project." }
31441
31791
  },
31442
- { role: "assistant", content: { type: "text", text: JSON.stringify(result, null, 2) } }
31792
+ { role: "assistant", content: { type: "text", text: toonInstallPlan(result) } }
31443
31793
  ]
31444
31794
  };
31445
31795
  });
@@ -31463,7 +31813,7 @@ server.registerPrompt("dryui-add", {
31463
31813
  role: "user",
31464
31814
  content: { type: "text", text: `Plan how to add ${name} to this project.` }
31465
31815
  },
31466
- { role: "assistant", content: { type: "text", text: JSON.stringify(result, null, 2) } }
31816
+ { role: "assistant", content: { type: "text", text: toonAddPlan(result) } }
31467
31817
  ]
31468
31818
  };
31469
31819
  });