@dryui/mcp 0.1.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.
@@ -0,0 +1,73 @@
1
+ export type DryuiFramework = 'sveltekit' | 'svelte' | 'unknown';
2
+ export type DryuiPackageManager = 'bun' | 'pnpm' | 'npm' | 'yarn' | 'unknown';
3
+ export type DryuiProjectStatus = 'ready' | 'partial' | 'unsupported';
4
+ export type DryuiPlanStepKind = 'install-package' | 'edit-file' | 'create-file' | 'note' | 'blocked';
5
+ export type DryuiPlanStepStatus = 'done' | 'pending' | 'info' | 'blocked';
6
+ export interface ProjectPlannerComponentDef {
7
+ readonly import: string;
8
+ readonly example: string;
9
+ }
10
+ export interface ProjectPlannerSpec {
11
+ readonly themeImports: {
12
+ readonly default: string;
13
+ readonly dark: string;
14
+ };
15
+ readonly components: Record<string, ProjectPlannerComponentDef>;
16
+ }
17
+ export interface ProjectDetection {
18
+ readonly inputPath: string;
19
+ readonly root: string | null;
20
+ readonly packageJsonPath: string | null;
21
+ readonly framework: DryuiFramework;
22
+ readonly packageManager: DryuiPackageManager;
23
+ readonly status: DryuiProjectStatus;
24
+ readonly dependencies: {
25
+ readonly ui: boolean;
26
+ readonly primitives: boolean;
27
+ };
28
+ readonly files: {
29
+ readonly appHtml: string | null;
30
+ readonly appCss: string | null;
31
+ readonly rootLayout: string | null;
32
+ readonly rootPage: string | null;
33
+ };
34
+ readonly theme: {
35
+ readonly defaultImported: boolean;
36
+ readonly darkImported: boolean;
37
+ readonly themeAuto: boolean;
38
+ };
39
+ readonly warnings: readonly string[];
40
+ }
41
+ export interface ProjectPlanStep {
42
+ readonly kind: DryuiPlanStepKind;
43
+ readonly status: DryuiPlanStepStatus;
44
+ readonly title: string;
45
+ readonly description: string;
46
+ readonly path?: string;
47
+ readonly command?: string;
48
+ readonly snippet?: string;
49
+ }
50
+ export interface InstallPlan {
51
+ readonly detection: ProjectDetection;
52
+ readonly steps: readonly ProjectPlanStep[];
53
+ }
54
+ export interface AddPlan {
55
+ readonly detection: ProjectDetection;
56
+ readonly installPlan: InstallPlan;
57
+ readonly targetType: 'component';
58
+ readonly name: string;
59
+ readonly importStatement: string | null;
60
+ readonly snippet: string;
61
+ readonly target: string | null;
62
+ readonly steps: readonly ProjectPlanStep[];
63
+ readonly warnings: readonly string[];
64
+ }
65
+ export interface PlanAddOptions {
66
+ readonly cwd?: string;
67
+ readonly target?: string;
68
+ readonly subpath?: boolean;
69
+ readonly withTheme?: boolean;
70
+ }
71
+ export declare function detectProject(spec: Pick<ProjectPlannerSpec, 'themeImports'>, inputPath?: string): ProjectDetection;
72
+ export declare function planInstall(spec: ProjectPlannerSpec, inputPath?: string): InstallPlan;
73
+ export declare function planAdd(spec: ProjectPlannerSpec, query: string, options?: PlanAddOptions): AddPlan;
@@ -0,0 +1,374 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toESMCache_node;
10
+ var __toESMCache_esm;
11
+ var __toESM = (mod, isNodeMode, target) => {
12
+ var canCache = mod != null && typeof mod === "object";
13
+ if (canCache) {
14
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
+ var cached = cache.get(mod);
16
+ if (cached)
17
+ return cached;
18
+ }
19
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
20
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
+ for (let key of __getOwnPropNames(mod))
22
+ if (!__hasOwnProp.call(to, key))
23
+ __defProp(to, key, {
24
+ get: __accessProp.bind(mod, key),
25
+ enumerable: true
26
+ });
27
+ if (canCache)
28
+ cache.set(mod, to);
29
+ return to;
30
+ };
31
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
+ var __returnValue = (v) => v;
33
+ function __exportSetter(name, newValue) {
34
+ this[name] = __returnValue.bind(null, newValue);
35
+ }
36
+ var __export = (target, all) => {
37
+ for (var name in all)
38
+ __defProp(target, name, {
39
+ get: all[name],
40
+ enumerable: true,
41
+ configurable: true,
42
+ set: __exportSetter.bind(all, name)
43
+ });
44
+ };
45
+
46
+ // src/project-planner.ts
47
+ import { existsSync, readFileSync, statSync } from "node:fs";
48
+ import { dirname, resolve } from "node:path";
49
+ var DIR_OVERRIDES = {
50
+ QRCode: "qr-code"
51
+ };
52
+ function componentDir(name) {
53
+ return DIR_OVERRIDES[name] ?? name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
54
+ }
55
+ function resolveStart(inputPath) {
56
+ const candidate = resolve(inputPath ?? process.cwd());
57
+ return existsSync(candidate) && statSync(candidate).isFile() ? dirname(candidate) : candidate;
58
+ }
59
+ function findUp(start, fileName) {
60
+ let current = start;
61
+ while (true) {
62
+ const candidate = resolve(current, fileName);
63
+ if (existsSync(candidate))
64
+ return candidate;
65
+ const parent = dirname(current);
66
+ if (parent === current)
67
+ return null;
68
+ current = parent;
69
+ }
70
+ }
71
+ function findUpAny(start, fileNames) {
72
+ let current = start;
73
+ while (true) {
74
+ for (const fileName of fileNames) {
75
+ const candidate = resolve(current, fileName);
76
+ if (existsSync(candidate))
77
+ return candidate;
78
+ }
79
+ const parent = dirname(current);
80
+ if (parent === current)
81
+ return null;
82
+ current = parent;
83
+ }
84
+ }
85
+ function detectPackageManager(root) {
86
+ if (!root)
87
+ return "unknown";
88
+ const lockfilePath = findUpAny(root, [
89
+ "bun.lock",
90
+ "bun.lockb",
91
+ "pnpm-lock.yaml",
92
+ "package-lock.json",
93
+ "yarn.lock"
94
+ ]);
95
+ if (!lockfilePath)
96
+ return "unknown";
97
+ if (lockfilePath.endsWith("bun.lock") || lockfilePath.endsWith("bun.lockb"))
98
+ return "bun";
99
+ if (lockfilePath.endsWith("pnpm-lock.yaml"))
100
+ return "pnpm";
101
+ if (lockfilePath.endsWith("package-lock.json"))
102
+ return "npm";
103
+ if (lockfilePath.endsWith("yarn.lock"))
104
+ return "yarn";
105
+ return "unknown";
106
+ }
107
+ function readPackageJson(packageJsonPath) {
108
+ if (!packageJsonPath)
109
+ return null;
110
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
111
+ }
112
+ function getDependencyNames(pkg) {
113
+ return new Set([
114
+ ...Object.keys(pkg?.dependencies ?? {}),
115
+ ...Object.keys(pkg?.devDependencies ?? {})
116
+ ]);
117
+ }
118
+ function detectFramework(dependencyNames) {
119
+ if (dependencyNames.has("@sveltejs/kit"))
120
+ return "sveltekit";
121
+ if (dependencyNames.has("svelte"))
122
+ return "svelte";
123
+ return "unknown";
124
+ }
125
+ function hasImport(filePath, importPath) {
126
+ if (!filePath)
127
+ return false;
128
+ return readFileSync(filePath, "utf-8").includes(importPath);
129
+ }
130
+ function hasThemeAuto(appHtmlPath) {
131
+ if (!appHtmlPath)
132
+ return false;
133
+ return readFileSync(appHtmlPath, "utf-8").includes("theme-auto");
134
+ }
135
+ function hasAnyImport(filePaths, importPath) {
136
+ return filePaths.some((filePath) => hasImport(filePath, importPath));
137
+ }
138
+ function importsAppCss(rootLayoutPath) {
139
+ if (!rootLayoutPath)
140
+ return false;
141
+ const content = readFileSync(rootLayoutPath, "utf-8");
142
+ return content.includes("app.css");
143
+ }
144
+ function buildStatus(framework, hasPackageJson, stepsNeeded) {
145
+ if (!hasPackageJson || framework === "unknown")
146
+ return "unsupported";
147
+ return stepsNeeded === 0 ? "ready" : "partial";
148
+ }
149
+ function installCommand(packageManager) {
150
+ switch (packageManager) {
151
+ case "bun":
152
+ return "bun add @dryui/ui";
153
+ case "pnpm":
154
+ return "pnpm add @dryui/ui";
155
+ case "yarn":
156
+ return "yarn add @dryui/ui";
157
+ default:
158
+ return "npm install @dryui/ui";
159
+ }
160
+ }
161
+ function buildThemeImportSnippet(spec) {
162
+ return [
163
+ '<script lang="ts">',
164
+ ` import '${spec.themeImports.default}';`,
165
+ ` import '${spec.themeImports.dark}';`,
166
+ "</script>"
167
+ ].join(`
168
+ `);
169
+ }
170
+ function buildThemeImportCssSnippet(spec) {
171
+ return [`@import '${spec.themeImports.default}';`, `@import '${spec.themeImports.dark}';`].join(`
172
+ `);
173
+ }
174
+ function getSuggestedTarget(root, explicitTarget) {
175
+ if (explicitTarget)
176
+ return resolve(root ?? process.cwd(), explicitTarget);
177
+ if (!root)
178
+ return null;
179
+ const rootPage = resolve(root, "src/routes/+page.svelte");
180
+ return existsSync(rootPage) ? rootPage : null;
181
+ }
182
+ function getImportStatement(name, component, subpath = false) {
183
+ if (subpath && component.import === "@dryui/ui") {
184
+ return `import { ${name} } from '${component.import}/${componentDir(name)}';`;
185
+ }
186
+ return `import { ${name} } from '${component.import}';`;
187
+ }
188
+ function findComponent(spec, query) {
189
+ const exact = spec.components[query];
190
+ if (exact)
191
+ return { name: query, def: exact };
192
+ const lower = query.toLowerCase();
193
+ for (const [name, def] of Object.entries(spec.components)) {
194
+ if (name.toLowerCase() === lower)
195
+ return { name, def };
196
+ }
197
+ return null;
198
+ }
199
+ function detectProject(spec, inputPath) {
200
+ const start = resolveStart(inputPath);
201
+ const packageJsonPath = findUp(start, "package.json");
202
+ const root = packageJsonPath ? dirname(packageJsonPath) : null;
203
+ const dependencyNames = getDependencyNames(readPackageJson(packageJsonPath));
204
+ const framework = detectFramework(dependencyNames);
205
+ const appHtmlPath = root ? resolve(root, "src/app.html") : null;
206
+ const appCssPath = root ? resolve(root, "src/app.css") : null;
207
+ const rootLayoutPath = root ? resolve(root, "src/routes/+layout.svelte") : null;
208
+ const rootPagePath = root ? resolve(root, "src/routes/+page.svelte") : null;
209
+ const appHtml = appHtmlPath && existsSync(appHtmlPath) ? appHtmlPath : null;
210
+ const appCss = appCssPath && existsSync(appCssPath) ? appCssPath : null;
211
+ const rootLayout = rootLayoutPath && existsSync(rootLayoutPath) ? rootLayoutPath : null;
212
+ const rootPage = rootPagePath && existsSync(rootPagePath) ? rootPagePath : null;
213
+ const themeImportFiles = rootLayout && importsAppCss(rootLayout) ? [rootLayout, appCss] : [rootLayout];
214
+ const defaultImported = hasAnyImport(themeImportFiles, spec.themeImports.default);
215
+ const darkImported = hasAnyImport(themeImportFiles, spec.themeImports.dark);
216
+ const stepsNeeded = Number(!dependencyNames.has("@dryui/ui")) + Number(!defaultImported) + Number(!darkImported) + Number(!(appHtml && hasThemeAuto(appHtml)));
217
+ const warnings = [];
218
+ if (!packageJsonPath)
219
+ warnings.push("No package.json found above the provided path.");
220
+ if (framework === "unknown")
221
+ warnings.push("DryUI planning currently targets Svelte and SvelteKit projects.");
222
+ return {
223
+ inputPath: start,
224
+ root,
225
+ packageJsonPath,
226
+ framework,
227
+ packageManager: detectPackageManager(root),
228
+ status: buildStatus(framework, Boolean(packageJsonPath), stepsNeeded),
229
+ dependencies: {
230
+ ui: dependencyNames.has("@dryui/ui"),
231
+ primitives: dependencyNames.has("@dryui/primitives")
232
+ },
233
+ files: {
234
+ appHtml,
235
+ appCss,
236
+ rootLayout,
237
+ rootPage
238
+ },
239
+ theme: {
240
+ defaultImported,
241
+ darkImported,
242
+ themeAuto: hasThemeAuto(appHtml)
243
+ },
244
+ warnings
245
+ };
246
+ }
247
+ function planInstall(spec, inputPath) {
248
+ const detection = detectProject(spec, inputPath);
249
+ const steps = [];
250
+ if (detection.status === "unsupported") {
251
+ steps.push({
252
+ kind: "blocked",
253
+ status: "blocked",
254
+ title: "Project detection incomplete",
255
+ description: detection.warnings.join(" ") || "DryUI install planning requires a Svelte or SvelteKit project with a package.json."
256
+ });
257
+ return { detection, steps };
258
+ }
259
+ if (!detection.dependencies.ui) {
260
+ steps.push({
261
+ kind: "install-package",
262
+ status: "pending",
263
+ title: "Install @dryui/ui",
264
+ description: "Add the styled DryUI package to the current project.",
265
+ command: installCommand(detection.packageManager)
266
+ });
267
+ }
268
+ if (!detection.theme.defaultImported || !detection.theme.darkImported) {
269
+ if (detection.files.appCss && detection.files.rootLayout && importsAppCss(detection.files.rootLayout)) {
270
+ steps.push({
271
+ kind: "edit-file",
272
+ status: "pending",
273
+ title: "Add theme imports to app.css",
274
+ description: "Ensure the app-level stylesheet imports both default and dark DryUI themes.",
275
+ path: detection.files.appCss,
276
+ snippet: buildThemeImportCssSnippet(spec)
277
+ });
278
+ } else if (!detection.files.rootLayout) {
279
+ const path = detection.root ? resolve(detection.root, "src/routes/+layout.svelte") : null;
280
+ steps.push({
281
+ kind: "create-file",
282
+ status: "pending",
283
+ title: "Create root layout with theme imports",
284
+ description: "Create src/routes/+layout.svelte and add the required DryUI theme imports.",
285
+ ...path ? { path } : {},
286
+ snippet: buildThemeImportSnippet(spec)
287
+ });
288
+ } else {
289
+ steps.push({
290
+ kind: "edit-file",
291
+ status: "pending",
292
+ title: "Add theme imports to the root layout",
293
+ description: "Ensure the root layout imports both default and dark DryUI themes.",
294
+ path: detection.files.rootLayout,
295
+ snippet: buildThemeImportSnippet(spec)
296
+ });
297
+ }
298
+ }
299
+ if (!detection.files.appHtml) {
300
+ steps.push({
301
+ kind: "blocked",
302
+ status: "blocked",
303
+ title: "app.html not found",
304
+ description: "DryUI expects src/app.html so the document can default to theme-auto mode."
305
+ });
306
+ } else if (!detection.theme.themeAuto) {
307
+ steps.push({
308
+ kind: "edit-file",
309
+ status: "pending",
310
+ title: "Set html theme mode to auto",
311
+ description: 'Add class="theme-auto" to the html element in src/app.html.',
312
+ path: detection.files.appHtml,
313
+ snippet: '<html class="theme-auto">'
314
+ });
315
+ }
316
+ if (steps.length === 0) {
317
+ steps.push({
318
+ kind: "note",
319
+ status: "done",
320
+ title: "DryUI install plan is complete",
321
+ description: "The project already has @dryui/ui, theme imports, and theme-auto configured."
322
+ });
323
+ }
324
+ return { detection, steps };
325
+ }
326
+ function planAdd(spec, query, options = {}) {
327
+ const installPlan = planInstall(spec, options.cwd);
328
+ const component = findComponent(spec, query);
329
+ if (component) {
330
+ const target = getSuggestedTarget(installPlan.detection.root, options.target);
331
+ const steps = [];
332
+ const warnings = [...installPlan.detection.warnings];
333
+ if (installPlan.steps.some((step) => step.status === "pending" || step.status === "blocked")) {
334
+ steps.push({
335
+ kind: "note",
336
+ status: "info",
337
+ title: "Complete install plan first",
338
+ description: "Apply the install plan before inserting DryUI components into project files."
339
+ });
340
+ }
341
+ steps.push(target ? {
342
+ kind: "edit-file",
343
+ status: "pending",
344
+ title: "Insert component into the target file",
345
+ description: "Add the import and snippet below to the chosen Svelte file.",
346
+ path: target,
347
+ snippet: `${getImportStatement(component.name, component.def, options.subpath)}
348
+
349
+ ${component.def.example}`
350
+ } : {
351
+ kind: "note",
352
+ status: "info",
353
+ title: "Choose a target Svelte file",
354
+ description: "No root page was found. Pick a target file and reuse the import and snippet in this plan."
355
+ });
356
+ return {
357
+ detection: installPlan.detection,
358
+ installPlan,
359
+ targetType: "component",
360
+ name: component.name,
361
+ importStatement: getImportStatement(component.name, component.def, options.subpath),
362
+ snippet: component.def.example,
363
+ target,
364
+ steps,
365
+ warnings
366
+ };
367
+ }
368
+ throw new Error(`Unknown component: "${query}"`);
369
+ }
370
+ export {
371
+ planInstall,
372
+ planAdd,
373
+ detectProject
374
+ };
@@ -0,0 +1,28 @@
1
+ export interface Issue {
2
+ readonly severity: 'error' | 'warning' | 'suggestion';
3
+ readonly code: string;
4
+ readonly line: number;
5
+ readonly message: string;
6
+ readonly fix: string | null;
7
+ }
8
+ export interface ReviewResult {
9
+ readonly issues: Issue[];
10
+ readonly summary: string;
11
+ readonly filename?: string;
12
+ }
13
+ export interface PropDef {
14
+ readonly type: string;
15
+ readonly required?: boolean;
16
+ }
17
+ export interface ComponentDef {
18
+ readonly compound: boolean;
19
+ readonly props?: Record<string, PropDef>;
20
+ readonly parts?: Record<string, {
21
+ readonly props: Record<string, PropDef>;
22
+ }>;
23
+ readonly cssVars: Record<string, string>;
24
+ }
25
+ export declare function reviewComponent(code: string, spec: {
26
+ components: Record<string, ComponentDef>;
27
+ thumbnails?: string[];
28
+ }, filename?: string): ReviewResult;