@dryui/cli 0.1.2 → 0.3.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 +1071 -506
  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.12",
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>"
@@ -18587,14 +18605,18 @@ var spec_default = {
18587
18605
  ]
18588
18606
  },
18589
18607
  cssVars: {
18608
+ "--dry-alpha-rgb": "Rgb",
18590
18609
  "--dry-color-picker-area-height": "Picker Area Height",
18591
18610
  "--dry-color-picker-area-width": "Picker Area Width",
18592
18611
  "--dry-color-picker-grid-dark": "Picker Grid Dark",
18593
18612
  "--dry-color-picker-grid-light": "Picker Grid Light",
18594
18613
  "--dry-color-picker-grid-size": "Picker Grid Size",
18595
18614
  "--dry-color-picker-indicator-border": "Picker Indicator Border",
18615
+ "--dry-color-picker-indicator-color": "Picker Indicator Color",
18616
+ "--dry-color-picker-indicator-left": "Picker Indicator Left",
18596
18617
  "--dry-color-picker-indicator-shadow": "Picker Indicator Shadow",
18597
18618
  "--dry-color-picker-indicator-size": "Picker Indicator Size",
18619
+ "--dry-color-picker-indicator-top": "Picker Indicator Top",
18598
18620
  "--dry-color-picker-slider-thumb-bg": "Picker Slider Thumb Bg",
18599
18621
  "--dry-color-picker-slider-thumb-border": "Picker Slider Thumb Border",
18600
18622
  "--dry-color-picker-slider-thumb-shadow": "Picker Slider Thumb Shadow",
@@ -18605,7 +18627,9 @@ var spec_default = {
18605
18627
  "--dry-color-picker-swatch-size": "Picker Swatch Size",
18606
18628
  "--dry-color-picker-width": "Picker Width",
18607
18629
  "--dry-slider-thumb-size": "Thumb size",
18608
- "--dry-slider-track-height": "Track height"
18630
+ "--dry-slider-track-height": "Track height",
18631
+ "--dry-swatch-color": "Text color",
18632
+ "--dry-swatch-opacity": "Opacity"
18609
18633
  },
18610
18634
  dataAttributes: [
18611
18635
  {
@@ -19081,7 +19105,13 @@ var spec_default = {
19081
19105
  ],
19082
19106
  note: "Forwards <div> attributes via rest props."
19083
19107
  },
19084
- cssVars: {},
19108
+ cssVars: {
19109
+ "--dry-spotlight-color": "Text color",
19110
+ "--dry-spotlight-intensity": "Intensity",
19111
+ "--dry-spotlight-radius": "Border radius",
19112
+ "--dry-spotlight-x": "X",
19113
+ "--dry-spotlight-y": "Y"
19114
+ },
19085
19115
  dataAttributes: [
19086
19116
  {
19087
19117
  name: "data-active",
@@ -19172,8 +19202,10 @@ var spec_default = {
19172
19202
  "--dry-aurora-color-2": "Color 2",
19173
19203
  "--dry-aurora-color-3": "Color 3",
19174
19204
  "--dry-aurora-duration": "Duration",
19205
+ "--dry-aurora-intensity": "Intensity",
19175
19206
  "--dry-aurora-shift": "Shift",
19176
- "--dry-aurora-surface": "Surface"
19207
+ "--dry-aurora-surface": "Surface",
19208
+ "--dry-aurora-waviness": "Waviness"
19177
19209
  },
19178
19210
  dataAttributes: [
19179
19211
  {
@@ -19835,6 +19867,9 @@ var spec_default = {
19835
19867
  dataAttributes: [
19836
19868
  {
19837
19869
  name: "data-virtual-list"
19870
+ },
19871
+ {
19872
+ name: "data-virtual-list-inner"
19838
19873
  }
19839
19874
  ],
19840
19875
  example: "<VirtualList>Content</VirtualList>"
@@ -22137,6 +22172,7 @@ var spec_default = {
22137
22172
  "--dry-image-comparison-handle-shadow": "Comparison Handle Shadow",
22138
22173
  "--dry-image-comparison-handle-width": "Comparison Handle Width",
22139
22174
  "--dry-image-comparison-handle-z-index": "Comparison Handle Z Index",
22175
+ "--dry-image-comparison-position": "Comparison Position",
22140
22176
  "--dry-image-comparison-radius": "Comparison Radius"
22141
22177
  },
22142
22178
  dataAttributes: [
@@ -24452,7 +24488,13 @@ var spec_default = {
24452
24488
  ],
24453
24489
  note: "Forwards <div> attributes via rest props."
24454
24490
  },
24455
- cssVars: {},
24491
+ cssVars: {
24492
+ "--dry-beam-angle": "Angle",
24493
+ "--dry-beam-color": "Text color",
24494
+ "--dry-beam-intensity": "Intensity",
24495
+ "--dry-beam-speed": "Speed",
24496
+ "--dry-beam-width": "Width"
24497
+ },
24456
24498
  dataAttributes: [
24457
24499
  {
24458
24500
  name: "data-beam"
@@ -24571,7 +24613,14 @@ var spec_default = {
24571
24613
  ],
24572
24614
  note: "Forwards <div> attributes via rest props."
24573
24615
  },
24574
- cssVars: {},
24616
+ cssVars: {
24617
+ "--dry-rays-blend": "Blend",
24618
+ "--dry-rays-color": "Text color",
24619
+ "--dry-rays-cx": "Cx",
24620
+ "--dry-rays-cy": "Cy",
24621
+ "--dry-rays-intensity": "Intensity",
24622
+ "--dry-rays-speed": "Speed"
24623
+ },
24575
24624
  dataAttributes: [
24576
24625
  {
24577
24626
  name: "data-animated"
@@ -29925,21 +29974,10 @@ var spec_default = {
29925
29974
  </body>
29926
29975
  </html>
29927
29976
 
29928
- <!-- 2. src/app.css — global reset with DryUI tokens -->
29977
+ <!-- 2. src/app.css — import themes (resets are built in) -->
29929
29978
  @import '@dryui/ui/themes/default.css';
29930
29979
  @import '@dryui/ui/themes/dark.css';
29931
29980
 
29932
- *, *::before, *::after { box-sizing: border-box; margin: 0; }
29933
-
29934
- html {
29935
- font-family: var(--dry-font-sans);
29936
- color: var(--dry-color-text-strong);
29937
- background: var(--dry-color-bg-base);
29938
- -webkit-font-smoothing: antialiased;
29939
- }
29940
-
29941
- body { margin: 0; min-height: 100dvh; }
29942
-
29943
29981
  <!-- 3. src/routes/+layout.svelte — root layout -->
29944
29982
  <script>
29945
29983
  import '../app.css';
@@ -31725,13 +31763,715 @@ body { margin: 0; min-height: 100dvh; }
31725
31763
  ]
31726
31764
  };
31727
31765
 
31728
- // ../mcp/src/spec-formatters.ts
31766
+ // ../mcp/src/project-planner.ts
31767
+ import { existsSync, readFileSync, statSync } from "node:fs";
31768
+ import { dirname, resolve } from "node:path";
31729
31769
  var DIR_OVERRIDES = {
31730
31770
  QRCode: "qr-code"
31731
31771
  };
31732
31772
  function componentDir(name) {
31733
31773
  return DIR_OVERRIDES[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
31734
31774
  }
31775
+ function resolveStart(inputPath) {
31776
+ const candidate = resolve(inputPath ?? process.cwd());
31777
+ return existsSync(candidate) && statSync(candidate).isFile() ? dirname(candidate) : candidate;
31778
+ }
31779
+ function findUp(start, fileName) {
31780
+ let current = start;
31781
+ while (true) {
31782
+ const candidate = resolve(current, fileName);
31783
+ if (existsSync(candidate))
31784
+ return candidate;
31785
+ const parent = dirname(current);
31786
+ if (parent === current)
31787
+ return null;
31788
+ current = parent;
31789
+ }
31790
+ }
31791
+ function findUpAny(start, fileNames) {
31792
+ let current = start;
31793
+ while (true) {
31794
+ for (const fileName of fileNames) {
31795
+ const candidate = resolve(current, fileName);
31796
+ if (existsSync(candidate))
31797
+ return candidate;
31798
+ }
31799
+ const parent = dirname(current);
31800
+ if (parent === current)
31801
+ return null;
31802
+ current = parent;
31803
+ }
31804
+ }
31805
+ function detectPackageManager(root) {
31806
+ if (!root)
31807
+ return "unknown";
31808
+ const lockfilePath = findUpAny(root, [
31809
+ "bun.lock",
31810
+ "bun.lockb",
31811
+ "pnpm-lock.yaml",
31812
+ "package-lock.json",
31813
+ "yarn.lock"
31814
+ ]);
31815
+ if (!lockfilePath)
31816
+ return "unknown";
31817
+ if (lockfilePath.endsWith("bun.lock") || lockfilePath.endsWith("bun.lockb"))
31818
+ return "bun";
31819
+ if (lockfilePath.endsWith("pnpm-lock.yaml"))
31820
+ return "pnpm";
31821
+ if (lockfilePath.endsWith("package-lock.json"))
31822
+ return "npm";
31823
+ if (lockfilePath.endsWith("yarn.lock"))
31824
+ return "yarn";
31825
+ return "unknown";
31826
+ }
31827
+ function readPackageJson(packageJsonPath) {
31828
+ if (!packageJsonPath)
31829
+ return null;
31830
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
31831
+ }
31832
+ function getDependencyNames(pkg) {
31833
+ return new Set([
31834
+ ...Object.keys(pkg?.dependencies ?? {}),
31835
+ ...Object.keys(pkg?.devDependencies ?? {})
31836
+ ]);
31837
+ }
31838
+ function detectFramework(dependencyNames) {
31839
+ if (dependencyNames.has("@sveltejs/kit"))
31840
+ return "sveltekit";
31841
+ if (dependencyNames.has("svelte"))
31842
+ return "svelte";
31843
+ return "unknown";
31844
+ }
31845
+ function hasImport(filePath, importPath) {
31846
+ if (!filePath)
31847
+ return false;
31848
+ return readFileSync(filePath, "utf-8").includes(importPath);
31849
+ }
31850
+ function hasThemeAuto(appHtmlPath) {
31851
+ if (!appHtmlPath)
31852
+ return false;
31853
+ return readFileSync(appHtmlPath, "utf-8").includes("theme-auto");
31854
+ }
31855
+ function hasAnyImport(filePaths, importPath) {
31856
+ return filePaths.some((filePath) => hasImport(filePath, importPath));
31857
+ }
31858
+ function importsAppCss(rootLayoutPath) {
31859
+ if (!rootLayoutPath)
31860
+ return false;
31861
+ const content = readFileSync(rootLayoutPath, "utf-8");
31862
+ return content.includes("app.css");
31863
+ }
31864
+ function buildStatus(framework, hasPackageJson, stepsNeeded) {
31865
+ if (!hasPackageJson || framework === "unknown")
31866
+ return "unsupported";
31867
+ return stepsNeeded === 0 ? "ready" : "partial";
31868
+ }
31869
+ function installCommand(packageManager) {
31870
+ switch (packageManager) {
31871
+ case "bun":
31872
+ return "bun add @dryui/ui";
31873
+ case "pnpm":
31874
+ return "pnpm add @dryui/ui";
31875
+ case "yarn":
31876
+ return "yarn add @dryui/ui";
31877
+ default:
31878
+ return "npm install @dryui/ui";
31879
+ }
31880
+ }
31881
+ function buildThemeImportLines(spec) {
31882
+ return ` import '${spec.themeImports.default}';
31883
+ import '${spec.themeImports.dark}';`;
31884
+ }
31885
+ function buildThemeImportSnippet(spec) {
31886
+ return ["<script>", buildThemeImportLines(spec), "</script>"].join(`
31887
+ `);
31888
+ }
31889
+ function buildRootLayoutSnippet(spec) {
31890
+ return [
31891
+ "<script>",
31892
+ buildThemeImportLines(spec),
31893
+ "",
31894
+ " let { children } = $props();",
31895
+ "</script>",
31896
+ "",
31897
+ "{@render children()}"
31898
+ ].join(`
31899
+ `);
31900
+ }
31901
+ function buildThemeImportCssSnippet(spec) {
31902
+ return [`@import '${spec.themeImports.default}';`, `@import '${spec.themeImports.dark}';`].join(`
31903
+ `);
31904
+ }
31905
+ function getSuggestedTarget(root, explicitTarget) {
31906
+ if (explicitTarget)
31907
+ return resolve(root ?? process.cwd(), explicitTarget);
31908
+ if (!root)
31909
+ return null;
31910
+ const rootPage = resolve(root, "src/routes/+page.svelte");
31911
+ return existsSync(rootPage) ? rootPage : null;
31912
+ }
31913
+ function getImportStatement(name, component, subpath = false) {
31914
+ if (subpath && component.import === "@dryui/ui") {
31915
+ return `import { ${name} } from '${component.import}/${componentDir(name)}';`;
31916
+ }
31917
+ return `import { ${name} } from '${component.import}';`;
31918
+ }
31919
+ function findComponent(spec, query) {
31920
+ const exact = spec.components[query];
31921
+ if (exact)
31922
+ return { name: query, def: exact };
31923
+ const lower = query.toLowerCase();
31924
+ for (const [name, def] of Object.entries(spec.components)) {
31925
+ if (name.toLowerCase() === lower)
31926
+ return { name, def };
31927
+ }
31928
+ return null;
31929
+ }
31930
+ function detectProject(spec, inputPath) {
31931
+ const start = resolveStart(inputPath);
31932
+ const packageJsonPath = findUp(start, "package.json");
31933
+ const root = packageJsonPath ? dirname(packageJsonPath) : null;
31934
+ const dependencyNames = getDependencyNames(readPackageJson(packageJsonPath));
31935
+ const framework = detectFramework(dependencyNames);
31936
+ const appHtmlPath = root ? resolve(root, "src/app.html") : null;
31937
+ const appCssPath = root ? resolve(root, "src/app.css") : null;
31938
+ const rootLayoutPath = root ? resolve(root, "src/routes/+layout.svelte") : null;
31939
+ const rootPagePath = root ? resolve(root, "src/routes/+page.svelte") : null;
31940
+ const appHtml = appHtmlPath && existsSync(appHtmlPath) ? appHtmlPath : null;
31941
+ const appCss = appCssPath && existsSync(appCssPath) ? appCssPath : null;
31942
+ const rootLayout = rootLayoutPath && existsSync(rootLayoutPath) ? rootLayoutPath : null;
31943
+ const rootPage = rootPagePath && existsSync(rootPagePath) ? rootPagePath : null;
31944
+ const themeImportFiles = rootLayout && importsAppCss(rootLayout) ? [rootLayout, appCss] : [rootLayout];
31945
+ const defaultImported = hasAnyImport(themeImportFiles, spec.themeImports.default);
31946
+ const darkImported = hasAnyImport(themeImportFiles, spec.themeImports.dark);
31947
+ const themeAuto = appHtml ? hasThemeAuto(appHtml) : false;
31948
+ const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!themeAuto);
31949
+ const warnings = [];
31950
+ if (!packageJsonPath)
31951
+ warnings.push("No package.json found above the provided path.");
31952
+ if (framework === "unknown")
31953
+ warnings.push("DryUI planning currently targets Svelte and SvelteKit projects.");
31954
+ return {
31955
+ inputPath: start,
31956
+ root,
31957
+ packageJsonPath,
31958
+ framework,
31959
+ packageManager: detectPackageManager(root),
31960
+ status: buildStatus(framework, Boolean(packageJsonPath), stepsNeeded),
31961
+ dependencies: {
31962
+ ui: dependencyNames.has("@dryui/ui"),
31963
+ primitives: dependencyNames.has("@dryui/primitives")
31964
+ },
31965
+ files: {
31966
+ appHtml,
31967
+ appCss,
31968
+ rootLayout,
31969
+ rootPage
31970
+ },
31971
+ theme: {
31972
+ defaultImported,
31973
+ darkImported,
31974
+ themeAuto
31975
+ },
31976
+ warnings
31977
+ };
31978
+ }
31979
+ function planInstall(spec, inputPath) {
31980
+ const detection = detectProject(spec, inputPath);
31981
+ const steps = [];
31982
+ if (detection.status === "unsupported") {
31983
+ steps.push({
31984
+ kind: "blocked",
31985
+ status: "blocked",
31986
+ title: "Project detection incomplete",
31987
+ description: detection.warnings.join(" ") || "DryUI install planning requires a Svelte or SvelteKit project with a package.json."
31988
+ });
31989
+ return { detection, steps };
31990
+ }
31991
+ if (!detection.dependencies.ui) {
31992
+ steps.push({
31993
+ kind: "install-package",
31994
+ status: "pending",
31995
+ title: "Install @dryui/ui",
31996
+ description: "Add the styled DryUI package to the current project.",
31997
+ command: installCommand(detection.packageManager)
31998
+ });
31999
+ }
32000
+ if (!detection.theme.defaultImported || !detection.theme.darkImported) {
32001
+ if (detection.files.appCss && detection.files.rootLayout && importsAppCss(detection.files.rootLayout)) {
32002
+ steps.push({
32003
+ kind: "edit-file",
32004
+ status: "pending",
32005
+ title: "Add theme imports to app.css",
32006
+ 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.",
32007
+ path: detection.files.appCss,
32008
+ snippet: buildThemeImportCssSnippet(spec)
32009
+ });
32010
+ } else if (!detection.files.rootLayout) {
32011
+ const path = detection.root ? resolve(detection.root, "src/routes/+layout.svelte") : null;
32012
+ steps.push({
32013
+ kind: "create-file",
32014
+ status: "pending",
32015
+ title: "Create root layout with theme imports",
32016
+ 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.",
32017
+ ...path ? { path } : {},
32018
+ snippet: buildRootLayoutSnippet(spec)
32019
+ });
32020
+ } else {
32021
+ steps.push({
32022
+ kind: "edit-file",
32023
+ status: "pending",
32024
+ title: "Add theme imports to the root layout",
32025
+ 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.",
32026
+ path: detection.files.rootLayout,
32027
+ snippet: buildThemeImportSnippet(spec)
32028
+ });
32029
+ }
32030
+ }
32031
+ if (!detection.files.appHtml) {
32032
+ steps.push({
32033
+ kind: "blocked",
32034
+ status: "blocked",
32035
+ title: "app.html not found",
32036
+ description: "DryUI expects src/app.html so the document can default to theme-auto mode."
32037
+ });
32038
+ } else if (!detection.theme.themeAuto) {
32039
+ steps.push({
32040
+ kind: "edit-file",
32041
+ status: "pending",
32042
+ title: "Set html theme mode to auto",
32043
+ 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.',
32044
+ path: detection.files.appHtml,
32045
+ snippet: 'class="theme-auto"'
32046
+ });
32047
+ }
32048
+ if (steps.length === 0) {
32049
+ steps.push({
32050
+ kind: "note",
32051
+ status: "done",
32052
+ title: "DryUI install plan is complete",
32053
+ description: "The project already has @dryui/ui, theme imports, and theme-auto configured."
32054
+ });
32055
+ }
32056
+ return { detection, steps };
32057
+ }
32058
+ function planAdd(spec, query, options = {}) {
32059
+ const installPlan = planInstall(spec, options.cwd);
32060
+ const component = findComponent(spec, query);
32061
+ if (component) {
32062
+ const target = getSuggestedTarget(installPlan.detection.root, options.target);
32063
+ const steps = [];
32064
+ const warnings = [...installPlan.detection.warnings];
32065
+ if (installPlan.steps.some((step) => step.status === "pending" || step.status === "blocked")) {
32066
+ steps.push({
32067
+ kind: "note",
32068
+ status: "info",
32069
+ title: "Complete install plan first",
32070
+ description: "Apply the install plan before inserting DryUI components into project files."
32071
+ });
32072
+ }
32073
+ steps.push(target ? {
32074
+ kind: "edit-file",
32075
+ status: "pending",
32076
+ title: "Insert component into the target file",
32077
+ description: "Add the import and snippet below to the chosen Svelte file.",
32078
+ path: target,
32079
+ snippet: `${getImportStatement(component.name, component.def, options.subpath)}
32080
+
32081
+ ${component.def.example}`
32082
+ } : {
32083
+ kind: "note",
32084
+ status: "info",
32085
+ title: "Choose a target Svelte file",
32086
+ description: "No root page was found. Pick a target file and reuse the import and snippet in this plan."
32087
+ });
32088
+ return {
32089
+ detection: installPlan.detection,
32090
+ installPlan,
32091
+ targetType: "component",
32092
+ name: component.name,
32093
+ importStatement: getImportStatement(component.name, component.def, options.subpath),
32094
+ snippet: component.def.example,
32095
+ target,
32096
+ steps,
32097
+ warnings
32098
+ };
32099
+ }
32100
+ throw new Error(`Unknown component: "${query}"`);
32101
+ }
32102
+
32103
+ // ../mcp/dist/toon.js
32104
+ function esc(value) {
32105
+ if (value.includes(",") || value.includes(`
32106
+ `)) {
32107
+ return `"${value.replace(/"/g, '""')}"`;
32108
+ }
32109
+ return value;
32110
+ }
32111
+ function header(resource, count, fields) {
32112
+ return `${resource}[${count}]{${fields.join(",")}}:`;
32113
+ }
32114
+ function row(...values) {
32115
+ return " " + values.map((v) => esc(String(v))).join(",");
32116
+ }
32117
+ function truncate(text, maxLen, hint) {
32118
+ if (text.length <= maxLen)
32119
+ return text;
32120
+ return `(truncated, ${text.length} chars -- ${hint})`;
32121
+ }
32122
+ function buildContextualHelp(ctx) {
32123
+ const hints = [];
32124
+ switch (ctx.command) {
32125
+ case "info":
32126
+ if (ctx.componentName) {
32127
+ hints.push(`compose "${ctx.componentName.toLowerCase()}" -- see composition patterns`);
32128
+ hints.push(`add ${ctx.componentName} -- get starter snippet`);
32129
+ }
32130
+ break;
32131
+ case "list":
32132
+ hints.push("info <Component> -- see full API reference");
32133
+ break;
32134
+ case "compose":
32135
+ if (ctx.componentName) {
32136
+ hints.push(`info ${ctx.componentName} -- full API reference`);
32137
+ hints.push(`add ${ctx.componentName} -- starter snippet`);
32138
+ }
32139
+ break;
32140
+ case "review":
32141
+ if (ctx.hasErrors) {
32142
+ hints.push("info <Component> -- check API for errored component");
32143
+ hints.push("diagnose <file.css> -- check theme if theme warnings present");
32144
+ } else if (ctx.isEmpty) {
32145
+ hints.push("lint . -- check entire workspace");
32146
+ }
32147
+ break;
32148
+ case "diagnose":
32149
+ if (ctx.hasErrors) {
32150
+ hints.push('compose "app shell" -- get correct theme setup');
32151
+ } else if (ctx.isEmpty) {
32152
+ hints.push("review <file.svelte> -- validate component usage");
32153
+ }
32154
+ break;
32155
+ case "doctor":
32156
+ case "lint":
32157
+ if (ctx.hasFindings) {
32158
+ hints.push("lint --max-severity error -- focus on errors only");
32159
+ hints.push("review <file.svelte> -- check specific file");
32160
+ } else if (ctx.isEmpty) {
32161
+ hints.push("detect -- verify project setup");
32162
+ }
32163
+ break;
32164
+ case "detect":
32165
+ if (ctx.status === "partial" || ctx.status === "unsupported") {
32166
+ hints.push("install -- see step-by-step setup plan");
32167
+ } else if (ctx.status === "ready") {
32168
+ hints.push("list -- see available components");
32169
+ hints.push('compose "app shell" -- get root layout template');
32170
+ }
32171
+ break;
32172
+ case "install":
32173
+ hints.push("detect -- verify project after completing steps");
32174
+ break;
32175
+ }
32176
+ return hints;
32177
+ }
32178
+ function formatHelp(hints) {
32179
+ if (hints.length === 0)
32180
+ return "";
32181
+ const lines = [`next[${hints.length}]:`];
32182
+ for (const hint of hints) {
32183
+ lines.push(" " + hint);
32184
+ }
32185
+ return lines.join(`
32186
+ `);
32187
+ }
32188
+ function toonComponent(name, def, opts) {
32189
+ const full = opts?.full ?? false;
32190
+ const lines = [];
32191
+ lines.push(`${name} -- ${def.description}`, `category: ${def.category} | tags: ${def.tags.join(",")}`, `import: import { ${name} } from '${def.import}'`, `compound: ${def.compound}`);
32192
+ if (def.compound && def.structure?.tree.length) {
32193
+ lines.push("", header("structure", def.structure.tree.length, ["node"]));
32194
+ for (const node of def.structure.tree) {
32195
+ lines.push(" " + node);
32196
+ }
32197
+ if (def.structure.note)
32198
+ lines.push(` note: ${def.structure.note}`);
32199
+ }
32200
+ if (def.compound && def.parts) {
32201
+ const partEntries = Object.entries(def.parts);
32202
+ lines.push("", header("parts", partEntries.length, ["part"]));
32203
+ for (const [partName, partDef] of partEntries) {
32204
+ lines.push(` ${name}.${partName}`);
32205
+ const props = Object.entries(partDef.props ?? {});
32206
+ if (props.length > 0) {
32207
+ for (const [propName, propDef] of props) {
32208
+ const flags = [propDef.type];
32209
+ if (propDef.required)
32210
+ flags.push("required");
32211
+ if (propDef.default !== undefined)
32212
+ flags.push(`default:${propDef.default}`);
32213
+ if (propDef.acceptedValues?.length)
32214
+ flags.push(`values:${propDef.acceptedValues.join("|")}`);
32215
+ lines.push(` ${propName}: ${flags.join(" | ")}`);
32216
+ }
32217
+ }
32218
+ }
32219
+ } else if (def.props) {
32220
+ const propEntries = Object.entries(def.props);
32221
+ if (propEntries.length > 0) {
32222
+ lines.push("", header("props", propEntries.length, ["name", "type", "required", "default"]));
32223
+ for (const [propName, propDef] of propEntries) {
32224
+ lines.push(row(propName, propDef.type, propDef.required ? "yes" : "no", propDef.default ?? "-"));
32225
+ }
32226
+ }
32227
+ }
32228
+ const cssEntries = Object.entries(def.cssVars);
32229
+ if (cssEntries.length > 0) {
32230
+ lines.push("", header("css-vars", cssEntries.length, ["name", "description"]));
32231
+ for (const [varName, desc] of cssEntries) {
32232
+ lines.push(row(varName, desc));
32233
+ }
32234
+ }
32235
+ if (def.dataAttributes.length > 0) {
32236
+ lines.push("", header("data-attrs", def.dataAttributes.length, ["name", "values"]));
32237
+ for (const attr of def.dataAttributes) {
32238
+ lines.push(row(attr.name, attr.values?.join("|") ?? "-"));
32239
+ }
32240
+ }
32241
+ if (def.example) {
32242
+ const example = full ? def.example : truncate(def.example, 400, `use add ${name} for full snippet`);
32243
+ lines.push("", "example:", example);
32244
+ }
32245
+ const help = buildContextualHelp({ command: "info", componentName: name });
32246
+ if (help.length > 0) {
32247
+ lines.push("", formatHelp(help));
32248
+ }
32249
+ return lines.join(`
32250
+ `);
32251
+ }
32252
+ function toonComponentList(components, category) {
32253
+ const entries = Object.entries(components);
32254
+ const filtered = category ? entries.filter(([, def]) => def.category.toLowerCase() === category.toLowerCase()) : entries;
32255
+ if (filtered.length === 0) {
32256
+ const cats = [...new Set(entries.map(([, d]) => d.category))].sort();
32257
+ return `components[0]: no matches
32258
+ available categories: ${cats.join(", ")}`;
32259
+ }
32260
+ const groups = {};
32261
+ for (const entry of filtered) {
32262
+ const cat = entry[1].category;
32263
+ (groups[cat] ??= []).push(entry);
32264
+ }
32265
+ const lines = [header("components", filtered.length, ["name", "category", "compound", "description"])];
32266
+ const sortedCats = Object.keys(groups).sort();
32267
+ for (const cat of sortedCats) {
32268
+ const items = groups[cat] ?? [];
32269
+ for (const [name, def] of items) {
32270
+ lines.push(row(name, cat, def.compound, def.description));
32271
+ }
32272
+ }
32273
+ const help = buildContextualHelp({ command: "list" });
32274
+ if (help.length > 0) {
32275
+ lines.push("", formatHelp(help));
32276
+ }
32277
+ return lines.join(`
32278
+ `);
32279
+ }
32280
+ function toonComposition(results, opts) {
32281
+ const full = opts?.full ?? false;
32282
+ const lines = [];
32283
+ const totalMatches = results.componentMatches.length + results.recipeMatches.length;
32284
+ if (totalMatches === 0) {
32285
+ return "matches[0]: none";
32286
+ }
32287
+ for (const comp of results.componentMatches) {
32288
+ lines.push(`-- ${comp.component}: ${comp.useWhen}`);
32289
+ for (const alt of comp.alternatives) {
32290
+ const snippet = full ? alt.snippet : truncate(alt.snippet, 500, `use info ${alt.component} for full snippet`);
32291
+ lines.push(` ${alt.rank}. ${alt.component} (${alt.useWhen})`);
32292
+ lines.push(snippet.split(`
32293
+ `).map((l) => " " + l).join(`
32294
+ `));
32295
+ }
32296
+ for (const ap of comp.antiPatterns) {
32297
+ lines.push(` anti-pattern: ${ap.pattern}`);
32298
+ lines.push(` reason: ${ap.reason} | fix: ${ap.fix}`);
32299
+ }
32300
+ if (comp.combinesWith.length) {
32301
+ lines.push(` combines-with: ${comp.combinesWith.join(",")}`);
32302
+ }
32303
+ lines.push("");
32304
+ }
32305
+ for (const recipe of results.recipeMatches) {
32306
+ const snippet = full ? recipe.snippet : truncate(recipe.snippet, 500, `use compose "${recipe.name}" --full for complete code`);
32307
+ lines.push(`-- recipe: ${recipe.name}`);
32308
+ lines.push(` ${recipe.description}`);
32309
+ lines.push(` components: ${recipe.components.join(",")}`);
32310
+ lines.push(" code:");
32311
+ lines.push(snippet.split(`
32312
+ `).map((l) => " " + l).join(`
32313
+ `));
32314
+ lines.push("");
32315
+ }
32316
+ const firstComponent = results.componentMatches[0]?.alternatives[0]?.component ?? results.recipeMatches[0]?.components[0] ?? undefined;
32317
+ const ctx = { command: "compose" };
32318
+ if (firstComponent)
32319
+ ctx.componentName = firstComponent;
32320
+ const help = buildContextualHelp(ctx);
32321
+ if (help.length > 0) {
32322
+ lines.push(formatHelp(help));
32323
+ }
32324
+ return lines.join(`
32325
+ `).trimEnd();
32326
+ }
32327
+ function toonReviewResult(result) {
32328
+ const lines = [];
32329
+ const hasBlockers = result.issues.some((i) => i.severity === "error");
32330
+ const autoFixable = result.issues.filter((i) => i.fix !== null).length;
32331
+ if (result.issues.length === 0) {
32332
+ lines.push("issues[0]: clean");
32333
+ lines.push(`hasBlockers: false | autoFixable: 0`);
32334
+ } else {
32335
+ lines.push(header("issues", result.issues.length, ["severity", "line", "code", "message"]));
32336
+ for (const issue of result.issues) {
32337
+ lines.push(row(issue.severity, issue.line, issue.code, issue.message));
32338
+ if (issue.fix) {
32339
+ lines.push(` fix: ${issue.fix}`);
32340
+ }
32341
+ }
32342
+ lines.push(`hasBlockers: ${hasBlockers} | autoFixable: ${autoFixable}`);
32343
+ }
32344
+ lines.push(result.summary);
32345
+ const help = buildContextualHelp({
32346
+ command: "review",
32347
+ hasErrors: hasBlockers,
32348
+ isEmpty: result.issues.length === 0
32349
+ });
32350
+ if (help.length > 0) {
32351
+ lines.push("", formatHelp(help));
32352
+ }
32353
+ return lines.join(`
32354
+ `);
32355
+ }
32356
+ function toonDiagnoseResult(result) {
32357
+ const lines = [];
32358
+ const { variables } = result;
32359
+ const missingCount = result.issues.filter((i) => i.code === "missing-token").length;
32360
+ const totalRequired = variables.required + missingCount;
32361
+ const coverage = totalRequired > 0 ? Math.round(variables.required / totalRequired * 100) : 100;
32362
+ lines.push(`tokens: ${variables.found} found, ${variables.required} required, ${variables.extra} extra | coverage: ${coverage}%`);
32363
+ if (result.issues.length === 0) {
32364
+ lines.push("issues[0]: clean");
32365
+ } else {
32366
+ lines.push(header("issues", result.issues.length, ["severity", "code", "variable", "message"]));
32367
+ for (const issue of result.issues) {
32368
+ lines.push(row(issue.severity, issue.code, issue.variable, issue.message));
32369
+ if (issue.fix) {
32370
+ lines.push(` fix: ${issue.fix}`);
32371
+ }
32372
+ }
32373
+ }
32374
+ lines.push(result.summary);
32375
+ const hasErrors = result.issues.some((i) => i.severity === "error");
32376
+ const help = buildContextualHelp({
32377
+ command: "diagnose",
32378
+ hasErrors,
32379
+ isEmpty: result.issues.length === 0
32380
+ });
32381
+ if (help.length > 0) {
32382
+ lines.push("", formatHelp(help));
32383
+ }
32384
+ return lines.join(`
32385
+ `);
32386
+ }
32387
+ var MAX_FINDINGS_DEFAULT = 50;
32388
+ function toonWorkspaceReport(report, opts) {
32389
+ const full = opts?.full ?? false;
32390
+ const title = opts?.title ?? "workspace";
32391
+ const command = opts?.command ?? (title.includes("lint") ? "lint" : "doctor");
32392
+ const lines = [];
32393
+ lines.push(`${title} | root: ${report.root}`);
32394
+ lines.push(`scanned: ${report.scannedFiles} files | errors: ${report.summary.error} | warnings: ${report.summary.warning} | info: ${report.summary.info}`);
32395
+ if (report.summary.byRule && Object.keys(report.summary.byRule).length > 0) {
32396
+ const sorted = Object.entries(report.summary.byRule).sort(([, a], [, b]) => b - a);
32397
+ const topRule = sorted[0];
32398
+ if (topRule) {
32399
+ lines.push(`top-rule: ${topRule[0]} (${topRule[1]} occurrences)`);
32400
+ }
32401
+ }
32402
+ if (report.findings.length === 0) {
32403
+ lines.push("findings[0]: clean");
32404
+ } else {
32405
+ const findings = full ? report.findings : report.findings.slice(0, MAX_FINDINGS_DEFAULT);
32406
+ const truncated = !full && report.findings.length > MAX_FINDINGS_DEFAULT;
32407
+ lines.push(header("findings", findings.length, ["severity", "rule", "file", "line", "message"]));
32408
+ for (const f of findings) {
32409
+ lines.push(row(f.severity, f.ruleId, f.file, f.line ?? "-", f.message));
32410
+ if (f.suggestedFixes.length > 0) {
32411
+ for (const fix of f.suggestedFixes) {
32412
+ lines.push(` fix: ${fix.description}${fix.replacement ? ` -> ${fix.replacement}` : ""}`);
32413
+ }
32414
+ }
32415
+ }
32416
+ if (truncated) {
32417
+ lines.push(` (showing ${MAX_FINDINGS_DEFAULT} of ${report.findings.length} -- use --full to see all)`);
32418
+ }
32419
+ }
32420
+ if (report.warnings.length > 0) {
32421
+ lines.push("", header("warnings", report.warnings.length, ["message"]));
32422
+ for (const w of report.warnings) {
32423
+ lines.push(" " + w);
32424
+ }
32425
+ }
32426
+ const help = buildContextualHelp({
32427
+ command,
32428
+ hasFindings: report.findings.length > 0,
32429
+ isEmpty: report.findings.length === 0
32430
+ });
32431
+ if (help.length > 0) {
32432
+ lines.push("", formatHelp(help));
32433
+ }
32434
+ return lines.join(`
32435
+ `);
32436
+ }
32437
+ function toonProjectDetection(detection) {
32438
+ const lines = [];
32439
+ lines.push(`project: ${detection.status} | framework: ${detection.framework} | pkg-manager: ${detection.packageManager}`);
32440
+ lines.push(`root: ${detection.root ?? "(not found)"}`);
32441
+ lines.push(`deps: ui=${detection.dependencies.ui}, primitives=${detection.dependencies.primitives}`);
32442
+ lines.push(`theme: default=${detection.theme.defaultImported}, dark=${detection.theme.darkImported}, auto=${detection.theme.themeAuto}`);
32443
+ if (detection.warnings.length > 0) {
32444
+ lines.push(header("warnings", detection.warnings.length, ["message"]));
32445
+ for (const w of detection.warnings) {
32446
+ lines.push(" " + w);
32447
+ }
32448
+ }
32449
+ const help = buildContextualHelp({ command: "detect", status: detection.status });
32450
+ if (help.length > 0) {
32451
+ lines.push("", formatHelp(help));
32452
+ }
32453
+ return lines.join(`
32454
+ `);
32455
+ }
32456
+ function toonError(code, message, suggestions) {
32457
+ const lines = [`error[1]{code,message}: ${esc(code)},${esc(message)}`];
32458
+ if (suggestions?.length) {
32459
+ lines.push(header("suggestions", suggestions.length, ["value"]));
32460
+ for (const s of suggestions) {
32461
+ lines.push(" " + s);
32462
+ }
32463
+ }
32464
+ return lines.join(`
32465
+ `);
32466
+ }
32467
+
32468
+ // ../mcp/src/spec-formatters.ts
32469
+ var DIR_OVERRIDES2 = {
32470
+ QRCode: "qr-code"
32471
+ };
32472
+ function componentDir2(name) {
32473
+ return DIR_OVERRIDES2[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
32474
+ }
31735
32475
  function pad(str, width) {
31736
32476
  return str.length >= width ? str : str + " ".repeat(width - str.length);
31737
32477
  }
@@ -31744,9 +32484,9 @@ function indent(text, spaces) {
31744
32484
  function getSubpathImport(name, def) {
31745
32485
  if (def.import !== "@dryui/ui")
31746
32486
  return null;
31747
- return `import { ${name} } from '${def.import}/${componentDir(name)}'`;
32487
+ return `import { ${name} } from '${def.import}/${componentDir2(name)}'`;
31748
32488
  }
31749
- function findComponent(query, components) {
32489
+ function findComponent2(query, components) {
31750
32490
  const exact = components[query];
31751
32491
  if (exact)
31752
32492
  return { name: query, def: exact };
@@ -31921,9 +32661,23 @@ function formatSimple(name, def) {
31921
32661
  }
31922
32662
 
31923
32663
  // src/run.ts
31924
- function runCommand(result) {
32664
+ import { existsSync as existsSync2 } from "node:fs";
32665
+ function fileNotFound(filePath, toon) {
32666
+ if (existsSync2(filePath))
32667
+ return null;
32668
+ return {
32669
+ output: "",
32670
+ error: toon ? toonError("not-found", `File not found: ${filePath}`) : `File not found: ${filePath}`,
32671
+ exitCode: 1
32672
+ };
32673
+ }
32674
+ function runCommand(result, mode = "text") {
31925
32675
  if (result.error) {
31926
- console.error(result.error);
32676
+ if (mode === "text") {
32677
+ console.error(result.error);
32678
+ } else {
32679
+ console.log(result.error);
32680
+ }
31927
32681
  } else {
31928
32682
  console.log(result.output);
31929
32683
  }
@@ -31931,13 +32685,20 @@ function runCommand(result) {
31931
32685
  }
31932
32686
 
31933
32687
  // src/commands/info.ts
31934
- function findComponent2(spec, query) {
31935
- return findComponent(query, spec.components);
32688
+ function findComponent3(spec, query) {
32689
+ return findComponent2(query, spec.components);
31936
32690
  }
31937
- function getInfo(query, spec) {
31938
- const result = findComponent2(spec, query);
32691
+ function getInfo(query, spec, options) {
32692
+ const result = findComponent3(spec, query);
31939
32693
  if (!result) {
31940
32694
  const available = Object.keys(spec.components).sort();
32695
+ if (options?.toon) {
32696
+ return {
32697
+ output: "",
32698
+ error: toonError("not-found", `Unknown component: "${query}"`, available),
32699
+ exitCode: 1
32700
+ };
32701
+ }
31941
32702
  const error = [
31942
32703
  `Unknown component: "${query}"`,
31943
32704
  "",
@@ -31948,27 +32709,37 @@ function getInfo(query, spec) {
31948
32709
  return { output: "", error, exitCode: 1 };
31949
32710
  }
31950
32711
  const { name, def } = result;
32712
+ if (options?.toon) {
32713
+ return { output: toonComponent(name, def, { full: options?.full }), error: null, exitCode: 0 };
32714
+ }
31951
32715
  const output = def.compound ? formatCompound(name, def) : formatSimple(name, def);
31952
32716
  return { output, error: null, exitCode: 0 };
31953
32717
  }
31954
32718
  function runInfo(args, spec) {
31955
32719
  if (args.length === 0 || args[0] === "--help") {
31956
- console.log("Usage: dryui info <component>");
32720
+ console.log("Usage: dryui info <component> [--toon] [--full]");
31957
32721
  console.log("");
31958
32722
  console.log("Show API reference for a DryUI component or composed output.");
31959
32723
  console.log("");
32724
+ console.log("Options:");
32725
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32726
+ console.log(" --full Include full example code (disables truncation)");
32727
+ console.log("");
31960
32728
  console.log("Examples:");
31961
32729
  console.log(" dryui info Button");
31962
32730
  console.log(" dryui info card # case-insensitive");
31963
- console.log(' dryui info "hero sections"');
32731
+ console.log(" dryui info Button --toon");
31964
32732
  process.exit(args[0] === "--help" ? 0 : 1);
31965
32733
  }
31966
- const query = args[0];
32734
+ const toon = args.includes("--toon");
32735
+ const full = args.includes("--full");
32736
+ const query = args.find((a) => !a.startsWith("--"));
31967
32737
  if (!query) {
31968
32738
  console.error("Error: missing component name");
31969
32739
  process.exit(1);
31970
32740
  }
31971
- runCommand(getInfo(query, spec));
32741
+ const mode = toon ? "toon" : "text";
32742
+ runCommand(getInfo(query, spec, { toon, full }), mode);
31972
32743
  }
31973
32744
 
31974
32745
  // src/format.ts
@@ -32028,7 +32799,7 @@ function formatWorkspaceReport(report, options) {
32028
32799
  // src/commands/project-planner.ts
32029
32800
  function importStatement(name, def, subpath = false) {
32030
32801
  if (subpath && def.import === "@dryui/ui") {
32031
- return `import { ${name} } from '${def.import}/${componentDir(name)}';`;
32802
+ return `import { ${name} } from '${def.import}/${componentDir2(name)}';`;
32032
32803
  }
32033
32804
  return `import { ${name} } from '${def.import}';`;
32034
32805
  }
@@ -32150,7 +32921,7 @@ function formatAddPlan(plan) {
32150
32921
  `);
32151
32922
  }
32152
32923
  function buildAddSnippet(query, spec, options = {}) {
32153
- const component = findComponent2(spec, query);
32924
+ const component = findComponent3(spec, query);
32154
32925
  if (!component) {
32155
32926
  return {
32156
32927
  output: "",
@@ -32166,342 +32937,6 @@ function buildAddSnippet(query, spec, options = {}) {
32166
32937
  };
32167
32938
  }
32168
32939
 
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
32940
  // src/commands/add.ts
32506
32941
  function parseProjectInput(positionals, spec) {
32507
32942
  const first = positionals[0];
@@ -32513,8 +32948,8 @@ function parseProjectInput(positionals, spec) {
32513
32948
  return { query: first };
32514
32949
  }
32515
32950
  if (positionals.length === 2) {
32516
- const firstIsQuery = Boolean(findComponent2(spec, first));
32517
- const secondIsQuery = Boolean(findComponent2(spec, second));
32951
+ const firstIsQuery = Boolean(findComponent3(spec, first));
32952
+ const secondIsQuery = Boolean(findComponent3(spec, second));
32518
32953
  if (firstIsQuery && !secondIsQuery) {
32519
32954
  return { query: first, cwd: second };
32520
32955
  }
@@ -32641,6 +33076,7 @@ function runAdd(args, spec) {
32641
33076
  // src/commands/detect.ts
32642
33077
  function parseDetectArgs(args) {
32643
33078
  let json = false;
33079
+ let toon = false;
32644
33080
  let path;
32645
33081
  for (let index = 0;index < args.length; index++) {
32646
33082
  const arg = args[index];
@@ -32650,6 +33086,10 @@ function parseDetectArgs(args) {
32650
33086
  json = true;
32651
33087
  continue;
32652
33088
  }
33089
+ if (arg === "--toon") {
33090
+ toon = true;
33091
+ continue;
33092
+ }
32653
33093
  if (arg.startsWith("--")) {
32654
33094
  continue;
32655
33095
  }
@@ -32657,24 +33097,29 @@ function parseDetectArgs(args) {
32657
33097
  path = arg;
32658
33098
  }
32659
33099
  }
32660
- return { json, path };
33100
+ return { json, toon, path };
32661
33101
  }
32662
33102
  function getDetect(inputPath, spec, options = {}) {
32663
33103
  const detection = detectProject(spec, inputPath);
33104
+ if (options.toon) {
33105
+ return { output: toonProjectDetection(detection), error: null, exitCode: 0 };
33106
+ }
32664
33107
  return options.json ? { output: JSON.stringify(detection, null, 2), error: null, exitCode: 0 } : { output: formatProjectDetection(detection), error: null, exitCode: 0 };
32665
33108
  }
32666
33109
  function runDetect(args, spec) {
32667
33110
  if (args[0] === "--help") {
32668
- console.log("Usage: dryui detect [--json] [path]");
33111
+ console.log("Usage: dryui detect [--json] [--toon] [path]");
32669
33112
  console.log("");
32670
33113
  console.log("Detect the current DryUI project setup.");
32671
33114
  console.log("");
32672
33115
  console.log("Options:");
32673
33116
  console.log(" --json Output raw JSON instead of formatted text");
33117
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32674
33118
  process.exit(0);
32675
33119
  }
32676
- const { json, path } = parseDetectArgs(args);
32677
- runCommand(getDetect(path, spec, { json }));
33120
+ const { json, toon, path } = parseDetectArgs(args);
33121
+ const mode = toon ? "toon" : json ? "json" : "text";
33122
+ runCommand(getDetect(path, spec, { json, toon }), mode);
32678
33123
  }
32679
33124
 
32680
33125
  // src/commands/install.ts
@@ -32781,7 +33226,14 @@ function groupByCategory(spec) {
32781
33226
  }
32782
33227
  return groups;
32783
33228
  }
32784
- function getList(category, spec, options = {}) {
33229
+ function getList(category, spec, options) {
33230
+ if (options?.toon) {
33231
+ return {
33232
+ output: toonComponentList(spec.components, category ?? undefined),
33233
+ error: null,
33234
+ exitCode: 0
33235
+ };
33236
+ }
32785
33237
  const sections = [];
32786
33238
  const validCategories = getCategories(spec);
32787
33239
  if (category && !validCategories.includes(category)) {
@@ -32815,18 +33267,20 @@ function getList(category, spec, options = {}) {
32815
33267
  }
32816
33268
  function runList(args, spec) {
32817
33269
  if (args[0] === "--help") {
32818
- console.log("Usage: dryui list [--category <category>]");
33270
+ console.log("Usage: dryui list [--category <category>] [--toon]");
32819
33271
  console.log("");
32820
33272
  console.log("List DryUI components.");
32821
33273
  console.log("");
32822
33274
  console.log("Options:");
32823
33275
  console.log(" --category <cat> Filter by category");
33276
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
32824
33277
  console.log("");
32825
33278
  console.log("Categories:");
32826
33279
  const cats = getCategories(spec);
32827
33280
  console.log(` ${cats.join(", ")}`);
32828
33281
  process.exit(0);
32829
33282
  }
33283
+ const toon = args.includes("--toon");
32830
33284
  let filterCategory = null;
32831
33285
  const catIdx = args.indexOf("--category");
32832
33286
  if (catIdx !== -1) {
@@ -32837,11 +33291,12 @@ function runList(args, spec) {
32837
33291
  }
32838
33292
  filterCategory = catValue.toLowerCase();
32839
33293
  }
32840
- runCommand(getList(filterCategory, spec));
33294
+ const mode = toon ? "toon" : "text";
33295
+ runCommand(getList(filterCategory, spec, { toon }), mode);
32841
33296
  }
32842
33297
 
32843
33298
  // src/commands/review.ts
32844
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
33299
+ import { readFileSync as readFileSync2 } from "node:fs";
32845
33300
 
32846
33301
  // ../mcp/src/utils.ts
32847
33302
  function buildLineOffsets(text) {
@@ -33542,12 +33997,19 @@ function reviewComponent(code, spec, filename) {
33542
33997
 
33543
33998
  // src/commands/review.ts
33544
33999
  function getReview(filePath, spec, options) {
33545
- if (!existsSync2(filePath)) {
33546
- return { output: "", error: `File not found: ${filePath}`, exitCode: 1 };
33547
- }
34000
+ const missing = fileNotFound(filePath, options?.toon);
34001
+ if (missing)
34002
+ return missing;
33548
34003
  const code = readFileSync2(filePath, "utf-8");
33549
34004
  const filename = filePath.split("/").pop();
33550
34005
  const result = reviewComponent(code, spec, filename);
34006
+ if (options?.toon) {
34007
+ return {
34008
+ output: toonReviewResult(result),
34009
+ error: null,
34010
+ exitCode: result.issues.some((i) => i.severity === "error") ? 1 : 0
34011
+ };
34012
+ }
33551
34013
  if (options?.json) {
33552
34014
  return {
33553
34015
  output: JSON.stringify(result, null, 2),
@@ -33574,7 +34036,7 @@ function getReview(filePath, spec, options) {
33574
34036
  }
33575
34037
  function runReview(args, spec) {
33576
34038
  if (args.length === 0 || args[0] === "--help") {
33577
- console.log("Usage: dryui review [--json] <file.svelte>");
34039
+ console.log("Usage: dryui review [--json] [--toon] <file.svelte>");
33578
34040
  console.log("");
33579
34041
  console.log("Validate a Svelte component against the DryUI spec.");
33580
34042
  console.log("Checks for incorrect component usage, missing props,");
@@ -33582,20 +34044,23 @@ function runReview(args, spec) {
33582
34044
  console.log("");
33583
34045
  console.log("Options:");
33584
34046
  console.log(" --json Output raw JSON instead of formatted text");
34047
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
33585
34048
  process.exit(args[0] === "--help" ? 0 : 1);
33586
34049
  }
33587
34050
  const jsonMode = args.includes("--json");
33588
- const fileArgs = args.filter((a) => a !== "--json");
34051
+ const toon = args.includes("--toon");
34052
+ const fileArgs = args.filter((a) => !a.startsWith("--"));
33589
34053
  const filePath = fileArgs[0];
33590
34054
  if (!filePath) {
33591
34055
  console.error("Error: missing file path");
33592
34056
  process.exit(1);
33593
34057
  }
33594
- runCommand(getReview(filePath, spec, { json: jsonMode }));
34058
+ const mode = toon ? "toon" : jsonMode ? "json" : "text";
34059
+ runCommand(getReview(filePath, spec, { json: jsonMode, toon }), mode);
33595
34060
  }
33596
34061
 
33597
34062
  // src/commands/diagnose.ts
33598
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
34063
+ import { readFileSync as readFileSync3 } from "node:fs";
33599
34064
 
33600
34065
  // ../mcp/src/theme-checker.ts
33601
34066
  function capture(match, index) {
@@ -34351,11 +34816,18 @@ function diagnoseTheme(css, spec) {
34351
34816
 
34352
34817
  // src/commands/diagnose.ts
34353
34818
  function getDiagnose(filePath, spec, options) {
34354
- if (!existsSync3(filePath)) {
34355
- return { output: "", error: `File not found: ${filePath}`, exitCode: 1 };
34356
- }
34819
+ const missing = fileNotFound(filePath, options?.toon);
34820
+ if (missing)
34821
+ return missing;
34357
34822
  const css = readFileSync3(filePath, "utf-8");
34358
34823
  const result = diagnoseTheme(css, spec);
34824
+ if (options?.toon) {
34825
+ return {
34826
+ output: toonDiagnoseResult(result),
34827
+ error: null,
34828
+ exitCode: result.issues.some((i) => i.severity === "error") ? 1 : 0
34829
+ };
34830
+ }
34359
34831
  if (options?.json) {
34360
34832
  return {
34361
34833
  output: JSON.stringify(result, null, 2),
@@ -34388,26 +34860,29 @@ function getDiagnose(filePath, spec, options) {
34388
34860
  }
34389
34861
  function runDiagnose(args, spec) {
34390
34862
  if (args.length === 0 || args[0] === "--help") {
34391
- console.log("Usage: dryui diagnose [--json] <file.css>");
34863
+ console.log("Usage: dryui diagnose [--json] [--toon] <file.css>");
34392
34864
  console.log("");
34393
34865
  console.log("Validate theme CSS for missing tokens, value errors,");
34394
34866
  console.log("contrast issues, and component token problems.");
34395
34867
  console.log("");
34396
34868
  console.log("Options:");
34397
34869
  console.log(" --json Output raw JSON instead of formatted text");
34870
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
34398
34871
  process.exit(args[0] === "--help" ? 0 : 1);
34399
34872
  }
34400
34873
  const jsonMode = args.includes("--json");
34401
- const fileArgs = args.filter((a) => a !== "--json");
34874
+ const toon = args.includes("--toon");
34875
+ const fileArgs = args.filter((a) => !a.startsWith("--"));
34402
34876
  const filePath = fileArgs[0];
34403
34877
  if (!filePath) {
34404
34878
  console.error("Error: missing file path");
34405
34879
  process.exit(1);
34406
34880
  }
34407
- runCommand(getDiagnose(filePath, spec, { json: jsonMode }));
34881
+ const mode = toon ? "toon" : jsonMode ? "json" : "text";
34882
+ runCommand(getDiagnose(filePath, spec, { json: jsonMode, toon }), mode);
34408
34883
  }
34409
34884
 
34410
- // src/commands/compose.ts
34885
+ // ../mcp/dist/composition-search.js
34411
34886
  var STOP_WORDS = new Set([
34412
34887
  "a",
34413
34888
  "an",
@@ -34473,81 +34948,58 @@ function scoreText(tokens, text) {
34473
34948
  const lower = text.toLowerCase();
34474
34949
  return tokens.reduce((score, t) => score + (lower.includes(t) ? 1 : 0), 0);
34475
34950
  }
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
- }
34951
+ function searchComposition(composition, query) {
34484
34952
  const q = query.toLowerCase();
34485
34953
  const tokens = tokenize(query);
34486
34954
  const exactComponentMatches = [];
34487
34955
  const exactRecipeMatches = [];
34488
- for (const comp of Object.values(spec.composition.components)) {
34956
+ for (const comp of Object.values(composition.components)) {
34489
34957
  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
34958
  exactComponentMatches.push(comp);
34491
34959
  }
34492
34960
  }
34493
- for (const recipe of Object.values(spec.composition.recipes)) {
34961
+ for (const recipe of Object.values(composition.recipes)) {
34494
34962
  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
34963
  exactRecipeMatches.push(recipe);
34496
34964
  }
34497
34965
  }
34498
- let componentMatches;
34499
- let recipeMatches;
34500
34966
  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
- }
34967
+ return { componentMatches: exactComponentMatches, recipeMatches: exactRecipeMatches };
34968
+ }
34969
+ if (!tokens.length) {
34970
+ return { componentMatches: [], recipeMatches: [] };
34971
+ }
34972
+ const scoredComponents = [];
34973
+ for (const comp of Object.values(composition.components)) {
34974
+ const corpus = [
34975
+ comp.component,
34976
+ comp.useWhen,
34977
+ ...comp.alternatives.flatMap((a) => [a.component, a.useWhen]),
34978
+ ...comp.antiPatterns.map((ap) => ap.pattern),
34979
+ ...comp.combinesWith
34980
+ ].join(" ");
34981
+ const score = scoreText(tokens, corpus);
34982
+ if (score > 0)
34983
+ scoredComponents.push({ comp, score });
34984
+ }
34985
+ const scoredRecipes = [];
34986
+ for (const recipe of Object.values(composition.recipes)) {
34987
+ const corpus = [recipe.name, recipe.description, ...recipe.tags, ...recipe.components].join(" ");
34988
+ const score = scoreText(tokens, corpus);
34989
+ if (score > 0)
34990
+ scoredRecipes.push({ recipe, score });
34991
+ }
34992
+ scoredComponents.sort((a, b) => b.score - a.score);
34993
+ scoredRecipes.sort((a, b) => b.score - a.score);
34994
+ const minScore = Math.max(1, Math.floor(tokens.length * 0.3));
34995
+ return {
34996
+ componentMatches: scoredComponents.filter((s) => s.score >= minScore).slice(0, 10).map((s) => s.comp),
34997
+ recipeMatches: scoredRecipes.filter((s) => s.score >= minScore).slice(0, 5).map((s) => s.recipe)
34998
+ };
34999
+ }
35000
+ function formatCompositionResult(results) {
34545
35001
  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) {
35002
+ for (const comp of results.componentMatches) {
34551
35003
  lines.push(`── ${comp.component} ──────────────────────────────`);
34552
35004
  lines.push(`[DEV GUIDANCE — do not render as page content]`);
34553
35005
  lines.push(`Use: ${comp.component} — ${comp.useWhen}`);
@@ -34570,7 +35022,7 @@ Try:
34570
35022
  }
34571
35023
  lines.push("");
34572
35024
  }
34573
- for (const recipe of recipeMatches) {
35025
+ for (const recipe of results.recipeMatches) {
34574
35026
  lines.push(`── Recipe: ${recipe.name} ──────────────────────────────`);
34575
35027
  lines.push(`[DEV GUIDANCE — do not render as page content]`);
34576
35028
  lines.push(recipe.description);
@@ -34580,29 +35032,88 @@ Try:
34580
35032
  lines.push(recipe.snippet);
34581
35033
  lines.push("");
34582
35034
  }
34583
- return { output: lines.join(`
34584
- `).trimEnd(), error: null, exitCode: 0 };
35035
+ return lines.join(`
35036
+ `);
35037
+ }
35038
+
35039
+ // src/commands/compose.ts
35040
+ var SETUP_PREAMBLE = [
35041
+ "⚠ SETUP: Root +layout.svelte must import '@dryui/ui/themes/default.css'",
35042
+ ' and dark.css. app.html needs <html class="theme-auto">.',
35043
+ ' Not set up? Run: dryui compose "app shell"',
35044
+ ""
35045
+ ].join(`
35046
+ `);
35047
+ function getCompose(query, spec, options) {
35048
+ if (!spec.composition) {
35049
+ return {
35050
+ output: "",
35051
+ error: "No composition data available. Rebuild the MCP package.",
35052
+ exitCode: 1
35053
+ };
35054
+ }
35055
+ const results = searchComposition(spec.composition, query);
35056
+ if (!results.componentMatches.length && !results.recipeMatches.length) {
35057
+ if (options?.toon) {
35058
+ return {
35059
+ output: "",
35060
+ error: toonError("no-results", `No composition guidance for "${query}"`, [
35061
+ "Try a component name (DatePicker, Avatar)",
35062
+ "Try a UI concept (date input, image placeholder)",
35063
+ "Try a pattern name (search-form, dashboard-page)"
35064
+ ]),
35065
+ exitCode: 1
35066
+ };
35067
+ }
35068
+ return {
35069
+ output: "",
35070
+ error: `No composition guidance found for "${query}".
35071
+
35072
+ Try:
35073
+ - A component name (e.g. "DatePicker", "Avatar")
35074
+ - A UI concept (e.g. "date input", "image placeholder")
35075
+ - A pattern name (e.g. "search-form", "dashboard-page")`,
35076
+ exitCode: 1
35077
+ };
35078
+ }
35079
+ if (options?.toon) {
35080
+ return {
35081
+ output: toonComposition(results, { full: options?.full }),
35082
+ error: null,
35083
+ exitCode: 0
35084
+ };
35085
+ }
35086
+ const output = SETUP_PREAMBLE + `
35087
+ ` + formatCompositionResult(results);
35088
+ return { output: output.trimEnd(), error: null, exitCode: 0 };
34585
35089
  }
34586
35090
  function runCompose(args, spec) {
34587
35091
  if (args.length === 0 || args[0] === "--help") {
34588
- console.log("Usage: dryui compose <query>");
35092
+ console.log("Usage: dryui compose <query> [--toon] [--full]");
34589
35093
  console.log("");
34590
35094
  console.log("Look up composition guidance for building UIs with DryUI.");
34591
35095
  console.log("Returns ranked component alternatives, anti-patterns, and recipes.");
34592
35096
  console.log("");
35097
+ console.log("Options:");
35098
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35099
+ console.log(" --full Include full code snippets (disables truncation)");
35100
+ console.log("");
34593
35101
  console.log("Examples:");
34594
35102
  console.log(' dryui compose "search form"');
34595
35103
  console.log(' dryui compose "hotel card"');
34596
- console.log(' dryui compose "travel booking"');
35104
+ console.log(' dryui compose "travel booking" --toon');
34597
35105
  process.exit(args[0] === "--help" ? 0 : 1);
34598
35106
  }
34599
- const query = args.join(" ").trim();
34600
- runCommand(getCompose(query, spec));
35107
+ const toon = args.includes("--toon");
35108
+ const full = args.includes("--full");
35109
+ const query = args.filter((a) => !a.startsWith("--")).join(" ").trim();
35110
+ const mode = toon ? "toon" : "text";
35111
+ runCommand(getCompose(query, spec, { toon, full }), mode);
34601
35112
  }
34602
35113
 
34603
35114
  // ../mcp/src/workspace-audit.ts
34604
35115
  import { execFileSync } from "node:child_process";
34605
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, realpathSync, statSync as statSync2 } from "node:fs";
35116
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync, realpathSync, statSync as statSync2 } from "node:fs";
34606
35117
  import { dirname as dirname2, relative, resolve as resolve2 } from "node:path";
34607
35118
  var DEFAULT_INCLUDE = [
34608
35119
  "packages/ui",
@@ -34635,7 +35146,7 @@ function normalizePath(path) {
34635
35146
  }
34636
35147
  function resolveRoot(inputPath) {
34637
35148
  const candidate = resolve2(inputPath ?? process.cwd());
34638
- if (existsSync4(candidate) && statSync2(candidate).isFile()) {
35149
+ if (existsSync3(candidate) && statSync2(candidate).isFile()) {
34639
35150
  return dirname2(candidate);
34640
35151
  }
34641
35152
  return candidate;
@@ -34706,7 +35217,7 @@ function collectChangedFiles(root) {
34706
35217
  }
34707
35218
  }
34708
35219
  function defaultInclude(root) {
34709
- const included = DEFAULT_INCLUDE.filter((entry) => existsSync4(resolve2(root, entry)));
35220
+ const included = DEFAULT_INCLUDE.filter((entry) => existsSync3(resolve2(root, entry)));
34710
35221
  return included.length > 0 ? included : ["."];
34711
35222
  }
34712
35223
  function isDefaultExcluded(path) {
@@ -34880,6 +35391,8 @@ function parseWorkspaceArgs(args) {
34880
35391
  const exclude = [];
34881
35392
  let path;
34882
35393
  let json = false;
35394
+ let toon = false;
35395
+ let full = false;
34883
35396
  let changed = false;
34884
35397
  let maxSeverity = "info";
34885
35398
  for (let index = 0;index < args.length; index++) {
@@ -34890,6 +35403,14 @@ function parseWorkspaceArgs(args) {
34890
35403
  json = true;
34891
35404
  continue;
34892
35405
  }
35406
+ if (arg === "--toon") {
35407
+ toon = true;
35408
+ continue;
35409
+ }
35410
+ if (arg === "--full") {
35411
+ full = true;
35412
+ continue;
35413
+ }
34893
35414
  if (arg === "--changed") {
34894
35415
  changed = true;
34895
35416
  continue;
@@ -34918,7 +35439,7 @@ function parseWorkspaceArgs(args) {
34918
35439
  if (!path)
34919
35440
  path = arg;
34920
35441
  }
34921
- return { path, options: { json, include, exclude, maxSeverity, changed } };
35442
+ return { path, options: { json, toon, full, include, exclude, maxSeverity, changed } };
34922
35443
  }
34923
35444
 
34924
35445
  // src/commands/doctor.ts
@@ -34931,6 +35452,13 @@ function getDoctor(inputPath, spec, options = {}) {
34931
35452
  ...options.maxSeverity ? { maxSeverity: options.maxSeverity } : {},
34932
35453
  ...options.changed === undefined ? {} : { changed: options.changed }
34933
35454
  });
35455
+ if (options.toon) {
35456
+ return {
35457
+ output: toonWorkspaceReport(report, { title: "doctor", command: "doctor", full: options.full }),
35458
+ error: null,
35459
+ exitCode: 0
35460
+ };
35461
+ }
34934
35462
  return {
34935
35463
  output: formatWorkspaceReport(report, {
34936
35464
  title: "DryUI workspace doctor",
@@ -34950,13 +35478,18 @@ function getDoctor(inputPath, spec, options = {}) {
34950
35478
  }
34951
35479
  function runDoctor(args, spec) {
34952
35480
  if (args[0] === "--help") {
34953
- console.log("Usage: dryui doctor [path] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
35481
+ console.log("Usage: dryui doctor [path] [--toon] [--full] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
34954
35482
  console.log("");
34955
35483
  console.log("Inspect workspace health with human-readable findings.");
35484
+ console.log("");
35485
+ console.log("Options:");
35486
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35487
+ console.log(" --full Show all findings (disables truncation at 50)");
34956
35488
  process.exit(0);
34957
35489
  }
34958
35490
  const { path, options } = parseWorkspaceArgs(args);
34959
- runCommand(getDoctor(path, spec, options));
35491
+ const mode = options.toon ? "toon" : "text";
35492
+ runCommand(getDoctor(path, spec, options), mode);
34960
35493
  }
34961
35494
 
34962
35495
  // src/commands/lint.ts
@@ -34969,6 +35502,13 @@ function getLint(inputPath, spec, options = {}) {
34969
35502
  ...options.changed === undefined ? {} : { changed: options.changed },
34970
35503
  ...options.maxSeverity ? { maxSeverity: options.maxSeverity } : {}
34971
35504
  });
35505
+ if (options.toon) {
35506
+ return {
35507
+ output: toonWorkspaceReport(report, { title: "lint", command: "lint", full: options.full }),
35508
+ error: null,
35509
+ exitCode: report.findings.length > 0 ? 1 : 0
35510
+ };
35511
+ }
34972
35512
  if (options.json) {
34973
35513
  return {
34974
35514
  output: JSON.stringify(report, null, 2),
@@ -34991,23 +35531,28 @@ function getLint(inputPath, spec, options = {}) {
34991
35531
  }
34992
35532
  function runLint(args, spec) {
34993
35533
  if (args[0] === "--help") {
34994
- console.log("Usage: dryui lint [path] [--json] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
35534
+ console.log("Usage: dryui lint [path] [--json] [--toon] [--full] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]");
34995
35535
  console.log("");
34996
35536
  console.log("Print deterministic workspace findings.");
35537
+ console.log("");
35538
+ console.log("Options:");
35539
+ console.log(" --toon Output in TOON format (token-optimized for agents)");
35540
+ console.log(" --full Show all findings (disables truncation at 50)");
34997
35541
  process.exit(0);
34998
35542
  }
34999
35543
  const { path, options } = parseWorkspaceArgs(args);
35000
- runCommand(getLint(path, spec, options));
35544
+ const mode = options.toon ? "toon" : options.json ? "json" : "text";
35545
+ runCommand(getLint(path, spec, options), mode);
35001
35546
  }
35002
35547
 
35003
35548
  // src/commands/feedback.ts
35004
- import { existsSync as existsSync6 } from "node:fs";
35549
+ import { existsSync as existsSync5 } from "node:fs";
35005
35550
  import { spawnSync } from "node:child_process";
35006
35551
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
35007
35552
  import { fileURLToPath } from "node:url";
35008
35553
 
35009
35554
  // ../feedback-server/src/config.ts
35010
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
35555
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "node:fs";
35011
35556
  import { homedir } from "node:os";
35012
35557
  import { dirname as dirname3, join } from "node:path";
35013
35558
  var DEFAULT_FEEDBACK_PORT = 4748;
@@ -35217,7 +35762,7 @@ __export(exports_util, {
35217
35762
  finalizeIssue: () => finalizeIssue,
35218
35763
  extend: () => extend,
35219
35764
  escapeRegex: () => escapeRegex,
35220
- esc: () => esc,
35765
+ esc: () => esc2,
35221
35766
  defineLazy: () => defineLazy,
35222
35767
  createTransparentProxy: () => createTransparentProxy,
35223
35768
  cloneDef: () => cloneDef,
@@ -35368,7 +35913,7 @@ function randomString(length = 10) {
35368
35913
  }
35369
35914
  return str;
35370
35915
  }
35371
- function esc(str) {
35916
+ function esc2(str) {
35372
35917
  return JSON.stringify(str);
35373
35918
  }
35374
35919
  function slugify(input) {
@@ -36853,10 +37398,10 @@ function isValidJWT(token, algorithm = null) {
36853
37398
  const tokensParts = token.split(".");
36854
37399
  if (tokensParts.length !== 3)
36855
37400
  return false;
36856
- const [header] = tokensParts;
36857
- if (!header)
37401
+ const [header2] = tokensParts;
37402
+ if (!header2)
36858
37403
  return false;
36859
- const parsedHeader = JSON.parse(atob(header));
37404
+ const parsedHeader = JSON.parse(atob(header2));
36860
37405
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
36861
37406
  return false;
36862
37407
  if (!parsedHeader.alg)
@@ -37137,7 +37682,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
37137
37682
  const doc = new Doc(["shape", "payload", "ctx"]);
37138
37683
  const normalized = _normalized.value;
37139
37684
  const parseStr = (key) => {
37140
- const k = esc(key);
37685
+ const k = esc2(key);
37141
37686
  return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
37142
37687
  };
37143
37688
  doc.write(`const input = payload.value;`);
@@ -37149,7 +37694,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
37149
37694
  doc.write(`const newResult = {};`);
37150
37695
  for (const key of normalized.keys) {
37151
37696
  const id = ids[key];
37152
- const k = esc(key);
37697
+ const k = esc2(key);
37153
37698
  const schema = shape[key];
37154
37699
  const isOptionalOut = schema?._zod?.optout === "optional";
37155
37700
  doc.write(`const ${id} = ${parseStr(key)};`);
@@ -40733,7 +41278,7 @@ function usage() {
40733
41278
  `);
40734
41279
  }
40735
41280
  function resolveServerEntry() {
40736
- if (existsSync6(SERVER_DIST_PATH))
41281
+ if (existsSync5(SERVER_DIST_PATH))
40737
41282
  return SERVER_DIST_PATH;
40738
41283
  return SERVER_SRC_PATH;
40739
41284
  }
@@ -40831,32 +41376,52 @@ var USAGE = `Usage: dryui <command> [options]
40831
41376
 
40832
41377
  Commands:
40833
41378
  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
41379
+ detect [--json] [--toon] [path]
41380
+ Detect DryUI project setup
41381
+ install [--json] [--toon] [path]
41382
+ Print a project install plan
40836
41383
  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]
41384
+ info <component> [--toon] Show component API reference
41385
+ list [--category <cat>] [--toon]
41386
+ List all components
41387
+ compose <query> [--toon] Look up composition guidance
41388
+ review [--json] [--toon] <file.svelte>
41389
+ Validate a Svelte file against DryUI spec
41390
+ diagnose [--json] [--toon] <file.css>
41391
+ Validate theme CSS
41392
+ doctor [path] [--toon] [--include <glob>] [--exclude <glob>] [--changed]
40843
41393
  Inspect workspace health
40844
- lint [path] [--json] [--include <glob>] [--exclude <glob>] [--max-severity <level>] [--changed]
41394
+ lint [path] [--json] [--toon] [--include <glob>] [--exclude <glob>] [--changed]
40845
41395
  Print deterministic workspace findings
40846
41396
  feedback <subcommand> Start or inspect the feedback server
40847
41397
 
40848
41398
  Options:
40849
41399
  --help Show help for a command
40850
- --version Show version`;
41400
+ --version Show version
41401
+ --toon Token-optimized output for AI agents (per-command)
41402
+ --full Disable truncation (use with --toon)`;
40851
41403
  function main() {
40852
41404
  const args = process.argv.slice(2);
40853
41405
  const command = args[0];
40854
- if (!command || command === "--help" || command === "-h") {
41406
+ if (command === "--version" || command === "-v") {
41407
+ console.log(VERSION);
41408
+ process.exit(0);
41409
+ }
41410
+ if (command === "--help" || command === "-h") {
40855
41411
  console.log(USAGE);
40856
41412
  process.exit(0);
40857
41413
  }
40858
- if (command === "--version" || command === "-v") {
40859
- console.log(VERSION);
41414
+ if (!command) {
41415
+ try {
41416
+ const detection = detectProject(spec_default, undefined);
41417
+ if (detection.status === "ready" || detection.status === "partial") {
41418
+ console.log(`dryui v${VERSION}
41419
+ `);
41420
+ console.log(toonProjectDetection(detection));
41421
+ process.exit(0);
41422
+ }
41423
+ } catch {}
41424
+ console.log(USAGE);
40860
41425
  process.exit(0);
40861
41426
  }
40862
41427
  const commandArgs = args.slice(1);