@dryui/cli 0.1.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +1034 -489
  2. package/package.json +5 -2
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
  }
@@ -6518,7 +6518,7 @@ var require_dist = __commonJS((exports, module) => {
6518
6518
  // package.json
6519
6519
  var package_default = {
6520
6520
  name: "@dryui/cli",
6521
- version: "0.1.2",
6521
+ version: "0.2.0",
6522
6522
  author: "Rob Balfre",
6523
6523
  license: "MIT",
6524
6524
  repository: {
@@ -6545,7 +6545,7 @@ var package_default = {
6545
6545
  },
6546
6546
  dependencies: {
6547
6547
  "@dryui/feedback-server": "^0.1.0",
6548
- "@dryui/mcp": "^0.1.3"
6548
+ "@dryui/mcp": "^0.2.0"
6549
6549
  },
6550
6550
  devDependencies: {
6551
6551
  "@types/node": "^25.5.0",
@@ -6554,7 +6554,7 @@ var package_default = {
6554
6554
  };
6555
6555
  // ../mcp/src/spec.json
6556
6556
  var spec_default = {
6557
- version: "0.1.5",
6557
+ version: "0.1.7",
6558
6558
  package: "@dryui/ui",
6559
6559
  themeImports: {
6560
6560
  default: "@dryui/ui/themes/default.css",
@@ -17688,6 +17688,26 @@ var spec_default = {
17688
17688
  type: "number",
17689
17689
  required: false,
17690
17690
  description: "Step interval used when incrementing numeric values."
17691
+ },
17692
+ size: {
17693
+ type: "'sm' | 'md' | 'lg'",
17694
+ required: false,
17695
+ acceptedValues: [
17696
+ "sm",
17697
+ "md",
17698
+ "lg"
17699
+ ],
17700
+ description: "Size preset affecting density, spacing, or typography.",
17701
+ default: "'md'"
17702
+ },
17703
+ name: {
17704
+ type: "string",
17705
+ required: false,
17706
+ description: "Field name used during native form submission."
17707
+ },
17708
+ class: {
17709
+ type: "string",
17710
+ required: false
17691
17711
  }
17692
17712
  },
17693
17713
  forwardedProps: {
@@ -17701,25 +17721,23 @@ var spec_default = {
17701
17721
  ],
17702
17722
  note: "Forwards <input> attributes via rest props."
17703
17723
  },
17704
- cssVars: {
17705
- "--dry-time-input-bg": "Input Bg",
17706
- "--dry-time-input-border": "Input Border",
17707
- "--dry-time-input-color": "Input Color",
17708
- "--dry-time-input-font-size": "Input Font Size",
17709
- "--dry-time-input-padding-x": "Input Padding X",
17710
- "--dry-time-input-padding-y": "Input Padding Y",
17711
- "--dry-time-input-radius": "Input Radius"
17712
- },
17724
+ cssVars: {},
17713
17725
  dataAttributes: [
17714
17726
  {
17715
17727
  name: "data-disabled",
17716
17728
  description: "Present when the component or part is disabled."
17717
17729
  },
17718
17730
  {
17719
- name: "data-time-input"
17731
+ name: "data-placeholder"
17732
+ },
17733
+ {
17734
+ name: "data-time-display"
17720
17735
  },
17721
17736
  {
17722
17737
  name: "data-time-input-wrapper"
17738
+ },
17739
+ {
17740
+ name: "data-time-separator"
17723
17741
  }
17724
17742
  ],
17725
17743
  example: "<TimeInput>Content</TimeInput>"
@@ -31725,13 +31743,715 @@ body { margin: 0; min-height: 100dvh; }
31725
31743
  ]
31726
31744
  };
31727
31745
 
31728
- // ../mcp/src/spec-formatters.ts
31746
+ // ../mcp/src/project-planner.ts
31747
+ import { existsSync, readFileSync, statSync } from "node:fs";
31748
+ import { dirname, resolve } from "node:path";
31729
31749
  var DIR_OVERRIDES = {
31730
31750
  QRCode: "qr-code"
31731
31751
  };
31732
31752
  function componentDir(name) {
31733
31753
  return DIR_OVERRIDES[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
31734
31754
  }
31755
+ function resolveStart(inputPath) {
31756
+ const candidate = resolve(inputPath ?? process.cwd());
31757
+ return existsSync(candidate) && statSync(candidate).isFile() ? dirname(candidate) : candidate;
31758
+ }
31759
+ function findUp(start, fileName) {
31760
+ let current = start;
31761
+ while (true) {
31762
+ const candidate = resolve(current, fileName);
31763
+ if (existsSync(candidate))
31764
+ return candidate;
31765
+ const parent = dirname(current);
31766
+ if (parent === current)
31767
+ return null;
31768
+ current = parent;
31769
+ }
31770
+ }
31771
+ function findUpAny(start, fileNames) {
31772
+ let current = start;
31773
+ while (true) {
31774
+ for (const fileName of fileNames) {
31775
+ const candidate = resolve(current, fileName);
31776
+ if (existsSync(candidate))
31777
+ return candidate;
31778
+ }
31779
+ const parent = dirname(current);
31780
+ if (parent === current)
31781
+ return null;
31782
+ current = parent;
31783
+ }
31784
+ }
31785
+ function detectPackageManager(root) {
31786
+ if (!root)
31787
+ return "unknown";
31788
+ const lockfilePath = findUpAny(root, [
31789
+ "bun.lock",
31790
+ "bun.lockb",
31791
+ "pnpm-lock.yaml",
31792
+ "package-lock.json",
31793
+ "yarn.lock"
31794
+ ]);
31795
+ if (!lockfilePath)
31796
+ return "unknown";
31797
+ if (lockfilePath.endsWith("bun.lock") || lockfilePath.endsWith("bun.lockb"))
31798
+ return "bun";
31799
+ if (lockfilePath.endsWith("pnpm-lock.yaml"))
31800
+ return "pnpm";
31801
+ if (lockfilePath.endsWith("package-lock.json"))
31802
+ return "npm";
31803
+ if (lockfilePath.endsWith("yarn.lock"))
31804
+ return "yarn";
31805
+ return "unknown";
31806
+ }
31807
+ function readPackageJson(packageJsonPath) {
31808
+ if (!packageJsonPath)
31809
+ return null;
31810
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
31811
+ }
31812
+ function getDependencyNames(pkg) {
31813
+ return new Set([
31814
+ ...Object.keys(pkg?.dependencies ?? {}),
31815
+ ...Object.keys(pkg?.devDependencies ?? {})
31816
+ ]);
31817
+ }
31818
+ function detectFramework(dependencyNames) {
31819
+ if (dependencyNames.has("@sveltejs/kit"))
31820
+ return "sveltekit";
31821
+ if (dependencyNames.has("svelte"))
31822
+ return "svelte";
31823
+ return "unknown";
31824
+ }
31825
+ function hasImport(filePath, importPath) {
31826
+ if (!filePath)
31827
+ return false;
31828
+ return readFileSync(filePath, "utf-8").includes(importPath);
31829
+ }
31830
+ function hasThemeAuto(appHtmlPath) {
31831
+ if (!appHtmlPath)
31832
+ return false;
31833
+ return readFileSync(appHtmlPath, "utf-8").includes("theme-auto");
31834
+ }
31835
+ function hasAnyImport(filePaths, importPath) {
31836
+ return filePaths.some((filePath) => hasImport(filePath, importPath));
31837
+ }
31838
+ function importsAppCss(rootLayoutPath) {
31839
+ if (!rootLayoutPath)
31840
+ return false;
31841
+ const content = readFileSync(rootLayoutPath, "utf-8");
31842
+ return content.includes("app.css");
31843
+ }
31844
+ function buildStatus(framework, hasPackageJson, stepsNeeded) {
31845
+ if (!hasPackageJson || framework === "unknown")
31846
+ return "unsupported";
31847
+ return stepsNeeded === 0 ? "ready" : "partial";
31848
+ }
31849
+ function installCommand(packageManager) {
31850
+ switch (packageManager) {
31851
+ case "bun":
31852
+ return "bun add @dryui/ui";
31853
+ case "pnpm":
31854
+ return "pnpm add @dryui/ui";
31855
+ case "yarn":
31856
+ return "yarn add @dryui/ui";
31857
+ default:
31858
+ return "npm install @dryui/ui";
31859
+ }
31860
+ }
31861
+ function buildThemeImportLines(spec) {
31862
+ return ` import '${spec.themeImports.default}';
31863
+ import '${spec.themeImports.dark}';`;
31864
+ }
31865
+ function buildThemeImportSnippet(spec) {
31866
+ return ["<script>", buildThemeImportLines(spec), "</script>"].join(`
31867
+ `);
31868
+ }
31869
+ function buildRootLayoutSnippet(spec) {
31870
+ return [
31871
+ "<script>",
31872
+ buildThemeImportLines(spec),
31873
+ "",
31874
+ " let { children } = $props();",
31875
+ "</script>",
31876
+ "",
31877
+ "{@render children()}"
31878
+ ].join(`
31879
+ `);
31880
+ }
31881
+ function buildThemeImportCssSnippet(spec) {
31882
+ return [`@import '${spec.themeImports.default}';`, `@import '${spec.themeImports.dark}';`].join(`
31883
+ `);
31884
+ }
31885
+ function getSuggestedTarget(root, explicitTarget) {
31886
+ if (explicitTarget)
31887
+ return resolve(root ?? process.cwd(), explicitTarget);
31888
+ if (!root)
31889
+ return null;
31890
+ const rootPage = resolve(root, "src/routes/+page.svelte");
31891
+ return existsSync(rootPage) ? rootPage : null;
31892
+ }
31893
+ function getImportStatement(name, component, subpath = false) {
31894
+ if (subpath && component.import === "@dryui/ui") {
31895
+ return `import { ${name} } from '${component.import}/${componentDir(name)}';`;
31896
+ }
31897
+ return `import { ${name} } from '${component.import}';`;
31898
+ }
31899
+ function findComponent(spec, query) {
31900
+ const exact = spec.components[query];
31901
+ if (exact)
31902
+ return { name: query, def: exact };
31903
+ const lower = query.toLowerCase();
31904
+ for (const [name, def] of Object.entries(spec.components)) {
31905
+ if (name.toLowerCase() === lower)
31906
+ return { name, def };
31907
+ }
31908
+ return null;
31909
+ }
31910
+ function detectProject(spec, inputPath) {
31911
+ const start = resolveStart(inputPath);
31912
+ const packageJsonPath = findUp(start, "package.json");
31913
+ const root = packageJsonPath ? dirname(packageJsonPath) : null;
31914
+ const dependencyNames = getDependencyNames(readPackageJson(packageJsonPath));
31915
+ const framework = detectFramework(dependencyNames);
31916
+ const appHtmlPath = root ? resolve(root, "src/app.html") : null;
31917
+ const appCssPath = root ? resolve(root, "src/app.css") : null;
31918
+ const rootLayoutPath = root ? resolve(root, "src/routes/+layout.svelte") : null;
31919
+ const rootPagePath = root ? resolve(root, "src/routes/+page.svelte") : null;
31920
+ const appHtml = appHtmlPath && existsSync(appHtmlPath) ? appHtmlPath : null;
31921
+ const appCss = appCssPath && existsSync(appCssPath) ? appCssPath : null;
31922
+ const rootLayout = rootLayoutPath && existsSync(rootLayoutPath) ? rootLayoutPath : null;
31923
+ const rootPage = rootPagePath && existsSync(rootPagePath) ? rootPagePath : null;
31924
+ const themeImportFiles = rootLayout && importsAppCss(rootLayout) ? [rootLayout, appCss] : [rootLayout];
31925
+ const defaultImported = hasAnyImport(themeImportFiles, spec.themeImports.default);
31926
+ const darkImported = hasAnyImport(themeImportFiles, spec.themeImports.dark);
31927
+ const themeAuto = appHtml ? hasThemeAuto(appHtml) : false;
31928
+ const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!themeAuto);
31929
+ const warnings = [];
31930
+ if (!packageJsonPath)
31931
+ warnings.push("No package.json found above the provided path.");
31932
+ if (framework === "unknown")
31933
+ warnings.push("DryUI planning currently targets Svelte and SvelteKit projects.");
31934
+ return {
31935
+ inputPath: start,
31936
+ root,
31937
+ packageJsonPath,
31938
+ framework,
31939
+ packageManager: detectPackageManager(root),
31940
+ status: buildStatus(framework, Boolean(packageJsonPath), stepsNeeded),
31941
+ dependencies: {
31942
+ ui: dependencyNames.has("@dryui/ui"),
31943
+ primitives: dependencyNames.has("@dryui/primitives")
31944
+ },
31945
+ files: {
31946
+ appHtml,
31947
+ appCss,
31948
+ rootLayout,
31949
+ rootPage
31950
+ },
31951
+ theme: {
31952
+ defaultImported,
31953
+ darkImported,
31954
+ themeAuto
31955
+ },
31956
+ warnings
31957
+ };
31958
+ }
31959
+ function planInstall(spec, inputPath) {
31960
+ const detection = detectProject(spec, inputPath);
31961
+ const steps = [];
31962
+ if (detection.status === "unsupported") {
31963
+ steps.push({
31964
+ kind: "blocked",
31965
+ status: "blocked",
31966
+ title: "Project detection incomplete",
31967
+ description: detection.warnings.join(" ") || "DryUI install planning requires a Svelte or SvelteKit project with a package.json."
31968
+ });
31969
+ return { detection, steps };
31970
+ }
31971
+ if (!detection.dependencies.ui) {
31972
+ steps.push({
31973
+ kind: "install-package",
31974
+ status: "pending",
31975
+ title: "Install @dryui/ui",
31976
+ description: "Add the styled DryUI package to the current project.",
31977
+ command: installCommand(detection.packageManager)
31978
+ });
31979
+ }
31980
+ if (!detection.theme.defaultImported || !detection.theme.darkImported) {
31981
+ if (detection.files.appCss && detection.files.rootLayout && importsAppCss(detection.files.rootLayout)) {
31982
+ steps.push({
31983
+ kind: "edit-file",
31984
+ status: "pending",
31985
+ title: "Add theme imports to app.css",
31986
+ description: "Prepend the two @import lines from the snippet to the TOP of the existing src/app.css file, before any other CSS rules. Do not create a second file.",
31987
+ path: detection.files.appCss,
31988
+ snippet: buildThemeImportCssSnippet(spec)
31989
+ });
31990
+ } else if (!detection.files.rootLayout) {
31991
+ const path = detection.root ? resolve(detection.root, "src/routes/+layout.svelte") : null;
31992
+ steps.push({
31993
+ kind: "create-file",
31994
+ status: "pending",
31995
+ title: "Create root layout with theme imports",
31996
+ description: "Create the file at the path below with the snippet as its full content. The file must include {@render children()} or pages will not render.",
31997
+ ...path ? { path } : {},
31998
+ snippet: buildRootLayoutSnippet(spec)
31999
+ });
32000
+ } else {
32001
+ steps.push({
32002
+ kind: "edit-file",
32003
+ status: "pending",
32004
+ title: "Add theme imports to the root layout",
32005
+ description: "Add the two import lines from the snippet into the EXISTING <script> block in this file. Do not create a second <script> block. If no <script> block exists, add one at the top of the file.",
32006
+ path: detection.files.rootLayout,
32007
+ snippet: buildThemeImportSnippet(spec)
32008
+ });
32009
+ }
32010
+ }
32011
+ if (!detection.files.appHtml) {
32012
+ steps.push({
32013
+ kind: "blocked",
32014
+ status: "blocked",
32015
+ title: "app.html not found",
32016
+ description: "DryUI expects src/app.html so the document can default to theme-auto mode."
32017
+ });
32018
+ } else if (!detection.theme.themeAuto) {
32019
+ steps.push({
32020
+ kind: "edit-file",
32021
+ status: "pending",
32022
+ title: "Set html theme mode to auto",
32023
+ 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.',
32024
+ path: detection.files.appHtml,
32025
+ snippet: 'class="theme-auto"'
32026
+ });
32027
+ }
32028
+ if (steps.length === 0) {
32029
+ steps.push({
32030
+ kind: "note",
32031
+ status: "done",
32032
+ title: "DryUI install plan is complete",
32033
+ description: "The project already has @dryui/ui, theme imports, and theme-auto configured."
32034
+ });
32035
+ }
32036
+ return { detection, steps };
32037
+ }
32038
+ function planAdd(spec, query, options = {}) {
32039
+ const installPlan = planInstall(spec, options.cwd);
32040
+ const component = findComponent(spec, query);
32041
+ if (component) {
32042
+ const target = getSuggestedTarget(installPlan.detection.root, options.target);
32043
+ const steps = [];
32044
+ const warnings = [...installPlan.detection.warnings];
32045
+ if (installPlan.steps.some((step) => step.status === "pending" || step.status === "blocked")) {
32046
+ steps.push({
32047
+ kind: "note",
32048
+ status: "info",
32049
+ title: "Complete install plan first",
32050
+ description: "Apply the install plan before inserting DryUI components into project files."
32051
+ });
32052
+ }
32053
+ steps.push(target ? {
32054
+ kind: "edit-file",
32055
+ status: "pending",
32056
+ title: "Insert component into the target file",
32057
+ description: "Add the import and snippet below to the chosen Svelte file.",
32058
+ path: target,
32059
+ snippet: `${getImportStatement(component.name, component.def, options.subpath)}
32060
+
32061
+ ${component.def.example}`
32062
+ } : {
32063
+ kind: "note",
32064
+ status: "info",
32065
+ title: "Choose a target Svelte file",
32066
+ description: "No root page was found. Pick a target file and reuse the import and snippet in this plan."
32067
+ });
32068
+ return {
32069
+ detection: installPlan.detection,
32070
+ installPlan,
32071
+ targetType: "component",
32072
+ name: component.name,
32073
+ importStatement: getImportStatement(component.name, component.def, options.subpath),
32074
+ snippet: component.def.example,
32075
+ target,
32076
+ steps,
32077
+ warnings
32078
+ };
32079
+ }
32080
+ throw new Error(`Unknown component: "${query}"`);
32081
+ }
32082
+
32083
+ // ../mcp/dist/toon.js
32084
+ function esc(value) {
32085
+ if (value.includes(",") || value.includes(`
32086
+ `)) {
32087
+ return `"${value.replace(/"/g, '""')}"`;
32088
+ }
32089
+ return value;
32090
+ }
32091
+ function header(resource, count, fields) {
32092
+ return `${resource}[${count}]{${fields.join(",")}}:`;
32093
+ }
32094
+ function row(...values) {
32095
+ return " " + values.map((v) => esc(String(v))).join(",");
32096
+ }
32097
+ function truncate(text, maxLen, hint) {
32098
+ if (text.length <= maxLen)
32099
+ return text;
32100
+ return `(truncated, ${text.length} chars -- ${hint})`;
32101
+ }
32102
+ function buildContextualHelp(ctx) {
32103
+ const hints = [];
32104
+ switch (ctx.command) {
32105
+ case "info":
32106
+ if (ctx.componentName) {
32107
+ hints.push(`compose "${ctx.componentName.toLowerCase()}" -- see composition patterns`);
32108
+ hints.push(`add ${ctx.componentName} -- get starter snippet`);
32109
+ }
32110
+ break;
32111
+ case "list":
32112
+ hints.push("info <Component> -- see full API reference");
32113
+ break;
32114
+ case "compose":
32115
+ if (ctx.componentName) {
32116
+ hints.push(`info ${ctx.componentName} -- full API reference`);
32117
+ hints.push(`add ${ctx.componentName} -- starter snippet`);
32118
+ }
32119
+ break;
32120
+ case "review":
32121
+ if (ctx.hasErrors) {
32122
+ hints.push("info <Component> -- check API for errored component");
32123
+ hints.push("diagnose <file.css> -- check theme if theme warnings present");
32124
+ } else if (ctx.isEmpty) {
32125
+ hints.push("lint . -- check entire workspace");
32126
+ }
32127
+ break;
32128
+ case "diagnose":
32129
+ if (ctx.hasErrors) {
32130
+ hints.push('compose "app shell" -- get correct theme setup');
32131
+ } else if (ctx.isEmpty) {
32132
+ hints.push("review <file.svelte> -- validate component usage");
32133
+ }
32134
+ break;
32135
+ case "doctor":
32136
+ case "lint":
32137
+ if (ctx.hasFindings) {
32138
+ hints.push("lint --max-severity error -- focus on errors only");
32139
+ hints.push("review <file.svelte> -- check specific file");
32140
+ } else if (ctx.isEmpty) {
32141
+ hints.push("detect -- verify project setup");
32142
+ }
32143
+ break;
32144
+ case "detect":
32145
+ if (ctx.status === "partial" || ctx.status === "unsupported") {
32146
+ hints.push("install -- see step-by-step setup plan");
32147
+ } else if (ctx.status === "ready") {
32148
+ hints.push("list -- see available components");
32149
+ hints.push('compose "app shell" -- get root layout template');
32150
+ }
32151
+ break;
32152
+ case "install":
32153
+ hints.push("detect -- verify project after completing steps");
32154
+ break;
32155
+ }
32156
+ return hints;
32157
+ }
32158
+ function formatHelp(hints) {
32159
+ if (hints.length === 0)
32160
+ return "";
32161
+ const lines = [`next[${hints.length}]:`];
32162
+ for (const hint of hints) {
32163
+ lines.push(" " + hint);
32164
+ }
32165
+ return lines.join(`
32166
+ `);
32167
+ }
32168
+ function toonComponent(name, def, opts) {
32169
+ const full = opts?.full ?? false;
32170
+ const lines = [];
32171
+ lines.push(`${name} -- ${def.description}`, `category: ${def.category} | tags: ${def.tags.join(",")}`, `import: import { ${name} } from '${def.import}'`, `compound: ${def.compound}`);
32172
+ if (def.compound && def.structure?.tree.length) {
32173
+ lines.push("", header("structure", def.structure.tree.length, ["node"]));
32174
+ for (const node of def.structure.tree) {
32175
+ lines.push(" " + node);
32176
+ }
32177
+ if (def.structure.note)
32178
+ lines.push(` note: ${def.structure.note}`);
32179
+ }
32180
+ if (def.compound && def.parts) {
32181
+ const partEntries = Object.entries(def.parts);
32182
+ lines.push("", header("parts", partEntries.length, ["part"]));
32183
+ for (const [partName, partDef] of partEntries) {
32184
+ lines.push(` ${name}.${partName}`);
32185
+ const props = Object.entries(partDef.props ?? {});
32186
+ if (props.length > 0) {
32187
+ for (const [propName, propDef] of props) {
32188
+ const flags = [propDef.type];
32189
+ if (propDef.required)
32190
+ flags.push("required");
32191
+ if (propDef.default !== undefined)
32192
+ flags.push(`default:${propDef.default}`);
32193
+ if (propDef.acceptedValues?.length)
32194
+ flags.push(`values:${propDef.acceptedValues.join("|")}`);
32195
+ lines.push(` ${propName}: ${flags.join(" | ")}`);
32196
+ }
32197
+ }
32198
+ }
32199
+ } else if (def.props) {
32200
+ const propEntries = Object.entries(def.props);
32201
+ if (propEntries.length > 0) {
32202
+ lines.push("", header("props", propEntries.length, ["name", "type", "required", "default"]));
32203
+ for (const [propName, propDef] of propEntries) {
32204
+ lines.push(row(propName, propDef.type, propDef.required ? "yes" : "no", propDef.default ?? "-"));
32205
+ }
32206
+ }
32207
+ }
32208
+ const cssEntries = Object.entries(def.cssVars);
32209
+ if (cssEntries.length > 0) {
32210
+ lines.push("", header("css-vars", cssEntries.length, ["name", "description"]));
32211
+ for (const [varName, desc] of cssEntries) {
32212
+ lines.push(row(varName, desc));
32213
+ }
32214
+ }
32215
+ if (def.dataAttributes.length > 0) {
32216
+ lines.push("", header("data-attrs", def.dataAttributes.length, ["name", "values"]));
32217
+ for (const attr of def.dataAttributes) {
32218
+ lines.push(row(attr.name, attr.values?.join("|") ?? "-"));
32219
+ }
32220
+ }
32221
+ if (def.example) {
32222
+ const example = full ? def.example : truncate(def.example, 400, `use add ${name} for full snippet`);
32223
+ lines.push("", "example:", example);
32224
+ }
32225
+ const help = buildContextualHelp({ command: "info", componentName: name });
32226
+ if (help.length > 0) {
32227
+ lines.push("", formatHelp(help));
32228
+ }
32229
+ return lines.join(`
32230
+ `);
32231
+ }
32232
+ function toonComponentList(components, category) {
32233
+ const entries = Object.entries(components);
32234
+ const filtered = category ? entries.filter(([, def]) => def.category.toLowerCase() === category.toLowerCase()) : entries;
32235
+ if (filtered.length === 0) {
32236
+ const cats = [...new Set(entries.map(([, d]) => d.category))].sort();
32237
+ return `components[0]: no matches
32238
+ available categories: ${cats.join(", ")}`;
32239
+ }
32240
+ const groups = {};
32241
+ for (const entry of filtered) {
32242
+ const cat = entry[1].category;
32243
+ (groups[cat] ??= []).push(entry);
32244
+ }
32245
+ const lines = [header("components", filtered.length, ["name", "category", "compound", "description"])];
32246
+ const sortedCats = Object.keys(groups).sort();
32247
+ for (const cat of sortedCats) {
32248
+ const items = groups[cat] ?? [];
32249
+ for (const [name, def] of items) {
32250
+ lines.push(row(name, cat, def.compound, def.description));
32251
+ }
32252
+ }
32253
+ const help = buildContextualHelp({ command: "list" });
32254
+ if (help.length > 0) {
32255
+ lines.push("", formatHelp(help));
32256
+ }
32257
+ return lines.join(`
32258
+ `);
32259
+ }
32260
+ function toonComposition(results, opts) {
32261
+ const full = opts?.full ?? false;
32262
+ const lines = [];
32263
+ const totalMatches = results.componentMatches.length + results.recipeMatches.length;
32264
+ if (totalMatches === 0) {
32265
+ return "matches[0]: none";
32266
+ }
32267
+ for (const comp of results.componentMatches) {
32268
+ lines.push(`-- ${comp.component}: ${comp.useWhen}`);
32269
+ for (const alt of comp.alternatives) {
32270
+ const snippet = full ? alt.snippet : truncate(alt.snippet, 500, `use info ${alt.component} for full snippet`);
32271
+ lines.push(` ${alt.rank}. ${alt.component} (${alt.useWhen})`);
32272
+ lines.push(snippet.split(`
32273
+ `).map((l) => " " + l).join(`
32274
+ `));
32275
+ }
32276
+ for (const ap of comp.antiPatterns) {
32277
+ lines.push(` anti-pattern: ${ap.pattern}`);
32278
+ lines.push(` reason: ${ap.reason} | fix: ${ap.fix}`);
32279
+ }
32280
+ if (comp.combinesWith.length) {
32281
+ lines.push(` combines-with: ${comp.combinesWith.join(",")}`);
32282
+ }
32283
+ lines.push("");
32284
+ }
32285
+ for (const recipe of results.recipeMatches) {
32286
+ const snippet = full ? recipe.snippet : truncate(recipe.snippet, 500, `use compose "${recipe.name}" --full for complete code`);
32287
+ lines.push(`-- recipe: ${recipe.name}`);
32288
+ lines.push(` ${recipe.description}`);
32289
+ lines.push(` components: ${recipe.components.join(",")}`);
32290
+ lines.push(" code:");
32291
+ lines.push(snippet.split(`
32292
+ `).map((l) => " " + l).join(`
32293
+ `));
32294
+ lines.push("");
32295
+ }
32296
+ const firstComponent = results.componentMatches[0]?.alternatives[0]?.component ?? results.recipeMatches[0]?.components[0] ?? undefined;
32297
+ const ctx = { command: "compose" };
32298
+ if (firstComponent)
32299
+ ctx.componentName = firstComponent;
32300
+ const help = buildContextualHelp(ctx);
32301
+ if (help.length > 0) {
32302
+ lines.push(formatHelp(help));
32303
+ }
32304
+ return lines.join(`
32305
+ `).trimEnd();
32306
+ }
32307
+ function toonReviewResult(result) {
32308
+ const lines = [];
32309
+ const hasBlockers = result.issues.some((i) => i.severity === "error");
32310
+ const autoFixable = result.issues.filter((i) => i.fix !== null).length;
32311
+ if (result.issues.length === 0) {
32312
+ lines.push("issues[0]: clean");
32313
+ lines.push(`hasBlockers: false | autoFixable: 0`);
32314
+ } else {
32315
+ lines.push(header("issues", result.issues.length, ["severity", "line", "code", "message"]));
32316
+ for (const issue of result.issues) {
32317
+ lines.push(row(issue.severity, issue.line, issue.code, issue.message));
32318
+ if (issue.fix) {
32319
+ lines.push(` fix: ${issue.fix}`);
32320
+ }
32321
+ }
32322
+ lines.push(`hasBlockers: ${hasBlockers} | autoFixable: ${autoFixable}`);
32323
+ }
32324
+ lines.push(result.summary);
32325
+ const help = buildContextualHelp({
32326
+ command: "review",
32327
+ hasErrors: hasBlockers,
32328
+ isEmpty: result.issues.length === 0
32329
+ });
32330
+ if (help.length > 0) {
32331
+ lines.push("", formatHelp(help));
32332
+ }
32333
+ return lines.join(`
32334
+ `);
32335
+ }
32336
+ function toonDiagnoseResult(result) {
32337
+ const lines = [];
32338
+ const { variables } = result;
32339
+ const missingCount = result.issues.filter((i) => i.code === "missing-token").length;
32340
+ const totalRequired = variables.required + missingCount;
32341
+ const coverage = totalRequired > 0 ? Math.round(variables.required / totalRequired * 100) : 100;
32342
+ lines.push(`tokens: ${variables.found} found, ${variables.required} required, ${variables.extra} extra | coverage: ${coverage}%`);
32343
+ if (result.issues.length === 0) {
32344
+ lines.push("issues[0]: clean");
32345
+ } else {
32346
+ lines.push(header("issues", result.issues.length, ["severity", "code", "variable", "message"]));
32347
+ for (const issue of result.issues) {
32348
+ lines.push(row(issue.severity, issue.code, issue.variable, issue.message));
32349
+ if (issue.fix) {
32350
+ lines.push(` fix: ${issue.fix}`);
32351
+ }
32352
+ }
32353
+ }
32354
+ lines.push(result.summary);
32355
+ const hasErrors = result.issues.some((i) => i.severity === "error");
32356
+ const help = buildContextualHelp({
32357
+ command: "diagnose",
32358
+ hasErrors,
32359
+ isEmpty: result.issues.length === 0
32360
+ });
32361
+ if (help.length > 0) {
32362
+ lines.push("", formatHelp(help));
32363
+ }
32364
+ return lines.join(`
32365
+ `);
32366
+ }
32367
+ var MAX_FINDINGS_DEFAULT = 50;
32368
+ function toonWorkspaceReport(report, opts) {
32369
+ const full = opts?.full ?? false;
32370
+ const title = opts?.title ?? "workspace";
32371
+ const command = opts?.command ?? (title.includes("lint") ? "lint" : "doctor");
32372
+ const lines = [];
32373
+ lines.push(`${title} | root: ${report.root}`);
32374
+ lines.push(`scanned: ${report.scannedFiles} files | errors: ${report.summary.error} | warnings: ${report.summary.warning} | info: ${report.summary.info}`);
32375
+ if (report.summary.byRule && Object.keys(report.summary.byRule).length > 0) {
32376
+ const sorted = Object.entries(report.summary.byRule).sort(([, a], [, b]) => b - a);
32377
+ const topRule = sorted[0];
32378
+ if (topRule) {
32379
+ lines.push(`top-rule: ${topRule[0]} (${topRule[1]} occurrences)`);
32380
+ }
32381
+ }
32382
+ if (report.findings.length === 0) {
32383
+ lines.push("findings[0]: clean");
32384
+ } else {
32385
+ const findings = full ? report.findings : report.findings.slice(0, MAX_FINDINGS_DEFAULT);
32386
+ const truncated = !full && report.findings.length > MAX_FINDINGS_DEFAULT;
32387
+ lines.push(header("findings", findings.length, ["severity", "rule", "file", "line", "message"]));
32388
+ for (const f of findings) {
32389
+ lines.push(row(f.severity, f.ruleId, f.file, f.line ?? "-", f.message));
32390
+ if (f.suggestedFixes.length > 0) {
32391
+ for (const fix of f.suggestedFixes) {
32392
+ lines.push(` fix: ${fix.description}${fix.replacement ? ` -> ${fix.replacement}` : ""}`);
32393
+ }
32394
+ }
32395
+ }
32396
+ if (truncated) {
32397
+ lines.push(` (showing ${MAX_FINDINGS_DEFAULT} of ${report.findings.length} -- use --full to see all)`);
32398
+ }
32399
+ }
32400
+ if (report.warnings.length > 0) {
32401
+ lines.push("", header("warnings", report.warnings.length, ["message"]));
32402
+ for (const w of report.warnings) {
32403
+ lines.push(" " + w);
32404
+ }
32405
+ }
32406
+ const help = buildContextualHelp({
32407
+ command,
32408
+ hasFindings: report.findings.length > 0,
32409
+ isEmpty: report.findings.length === 0
32410
+ });
32411
+ if (help.length > 0) {
32412
+ lines.push("", formatHelp(help));
32413
+ }
32414
+ return lines.join(`
32415
+ `);
32416
+ }
32417
+ function toonProjectDetection(detection) {
32418
+ const lines = [];
32419
+ lines.push(`project: ${detection.status} | framework: ${detection.framework} | pkg-manager: ${detection.packageManager}`);
32420
+ lines.push(`root: ${detection.root ?? "(not found)"}`);
32421
+ lines.push(`deps: ui=${detection.dependencies.ui}, primitives=${detection.dependencies.primitives}`);
32422
+ lines.push(`theme: default=${detection.theme.defaultImported}, dark=${detection.theme.darkImported}, auto=${detection.theme.themeAuto}`);
32423
+ if (detection.warnings.length > 0) {
32424
+ lines.push(header("warnings", detection.warnings.length, ["message"]));
32425
+ for (const w of detection.warnings) {
32426
+ lines.push(" " + w);
32427
+ }
32428
+ }
32429
+ const help = buildContextualHelp({ command: "detect", status: detection.status });
32430
+ if (help.length > 0) {
32431
+ lines.push("", formatHelp(help));
32432
+ }
32433
+ return lines.join(`
32434
+ `);
32435
+ }
32436
+ function toonError(code, message, suggestions) {
32437
+ const lines = [`error[1]{code,message}: ${esc(code)},${esc(message)}`];
32438
+ if (suggestions?.length) {
32439
+ lines.push(header("suggestions", suggestions.length, ["value"]));
32440
+ for (const s of suggestions) {
32441
+ lines.push(" " + s);
32442
+ }
32443
+ }
32444
+ return lines.join(`
32445
+ `);
32446
+ }
32447
+
32448
+ // ../mcp/src/spec-formatters.ts
32449
+ var DIR_OVERRIDES2 = {
32450
+ QRCode: "qr-code"
32451
+ };
32452
+ function componentDir2(name) {
32453
+ return DIR_OVERRIDES2[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
32454
+ }
31735
32455
  function pad(str, width) {
31736
32456
  return str.length >= width ? str : str + " ".repeat(width - str.length);
31737
32457
  }
@@ -31744,9 +32464,9 @@ function indent(text, spaces) {
31744
32464
  function getSubpathImport(name, def) {
31745
32465
  if (def.import !== "@dryui/ui")
31746
32466
  return null;
31747
- return `import { ${name} } from '${def.import}/${componentDir(name)}'`;
32467
+ return `import { ${name} } from '${def.import}/${componentDir2(name)}'`;
31748
32468
  }
31749
- function findComponent(query, components) {
32469
+ function findComponent2(query, components) {
31750
32470
  const exact = components[query];
31751
32471
  if (exact)
31752
32472
  return { name: query, def: exact };
@@ -31921,9 +32641,23 @@ function formatSimple(name, def) {
31921
32641
  }
31922
32642
 
31923
32643
  // src/run.ts
31924
- function runCommand(result) {
32644
+ import { existsSync as existsSync2 } from "node:fs";
32645
+ function fileNotFound(filePath, toon) {
32646
+ if (existsSync2(filePath))
32647
+ return null;
32648
+ return {
32649
+ output: "",
32650
+ error: toon ? toonError("not-found", `File not found: ${filePath}`) : `File not found: ${filePath}`,
32651
+ exitCode: 1
32652
+ };
32653
+ }
32654
+ function runCommand(result, mode = "text") {
31925
32655
  if (result.error) {
31926
- console.error(result.error);
32656
+ if (mode === "text") {
32657
+ console.error(result.error);
32658
+ } else {
32659
+ console.log(result.error);
32660
+ }
31927
32661
  } else {
31928
32662
  console.log(result.output);
31929
32663
  }
@@ -31931,13 +32665,20 @@ function runCommand(result) {
31931
32665
  }
31932
32666
 
31933
32667
  // src/commands/info.ts
31934
- function findComponent2(spec, query) {
31935
- return findComponent(query, spec.components);
32668
+ function findComponent3(spec, query) {
32669
+ return findComponent2(query, spec.components);
31936
32670
  }
31937
- function getInfo(query, spec) {
31938
- const result = findComponent2(spec, query);
32671
+ function getInfo(query, spec, options) {
32672
+ const result = findComponent3(spec, query);
31939
32673
  if (!result) {
31940
32674
  const available = Object.keys(spec.components).sort();
32675
+ if (options?.toon) {
32676
+ return {
32677
+ output: "",
32678
+ error: toonError("not-found", `Unknown component: "${query}"`, available),
32679
+ exitCode: 1
32680
+ };
32681
+ }
31941
32682
  const error = [
31942
32683
  `Unknown component: "${query}"`,
31943
32684
  "",
@@ -31948,27 +32689,37 @@ function getInfo(query, spec) {
31948
32689
  return { output: "", error, exitCode: 1 };
31949
32690
  }
31950
32691
  const { name, def } = result;
32692
+ if (options?.toon) {
32693
+ return { output: toonComponent(name, def, { full: options?.full }), error: null, exitCode: 0 };
32694
+ }
31951
32695
  const output = def.compound ? formatCompound(name, def) : formatSimple(name, def);
31952
32696
  return { output, error: null, exitCode: 0 };
31953
32697
  }
31954
32698
  function runInfo(args, spec) {
31955
32699
  if (args.length === 0 || args[0] === "--help") {
31956
- console.log("Usage: dryui info <component>");
32700
+ console.log("Usage: dryui info <component> [--toon] [--full]");
31957
32701
  console.log("");
31958
32702
  console.log("Show API reference for a DryUI component or composed output.");
31959
32703
  console.log("");
32704
+ console.log("Options:");
32705
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32706
+ console.log(" --full Include full example code (disables truncation)");
32707
+ console.log("");
31960
32708
  console.log("Examples:");
31961
32709
  console.log(" dryui info Button");
31962
32710
  console.log(" dryui info card # case-insensitive");
31963
- console.log(' dryui info "hero sections"');
32711
+ console.log(" dryui info Button --toon");
31964
32712
  process.exit(args[0] === "--help" ? 0 : 1);
31965
32713
  }
31966
- const query = args[0];
32714
+ const toon = args.includes("--toon");
32715
+ const full = args.includes("--full");
32716
+ const query = args.find((a) => !a.startsWith("--"));
31967
32717
  if (!query) {
31968
32718
  console.error("Error: missing component name");
31969
32719
  process.exit(1);
31970
32720
  }
31971
- runCommand(getInfo(query, spec));
32721
+ const mode = toon ? "toon" : "text";
32722
+ runCommand(getInfo(query, spec, { toon, full }), mode);
31972
32723
  }
31973
32724
 
31974
32725
  // src/format.ts
@@ -32028,7 +32779,7 @@ function formatWorkspaceReport(report, options) {
32028
32779
  // src/commands/project-planner.ts
32029
32780
  function importStatement(name, def, subpath = false) {
32030
32781
  if (subpath && def.import === "@dryui/ui") {
32031
- return `import { ${name} } from '${def.import}/${componentDir(name)}';`;
32782
+ return `import { ${name} } from '${def.import}/${componentDir2(name)}';`;
32032
32783
  }
32033
32784
  return `import { ${name} } from '${def.import}';`;
32034
32785
  }
@@ -32150,7 +32901,7 @@ function formatAddPlan(plan) {
32150
32901
  `);
32151
32902
  }
32152
32903
  function buildAddSnippet(query, spec, options = {}) {
32153
- const component = findComponent2(spec, query);
32904
+ const component = findComponent3(spec, query);
32154
32905
  if (!component) {
32155
32906
  return {
32156
32907
  output: "",
@@ -32166,342 +32917,6 @@ function buildAddSnippet(query, spec, options = {}) {
32166
32917
  };
32167
32918
  }
32168
32919
 
32169
- // ../mcp/src/project-planner.ts
32170
- import { existsSync, readFileSync, statSync } from "node:fs";
32171
- import { dirname, resolve } from "node:path";
32172
- var DIR_OVERRIDES2 = {
32173
- QRCode: "qr-code"
32174
- };
32175
- function componentDir2(name) {
32176
- return DIR_OVERRIDES2[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
32177
- }
32178
- function resolveStart(inputPath) {
32179
- const candidate = resolve(inputPath ?? process.cwd());
32180
- return existsSync(candidate) && statSync(candidate).isFile() ? dirname(candidate) : candidate;
32181
- }
32182
- function findUp(start, fileName) {
32183
- let current = start;
32184
- while (true) {
32185
- const candidate = resolve(current, fileName);
32186
- if (existsSync(candidate))
32187
- return candidate;
32188
- const parent = dirname(current);
32189
- if (parent === current)
32190
- return null;
32191
- current = parent;
32192
- }
32193
- }
32194
- function findUpAny(start, fileNames) {
32195
- let current = start;
32196
- while (true) {
32197
- for (const fileName of fileNames) {
32198
- const candidate = resolve(current, fileName);
32199
- if (existsSync(candidate))
32200
- return candidate;
32201
- }
32202
- const parent = dirname(current);
32203
- if (parent === current)
32204
- return null;
32205
- current = parent;
32206
- }
32207
- }
32208
- function detectPackageManager(root) {
32209
- if (!root)
32210
- return "unknown";
32211
- const lockfilePath = findUpAny(root, [
32212
- "bun.lock",
32213
- "bun.lockb",
32214
- "pnpm-lock.yaml",
32215
- "package-lock.json",
32216
- "yarn.lock"
32217
- ]);
32218
- if (!lockfilePath)
32219
- return "unknown";
32220
- if (lockfilePath.endsWith("bun.lock") || lockfilePath.endsWith("bun.lockb"))
32221
- return "bun";
32222
- if (lockfilePath.endsWith("pnpm-lock.yaml"))
32223
- return "pnpm";
32224
- if (lockfilePath.endsWith("package-lock.json"))
32225
- return "npm";
32226
- if (lockfilePath.endsWith("yarn.lock"))
32227
- return "yarn";
32228
- return "unknown";
32229
- }
32230
- function readPackageJson(packageJsonPath) {
32231
- if (!packageJsonPath)
32232
- return null;
32233
- return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
32234
- }
32235
- function getDependencyNames(pkg) {
32236
- return new Set([
32237
- ...Object.keys(pkg?.dependencies ?? {}),
32238
- ...Object.keys(pkg?.devDependencies ?? {})
32239
- ]);
32240
- }
32241
- function detectFramework(dependencyNames) {
32242
- if (dependencyNames.has("@sveltejs/kit"))
32243
- return "sveltekit";
32244
- if (dependencyNames.has("svelte"))
32245
- return "svelte";
32246
- return "unknown";
32247
- }
32248
- function hasImport(filePath, importPath) {
32249
- if (!filePath)
32250
- return false;
32251
- return readFileSync(filePath, "utf-8").includes(importPath);
32252
- }
32253
- function hasThemeAuto(appHtmlPath) {
32254
- if (!appHtmlPath)
32255
- return false;
32256
- return readFileSync(appHtmlPath, "utf-8").includes("theme-auto");
32257
- }
32258
- function hasAnyImport(filePaths, importPath) {
32259
- return filePaths.some((filePath) => hasImport(filePath, importPath));
32260
- }
32261
- function importsAppCss(rootLayoutPath) {
32262
- if (!rootLayoutPath)
32263
- return false;
32264
- const content = readFileSync(rootLayoutPath, "utf-8");
32265
- return content.includes("app.css");
32266
- }
32267
- function buildStatus(framework, hasPackageJson, stepsNeeded) {
32268
- if (!hasPackageJson || framework === "unknown")
32269
- return "unsupported";
32270
- return stepsNeeded === 0 ? "ready" : "partial";
32271
- }
32272
- function installCommand(packageManager) {
32273
- switch (packageManager) {
32274
- case "bun":
32275
- return "bun add @dryui/ui";
32276
- case "pnpm":
32277
- return "pnpm add @dryui/ui";
32278
- case "yarn":
32279
- return "yarn add @dryui/ui";
32280
- default:
32281
- return "npm install @dryui/ui";
32282
- }
32283
- }
32284
- function buildThemeImportLines(spec) {
32285
- return ` import '${spec.themeImports.default}';
32286
- import '${spec.themeImports.dark}';`;
32287
- }
32288
- function buildThemeImportSnippet(spec) {
32289
- return ["<script>", buildThemeImportLines(spec), "</script>"].join(`
32290
- `);
32291
- }
32292
- function buildRootLayoutSnippet(spec) {
32293
- return [
32294
- "<script>",
32295
- buildThemeImportLines(spec),
32296
- "",
32297
- " let { children } = $props();",
32298
- "</script>",
32299
- "",
32300
- "{@render children()}"
32301
- ].join(`
32302
- `);
32303
- }
32304
- function buildThemeImportCssSnippet(spec) {
32305
- return [`@import '${spec.themeImports.default}';`, `@import '${spec.themeImports.dark}';`].join(`
32306
- `);
32307
- }
32308
- function getSuggestedTarget(root, explicitTarget) {
32309
- if (explicitTarget)
32310
- return resolve(root ?? process.cwd(), explicitTarget);
32311
- if (!root)
32312
- return null;
32313
- const rootPage = resolve(root, "src/routes/+page.svelte");
32314
- return existsSync(rootPage) ? rootPage : null;
32315
- }
32316
- function getImportStatement(name, component, subpath = false) {
32317
- if (subpath && component.import === "@dryui/ui") {
32318
- return `import { ${name} } from '${component.import}/${componentDir2(name)}';`;
32319
- }
32320
- return `import { ${name} } from '${component.import}';`;
32321
- }
32322
- function findComponent3(spec, query) {
32323
- const exact = spec.components[query];
32324
- if (exact)
32325
- return { name: query, def: exact };
32326
- const lower = query.toLowerCase();
32327
- for (const [name, def] of Object.entries(spec.components)) {
32328
- if (name.toLowerCase() === lower)
32329
- return { name, def };
32330
- }
32331
- return null;
32332
- }
32333
- function detectProject(spec, inputPath) {
32334
- const start = resolveStart(inputPath);
32335
- const packageJsonPath = findUp(start, "package.json");
32336
- const root = packageJsonPath ? dirname(packageJsonPath) : null;
32337
- const dependencyNames = getDependencyNames(readPackageJson(packageJsonPath));
32338
- const framework = detectFramework(dependencyNames);
32339
- const appHtmlPath = root ? resolve(root, "src/app.html") : null;
32340
- const appCssPath = root ? resolve(root, "src/app.css") : null;
32341
- const rootLayoutPath = root ? resolve(root, "src/routes/+layout.svelte") : null;
32342
- const rootPagePath = root ? resolve(root, "src/routes/+page.svelte") : null;
32343
- const appHtml = appHtmlPath && existsSync(appHtmlPath) ? appHtmlPath : null;
32344
- const appCss = appCssPath && existsSync(appCssPath) ? appCssPath : null;
32345
- const rootLayout = rootLayoutPath && existsSync(rootLayoutPath) ? rootLayoutPath : null;
32346
- const rootPage = rootPagePath && existsSync(rootPagePath) ? rootPagePath : null;
32347
- const themeImportFiles = rootLayout && importsAppCss(rootLayout) ? [rootLayout, appCss] : [rootLayout];
32348
- const defaultImported = hasAnyImport(themeImportFiles, spec.themeImports.default);
32349
- const darkImported = hasAnyImport(themeImportFiles, spec.themeImports.dark);
32350
- const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!(appHtml && hasThemeAuto(appHtml)));
32351
- const warnings = [];
32352
- if (!packageJsonPath)
32353
- warnings.push("No package.json found above the provided path.");
32354
- if (framework === "unknown")
32355
- warnings.push("DryUI planning currently targets Svelte and SvelteKit projects.");
32356
- return {
32357
- inputPath: start,
32358
- root,
32359
- packageJsonPath,
32360
- framework,
32361
- packageManager: detectPackageManager(root),
32362
- status: buildStatus(framework, Boolean(packageJsonPath), stepsNeeded),
32363
- dependencies: {
32364
- ui: dependencyNames.has("@dryui/ui"),
32365
- primitives: dependencyNames.has("@dryui/primitives")
32366
- },
32367
- files: {
32368
- appHtml,
32369
- appCss,
32370
- rootLayout,
32371
- rootPage
32372
- },
32373
- theme: {
32374
- defaultImported,
32375
- darkImported,
32376
- themeAuto: hasThemeAuto(appHtml)
32377
- },
32378
- warnings
32379
- };
32380
- }
32381
- function planInstall(spec, inputPath) {
32382
- const detection = detectProject(spec, inputPath);
32383
- const steps = [];
32384
- if (detection.status === "unsupported") {
32385
- steps.push({
32386
- kind: "blocked",
32387
- status: "blocked",
32388
- title: "Project detection incomplete",
32389
- description: detection.warnings.join(" ") || "DryUI install planning requires a Svelte or SvelteKit project with a package.json."
32390
- });
32391
- return { detection, steps };
32392
- }
32393
- if (!detection.dependencies.ui) {
32394
- steps.push({
32395
- kind: "install-package",
32396
- status: "pending",
32397
- title: "Install @dryui/ui",
32398
- description: "Add the styled DryUI package to the current project.",
32399
- command: installCommand(detection.packageManager)
32400
- });
32401
- }
32402
- if (!detection.theme.defaultImported || !detection.theme.darkImported) {
32403
- if (detection.files.appCss && detection.files.rootLayout && importsAppCss(detection.files.rootLayout)) {
32404
- steps.push({
32405
- kind: "edit-file",
32406
- status: "pending",
32407
- title: "Add theme imports to app.css",
32408
- description: "Prepend the two @import lines from the snippet to the TOP of the existing src/app.css file, before any other CSS rules. Do not create a second file.",
32409
- path: detection.files.appCss,
32410
- snippet: buildThemeImportCssSnippet(spec)
32411
- });
32412
- } else if (!detection.files.rootLayout) {
32413
- const path = detection.root ? resolve(detection.root, "src/routes/+layout.svelte") : null;
32414
- steps.push({
32415
- kind: "create-file",
32416
- status: "pending",
32417
- title: "Create root layout with theme imports",
32418
- description: "Create the file at the path below with the snippet as its full content. The file must include {@render children()} or pages will not render.",
32419
- ...path ? { path } : {},
32420
- snippet: buildRootLayoutSnippet(spec)
32421
- });
32422
- } else {
32423
- steps.push({
32424
- kind: "edit-file",
32425
- status: "pending",
32426
- title: "Add theme imports to the root layout",
32427
- description: "Add the two import lines from the snippet into the EXISTING <script> block in this file. Do not create a second <script> block. If no <script> block exists, add one at the top of the file.",
32428
- path: detection.files.rootLayout,
32429
- snippet: buildThemeImportSnippet(spec)
32430
- });
32431
- }
32432
- }
32433
- if (!detection.files.appHtml) {
32434
- steps.push({
32435
- kind: "blocked",
32436
- status: "blocked",
32437
- title: "app.html not found",
32438
- description: "DryUI expects src/app.html so the document can default to theme-auto mode."
32439
- });
32440
- } else if (!detection.theme.themeAuto) {
32441
- steps.push({
32442
- kind: "edit-file",
32443
- status: "pending",
32444
- title: "Set html theme mode to auto",
32445
- 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.',
32446
- path: detection.files.appHtml,
32447
- snippet: '<html lang="en" class="theme-auto">'
32448
- });
32449
- }
32450
- if (steps.length === 0) {
32451
- steps.push({
32452
- kind: "note",
32453
- status: "done",
32454
- title: "DryUI install plan is complete",
32455
- description: "The project already has @dryui/ui, theme imports, and theme-auto configured."
32456
- });
32457
- }
32458
- return { detection, steps };
32459
- }
32460
- function planAdd(spec, query, options = {}) {
32461
- const installPlan = planInstall(spec, options.cwd);
32462
- const component = findComponent3(spec, query);
32463
- if (component) {
32464
- const target = getSuggestedTarget(installPlan.detection.root, options.target);
32465
- const steps = [];
32466
- const warnings = [...installPlan.detection.warnings];
32467
- if (installPlan.steps.some((step) => step.status === "pending" || step.status === "blocked")) {
32468
- steps.push({
32469
- kind: "note",
32470
- status: "info",
32471
- title: "Complete install plan first",
32472
- description: "Apply the install plan before inserting DryUI components into project files."
32473
- });
32474
- }
32475
- steps.push(target ? {
32476
- kind: "edit-file",
32477
- status: "pending",
32478
- title: "Insert component into the target file",
32479
- description: "Add the import and snippet below to the chosen Svelte file.",
32480
- path: target,
32481
- snippet: `${getImportStatement(component.name, component.def, options.subpath)}
32482
-
32483
- ${component.def.example}`
32484
- } : {
32485
- kind: "note",
32486
- status: "info",
32487
- title: "Choose a target Svelte file",
32488
- description: "No root page was found. Pick a target file and reuse the import and snippet in this plan."
32489
- });
32490
- return {
32491
- detection: installPlan.detection,
32492
- installPlan,
32493
- targetType: "component",
32494
- name: component.name,
32495
- importStatement: getImportStatement(component.name, component.def, options.subpath),
32496
- snippet: component.def.example,
32497
- target,
32498
- steps,
32499
- warnings
32500
- };
32501
- }
32502
- throw new Error(`Unknown component: "${query}"`);
32503
- }
32504
-
32505
32920
  // src/commands/add.ts
32506
32921
  function parseProjectInput(positionals, spec) {
32507
32922
  const first = positionals[0];
@@ -32513,8 +32928,8 @@ function parseProjectInput(positionals, spec) {
32513
32928
  return { query: first };
32514
32929
  }
32515
32930
  if (positionals.length === 2) {
32516
- const firstIsQuery = Boolean(findComponent2(spec, first));
32517
- const secondIsQuery = Boolean(findComponent2(spec, second));
32931
+ const firstIsQuery = Boolean(findComponent3(spec, first));
32932
+ const secondIsQuery = Boolean(findComponent3(spec, second));
32518
32933
  if (firstIsQuery && !secondIsQuery) {
32519
32934
  return { query: first, cwd: second };
32520
32935
  }
@@ -32641,6 +33056,7 @@ function runAdd(args, spec) {
32641
33056
  // src/commands/detect.ts
32642
33057
  function parseDetectArgs(args) {
32643
33058
  let json = false;
33059
+ let toon = false;
32644
33060
  let path;
32645
33061
  for (let index = 0;index < args.length; index++) {
32646
33062
  const arg = args[index];
@@ -32650,6 +33066,10 @@ function parseDetectArgs(args) {
32650
33066
  json = true;
32651
33067
  continue;
32652
33068
  }
33069
+ if (arg === "--toon") {
33070
+ toon = true;
33071
+ continue;
33072
+ }
32653
33073
  if (arg.startsWith("--")) {
32654
33074
  continue;
32655
33075
  }
@@ -32657,24 +33077,29 @@ function parseDetectArgs(args) {
32657
33077
  path = arg;
32658
33078
  }
32659
33079
  }
32660
- return { json, path };
33080
+ return { json, toon, path };
32661
33081
  }
32662
33082
  function getDetect(inputPath, spec, options = {}) {
32663
33083
  const detection = detectProject(spec, inputPath);
33084
+ if (options.toon) {
33085
+ return { output: toonProjectDetection(detection), error: null, exitCode: 0 };
33086
+ }
32664
33087
  return options.json ? { output: JSON.stringify(detection, null, 2), error: null, exitCode: 0 } : { output: formatProjectDetection(detection), error: null, exitCode: 0 };
32665
33088
  }
32666
33089
  function runDetect(args, spec) {
32667
33090
  if (args[0] === "--help") {
32668
- console.log("Usage: dryui detect [--json] [path]");
33091
+ console.log("Usage: dryui detect [--json] [--toon] [path]");
32669
33092
  console.log("");
32670
33093
  console.log("Detect the current DryUI project setup.");
32671
33094
  console.log("");
32672
33095
  console.log("Options:");
32673
33096
  console.log(" --json Output raw JSON instead of formatted text");
33097
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32674
33098
  process.exit(0);
32675
33099
  }
32676
- const { json, path } = parseDetectArgs(args);
32677
- runCommand(getDetect(path, spec, { json }));
33100
+ const { json, toon, path } = parseDetectArgs(args);
33101
+ const mode = toon ? "toon" : json ? "json" : "text";
33102
+ runCommand(getDetect(path, spec, { json, toon }), mode);
32678
33103
  }
32679
33104
 
32680
33105
  // src/commands/install.ts
@@ -32781,7 +33206,14 @@ function groupByCategory(spec) {
32781
33206
  }
32782
33207
  return groups;
32783
33208
  }
32784
- function getList(category, spec, options = {}) {
33209
+ function getList(category, spec, options) {
33210
+ if (options?.toon) {
33211
+ return {
33212
+ output: toonComponentList(spec.components, category ?? undefined),
33213
+ error: null,
33214
+ exitCode: 0
33215
+ };
33216
+ }
32785
33217
  const sections = [];
32786
33218
  const validCategories = getCategories(spec);
32787
33219
  if (category && !validCategories.includes(category)) {
@@ -32815,18 +33247,20 @@ function getList(category, spec, options = {}) {
32815
33247
  }
32816
33248
  function runList(args, spec) {
32817
33249
  if (args[0] === "--help") {
32818
- console.log("Usage: dryui list [--category <category>]");
33250
+ console.log("Usage: dryui list [--category <category>] [--toon]");
32819
33251
  console.log("");
32820
33252
  console.log("List DryUI components.");
32821
33253
  console.log("");
32822
33254
  console.log("Options:");
32823
33255
  console.log(" --category <cat> Filter by category");
33256
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32824
33257
  console.log("");
32825
33258
  console.log("Categories:");
32826
33259
  const cats = getCategories(spec);
32827
33260
  console.log(` ${cats.join(", ")}`);
32828
33261
  process.exit(0);
32829
33262
  }
33263
+ const toon = args.includes("--toon");
32830
33264
  let filterCategory = null;
32831
33265
  const catIdx = args.indexOf("--category");
32832
33266
  if (catIdx !== -1) {
@@ -32837,11 +33271,12 @@ function runList(args, spec) {
32837
33271
  }
32838
33272
  filterCategory = catValue.toLowerCase();
32839
33273
  }
32840
- runCommand(getList(filterCategory, spec));
33274
+ const mode = toon ? "toon" : "text";
33275
+ runCommand(getList(filterCategory, spec, { toon }), mode);
32841
33276
  }
32842
33277
 
32843
33278
  // src/commands/review.ts
32844
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
33279
+ import { readFileSync as readFileSync2 } from "node:fs";
32845
33280
 
32846
33281
  // ../mcp/src/utils.ts
32847
33282
  function buildLineOffsets(text) {
@@ -33542,12 +33977,19 @@ function reviewComponent(code, spec, filename) {
33542
33977
 
33543
33978
  // src/commands/review.ts
33544
33979
  function getReview(filePath, spec, options) {
33545
- if (!existsSync2(filePath)) {
33546
- return { output: "", error: `File not found: ${filePath}`, exitCode: 1 };
33547
- }
33980
+ const missing = fileNotFound(filePath, options?.toon);
33981
+ if (missing)
33982
+ return missing;
33548
33983
  const code = readFileSync2(filePath, "utf-8");
33549
33984
  const filename = filePath.split("/").pop();
33550
33985
  const result = reviewComponent(code, spec, filename);
33986
+ if (options?.toon) {
33987
+ return {
33988
+ output: toonReviewResult(result),
33989
+ error: null,
33990
+ exitCode: result.issues.some((i) => i.severity === "error") ? 1 : 0
33991
+ };
33992
+ }
33551
33993
  if (options?.json) {
33552
33994
  return {
33553
33995
  output: JSON.stringify(result, null, 2),
@@ -33574,7 +34016,7 @@ function getReview(filePath, spec, options) {
33574
34016
  }
33575
34017
  function runReview(args, spec) {
33576
34018
  if (args.length === 0 || args[0] === "--help") {
33577
- console.log("Usage: dryui review [--json] <file.svelte>");
34019
+ console.log("Usage: dryui review [--json] [--toon] <file.svelte>");
33578
34020
  console.log("");
33579
34021
  console.log("Validate a Svelte component against the DryUI spec.");
33580
34022
  console.log("Checks for incorrect component usage, missing props,");
@@ -33582,20 +34024,23 @@ function runReview(args, spec) {
33582
34024
  console.log("");
33583
34025
  console.log("Options:");
33584
34026
  console.log(" --json Output raw JSON instead of formatted text");
34027
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
33585
34028
  process.exit(args[0] === "--help" ? 0 : 1);
33586
34029
  }
33587
34030
  const jsonMode = args.includes("--json");
33588
- const fileArgs = args.filter((a) => a !== "--json");
34031
+ const toon = args.includes("--toon");
34032
+ const fileArgs = args.filter((a) => !a.startsWith("--"));
33589
34033
  const filePath = fileArgs[0];
33590
34034
  if (!filePath) {
33591
34035
  console.error("Error: missing file path");
33592
34036
  process.exit(1);
33593
34037
  }
33594
- runCommand(getReview(filePath, spec, { json: jsonMode }));
34038
+ const mode = toon ? "toon" : jsonMode ? "json" : "text";
34039
+ runCommand(getReview(filePath, spec, { json: jsonMode, toon }), mode);
33595
34040
  }
33596
34041
 
33597
34042
  // src/commands/diagnose.ts
33598
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
34043
+ import { readFileSync as readFileSync3 } from "node:fs";
33599
34044
 
33600
34045
  // ../mcp/src/theme-checker.ts
33601
34046
  function capture(match, index) {
@@ -34351,11 +34796,18 @@ function diagnoseTheme(css, spec) {
34351
34796
 
34352
34797
  // src/commands/diagnose.ts
34353
34798
  function getDiagnose(filePath, spec, options) {
34354
- if (!existsSync3(filePath)) {
34355
- return { output: "", error: `File not found: ${filePath}`, exitCode: 1 };
34356
- }
34799
+ const missing = fileNotFound(filePath, options?.toon);
34800
+ if (missing)
34801
+ return missing;
34357
34802
  const css = readFileSync3(filePath, "utf-8");
34358
34803
  const result = diagnoseTheme(css, spec);
34804
+ if (options?.toon) {
34805
+ return {
34806
+ output: toonDiagnoseResult(result),
34807
+ error: null,
34808
+ exitCode: result.issues.some((i) => i.severity === "error") ? 1 : 0
34809
+ };
34810
+ }
34359
34811
  if (options?.json) {
34360
34812
  return {
34361
34813
  output: JSON.stringify(result, null, 2),
@@ -34388,26 +34840,29 @@ function getDiagnose(filePath, spec, options) {
34388
34840
  }
34389
34841
  function runDiagnose(args, spec) {
34390
34842
  if (args.length === 0 || args[0] === "--help") {
34391
- console.log("Usage: dryui diagnose [--json] <file.css>");
34843
+ console.log("Usage: dryui diagnose [--json] [--toon] <file.css>");
34392
34844
  console.log("");
34393
34845
  console.log("Validate theme CSS for missing tokens, value errors,");
34394
34846
  console.log("contrast issues, and component token problems.");
34395
34847
  console.log("");
34396
34848
  console.log("Options:");
34397
34849
  console.log(" --json Output raw JSON instead of formatted text");
34850
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
34398
34851
  process.exit(args[0] === "--help" ? 0 : 1);
34399
34852
  }
34400
34853
  const jsonMode = args.includes("--json");
34401
- const fileArgs = args.filter((a) => a !== "--json");
34854
+ const toon = args.includes("--toon");
34855
+ const fileArgs = args.filter((a) => !a.startsWith("--"));
34402
34856
  const filePath = fileArgs[0];
34403
34857
  if (!filePath) {
34404
34858
  console.error("Error: missing file path");
34405
34859
  process.exit(1);
34406
34860
  }
34407
- runCommand(getDiagnose(filePath, spec, { json: jsonMode }));
34861
+ const mode = toon ? "toon" : jsonMode ? "json" : "text";
34862
+ runCommand(getDiagnose(filePath, spec, { json: jsonMode, toon }), mode);
34408
34863
  }
34409
34864
 
34410
- // src/commands/compose.ts
34865
+ // ../mcp/dist/composition-search.js
34411
34866
  var STOP_WORDS = new Set([
34412
34867
  "a",
34413
34868
  "an",
@@ -34473,81 +34928,58 @@ function scoreText(tokens, text) {
34473
34928
  const lower = text.toLowerCase();
34474
34929
  return tokens.reduce((score, t) => score + (lower.includes(t) ? 1 : 0), 0);
34475
34930
  }
34476
- function getCompose(query, spec) {
34477
- if (!spec.composition) {
34478
- return {
34479
- output: "",
34480
- error: "No composition data available. Rebuild the MCP package.",
34481
- exitCode: 1
34482
- };
34483
- }
34931
+ function searchComposition(composition, query) {
34484
34932
  const q = query.toLowerCase();
34485
34933
  const tokens = tokenize(query);
34486
34934
  const exactComponentMatches = [];
34487
34935
  const exactRecipeMatches = [];
34488
- for (const comp of Object.values(spec.composition.components)) {
34936
+ for (const comp of Object.values(composition.components)) {
34489
34937
  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))) {
34490
34938
  exactComponentMatches.push(comp);
34491
34939
  }
34492
34940
  }
34493
- for (const recipe of Object.values(spec.composition.recipes)) {
34941
+ for (const recipe of Object.values(composition.recipes)) {
34494
34942
  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))) {
34495
34943
  exactRecipeMatches.push(recipe);
34496
34944
  }
34497
34945
  }
34498
- let componentMatches;
34499
- let recipeMatches;
34500
34946
  if (exactComponentMatches.length || exactRecipeMatches.length) {
34501
- componentMatches = exactComponentMatches;
34502
- recipeMatches = exactRecipeMatches;
34503
- } else if (tokens.length) {
34504
- const scoredComponents = [];
34505
- for (const comp of Object.values(spec.composition.components)) {
34506
- const corpus = [
34507
- comp.component,
34508
- comp.useWhen,
34509
- ...comp.alternatives.flatMap((a) => [a.component, a.useWhen]),
34510
- ...comp.antiPatterns.map((ap) => ap.pattern),
34511
- ...comp.combinesWith
34512
- ].join(" ");
34513
- const score = scoreText(tokens, corpus);
34514
- if (score > 0)
34515
- scoredComponents.push({ comp, score });
34516
- }
34517
- const scoredRecipes = [];
34518
- for (const recipe of Object.values(spec.composition.recipes)) {
34519
- const corpus = [recipe.name, recipe.description, ...recipe.tags, ...recipe.components].join(" ");
34520
- const score = scoreText(tokens, corpus);
34521
- if (score > 0)
34522
- scoredRecipes.push({ recipe, score });
34523
- }
34524
- scoredComponents.sort((a, b) => b.score - a.score);
34525
- scoredRecipes.sort((a, b) => b.score - a.score);
34526
- const minScore = Math.max(1, Math.floor(tokens.length * 0.3));
34527
- componentMatches = scoredComponents.filter((s) => s.score >= minScore).slice(0, 10).map((s) => s.comp);
34528
- recipeMatches = scoredRecipes.filter((s) => s.score >= minScore).slice(0, 5).map((s) => s.recipe);
34529
- } else {
34530
- componentMatches = [];
34531
- recipeMatches = [];
34532
- }
34533
- if (!componentMatches.length && !recipeMatches.length) {
34534
- return {
34535
- output: "",
34536
- error: `No composition guidance found for "${query}".
34537
-
34538
- Try:
34539
- - A component name (e.g. "DatePicker", "Avatar")
34540
- - A UI concept (e.g. "date input", "image placeholder")
34541
- - A pattern name (e.g. "search-form", "dashboard-page")`,
34542
- exitCode: 1
34543
- };
34544
- }
34947
+ return { componentMatches: exactComponentMatches, recipeMatches: exactRecipeMatches };
34948
+ }
34949
+ if (!tokens.length) {
34950
+ return { componentMatches: [], recipeMatches: [] };
34951
+ }
34952
+ const scoredComponents = [];
34953
+ for (const comp of Object.values(composition.components)) {
34954
+ const corpus = [
34955
+ comp.component,
34956
+ comp.useWhen,
34957
+ ...comp.alternatives.flatMap((a) => [a.component, a.useWhen]),
34958
+ ...comp.antiPatterns.map((ap) => ap.pattern),
34959
+ ...comp.combinesWith
34960
+ ].join(" ");
34961
+ const score = scoreText(tokens, corpus);
34962
+ if (score > 0)
34963
+ scoredComponents.push({ comp, score });
34964
+ }
34965
+ const scoredRecipes = [];
34966
+ for (const recipe of Object.values(composition.recipes)) {
34967
+ const corpus = [recipe.name, recipe.description, ...recipe.tags, ...recipe.components].join(" ");
34968
+ const score = scoreText(tokens, corpus);
34969
+ if (score > 0)
34970
+ scoredRecipes.push({ recipe, score });
34971
+ }
34972
+ scoredComponents.sort((a, b) => b.score - a.score);
34973
+ scoredRecipes.sort((a, b) => b.score - a.score);
34974
+ const minScore = Math.max(1, Math.floor(tokens.length * 0.3));
34975
+ return {
34976
+ componentMatches: scoredComponents.filter((s) => s.score >= minScore).slice(0, 10).map((s) => s.comp),
34977
+ recipeMatches: scoredRecipes.filter((s) => s.score >= minScore).slice(0, 5).map((s) => s.recipe)
34978
+ };
34979
+ }
34980
+ function formatCompositionResult(results) {
34545
34981
  const lines = [];
34546
- lines.push("⚠ SETUP: Root +layout.svelte must import '@dryui/ui/themes/default.css'");
34547
- lines.push(' and dark.css. app.html needs <html class="theme-auto">.');
34548
- lines.push(' Not set up? Run: dryui compose "app shell"');
34549
- lines.push("");
34550
- for (const comp of componentMatches) {
34982
+ for (const comp of results.componentMatches) {
34551
34983
  lines.push(`── ${comp.component} ──────────────────────────────`);
34552
34984
  lines.push(`[DEV GUIDANCE — do not render as page content]`);
34553
34985
  lines.push(`Use: ${comp.component} — ${comp.useWhen}`);
@@ -34570,7 +35002,7 @@ Try:
34570
35002
  }
34571
35003
  lines.push("");
34572
35004
  }
34573
- for (const recipe of recipeMatches) {
35005
+ for (const recipe of results.recipeMatches) {
34574
35006
  lines.push(`── Recipe: ${recipe.name} ──────────────────────────────`);
34575
35007
  lines.push(`[DEV GUIDANCE — do not render as page content]`);
34576
35008
  lines.push(recipe.description);
@@ -34580,29 +35012,88 @@ Try:
34580
35012
  lines.push(recipe.snippet);
34581
35013
  lines.push("");
34582
35014
  }
34583
- return { output: lines.join(`
34584
- `).trimEnd(), error: null, exitCode: 0 };
35015
+ return lines.join(`
35016
+ `);
35017
+ }
35018
+
35019
+ // src/commands/compose.ts
35020
+ var SETUP_PREAMBLE = [
35021
+ "⚠ SETUP: Root +layout.svelte must import '@dryui/ui/themes/default.css'",
35022
+ ' and dark.css. app.html needs <html class="theme-auto">.',
35023
+ ' Not set up? Run: dryui compose "app shell"',
35024
+ ""
35025
+ ].join(`
35026
+ `);
35027
+ function getCompose(query, spec, options) {
35028
+ if (!spec.composition) {
35029
+ return {
35030
+ output: "",
35031
+ error: "No composition data available. Rebuild the MCP package.",
35032
+ exitCode: 1
35033
+ };
35034
+ }
35035
+ const results = searchComposition(spec.composition, query);
35036
+ if (!results.componentMatches.length && !results.recipeMatches.length) {
35037
+ if (options?.toon) {
35038
+ return {
35039
+ output: "",
35040
+ error: toonError("no-results", `No composition guidance for "${query}"`, [
35041
+ "Try a component name (DatePicker, Avatar)",
35042
+ "Try a UI concept (date input, image placeholder)",
35043
+ "Try a pattern name (search-form, dashboard-page)"
35044
+ ]),
35045
+ exitCode: 1
35046
+ };
35047
+ }
35048
+ return {
35049
+ output: "",
35050
+ error: `No composition guidance found for "${query}".
35051
+
35052
+ Try:
35053
+ - A component name (e.g. "DatePicker", "Avatar")
35054
+ - A UI concept (e.g. "date input", "image placeholder")
35055
+ - A pattern name (e.g. "search-form", "dashboard-page")`,
35056
+ exitCode: 1
35057
+ };
35058
+ }
35059
+ if (options?.toon) {
35060
+ return {
35061
+ output: toonComposition(results, { full: options?.full }),
35062
+ error: null,
35063
+ exitCode: 0
35064
+ };
35065
+ }
35066
+ const output = SETUP_PREAMBLE + `
35067
+ ` + formatCompositionResult(results);
35068
+ return { output: output.trimEnd(), error: null, exitCode: 0 };
34585
35069
  }
34586
35070
  function runCompose(args, spec) {
34587
35071
  if (args.length === 0 || args[0] === "--help") {
34588
- console.log("Usage: dryui compose <query>");
35072
+ console.log("Usage: dryui compose <query> [--toon] [--full]");
34589
35073
  console.log("");
34590
35074
  console.log("Look up composition guidance for building UIs with DryUI.");
34591
35075
  console.log("Returns ranked component alternatives, anti-patterns, and recipes.");
34592
35076
  console.log("");
35077
+ console.log("Options:");
35078
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35079
+ console.log(" --full Include full code snippets (disables truncation)");
35080
+ console.log("");
34593
35081
  console.log("Examples:");
34594
35082
  console.log(' dryui compose "search form"');
34595
35083
  console.log(' dryui compose "hotel card"');
34596
- console.log(' dryui compose "travel booking"');
35084
+ console.log(' dryui compose "travel booking" --toon');
34597
35085
  process.exit(args[0] === "--help" ? 0 : 1);
34598
35086
  }
34599
- const query = args.join(" ").trim();
34600
- runCommand(getCompose(query, spec));
35087
+ const toon = args.includes("--toon");
35088
+ const full = args.includes("--full");
35089
+ const query = args.filter((a) => !a.startsWith("--")).join(" ").trim();
35090
+ const mode = toon ? "toon" : "text";
35091
+ runCommand(getCompose(query, spec, { toon, full }), mode);
34601
35092
  }
34602
35093
 
34603
35094
  // ../mcp/src/workspace-audit.ts
34604
35095
  import { execFileSync } from "node:child_process";
34605
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, realpathSync, statSync as statSync2 } from "node:fs";
35096
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync, realpathSync, statSync as statSync2 } from "node:fs";
34606
35097
  import { dirname as dirname2, relative, resolve as resolve2 } from "node:path";
34607
35098
  var DEFAULT_INCLUDE = [
34608
35099
  "packages/ui",
@@ -34635,7 +35126,7 @@ function normalizePath(path) {
34635
35126
  }
34636
35127
  function resolveRoot(inputPath) {
34637
35128
  const candidate = resolve2(inputPath ?? process.cwd());
34638
- if (existsSync4(candidate) && statSync2(candidate).isFile()) {
35129
+ if (existsSync3(candidate) && statSync2(candidate).isFile()) {
34639
35130
  return dirname2(candidate);
34640
35131
  }
34641
35132
  return candidate;
@@ -34706,7 +35197,7 @@ function collectChangedFiles(root) {
34706
35197
  }
34707
35198
  }
34708
35199
  function defaultInclude(root) {
34709
- const included = DEFAULT_INCLUDE.filter((entry) => existsSync4(resolve2(root, entry)));
35200
+ const included = DEFAULT_INCLUDE.filter((entry) => existsSync3(resolve2(root, entry)));
34710
35201
  return included.length > 0 ? included : ["."];
34711
35202
  }
34712
35203
  function isDefaultExcluded(path) {
@@ -34880,6 +35371,8 @@ function parseWorkspaceArgs(args) {
34880
35371
  const exclude = [];
34881
35372
  let path;
34882
35373
  let json = false;
35374
+ let toon = false;
35375
+ let full = false;
34883
35376
  let changed = false;
34884
35377
  let maxSeverity = "info";
34885
35378
  for (let index = 0;index < args.length; index++) {
@@ -34890,6 +35383,14 @@ function parseWorkspaceArgs(args) {
34890
35383
  json = true;
34891
35384
  continue;
34892
35385
  }
35386
+ if (arg === "--toon") {
35387
+ toon = true;
35388
+ continue;
35389
+ }
35390
+ if (arg === "--full") {
35391
+ full = true;
35392
+ continue;
35393
+ }
34893
35394
  if (arg === "--changed") {
34894
35395
  changed = true;
34895
35396
  continue;
@@ -34918,7 +35419,7 @@ function parseWorkspaceArgs(args) {
34918
35419
  if (!path)
34919
35420
  path = arg;
34920
35421
  }
34921
- return { path, options: { json, include, exclude, maxSeverity, changed } };
35422
+ return { path, options: { json, toon, full, include, exclude, maxSeverity, changed } };
34922
35423
  }
34923
35424
 
34924
35425
  // src/commands/doctor.ts
@@ -34931,6 +35432,13 @@ function getDoctor(inputPath, spec, options = {}) {
34931
35432
  ...options.maxSeverity ? { maxSeverity: options.maxSeverity } : {},
34932
35433
  ...options.changed === undefined ? {} : { changed: options.changed }
34933
35434
  });
35435
+ if (options.toon) {
35436
+ return {
35437
+ output: toonWorkspaceReport(report, { title: "doctor", command: "doctor", full: options.full }),
35438
+ error: null,
35439
+ exitCode: 0
35440
+ };
35441
+ }
34934
35442
  return {
34935
35443
  output: formatWorkspaceReport(report, {
34936
35444
  title: "DryUI workspace doctor",
@@ -34950,13 +35458,18 @@ function getDoctor(inputPath, spec, options = {}) {
34950
35458
  }
34951
35459
  function runDoctor(args, spec) {
34952
35460
  if (args[0] === "--help") {
34953
- console.log("Usage: dryui doctor [path] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
35461
+ console.log("Usage: dryui doctor [path] [--toon] [--full] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
34954
35462
  console.log("");
34955
35463
  console.log("Inspect workspace health with human-readable findings.");
35464
+ console.log("");
35465
+ console.log("Options:");
35466
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35467
+ console.log(" --full Show all findings (disables truncation at 50)");
34956
35468
  process.exit(0);
34957
35469
  }
34958
35470
  const { path, options } = parseWorkspaceArgs(args);
34959
- runCommand(getDoctor(path, spec, options));
35471
+ const mode = options.toon ? "toon" : "text";
35472
+ runCommand(getDoctor(path, spec, options), mode);
34960
35473
  }
34961
35474
 
34962
35475
  // src/commands/lint.ts
@@ -34969,6 +35482,13 @@ function getLint(inputPath, spec, options = {}) {
34969
35482
  ...options.changed === undefined ? {} : { changed: options.changed },
34970
35483
  ...options.maxSeverity ? { maxSeverity: options.maxSeverity } : {}
34971
35484
  });
35485
+ if (options.toon) {
35486
+ return {
35487
+ output: toonWorkspaceReport(report, { title: "lint", command: "lint", full: options.full }),
35488
+ error: null,
35489
+ exitCode: report.findings.length > 0 ? 1 : 0
35490
+ };
35491
+ }
34972
35492
  if (options.json) {
34973
35493
  return {
34974
35494
  output: JSON.stringify(report, null, 2),
@@ -34991,23 +35511,28 @@ function getLint(inputPath, spec, options = {}) {
34991
35511
  }
34992
35512
  function runLint(args, spec) {
34993
35513
  if (args[0] === "--help") {
34994
- console.log("Usage: dryui lint [path] [--json] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
35514
+ console.log("Usage: dryui lint [path] [--json] [--toon] [--full] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
34995
35515
  console.log("");
34996
35516
  console.log("Print deterministic workspace findings.");
35517
+ console.log("");
35518
+ console.log("Options:");
35519
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35520
+ console.log(" --full Show all findings (disables truncation at 50)");
34997
35521
  process.exit(0);
34998
35522
  }
34999
35523
  const { path, options } = parseWorkspaceArgs(args);
35000
- runCommand(getLint(path, spec, options));
35524
+ const mode = options.toon ? "toon" : options.json ? "json" : "text";
35525
+ runCommand(getLint(path, spec, options), mode);
35001
35526
  }
35002
35527
 
35003
35528
  // src/commands/feedback.ts
35004
- import { existsSync as existsSync6 } from "node:fs";
35529
+ import { existsSync as existsSync5 } from "node:fs";
35005
35530
  import { spawnSync } from "node:child_process";
35006
35531
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
35007
35532
  import { fileURLToPath } from "node:url";
35008
35533
 
35009
35534
  // ../feedback-server/src/config.ts
35010
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
35535
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
35011
35536
  import { homedir } from "node:os";
35012
35537
  import { dirname as dirname3, join } from "node:path";
35013
35538
  var DEFAULT_FEEDBACK_PORT = 4748;
@@ -35217,7 +35742,7 @@ __export(exports_util, {
35217
35742
  finalizeIssue: () => finalizeIssue,
35218
35743
  extend: () => extend,
35219
35744
  escapeRegex: () => escapeRegex,
35220
- esc: () => esc,
35745
+ esc: () => esc2,
35221
35746
  defineLazy: () => defineLazy,
35222
35747
  createTransparentProxy: () => createTransparentProxy,
35223
35748
  cloneDef: () => cloneDef,
@@ -35368,7 +35893,7 @@ function randomString(length = 10) {
35368
35893
  }
35369
35894
  return str;
35370
35895
  }
35371
- function esc(str) {
35896
+ function esc2(str) {
35372
35897
  return JSON.stringify(str);
35373
35898
  }
35374
35899
  function slugify(input) {
@@ -36853,10 +37378,10 @@ function isValidJWT(token, algorithm = null) {
36853
37378
  const tokensParts = token.split(".");
36854
37379
  if (tokensParts.length !== 3)
36855
37380
  return false;
36856
- const [header] = tokensParts;
36857
- if (!header)
37381
+ const [header2] = tokensParts;
37382
+ if (!header2)
36858
37383
  return false;
36859
- const parsedHeader = JSON.parse(atob(header));
37384
+ const parsedHeader = JSON.parse(atob(header2));
36860
37385
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
36861
37386
  return false;
36862
37387
  if (!parsedHeader.alg)
@@ -37137,7 +37662,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
37137
37662
  const doc = new Doc(["shape", "payload", "ctx"]);
37138
37663
  const normalized = _normalized.value;
37139
37664
  const parseStr = (key) => {
37140
- const k = esc(key);
37665
+ const k = esc2(key);
37141
37666
  return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
37142
37667
  };
37143
37668
  doc.write(`const input = payload.value;`);
@@ -37149,7 +37674,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
37149
37674
  doc.write(`const newResult = {};`);
37150
37675
  for (const key of normalized.keys) {
37151
37676
  const id = ids[key];
37152
- const k = esc(key);
37677
+ const k = esc2(key);
37153
37678
  const schema = shape[key];
37154
37679
  const isOptionalOut = schema?._zod?.optout === "optional";
37155
37680
  doc.write(`const ${id} = ${parseStr(key)};`);
@@ -40733,7 +41258,7 @@ function usage() {
40733
41258
  `);
40734
41259
  }
40735
41260
  function resolveServerEntry() {
40736
- if (existsSync6(SERVER_DIST_PATH))
41261
+ if (existsSync5(SERVER_DIST_PATH))
40737
41262
  return SERVER_DIST_PATH;
40738
41263
  return SERVER_SRC_PATH;
40739
41264
  }
@@ -40831,32 +41356,52 @@ var USAGE = `Usage: dryui <command> [options]
40831
41356
 
40832
41357
  Commands:
40833
41358
  init Print setup snippets for a new DryUI app
40834
- detect [--json] [path] Detect DryUI project setup
40835
- install [--json] [path] Print a project install plan
41359
+ detect [--json] [--toon] [path]
41360
+ Detect DryUI project setup
41361
+ install [--json] [--toon] [path]
41362
+ Print a project install plan
40836
41363
  add <component> Print a copyable starter snippet for a component
40837
- info <component> Show component API reference
40838
- list [--category <cat>] List all components
40839
- compose <query> Look up composition guidance
40840
- review [--json] <file.svelte> Validate a Svelte file against DryUI spec
40841
- diagnose [--json] <file.css> Validate theme CSS
40842
- doctor [path] [--include <glob>] [--exclude <glob>] [--changed]
41364
+ info <component> [--toon] Show component API reference
41365
+ list [--category <cat>] [--toon]
41366
+ List all components
41367
+ compose <query> [--toon] Look up composition guidance
41368
+ review [--json] [--toon] <file.svelte>
41369
+ Validate a Svelte file against DryUI spec
41370
+ diagnose [--json] [--toon] <file.css>
41371
+ Validate theme CSS
41372
+ doctor [path] [--toon] [--include <glob>] [--exclude <glob>] [--changed]
40843
41373
  Inspect workspace health
40844
- lint [path] [--json] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]
41374
+ lint [path] [--json] [--toon] [--include <glob>] [--exclude <glob>] [--changed]
40845
41375
  Print deterministic workspace findings
40846
41376
  feedback <subcommand> Start or inspect the feedback server
40847
41377
 
40848
41378
  Options:
40849
41379
  --help Show help for a command
40850
- --version Show version`;
41380
+ --version Show version
41381
+ --toon Token-optimized output for AI agents (per-command)
41382
+ --full Disable truncation (use with --toon)`;
40851
41383
  function main() {
40852
41384
  const args = process.argv.slice(2);
40853
41385
  const command = args[0];
40854
- if (!command || command === "--help" || command === "-h") {
41386
+ if (command === "--version" || command === "-v") {
41387
+ console.log(VERSION);
41388
+ process.exit(0);
41389
+ }
41390
+ if (command === "--help" || command === "-h") {
40855
41391
  console.log(USAGE);
40856
41392
  process.exit(0);
40857
41393
  }
40858
- if (command === "--version" || command === "-v") {
40859
- console.log(VERSION);
41394
+ if (!command) {
41395
+ try {
41396
+ const detection = detectProject(spec_default, undefined);
41397
+ if (detection.status === "ready" || detection.status === "partial") {
41398
+ console.log(`dryui v${VERSION}
41399
+ `);
41400
+ console.log(toonProjectDetection(detection));
41401
+ process.exit(0);
41402
+ }
41403
+ } catch {}
41404
+ console.log(USAGE);
40860
41405
  process.exit(0);
40861
41406
  }
40862
41407
  const commandArgs = args.slice(1);