@decantr/mcp-server 1.0.0-beta.1 → 1.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +2 -0
- package/dist/chunk-A5IEC7O2.js +1939 -0
- package/dist/index.js +1 -510
- package/package.json +2 -2
|
@@ -0,0 +1,1939 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
CallToolRequestSchema
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
|
|
9
|
+
// src/tools.ts
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
12
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
13
|
+
import { validateEssence, evaluateGuard, isV3 as isV32 } from "@decantr/essence-spec";
|
|
14
|
+
import { resolvePatternPreset } from "@decantr/registry";
|
|
15
|
+
|
|
16
|
+
// src/helpers.ts
|
|
17
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
18
|
+
import { join, dirname } from "path";
|
|
19
|
+
import { RegistryAPIClient } from "@decantr/registry";
|
|
20
|
+
import { isV3, migrateV2ToV3 } from "@decantr/essence-spec";
|
|
21
|
+
var MAX_INPUT_LENGTH = 1e3;
|
|
22
|
+
function validateStringArg(args, field) {
|
|
23
|
+
const val = args[field];
|
|
24
|
+
if (!val || typeof val !== "string") {
|
|
25
|
+
return `Required parameter "${field}" must be a non-empty string.`;
|
|
26
|
+
}
|
|
27
|
+
if (val.length > MAX_INPUT_LENGTH) {
|
|
28
|
+
return `Parameter "${field}" exceeds maximum length of ${MAX_INPUT_LENGTH} characters.`;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function fuzzyScore(query, text) {
|
|
33
|
+
const q = query.toLowerCase();
|
|
34
|
+
const t = text.toLowerCase();
|
|
35
|
+
if (t === q) return 100;
|
|
36
|
+
if (t.startsWith(q)) return 90;
|
|
37
|
+
if (t.includes(q)) return 80;
|
|
38
|
+
let qi = 0;
|
|
39
|
+
for (let ti = 0; ti < t.length && qi < q.length; ti++) {
|
|
40
|
+
if (t[ti] === q[qi]) qi++;
|
|
41
|
+
}
|
|
42
|
+
return qi === q.length ? 60 : 0;
|
|
43
|
+
}
|
|
44
|
+
var _apiClient = null;
|
|
45
|
+
function getAPIClient() {
|
|
46
|
+
if (!_apiClient) {
|
|
47
|
+
_apiClient = new RegistryAPIClient({
|
|
48
|
+
baseUrl: process.env.DECANTR_API_URL || void 0,
|
|
49
|
+
apiKey: process.env.DECANTR_API_KEY || void 0
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return _apiClient;
|
|
53
|
+
}
|
|
54
|
+
async function readEssenceFile(essencePath) {
|
|
55
|
+
const resolvedPath = essencePath || join(process.cwd(), "decantr.essence.json");
|
|
56
|
+
const raw = await readFile(resolvedPath, "utf-8");
|
|
57
|
+
const essence = JSON.parse(raw);
|
|
58
|
+
return { essence, raw, path: resolvedPath };
|
|
59
|
+
}
|
|
60
|
+
async function writeEssenceFile(essencePath, essence) {
|
|
61
|
+
const dir = dirname(essencePath);
|
|
62
|
+
await mkdir(dir, { recursive: true });
|
|
63
|
+
await writeFile(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
64
|
+
}
|
|
65
|
+
async function mutateEssenceFile(essencePath, mutate) {
|
|
66
|
+
const { essence, path } = await readEssenceFile(essencePath);
|
|
67
|
+
const v3 = isV3(essence) ? structuredClone(essence) : migrateV2ToV3(essence);
|
|
68
|
+
const updated = mutate(v3);
|
|
69
|
+
await writeEssenceFile(path, updated);
|
|
70
|
+
return { essence: updated, path };
|
|
71
|
+
}
|
|
72
|
+
async function readDriftLog(projectRoot) {
|
|
73
|
+
const root = projectRoot || process.cwd();
|
|
74
|
+
const logPath = join(root, ".decantr", "drift-log.json");
|
|
75
|
+
try {
|
|
76
|
+
const raw = await readFile(logPath, "utf-8");
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function writeDriftLog(entries, projectRoot) {
|
|
83
|
+
const root = projectRoot || process.cwd();
|
|
84
|
+
const logPath = join(root, ".decantr", "drift-log.json");
|
|
85
|
+
await mkdir(dirname(logPath), { recursive: true });
|
|
86
|
+
await writeFile(logPath, JSON.stringify(entries, null, 2) + "\n", "utf-8");
|
|
87
|
+
return logPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/component-manifest.ts
|
|
91
|
+
var COMPONENT_MANIFEST = {
|
|
92
|
+
// ─── Original Components ───────────────────────────────────────
|
|
93
|
+
Button: {
|
|
94
|
+
name: "Button",
|
|
95
|
+
category: "original",
|
|
96
|
+
description: "Primary action button with variants, sizes, loading state, and icon support.",
|
|
97
|
+
props: [
|
|
98
|
+
{ name: "variant", type: "'default' | 'primary' | 'secondary' | 'tertiary' | 'destructive' | 'success' | 'warning' | 'outline' | 'ghost' | 'link'", required: false, default: "'default'", description: "Visual style variant." },
|
|
99
|
+
{ name: "size", type: "'xs' | 'sm' | 'default' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg'", required: false, default: "'default'", description: "Button size." },
|
|
100
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the button. Accepts a signal for reactive state." },
|
|
101
|
+
{ name: "loading", type: "boolean | (() => boolean)", required: false, description: "Show loading spinner overlay and disable the button." },
|
|
102
|
+
{ name: "block", type: "boolean", required: false, description: "Full-width button (display: block)." },
|
|
103
|
+
{ name: "rounded", type: "boolean", required: false, description: "Fully rounded pill shape." },
|
|
104
|
+
{ name: "iconLeft", type: "string | Node", required: false, description: "Leading icon. String = icon name, Node = custom element." },
|
|
105
|
+
{ name: "iconRight", type: "string | Node", required: false, description: "Trailing icon. String = icon name, Node = custom element." },
|
|
106
|
+
{ name: "onclick", type: "(e: MouseEvent) => void", required: false, description: "Click handler." },
|
|
107
|
+
{ name: "type", type: "'button' | 'submit' | 'reset'", required: false, default: "'button'", description: "HTML button type attribute." },
|
|
108
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
109
|
+
],
|
|
110
|
+
usage: `import { Button } from '@decantr/ui';
|
|
111
|
+
|
|
112
|
+
// Basic
|
|
113
|
+
Button({ variant: 'primary' }, 'Save')
|
|
114
|
+
|
|
115
|
+
// With icons
|
|
116
|
+
Button({ variant: 'outline', iconLeft: 'plus' }, 'Add Item')
|
|
117
|
+
|
|
118
|
+
// Loading state (reactive)
|
|
119
|
+
const [loading, setLoading] = createSignal(false);
|
|
120
|
+
Button({ variant: 'primary', loading, onclick: () => setLoading(true) }, 'Submit')
|
|
121
|
+
|
|
122
|
+
// Button group
|
|
123
|
+
Button.Group({},
|
|
124
|
+
Button({ variant: 'outline' }, 'Left'),
|
|
125
|
+
Button({ variant: 'outline' }, 'Center'),
|
|
126
|
+
Button({ variant: 'outline' }, 'Right'),
|
|
127
|
+
)`,
|
|
128
|
+
subComponents: ["Button.Group"],
|
|
129
|
+
relatedComponents: ["Spinner", "Dropdown"]
|
|
130
|
+
},
|
|
131
|
+
Card: {
|
|
132
|
+
name: "Card",
|
|
133
|
+
category: "original",
|
|
134
|
+
description: "Container with optional title, header, footer, cover image, actions, and loading skeleton.",
|
|
135
|
+
props: [
|
|
136
|
+
{ name: "title", type: "string | Node", required: false, description: "Card title \u2014 auto-creates a Card.Header." },
|
|
137
|
+
{ name: "extra", type: "Node | Function", required: false, description: "Top-right header content (e.g., action link)." },
|
|
138
|
+
{ name: "hoverable", type: "boolean", required: false, description: "Add hover elevation effect." },
|
|
139
|
+
{ name: "bordered", type: "boolean", required: false, default: "true", description: "Show border. Set false for borderless." },
|
|
140
|
+
{ name: "loading", type: "boolean | (() => boolean)", required: false, description: "Show skeleton loading placeholder." },
|
|
141
|
+
{ name: "size", type: "'default' | 'sm'", required: false, default: "'default'", description: "Card size variant." },
|
|
142
|
+
{ name: "type", type: "'inner'", required: false, description: "Inner card variant for nesting." },
|
|
143
|
+
{ name: "cover", type: "Node", required: false, description: "Cover image shorthand \u2014 wraps in Card.Cover." },
|
|
144
|
+
{ name: "actions", type: "Node[]", required: false, description: "Bottom action bar items \u2014 wraps in Card.Actions." },
|
|
145
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
146
|
+
],
|
|
147
|
+
usage: `import { Card } from '@decantr/ui';
|
|
148
|
+
|
|
149
|
+
// Simple card
|
|
150
|
+
Card({ title: 'My Card' }, 'Card content here.')
|
|
151
|
+
|
|
152
|
+
// With shorthand props
|
|
153
|
+
Card({
|
|
154
|
+
title: 'Product',
|
|
155
|
+
extra: Button({ variant: 'link' }, 'Edit'),
|
|
156
|
+
cover: h('img', { src: '/photo.jpg', alt: 'Product' }),
|
|
157
|
+
actions: [Button({}, 'Buy'), Button({ variant: 'ghost' }, 'Share')],
|
|
158
|
+
}, 'Description text.')
|
|
159
|
+
|
|
160
|
+
// Compound (explicit sections)
|
|
161
|
+
Card({ hoverable: true },
|
|
162
|
+
Card.Header({}, 'Custom Header'),
|
|
163
|
+
Card.Body({}, 'Body content'),
|
|
164
|
+
Card.Meta({ avatar: Avatar({ src: '/me.jpg' }), title: 'Name', description: 'Role' }),
|
|
165
|
+
Card.Footer({}, 'Footer'),
|
|
166
|
+
)`,
|
|
167
|
+
subComponents: ["Card.Header", "Card.Body", "Card.Footer", "Card.Cover", "Card.Meta", "Card.Grid", "Card.Actions"],
|
|
168
|
+
relatedComponents: ["Skeleton", "Avatar"]
|
|
169
|
+
},
|
|
170
|
+
Modal: {
|
|
171
|
+
name: "Modal",
|
|
172
|
+
category: "original",
|
|
173
|
+
description: "Dialog overlay with focus trap, backdrop click-to-close, and animated open/close transitions.",
|
|
174
|
+
props: [
|
|
175
|
+
{ name: "title", type: "string", required: false, description: "Modal title \u2014 auto-creates header with close button." },
|
|
176
|
+
{ name: "footer", type: "Node | Node[]", required: false, description: "Footer content (e.g., action buttons)." },
|
|
177
|
+
{ name: "visible", type: "() => boolean", required: true, description: "Signal getter controlling visibility. MUST be a signal function." },
|
|
178
|
+
{ name: "onClose", type: "() => void", required: false, description: "Called when modal is closed (via close button, backdrop, or Escape)." },
|
|
179
|
+
{ name: "width", type: "string", required: false, default: "'480px'", description: "CSS width of the modal panel." },
|
|
180
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
181
|
+
],
|
|
182
|
+
usage: `import { Modal, Button } from '@decantr/ui';
|
|
183
|
+
import { createSignal } from '@decantr/ui/state';
|
|
184
|
+
|
|
185
|
+
const [open, setOpen] = createSignal(false);
|
|
186
|
+
|
|
187
|
+
Button({ onclick: () => setOpen(true) }, 'Open Modal')
|
|
188
|
+
|
|
189
|
+
Modal({
|
|
190
|
+
title: 'Confirm Action',
|
|
191
|
+
visible: open,
|
|
192
|
+
onClose: () => setOpen(false),
|
|
193
|
+
footer: [
|
|
194
|
+
Button({ variant: 'ghost', onclick: () => setOpen(false) }, 'Cancel'),
|
|
195
|
+
Button({ variant: 'primary', onclick: handleConfirm }, 'Confirm'),
|
|
196
|
+
],
|
|
197
|
+
}, 'Are you sure you want to proceed?')`,
|
|
198
|
+
subComponents: ["Modal.Header", "Modal.Body", "Modal.Footer"],
|
|
199
|
+
relatedComponents: ["Drawer", "AlertDialog", "Button"]
|
|
200
|
+
},
|
|
201
|
+
Tabs: {
|
|
202
|
+
name: "Tabs",
|
|
203
|
+
category: "original",
|
|
204
|
+
description: "Tabbed interface with roving tabindex keyboard navigation and sliding indicator.",
|
|
205
|
+
props: [
|
|
206
|
+
{ name: "tabs", type: "Array<{ id: string, label: string, content?: () => Node, disabled?: boolean, closable?: boolean }>", required: true, description: "Tab definitions. content is a render function called lazily." },
|
|
207
|
+
{ name: "active", type: "string | (() => string)", required: false, description: "Active tab ID. Use a signal for controlled mode." },
|
|
208
|
+
{ name: "onchange", type: "(tabId: string) => void", required: false, description: "Called when active tab changes." },
|
|
209
|
+
{ name: "onclose", type: "(tabId: string) => void", required: false, description: "Called when a closable tab is closed." },
|
|
210
|
+
{ name: "orientation", type: "'horizontal' | 'vertical'", required: false, default: "'horizontal'", description: "Tab list orientation." },
|
|
211
|
+
{ name: "size", type: "'sm' | 'lg'", required: false, description: "Tab size." },
|
|
212
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable all tabs." },
|
|
213
|
+
{ name: "destroyInactive", type: "boolean", required: false, default: "true", description: "When false, all panels stay in DOM (hidden). When true, only active panel is rendered." },
|
|
214
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
215
|
+
],
|
|
216
|
+
usage: `import { Tabs } from '@decantr/ui';
|
|
217
|
+
|
|
218
|
+
Tabs({
|
|
219
|
+
tabs: [
|
|
220
|
+
{ id: 'overview', label: 'Overview', content: () => h('div', {}, 'Overview content') },
|
|
221
|
+
{ id: 'details', label: 'Details', content: () => h('div', {}, 'Details content') },
|
|
222
|
+
{ id: 'settings', label: 'Settings', content: () => h('div', {}, 'Settings content') },
|
|
223
|
+
],
|
|
224
|
+
active: 'overview',
|
|
225
|
+
onchange: (id) => console.log('Tab changed:', id),
|
|
226
|
+
})`,
|
|
227
|
+
relatedComponents: ["Accordion", "Segmented"]
|
|
228
|
+
},
|
|
229
|
+
Table: {
|
|
230
|
+
name: "Table",
|
|
231
|
+
category: "original",
|
|
232
|
+
description: "Simple static data table. For sorting, pagination, and selection, use DataTable instead.",
|
|
233
|
+
props: [
|
|
234
|
+
{ name: "columns", type: "Array<{ key: string, label: string, width?: string, render?: (value, row) => Node | string }>", required: true, description: "Column definitions." },
|
|
235
|
+
{ name: "data", type: "Object[]", required: true, description: "Row data array." },
|
|
236
|
+
{ name: "striped", type: "boolean", required: false, description: "Striped row backgrounds." },
|
|
237
|
+
{ name: "hoverable", type: "boolean", required: false, description: "Row hover highlight." },
|
|
238
|
+
{ name: "compact", type: "boolean", required: false, description: "Compact row padding." },
|
|
239
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
240
|
+
],
|
|
241
|
+
usage: `import { Table } from '@decantr/ui';
|
|
242
|
+
|
|
243
|
+
Table({
|
|
244
|
+
columns: [
|
|
245
|
+
{ key: 'name', label: 'Name' },
|
|
246
|
+
{ key: 'email', label: 'Email' },
|
|
247
|
+
{ key: 'role', label: 'Role', render: (val) => Badge({ variant: 'info' }, val) },
|
|
248
|
+
],
|
|
249
|
+
data: [
|
|
250
|
+
{ name: 'Alice', email: 'alice@example.com', role: 'Admin' },
|
|
251
|
+
{ name: 'Bob', email: 'bob@example.com', role: 'User' },
|
|
252
|
+
],
|
|
253
|
+
striped: true,
|
|
254
|
+
hoverable: true,
|
|
255
|
+
})`,
|
|
256
|
+
relatedComponents: ["DataTable", "Pagination"]
|
|
257
|
+
},
|
|
258
|
+
Input: {
|
|
259
|
+
name: "Input",
|
|
260
|
+
category: "original",
|
|
261
|
+
description: "Text input with prefix/suffix slots, validation states, reactive value, and built-in form field wrapping.",
|
|
262
|
+
props: [
|
|
263
|
+
{ name: "type", type: "string", required: false, default: "'text'", description: "HTML input type (text, password, email, etc.)." },
|
|
264
|
+
{ name: "placeholder", type: "string", required: false, description: "Placeholder text." },
|
|
265
|
+
{ name: "value", type: "string | (() => string)", required: false, description: "Input value. Use a signal for two-way binding." },
|
|
266
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the input." },
|
|
267
|
+
{ name: "readonly", type: "boolean | (() => boolean)", required: false, description: "Make input read-only." },
|
|
268
|
+
{ name: "prefix", type: "string | Node", required: false, description: 'Leading content inside the input (e.g., icon, "$").' },
|
|
269
|
+
{ name: "suffix", type: "string | Node", required: false, description: "Trailing content inside the input (e.g., icon, unit)." },
|
|
270
|
+
{ name: "error", type: "boolean | string | (() => boolean | string)", required: false, description: "Error state. String shows as message." },
|
|
271
|
+
{ name: "success", type: "boolean | string | (() => boolean | string)", required: false, description: "Success state." },
|
|
272
|
+
{ name: "loading", type: "boolean | (() => boolean)", required: false, description: "Loading indicator." },
|
|
273
|
+
{ name: "variant", type: "'outlined' | 'filled' | 'ghost'", required: false, default: "'outlined'", description: "Visual style variant." },
|
|
274
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg'", required: false, description: "Input size." },
|
|
275
|
+
{ name: "label", type: "string", required: false, description: "Form field label. When set, auto-wraps in a form field." },
|
|
276
|
+
{ name: "help", type: "string", required: false, description: "Help text below the input." },
|
|
277
|
+
{ name: "required", type: "boolean", required: false, description: "Mark as required (adds aria-required)." },
|
|
278
|
+
{ name: "oninput", type: "(e: Event) => void", required: false, description: "Fires on every keystroke." },
|
|
279
|
+
{ name: "onchange", type: "(value: string) => void", required: false, description: "Fires on blur/commit." },
|
|
280
|
+
{ name: "ref", type: "(el: HTMLInputElement) => void", required: false, description: "Callback with the raw input element." },
|
|
281
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
282
|
+
],
|
|
283
|
+
usage: `import { Input } from '@decantr/ui';
|
|
284
|
+
import { createSignal } from '@decantr/ui/state';
|
|
285
|
+
|
|
286
|
+
// Simple
|
|
287
|
+
Input({ placeholder: 'Enter your name' })
|
|
288
|
+
|
|
289
|
+
// With label and validation
|
|
290
|
+
const [email, setEmail] = createSignal('');
|
|
291
|
+
Input({
|
|
292
|
+
type: 'email',
|
|
293
|
+
label: 'Email Address',
|
|
294
|
+
placeholder: 'you@example.com',
|
|
295
|
+
value: email,
|
|
296
|
+
oninput: (e) => setEmail(e.target.value),
|
|
297
|
+
error: () => email() && !email().includes('@') ? 'Invalid email' : false,
|
|
298
|
+
help: 'We will never share your email.',
|
|
299
|
+
required: true,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// With prefix/suffix
|
|
303
|
+
Input({ prefix: '$', suffix: '.00', placeholder: '0' })`,
|
|
304
|
+
relatedComponents: ["Textarea", "Select", "InputNumber", "InputGroup", "Form"]
|
|
305
|
+
},
|
|
306
|
+
Textarea: {
|
|
307
|
+
name: "Textarea",
|
|
308
|
+
category: "original",
|
|
309
|
+
description: "Multi-line text input with resizing, validation states, and form field wrapping.",
|
|
310
|
+
props: [
|
|
311
|
+
{ name: "placeholder", type: "string", required: false, description: "Placeholder text." },
|
|
312
|
+
{ name: "value", type: "string | (() => string)", required: false, description: "Textarea value. Signal for reactive binding." },
|
|
313
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the textarea." },
|
|
314
|
+
{ name: "readonly", type: "boolean | (() => boolean)", required: false, description: "Make read-only." },
|
|
315
|
+
{ name: "error", type: "boolean | string | (() => boolean | string)", required: false, description: "Error state." },
|
|
316
|
+
{ name: "success", type: "boolean | string | (() => boolean | string)", required: false, description: "Success state." },
|
|
317
|
+
{ name: "variant", type: "'outlined' | 'filled' | 'ghost'", required: false, default: "'outlined'", description: "Visual style." },
|
|
318
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg'", required: false, description: "Textarea size." },
|
|
319
|
+
{ name: "rows", type: "number", required: false, default: "3", description: "Visible rows." },
|
|
320
|
+
{ name: "resize", type: "'none' | 'vertical' | 'horizontal' | 'both'", required: false, default: "'vertical'", description: "Resize behavior." },
|
|
321
|
+
{ name: "label", type: "string", required: false, description: "Form field label." },
|
|
322
|
+
{ name: "help", type: "string", required: false, description: "Help text below." },
|
|
323
|
+
{ name: "required", type: "boolean", required: false, description: "Mark as required." },
|
|
324
|
+
{ name: "oninput", type: "(e: Event) => void", required: false, description: "Input handler." },
|
|
325
|
+
{ name: "ref", type: "(el: HTMLTextAreaElement) => void", required: false, description: "Callback with the raw textarea element." },
|
|
326
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
327
|
+
],
|
|
328
|
+
usage: `import { Textarea } from '@decantr/ui';
|
|
329
|
+
|
|
330
|
+
Textarea({
|
|
331
|
+
label: 'Description',
|
|
332
|
+
placeholder: 'Tell us about your project...',
|
|
333
|
+
rows: 5,
|
|
334
|
+
help: 'Markdown supported.',
|
|
335
|
+
})`,
|
|
336
|
+
relatedComponents: ["Input", "Form"]
|
|
337
|
+
},
|
|
338
|
+
Select: {
|
|
339
|
+
name: "Select",
|
|
340
|
+
category: "original",
|
|
341
|
+
description: "Custom dropdown select with keyboard navigation, search, and form field support.",
|
|
342
|
+
props: [
|
|
343
|
+
{ name: "options", type: "Array<{ value: string, label: string, disabled?: boolean }>", required: true, description: "Option list." },
|
|
344
|
+
{ name: "value", type: "string | (() => string)", required: false, description: "Selected value. Signal for controlled mode." },
|
|
345
|
+
{ name: "placeholder", type: "string", required: false, description: "Placeholder text when no value selected." },
|
|
346
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the select." },
|
|
347
|
+
{ name: "error", type: "boolean | string | (() => boolean | string)", required: false, description: "Error state." },
|
|
348
|
+
{ name: "success", type: "boolean | string | (() => boolean | string)", required: false, description: "Success state." },
|
|
349
|
+
{ name: "variant", type: "'outlined' | 'filled' | 'ghost'", required: false, default: "'outlined'", description: "Visual style." },
|
|
350
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg'", required: false, description: "Select size." },
|
|
351
|
+
{ name: "label", type: "string", required: false, description: "Form field label." },
|
|
352
|
+
{ name: "help", type: "string", required: false, description: "Help text." },
|
|
353
|
+
{ name: "required", type: "boolean", required: false, description: "Mark as required." },
|
|
354
|
+
{ name: "onchange", type: "(value: string) => void", required: false, description: "Called when selection changes." },
|
|
355
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
356
|
+
],
|
|
357
|
+
usage: `import { Select } from '@decantr/ui';
|
|
358
|
+
|
|
359
|
+
Select({
|
|
360
|
+
label: 'Country',
|
|
361
|
+
placeholder: 'Choose a country',
|
|
362
|
+
options: [
|
|
363
|
+
{ value: 'us', label: 'United States' },
|
|
364
|
+
{ value: 'uk', label: 'United Kingdom' },
|
|
365
|
+
{ value: 'de', label: 'Germany', disabled: true },
|
|
366
|
+
],
|
|
367
|
+
onchange: (val) => console.log('Selected:', val),
|
|
368
|
+
})`,
|
|
369
|
+
relatedComponents: ["Combobox", "Cascader", "TreeSelect", "Input"]
|
|
370
|
+
},
|
|
371
|
+
Checkbox: {
|
|
372
|
+
name: "Checkbox",
|
|
373
|
+
category: "original",
|
|
374
|
+
description: "Checkbox input with label, indeterminate state, and validation support.",
|
|
375
|
+
props: [
|
|
376
|
+
{ name: "checked", type: "boolean | (() => boolean)", required: false, description: "Checked state. Signal for reactive binding." },
|
|
377
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the checkbox." },
|
|
378
|
+
{ name: "label", type: "string", required: false, description: "Label text next to the checkbox." },
|
|
379
|
+
{ name: "indeterminate", type: "boolean", required: false, description: 'Indeterminate (dash) state for "select all" patterns.' },
|
|
380
|
+
{ name: "error", type: "boolean | string | (() => boolean | string)", required: false, description: "Error state." },
|
|
381
|
+
{ name: "success", type: "boolean | string | (() => boolean | string)", required: false, description: "Success state." },
|
|
382
|
+
{ name: "help", type: "string", required: false, description: "Help text (wraps in form field)." },
|
|
383
|
+
{ name: "required", type: "boolean", required: false, description: "Mark as required." },
|
|
384
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg'", required: false, description: "Checkbox size." },
|
|
385
|
+
{ name: "onchange", type: "(checked: boolean) => void", required: false, description: "Called when checked state changes." },
|
|
386
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
387
|
+
],
|
|
388
|
+
usage: `import { Checkbox } from '@decantr/ui';
|
|
389
|
+
import { createSignal } from '@decantr/ui/state';
|
|
390
|
+
|
|
391
|
+
const [agreed, setAgreed] = createSignal(false);
|
|
392
|
+
|
|
393
|
+
Checkbox({
|
|
394
|
+
checked: agreed,
|
|
395
|
+
onchange: setAgreed,
|
|
396
|
+
label: 'I agree to the terms and conditions',
|
|
397
|
+
required: true,
|
|
398
|
+
})`,
|
|
399
|
+
relatedComponents: ["Switch", "RadioGroup", "Form"]
|
|
400
|
+
},
|
|
401
|
+
Switch: {
|
|
402
|
+
name: "Switch",
|
|
403
|
+
category: "original",
|
|
404
|
+
description: 'Toggle switch with accessible role="switch" and ARIA attributes.',
|
|
405
|
+
props: [
|
|
406
|
+
{ name: "checked", type: "boolean | (() => boolean)", required: false, description: "On/off state. Signal for reactive binding." },
|
|
407
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable the switch." },
|
|
408
|
+
{ name: "label", type: "string", required: false, description: "Label text." },
|
|
409
|
+
{ name: "error", type: "boolean | string | (() => boolean | string)", required: false, description: "Error state." },
|
|
410
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg'", required: false, description: "Switch size." },
|
|
411
|
+
{ name: "name", type: "string", required: false, description: "Form field name." },
|
|
412
|
+
{ name: "required", type: "boolean", required: false, description: "Mark as required." },
|
|
413
|
+
{ name: "onchange", type: "(checked: boolean) => void", required: false, description: "Called when toggled." },
|
|
414
|
+
{ name: "ref", type: "(el: HTMLInputElement) => void", required: false, description: "Callback with the raw input element." },
|
|
415
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
416
|
+
],
|
|
417
|
+
usage: `import { Switch } from '@decantr/ui';
|
|
418
|
+
import { createSignal } from '@decantr/ui/state';
|
|
419
|
+
|
|
420
|
+
const [dark, setDark] = createSignal(false);
|
|
421
|
+
|
|
422
|
+
Switch({
|
|
423
|
+
checked: dark,
|
|
424
|
+
onchange: setDark,
|
|
425
|
+
label: 'Dark mode',
|
|
426
|
+
})`,
|
|
427
|
+
relatedComponents: ["Checkbox", "Toggle"]
|
|
428
|
+
},
|
|
429
|
+
Badge: {
|
|
430
|
+
name: "Badge",
|
|
431
|
+
category: "original",
|
|
432
|
+
description: "Status indicator pill, dot indicator, or superscript count badge.",
|
|
433
|
+
props: [
|
|
434
|
+
{ name: "count", type: "number | (() => number)", required: false, description: "Numeric count to display." },
|
|
435
|
+
{ name: "color", type: "string", required: false, description: "CSS color override." },
|
|
436
|
+
{ name: "dot", type: "boolean", required: false, description: "Show as dot instead of pill." },
|
|
437
|
+
{ name: "status", type: "'success' | 'error' | 'warning' | 'info' | 'processing' | 'primary'", required: false, description: "Semantic status color." },
|
|
438
|
+
{ name: "variant", type: "string", required: false, description: "Alias for status." },
|
|
439
|
+
{ name: "solid", type: "boolean", required: false, description: "Use saturated/solid colors instead of subtle." },
|
|
440
|
+
{ name: "icon", type: "string | Node", required: false, description: "Leading icon." },
|
|
441
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
442
|
+
],
|
|
443
|
+
usage: `import { Badge } from '@decantr/ui';
|
|
444
|
+
|
|
445
|
+
// Label badge
|
|
446
|
+
Badge({ variant: 'success' }, 'Active')
|
|
447
|
+
|
|
448
|
+
// Count superscript on another element
|
|
449
|
+
Badge({ count: 5 }, icon('bell', { size: '1.5em' }))
|
|
450
|
+
|
|
451
|
+
// Status dot
|
|
452
|
+
Badge({ dot: true, status: 'processing' })`,
|
|
453
|
+
relatedComponents: ["Chip", "Tag", "Alert"]
|
|
454
|
+
},
|
|
455
|
+
Alert: {
|
|
456
|
+
name: "Alert",
|
|
457
|
+
category: "original",
|
|
458
|
+
description: "Inline alert banner with icon, dismissible option, and semantic variants.",
|
|
459
|
+
props: [
|
|
460
|
+
{ name: "variant", type: "'info' | 'success' | 'warning' | 'error'", required: false, default: "'info'", description: "Semantic variant." },
|
|
461
|
+
{ name: "dismissible", type: "boolean", required: false, description: "Show close button. Removes from DOM on dismiss." },
|
|
462
|
+
{ name: "onDismiss", type: "() => void", required: false, description: "Called when dismissed." },
|
|
463
|
+
{ name: "icon", type: "string | Node", required: false, description: "Leading icon." },
|
|
464
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
465
|
+
],
|
|
466
|
+
usage: `import { Alert } from '@decantr/ui';
|
|
467
|
+
|
|
468
|
+
Alert({ variant: 'warning', dismissible: true },
|
|
469
|
+
'Your session will expire in 5 minutes.'
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
Alert({ variant: 'error', icon: 'alert-circle' },
|
|
473
|
+
'Failed to save changes. Please try again.'
|
|
474
|
+
)`,
|
|
475
|
+
relatedComponents: ["AlertDialog", "Banner", "notification", "message"]
|
|
476
|
+
},
|
|
477
|
+
Accordion: {
|
|
478
|
+
name: "Accordion",
|
|
479
|
+
category: "original",
|
|
480
|
+
description: "Collapsible content panels with animated open/close, keyboard navigation, and single/multiple mode.",
|
|
481
|
+
props: [
|
|
482
|
+
{ name: "items", type: "Array<{ id: string, title: string, content: (() => Node) | string, disabled?: boolean }>", required: true, description: "Accordion item definitions. content is lazy-rendered." },
|
|
483
|
+
{ name: "multiple", type: "boolean", required: false, default: "false", description: "Allow multiple panels open simultaneously." },
|
|
484
|
+
{ name: "collapsible", type: "boolean", required: false, default: "true", description: "Allow collapsing the last open panel (single mode)." },
|
|
485
|
+
{ name: "defaultOpen", type: "string[]", required: false, description: "Array of item IDs to open initially." },
|
|
486
|
+
{ name: "disabled", type: "boolean | (() => boolean)", required: false, description: "Disable all items or per-item via callback." },
|
|
487
|
+
{ name: "onValueChange", type: "(openIds: string[]) => void", required: false, description: "Called when open panels change." },
|
|
488
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
489
|
+
],
|
|
490
|
+
usage: `import { Accordion } from '@decantr/ui';
|
|
491
|
+
|
|
492
|
+
Accordion({
|
|
493
|
+
items: [
|
|
494
|
+
{ id: 'faq1', title: 'What is Decantr?', content: () => h('p', {}, 'A Design Intelligence API.') },
|
|
495
|
+
{ id: 'faq2', title: 'How does it work?', content: () => h('p', {}, 'Through the Design Pipeline.') },
|
|
496
|
+
{ id: 'faq3', title: 'Is it free?', content: 'Community tier is free.' },
|
|
497
|
+
],
|
|
498
|
+
defaultOpen: ['faq1'],
|
|
499
|
+
})`,
|
|
500
|
+
relatedComponents: ["Collapsible", "Tabs"]
|
|
501
|
+
},
|
|
502
|
+
Dropdown: {
|
|
503
|
+
name: "Dropdown",
|
|
504
|
+
category: "original",
|
|
505
|
+
description: "Menu triggered by a button with keyboard navigation, separators, icons, and shortcuts.",
|
|
506
|
+
props: [
|
|
507
|
+
{ name: "trigger", type: "() => HTMLElement", required: false, description: 'Function returning the trigger element. Defaults to a "Menu" button.' },
|
|
508
|
+
{ name: "items", type: "Array<{ label: string, value?: string, icon?: string | Node, shortcut?: string, disabled?: boolean, separator?: boolean, onclick?: () => void }>", required: true, description: "Menu items. Set separator: true for dividers." },
|
|
509
|
+
{ name: "align", type: "'left' | 'right'", required: false, default: "'left'", description: "Dropdown alignment." },
|
|
510
|
+
{ name: "block", type: "boolean", required: false, description: "Full-width trigger." },
|
|
511
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
512
|
+
],
|
|
513
|
+
usage: `import { Dropdown, Button } from '@decantr/ui';
|
|
514
|
+
|
|
515
|
+
Dropdown({
|
|
516
|
+
trigger: () => Button({ variant: 'outline', iconRight: 'chevron-down' }, 'Actions'),
|
|
517
|
+
items: [
|
|
518
|
+
{ label: 'Edit', icon: 'pencil', onclick: handleEdit },
|
|
519
|
+
{ label: 'Duplicate', icon: 'copy', shortcut: 'Ctrl+D' },
|
|
520
|
+
{ separator: true },
|
|
521
|
+
{ label: 'Delete', icon: 'trash', onclick: handleDelete },
|
|
522
|
+
],
|
|
523
|
+
})`,
|
|
524
|
+
relatedComponents: ["ContextMenu", "Menu", "Popover"]
|
|
525
|
+
},
|
|
526
|
+
Drawer: {
|
|
527
|
+
name: "Drawer",
|
|
528
|
+
category: "original",
|
|
529
|
+
description: "Slide-over panel from any edge with focus trap, animated transitions, and compound sections.",
|
|
530
|
+
props: [
|
|
531
|
+
{ name: "visible", type: "() => boolean", required: true, description: "Signal getter controlling visibility. MUST be a signal function." },
|
|
532
|
+
{ name: "onClose", type: "() => void", required: false, description: "Called when drawer is closed." },
|
|
533
|
+
{ name: "side", type: "'left' | 'right' | 'top' | 'bottom'", required: false, default: "'right'", description: "Edge to slide from." },
|
|
534
|
+
{ name: "title", type: "string", required: false, description: "Drawer title \u2014 auto-creates header with close button." },
|
|
535
|
+
{ name: "footer", type: "Node | Node[]", required: false, description: "Footer content." },
|
|
536
|
+
{ name: "size", type: "string", required: false, default: "'320px'", description: "CSS width (left/right) or height (top/bottom)." },
|
|
537
|
+
{ name: "closeOnOutside", type: "boolean", required: false, default: "true", description: "Close when clicking outside the panel." },
|
|
538
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
539
|
+
],
|
|
540
|
+
usage: `import { Drawer, Button } from '@decantr/ui';
|
|
541
|
+
import { createSignal } from '@decantr/ui/state';
|
|
542
|
+
|
|
543
|
+
const [open, setOpen] = createSignal(false);
|
|
544
|
+
|
|
545
|
+
Button({ onclick: () => setOpen(true) }, 'Open Drawer')
|
|
546
|
+
|
|
547
|
+
Drawer({
|
|
548
|
+
title: 'Settings',
|
|
549
|
+
visible: open,
|
|
550
|
+
onClose: () => setOpen(false),
|
|
551
|
+
side: 'right',
|
|
552
|
+
size: '400px',
|
|
553
|
+
}, 'Drawer content here.')`,
|
|
554
|
+
subComponents: ["Drawer.Header", "Drawer.Body", "Drawer.Footer"],
|
|
555
|
+
relatedComponents: ["Modal", "AlertDialog"]
|
|
556
|
+
},
|
|
557
|
+
Pagination: {
|
|
558
|
+
name: "Pagination",
|
|
559
|
+
category: "original",
|
|
560
|
+
description: "Page navigation with prev/next, page numbers, ellipsis, and reactive signals.",
|
|
561
|
+
props: [
|
|
562
|
+
{ name: "total", type: "number | (() => number)", required: true, description: "Total number of items." },
|
|
563
|
+
{ name: "perPage", type: "number", required: false, default: "10", description: "Items per page." },
|
|
564
|
+
{ name: "current", type: "number | (() => number)", required: false, default: "1", description: "Current page. Signal for controlled mode." },
|
|
565
|
+
{ name: "onchange", type: "(page: number) => void", required: false, description: "Called when page changes." },
|
|
566
|
+
{ name: "siblings", type: "number", required: false, default: "1", description: "Number of page buttons shown around current page." },
|
|
567
|
+
{ name: "size", type: "'sm' | 'lg'", required: false, description: "Pagination size." },
|
|
568
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
569
|
+
],
|
|
570
|
+
usage: `import { Pagination } from '@decantr/ui';
|
|
571
|
+
import { createSignal } from '@decantr/ui/state';
|
|
572
|
+
|
|
573
|
+
const [page, setPage] = createSignal(1);
|
|
574
|
+
|
|
575
|
+
Pagination({
|
|
576
|
+
total: 200,
|
|
577
|
+
perPage: 10,
|
|
578
|
+
current: page,
|
|
579
|
+
onchange: setPage,
|
|
580
|
+
})`,
|
|
581
|
+
relatedComponents: ["Table", "DataTable"]
|
|
582
|
+
},
|
|
583
|
+
Tooltip: {
|
|
584
|
+
name: "Tooltip",
|
|
585
|
+
category: "original",
|
|
586
|
+
description: "Informational popup on hover or focus with configurable position and delay.",
|
|
587
|
+
props: [
|
|
588
|
+
{ name: "content", type: "string", required: true, description: "Tooltip text content." },
|
|
589
|
+
{ name: "position", type: "'top' | 'bottom' | 'left' | 'right'", required: false, default: "'top'", description: "Tooltip position relative to the trigger." },
|
|
590
|
+
{ name: "delay", type: "number", required: false, default: "200", description: "Show delay in milliseconds." },
|
|
591
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
592
|
+
],
|
|
593
|
+
usage: `import { Tooltip, Button } from '@decantr/ui';
|
|
594
|
+
|
|
595
|
+
Tooltip({ content: 'Save your changes', position: 'bottom' },
|
|
596
|
+
Button({ variant: 'primary' }, 'Save')
|
|
597
|
+
)`,
|
|
598
|
+
relatedComponents: ["Popover", "HoverCard"]
|
|
599
|
+
},
|
|
600
|
+
Progress: {
|
|
601
|
+
name: "Progress",
|
|
602
|
+
category: "original",
|
|
603
|
+
description: "Progress bar with percentage, variants, striped/animated styles, and label support.",
|
|
604
|
+
props: [
|
|
605
|
+
{ name: "value", type: "number | (() => number)", required: false, description: "Current value (0 to max). Signal for reactive updates." },
|
|
606
|
+
{ name: "max", type: "number", required: false, default: "100", description: "Maximum value." },
|
|
607
|
+
{ name: "label", type: "string", required: false, description: "Label text (displayed inside bar for md/lg, outside for other sizes)." },
|
|
608
|
+
{ name: "variant", type: "'primary' | 'success' | 'warning' | 'error'", required: false, description: "Color variant." },
|
|
609
|
+
{ name: "size", type: "'sm' | 'md' | 'lg'", required: false, description: "Bar height." },
|
|
610
|
+
{ name: "striped", type: "boolean", required: false, description: "Add striped pattern." },
|
|
611
|
+
{ name: "animated", type: "boolean", required: false, description: "Animate the stripes." },
|
|
612
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
613
|
+
],
|
|
614
|
+
usage: `import { Progress } from '@decantr/ui';
|
|
615
|
+
import { createSignal } from '@decantr/ui/state';
|
|
616
|
+
|
|
617
|
+
const [progress, setProgress] = createSignal(45);
|
|
618
|
+
|
|
619
|
+
Progress({
|
|
620
|
+
value: progress,
|
|
621
|
+
variant: 'primary',
|
|
622
|
+
size: 'md',
|
|
623
|
+
label: '45%',
|
|
624
|
+
striped: true,
|
|
625
|
+
animated: true,
|
|
626
|
+
})`,
|
|
627
|
+
relatedComponents: ["Spinner", "Statistic"]
|
|
628
|
+
},
|
|
629
|
+
Spinner: {
|
|
630
|
+
name: "Spinner",
|
|
631
|
+
category: "original",
|
|
632
|
+
description: "Loading indicator with multiple animation variants: ring, dots, pulse, bars, orbit.",
|
|
633
|
+
props: [
|
|
634
|
+
{ name: "variant", type: "'ring' | 'dots' | 'pulse' | 'bars' | 'orbit'", required: false, default: "'ring'", description: "Animation style." },
|
|
635
|
+
{ name: "size", type: "'xs' | 'sm' | 'lg' | 'xl'", required: false, description: "Spinner size." },
|
|
636
|
+
{ name: "color", type: "'primary' | 'success' | 'warning' | 'destructive' | 'info' | 'muted'", required: false, description: "Semantic color." },
|
|
637
|
+
{ name: "icon", type: "string", required: false, description: "Icon name for hybrid mode (ring spins around static center icon)." },
|
|
638
|
+
{ name: "label", type: "string", required: false, default: "'Loading'", description: "Accessible label (screen reader text)." },
|
|
639
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
640
|
+
],
|
|
641
|
+
usage: `import { Spinner } from '@decantr/ui';
|
|
642
|
+
|
|
643
|
+
// Default ring spinner
|
|
644
|
+
Spinner({ size: 'lg', color: 'primary' })
|
|
645
|
+
|
|
646
|
+
// Dots variant
|
|
647
|
+
Spinner({ variant: 'dots', size: 'sm' })
|
|
648
|
+
|
|
649
|
+
// Hybrid: ring around an icon
|
|
650
|
+
Spinner({ icon: 'cloud', size: 'xl' })`,
|
|
651
|
+
relatedComponents: ["Progress", "Button"]
|
|
652
|
+
},
|
|
653
|
+
// ─── Data Display ──────────────────────────────────────────────
|
|
654
|
+
DataTable: {
|
|
655
|
+
name: "DataTable",
|
|
656
|
+
category: "data-display",
|
|
657
|
+
description: "Enterprise data grid with sorting, pagination, multi-selection, column pinning, cell editing, row expansion, filtering, CSV export, virtual scrolling, and column resizing.",
|
|
658
|
+
props: [
|
|
659
|
+
{ name: "columns", type: "Array<{ key: string, label: string, width?: string, sortable?: boolean, filterable?: boolean, pinned?: 'left' | 'right', render?: (val, row) => Node, editable?: boolean, align?: 'left' | 'center' | 'right', sort?: (a, b) => number }>", required: true, description: "Column definitions with sorting, filtering, pinning, and cell editing options." },
|
|
660
|
+
{ name: "data", type: "Array<Object> | (() => Array<Object>)", required: true, description: "Row data. Signal for reactive updates." },
|
|
661
|
+
{ name: "pagination", type: "{ pageSize?: number, serverSide?: boolean, total?: number | (() => number), onPageChange?: ({page, pageSize}) => void }", required: false, description: "Enable pagination. Supports client-side and server-side modes." },
|
|
662
|
+
{ name: "selection", type: "'single' | 'multi' | 'none'", required: false, default: "'none'", description: "Row selection mode." },
|
|
663
|
+
{ name: "onSelectionChange", type: "(selectedRows: Object[]) => void", required: false, description: "Called when selection changes." },
|
|
664
|
+
{ name: "striped", type: "boolean", required: false, default: "false", description: "Striped rows." },
|
|
665
|
+
{ name: "hoverable", type: "boolean", required: false, default: "true", description: "Row hover highlight." },
|
|
666
|
+
{ name: "stickyHeader", type: "boolean", required: false, default: "false", description: "Sticky table header on scroll." },
|
|
667
|
+
{ name: "onSort", type: '({ key: string, direction: "asc" | "desc" }) => void', required: false, description: "Called when sort changes (for server-side sorting)." },
|
|
668
|
+
{ name: "rowKey", type: "(row: Object, index: number) => string | number", required: false, description: "Unique key extractor for each row. Defaults to index." },
|
|
669
|
+
{ name: "onCellEdit", type: "({ row, column, value, oldValue }) => void", required: false, description: "Called when an editable cell is committed." },
|
|
670
|
+
{ name: "expandable", type: "boolean", required: false, default: "false", description: "Enable row expansion." },
|
|
671
|
+
{ name: "expandRender", type: "(row: Object) => Node | string", required: false, description: "Render function for expanded row content." },
|
|
672
|
+
{ name: "exportable", type: "boolean", required: false, default: "false", description: 'Show "Export CSV" button.' },
|
|
673
|
+
{ name: "emptyText", type: "string", required: false, default: "'No data'", description: "Text shown when data is empty." },
|
|
674
|
+
{ name: "class", type: "string", required: false, description: "Additional CSS class." }
|
|
675
|
+
],
|
|
676
|
+
usage: `import { DataTable } from '@decantr/ui';
|
|
677
|
+
|
|
678
|
+
DataTable({
|
|
679
|
+
columns: [
|
|
680
|
+
{ key: 'name', label: 'Name', sortable: true, filterable: true },
|
|
681
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
682
|
+
{ key: 'role', label: 'Role', render: (val) => Badge({ variant: val === 'admin' ? 'primary' : 'info' }, val) },
|
|
683
|
+
{ key: 'status', label: 'Status', pinned: 'right' },
|
|
684
|
+
],
|
|
685
|
+
data: users,
|
|
686
|
+
pagination: { pageSize: 20 },
|
|
687
|
+
selection: 'multi',
|
|
688
|
+
onSelectionChange: (rows) => console.log('Selected:', rows),
|
|
689
|
+
striped: true,
|
|
690
|
+
stickyHeader: true,
|
|
691
|
+
exportable: true,
|
|
692
|
+
})`,
|
|
693
|
+
relatedComponents: ["Table", "Pagination", "Checkbox"]
|
|
694
|
+
},
|
|
695
|
+
// ─── Composition API ───────────────────────────────────────────
|
|
696
|
+
compose: {
|
|
697
|
+
name: "compose",
|
|
698
|
+
category: "utility",
|
|
699
|
+
description: "Render a single pattern by ID using the current DNA context. Must be called inside an EssenceProvider.",
|
|
700
|
+
props: [
|
|
701
|
+
{ name: "patternId", type: "string", required: true, description: 'Pattern ID from the registry (e.g., "hero", "data-table", "kpi-grid").' },
|
|
702
|
+
{ name: "options.props", type: "Record<string, unknown>", required: false, description: "Props to pass to the pattern renderer." },
|
|
703
|
+
{ name: "options.preset", type: "string", required: false, description: "Preset name for the pattern." },
|
|
704
|
+
{ name: "options.slots", type: "Record<string, () => HTMLElement>", required: false, description: "Named slot render functions." }
|
|
705
|
+
],
|
|
706
|
+
usage: `import { compose } from '@decantr/ui/compose';
|
|
707
|
+
|
|
708
|
+
// Inside an EssenceProvider:
|
|
709
|
+
compose('hero', { props: { title: 'Welcome', subtitle: 'Get started' } })
|
|
710
|
+
compose('data-table', { preset: 'product', props: { data: products } })`,
|
|
711
|
+
relatedComponents: ["composePage", "EssenceProvider"]
|
|
712
|
+
},
|
|
713
|
+
composePage: {
|
|
714
|
+
name: "composePage",
|
|
715
|
+
category: "utility",
|
|
716
|
+
description: "Render a full page from the essence blueprint. Finds the page by ID, resolves each pattern in the layout, and wraps in a shell.",
|
|
717
|
+
props: [
|
|
718
|
+
{ name: "pageId", type: "string", required: true, description: 'Page ID from the blueprint (e.g., "landing", "dashboard").' }
|
|
719
|
+
],
|
|
720
|
+
usage: `import { composePage } from '@decantr/ui/compose';
|
|
721
|
+
|
|
722
|
+
// Inside an EssenceProvider:
|
|
723
|
+
composePage('landing')
|
|
724
|
+
composePage('dashboard')`,
|
|
725
|
+
relatedComponents: ["compose", "EssenceProvider", "EssenceApp"]
|
|
726
|
+
},
|
|
727
|
+
EssenceProvider: {
|
|
728
|
+
name: "EssenceProvider",
|
|
729
|
+
category: "utility",
|
|
730
|
+
description: "Context provider that makes the Essence spec (DNA tokens, guard settings) available to all descendant components and compose() calls.",
|
|
731
|
+
props: [
|
|
732
|
+
{ name: "essence", type: "EssenceV3", required: false, description: "Full v3 essence spec object. If omitted, inherits from parent provider." },
|
|
733
|
+
{ name: "overrides", type: "Partial<EssenceContextValue>", required: false, description: "Partial DNA overrides (style, mode, shape, density, etc.) for nested providers." }
|
|
734
|
+
],
|
|
735
|
+
usage: `import { EssenceProvider } from '@decantr/ui/essence';
|
|
736
|
+
import essence from './decantr.essence.json';
|
|
737
|
+
|
|
738
|
+
EssenceProvider({ essence },
|
|
739
|
+
composePage('landing'),
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
// Nested override:
|
|
743
|
+
EssenceProvider({ overrides: { mode: 'dark', density: 'compact' } },
|
|
744
|
+
compose('sidebar'),
|
|
745
|
+
)`,
|
|
746
|
+
relatedComponents: ["EssenceApp", "compose", "composePage"]
|
|
747
|
+
},
|
|
748
|
+
EssenceApp: {
|
|
749
|
+
name: "EssenceApp",
|
|
750
|
+
category: "utility",
|
|
751
|
+
description: "Top-level convenience component that wraps children in an EssenceProvider. Use as the root of your app.",
|
|
752
|
+
props: [
|
|
753
|
+
{ name: "essence", type: "EssenceV3", required: true, description: "Full v3 essence spec object." }
|
|
754
|
+
],
|
|
755
|
+
usage: `import { EssenceApp } from '@decantr/ui/compose';
|
|
756
|
+
import essence from './decantr.essence.json';
|
|
757
|
+
|
|
758
|
+
document.body.appendChild(
|
|
759
|
+
EssenceApp({ essence },
|
|
760
|
+
composePage('landing'),
|
|
761
|
+
)
|
|
762
|
+
);`,
|
|
763
|
+
relatedComponents: ["EssenceProvider", "compose", "composePage"]
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
var CATEGORIES = [...new Set(Object.values(COMPONENT_MANIFEST).map((c) => c.category))];
|
|
767
|
+
function getComponentsByCategory(category) {
|
|
768
|
+
const entries = Object.values(COMPONENT_MANIFEST);
|
|
769
|
+
const filtered = category && category !== "all" ? entries.filter((c) => c.category === category) : entries;
|
|
770
|
+
const grouped = {};
|
|
771
|
+
for (const entry of filtered) {
|
|
772
|
+
if (!grouped[entry.category]) grouped[entry.category] = [];
|
|
773
|
+
grouped[entry.category].push(entry);
|
|
774
|
+
}
|
|
775
|
+
return grouped;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/tools.ts
|
|
779
|
+
var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
|
|
780
|
+
function deriveZones(inputs) {
|
|
781
|
+
const zoneMap = /* @__PURE__ */ new Map();
|
|
782
|
+
for (const input of inputs) {
|
|
783
|
+
const existing = zoneMap.get(input.role);
|
|
784
|
+
if (existing) {
|
|
785
|
+
existing.archetypes.push(input.archetypeId);
|
|
786
|
+
existing.features.push(...input.features);
|
|
787
|
+
existing.descriptions.push(input.description);
|
|
788
|
+
} else {
|
|
789
|
+
zoneMap.set(input.role, {
|
|
790
|
+
role: input.role,
|
|
791
|
+
archetypes: [input.archetypeId],
|
|
792
|
+
shell: input.shell,
|
|
793
|
+
features: [...input.features],
|
|
794
|
+
descriptions: [input.description]
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
for (const zone of zoneMap.values()) {
|
|
799
|
+
zone.features = [...new Set(zone.features)];
|
|
800
|
+
}
|
|
801
|
+
return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
|
|
802
|
+
}
|
|
803
|
+
var GATEWAY_TRIGGER_MAP = {
|
|
804
|
+
auth: "authentication",
|
|
805
|
+
login: "authentication",
|
|
806
|
+
mfa: "authentication",
|
|
807
|
+
payment: "payment",
|
|
808
|
+
subscription: "payment",
|
|
809
|
+
checkout: "payment",
|
|
810
|
+
onboarding: "onboarding",
|
|
811
|
+
"setup-wizard": "onboarding",
|
|
812
|
+
welcome: "onboarding",
|
|
813
|
+
invite: "invitation",
|
|
814
|
+
"access-code": "invitation"
|
|
815
|
+
};
|
|
816
|
+
function resolveGatewayTrigger(features) {
|
|
817
|
+
for (const feature of features) {
|
|
818
|
+
const trigger = GATEWAY_TRIGGER_MAP[feature];
|
|
819
|
+
if (trigger) return trigger;
|
|
820
|
+
}
|
|
821
|
+
return "authentication";
|
|
822
|
+
}
|
|
823
|
+
function deriveTransitions(zones) {
|
|
824
|
+
const transitions = [];
|
|
825
|
+
const roles = new Set(zones.map((z) => z.role));
|
|
826
|
+
const gateway = zones.find((z) => z.role === "gateway");
|
|
827
|
+
const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
|
|
828
|
+
const hasApp = roles.has("primary") || roles.has("auxiliary");
|
|
829
|
+
const hasGateway = roles.has("gateway");
|
|
830
|
+
const hasPublic = roles.has("public");
|
|
831
|
+
if (hasPublic && hasGateway) {
|
|
832
|
+
transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
|
|
833
|
+
}
|
|
834
|
+
if (hasPublic && hasApp && !hasGateway) {
|
|
835
|
+
transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
|
|
836
|
+
}
|
|
837
|
+
if (hasGateway && hasApp) {
|
|
838
|
+
transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
|
|
839
|
+
transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
|
|
840
|
+
}
|
|
841
|
+
if (hasApp && hasPublic) {
|
|
842
|
+
transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
|
|
843
|
+
}
|
|
844
|
+
return transitions;
|
|
845
|
+
}
|
|
846
|
+
var READ_ONLY = {
|
|
847
|
+
readOnlyHint: true,
|
|
848
|
+
destructiveHint: false,
|
|
849
|
+
idempotentHint: true,
|
|
850
|
+
openWorldHint: false
|
|
851
|
+
};
|
|
852
|
+
var READ_ONLY_NETWORK = {
|
|
853
|
+
readOnlyHint: true,
|
|
854
|
+
destructiveHint: false,
|
|
855
|
+
idempotentHint: true,
|
|
856
|
+
openWorldHint: true
|
|
857
|
+
};
|
|
858
|
+
var WRITE_TOOL = {
|
|
859
|
+
readOnlyHint: false,
|
|
860
|
+
destructiveHint: false,
|
|
861
|
+
idempotentHint: false,
|
|
862
|
+
openWorldHint: false
|
|
863
|
+
};
|
|
864
|
+
var TOOLS = [
|
|
865
|
+
// 1. decantr_read_essence — local read
|
|
866
|
+
{
|
|
867
|
+
name: "decantr_read_essence",
|
|
868
|
+
title: "Read Essence",
|
|
869
|
+
description: "Read and return the current decantr.essence.json file from the working directory. For v3 files, optionally filter by layer (dna, blueprint, or full).",
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: {
|
|
873
|
+
path: { type: "string", description: "Optional path to essence file. Defaults to ./decantr.essence.json." },
|
|
874
|
+
layer: { type: "string", enum: ["dna", "blueprint", "full"], description: "For v3 essences: return only the specified layer. Defaults to full." }
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
annotations: READ_ONLY
|
|
878
|
+
},
|
|
879
|
+
// 2. decantr_validate — local read
|
|
880
|
+
{
|
|
881
|
+
name: "decantr_validate",
|
|
882
|
+
title: "Validate Essence",
|
|
883
|
+
description: "Validate a decantr.essence.json file against the schema and guard rules. For v3, reports DNA vs Blueprint violations separately.",
|
|
884
|
+
inputSchema: {
|
|
885
|
+
type: "object",
|
|
886
|
+
properties: {
|
|
887
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
annotations: READ_ONLY
|
|
891
|
+
},
|
|
892
|
+
// 3. decantr_search_registry — network
|
|
893
|
+
{
|
|
894
|
+
name: "decantr_search_registry",
|
|
895
|
+
title: "Search Registry",
|
|
896
|
+
description: "Search the Decantr community content registry for patterns, archetypes, recipes, and styles.",
|
|
897
|
+
inputSchema: {
|
|
898
|
+
type: "object",
|
|
899
|
+
properties: {
|
|
900
|
+
query: { type: "string", description: 'Search query (e.g. "kanban", "neon", "dashboard")' },
|
|
901
|
+
type: { type: "string", description: "Filter by type: pattern, archetype, recipe, style" }
|
|
902
|
+
},
|
|
903
|
+
required: ["query"]
|
|
904
|
+
},
|
|
905
|
+
annotations: READ_ONLY_NETWORK
|
|
906
|
+
},
|
|
907
|
+
// 4. decantr_resolve_pattern — network
|
|
908
|
+
{
|
|
909
|
+
name: "decantr_resolve_pattern",
|
|
910
|
+
title: "Resolve Pattern",
|
|
911
|
+
description: "Get full pattern details including layout spec, components, presets, and code examples.",
|
|
912
|
+
inputSchema: {
|
|
913
|
+
type: "object",
|
|
914
|
+
properties: {
|
|
915
|
+
id: { type: "string", description: 'Pattern ID (e.g. "hero", "data-table", "kpi-grid")' },
|
|
916
|
+
preset: { type: "string", description: 'Optional preset name (e.g. "product", "content")' },
|
|
917
|
+
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
918
|
+
},
|
|
919
|
+
required: ["id"]
|
|
920
|
+
},
|
|
921
|
+
annotations: READ_ONLY_NETWORK
|
|
922
|
+
},
|
|
923
|
+
// 5. decantr_resolve_archetype — network
|
|
924
|
+
{
|
|
925
|
+
name: "decantr_resolve_archetype",
|
|
926
|
+
title: "Resolve Archetype",
|
|
927
|
+
description: "Get archetype details including default pages, layouts, features, and suggested theme.",
|
|
928
|
+
inputSchema: {
|
|
929
|
+
type: "object",
|
|
930
|
+
properties: {
|
|
931
|
+
id: { type: "string", description: 'Archetype ID (e.g. "saas-dashboard", "ecommerce")' },
|
|
932
|
+
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
933
|
+
},
|
|
934
|
+
required: ["id"]
|
|
935
|
+
},
|
|
936
|
+
annotations: READ_ONLY_NETWORK
|
|
937
|
+
},
|
|
938
|
+
// 6. decantr_resolve_recipe — network
|
|
939
|
+
{
|
|
940
|
+
name: "decantr_resolve_recipe",
|
|
941
|
+
title: "Resolve Recipe",
|
|
942
|
+
description: "Get recipe visual treatment overrides, decoration rules, shell styles, spatial hints, visual effects, and pattern preferences.",
|
|
943
|
+
inputSchema: {
|
|
944
|
+
type: "object",
|
|
945
|
+
properties: {
|
|
946
|
+
id: { type: "string", description: 'Recipe ID (e.g. "auradecantism")' },
|
|
947
|
+
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
948
|
+
},
|
|
949
|
+
required: ["id"]
|
|
950
|
+
},
|
|
951
|
+
annotations: READ_ONLY_NETWORK
|
|
952
|
+
},
|
|
953
|
+
// 7. decantr_resolve_blueprint — network
|
|
954
|
+
{
|
|
955
|
+
name: "decantr_resolve_blueprint",
|
|
956
|
+
title: "Resolve Blueprint",
|
|
957
|
+
description: "Get a blueprint (app composition) with its archetype list, suggested theme, personality traits, and full page structure.",
|
|
958
|
+
inputSchema: {
|
|
959
|
+
type: "object",
|
|
960
|
+
properties: {
|
|
961
|
+
id: { type: "string", description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")' },
|
|
962
|
+
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
963
|
+
},
|
|
964
|
+
required: ["id"]
|
|
965
|
+
},
|
|
966
|
+
annotations: READ_ONLY_NETWORK
|
|
967
|
+
},
|
|
968
|
+
// 8. decantr_suggest_patterns — network
|
|
969
|
+
{
|
|
970
|
+
name: "decantr_suggest_patterns",
|
|
971
|
+
title: "Suggest Patterns",
|
|
972
|
+
description: "Given a page description, suggest appropriate patterns from the registry. Returns ranked pattern matches with layout specs and component lists.",
|
|
973
|
+
inputSchema: {
|
|
974
|
+
type: "object",
|
|
975
|
+
properties: {
|
|
976
|
+
description: { type: "string", description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")' }
|
|
977
|
+
},
|
|
978
|
+
required: ["description"]
|
|
979
|
+
},
|
|
980
|
+
annotations: READ_ONLY_NETWORK
|
|
981
|
+
},
|
|
982
|
+
// 9. decantr_check_drift — local read
|
|
983
|
+
{
|
|
984
|
+
name: "decantr_check_drift",
|
|
985
|
+
title: "Check Drift",
|
|
986
|
+
description: "Check if code changes violate the design intent captured in the Essence spec. For v3, returns separate dna_violations and blueprint_drift with autoFixable flags.",
|
|
987
|
+
inputSchema: {
|
|
988
|
+
type: "object",
|
|
989
|
+
properties: {
|
|
990
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
|
|
991
|
+
page_id: { type: "string", description: 'Page ID being modified (e.g. "overview", "settings")' },
|
|
992
|
+
components_used: {
|
|
993
|
+
type: "array",
|
|
994
|
+
items: { type: "string" },
|
|
995
|
+
description: "List of component names used in the generated code. Checked against page layout patterns."
|
|
996
|
+
},
|
|
997
|
+
theme_used: { type: "string", description: "Theme/style name used in the generated code" }
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
annotations: READ_ONLY
|
|
1001
|
+
},
|
|
1002
|
+
// 10. decantr_create_essence — network (fetches archetype)
|
|
1003
|
+
{
|
|
1004
|
+
name: "decantr_create_essence",
|
|
1005
|
+
title: "Create Essence",
|
|
1006
|
+
description: "Generate a valid v3 Essence spec skeleton from a project description. Returns a structured essence.json template based on the closest matching archetype and blueprint.",
|
|
1007
|
+
inputSchema: {
|
|
1008
|
+
type: "object",
|
|
1009
|
+
properties: {
|
|
1010
|
+
description: { type: "string", description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")' },
|
|
1011
|
+
framework: { type: "string", description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".' }
|
|
1012
|
+
},
|
|
1013
|
+
required: ["description"]
|
|
1014
|
+
},
|
|
1015
|
+
annotations: READ_ONLY_NETWORK
|
|
1016
|
+
},
|
|
1017
|
+
// 11. decantr_accept_drift — WRITE tool (NEW)
|
|
1018
|
+
{
|
|
1019
|
+
name: "decantr_accept_drift",
|
|
1020
|
+
title: "Accept Drift",
|
|
1021
|
+
description: "Resolve guard violations by accepting, scoping, rejecting, or deferring drift. For DNA violations, requires explicit confirmation. Updates the essence file or drift log.",
|
|
1022
|
+
inputSchema: {
|
|
1023
|
+
type: "object",
|
|
1024
|
+
properties: {
|
|
1025
|
+
violations: {
|
|
1026
|
+
type: "array",
|
|
1027
|
+
items: {
|
|
1028
|
+
type: "object",
|
|
1029
|
+
properties: {
|
|
1030
|
+
rule: { type: "string" },
|
|
1031
|
+
page_id: { type: "string" },
|
|
1032
|
+
details: { type: "string" }
|
|
1033
|
+
},
|
|
1034
|
+
required: ["rule"]
|
|
1035
|
+
},
|
|
1036
|
+
description: "The violations to resolve."
|
|
1037
|
+
},
|
|
1038
|
+
resolution: {
|
|
1039
|
+
type: "string",
|
|
1040
|
+
enum: ["accept", "accept_scoped", "reject", "defer"],
|
|
1041
|
+
description: "How to resolve: accept updates the essence, accept_scoped limits to a page, reject is a no-op, defer logs for later."
|
|
1042
|
+
},
|
|
1043
|
+
scope: { type: "string", description: "For accept_scoped: the page or section scope." },
|
|
1044
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
|
|
1045
|
+
confirm_dna: { type: "boolean", description: "Required to be true when accepting DNA-layer violations." }
|
|
1046
|
+
},
|
|
1047
|
+
required: ["violations", "resolution"]
|
|
1048
|
+
},
|
|
1049
|
+
annotations: WRITE_TOOL
|
|
1050
|
+
},
|
|
1051
|
+
// 12. decantr_update_essence — WRITE tool (NEW)
|
|
1052
|
+
{
|
|
1053
|
+
name: "decantr_update_essence",
|
|
1054
|
+
title: "Update Essence",
|
|
1055
|
+
description: "Mutate the essence file: add/remove/update pages, update DNA or blueprint fields, add/remove features. Operates on v3 format (auto-migrates v2).",
|
|
1056
|
+
inputSchema: {
|
|
1057
|
+
type: "object",
|
|
1058
|
+
properties: {
|
|
1059
|
+
operation: {
|
|
1060
|
+
type: "string",
|
|
1061
|
+
enum: ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"],
|
|
1062
|
+
description: "The mutation operation to perform."
|
|
1063
|
+
},
|
|
1064
|
+
payload: {
|
|
1065
|
+
type: "object",
|
|
1066
|
+
description: "Operation-specific payload. See tool docs for each operation."
|
|
1067
|
+
},
|
|
1068
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
|
|
1069
|
+
},
|
|
1070
|
+
required: ["operation", "payload"]
|
|
1071
|
+
},
|
|
1072
|
+
annotations: WRITE_TOOL
|
|
1073
|
+
},
|
|
1074
|
+
// 13. decantr_get_section_context — local read
|
|
1075
|
+
{
|
|
1076
|
+
name: "decantr_get_section_context",
|
|
1077
|
+
title: "Get Section Context",
|
|
1078
|
+
description: "Get the self-contained context for a specific section of the project. Returns guard rules, theme tokens, visual treatments, recipe decorators, pattern specs, zone context, and pages \u2014 everything an AI needs to work on that section.",
|
|
1079
|
+
inputSchema: {
|
|
1080
|
+
type: "object",
|
|
1081
|
+
properties: {
|
|
1082
|
+
section_id: { type: "string", description: 'Section ID (archetype ID, e.g., "ai-chatbot", "auth-full", "settings-full")' }
|
|
1083
|
+
},
|
|
1084
|
+
required: ["section_id"]
|
|
1085
|
+
},
|
|
1086
|
+
annotations: READ_ONLY
|
|
1087
|
+
},
|
|
1088
|
+
// 14. decantr_component_api — local read (static manifest)
|
|
1089
|
+
{
|
|
1090
|
+
name: "decantr_component_api",
|
|
1091
|
+
title: "Component API",
|
|
1092
|
+
description: 'Query the @decantr/ui component API. Pass a component name to get its full props, usage examples, and related components. Pass "list" to see all available components grouped by category.',
|
|
1093
|
+
inputSchema: {
|
|
1094
|
+
type: "object",
|
|
1095
|
+
properties: {
|
|
1096
|
+
component: {
|
|
1097
|
+
type: "string",
|
|
1098
|
+
description: "Component name (e.g., 'Button', 'Modal', 'Card') or 'list' to see all available components."
|
|
1099
|
+
},
|
|
1100
|
+
category: {
|
|
1101
|
+
type: "string",
|
|
1102
|
+
description: "Filter by category when listing components.",
|
|
1103
|
+
enum: ["original", "general", "layout", "navigation", "form", "data-display", "feedback", "media", "utility", "all"]
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
required: ["component"]
|
|
1107
|
+
},
|
|
1108
|
+
annotations: READ_ONLY
|
|
1109
|
+
}
|
|
1110
|
+
];
|
|
1111
|
+
async function handleTool(name, args) {
|
|
1112
|
+
const apiClient = getAPIClient();
|
|
1113
|
+
switch (name) {
|
|
1114
|
+
case "decantr_read_essence": {
|
|
1115
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
1116
|
+
try {
|
|
1117
|
+
const raw = await readFile2(essencePath, "utf-8");
|
|
1118
|
+
const essence = JSON.parse(raw);
|
|
1119
|
+
const layer = args.layer;
|
|
1120
|
+
if (layer && isV32(essence)) {
|
|
1121
|
+
if (layer === "dna") return essence.dna;
|
|
1122
|
+
if (layer === "blueprint") return essence.blueprint;
|
|
1123
|
+
}
|
|
1124
|
+
return essence;
|
|
1125
|
+
} catch (e) {
|
|
1126
|
+
return { error: `Could not read essence file: ${e.message}` };
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
case "decantr_validate": {
|
|
1130
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
1131
|
+
let essence;
|
|
1132
|
+
try {
|
|
1133
|
+
essence = JSON.parse(await readFile2(essencePath, "utf-8"));
|
|
1134
|
+
} catch (e) {
|
|
1135
|
+
return { valid: false, errors: [`Could not read: ${e.message}`], guardViolations: [] };
|
|
1136
|
+
}
|
|
1137
|
+
const result = validateEssence(essence);
|
|
1138
|
+
let guardViolations = [];
|
|
1139
|
+
if (result.valid && typeof essence === "object" && essence !== null) {
|
|
1140
|
+
try {
|
|
1141
|
+
guardViolations = evaluateGuard(essence, {});
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (result.valid && typeof essence === "object" && essence !== null && isV32(essence)) {
|
|
1146
|
+
const dnaViolations = guardViolations.filter((v) => v.layer === "dna");
|
|
1147
|
+
const blueprintViolations = guardViolations.filter((v) => v.layer === "blueprint");
|
|
1148
|
+
const otherViolations = guardViolations.filter((v) => !v.layer);
|
|
1149
|
+
return {
|
|
1150
|
+
...result,
|
|
1151
|
+
format: "v3",
|
|
1152
|
+
dna_violations: dnaViolations,
|
|
1153
|
+
blueprint_violations: blueprintViolations,
|
|
1154
|
+
guardViolations: otherViolations
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
return { ...result, guardViolations };
|
|
1158
|
+
}
|
|
1159
|
+
case "decantr_search_registry": {
|
|
1160
|
+
const err = validateStringArg(args, "query");
|
|
1161
|
+
if (err) return { error: err };
|
|
1162
|
+
try {
|
|
1163
|
+
const response = await apiClient.search({
|
|
1164
|
+
q: args.query,
|
|
1165
|
+
type: args.type
|
|
1166
|
+
});
|
|
1167
|
+
return {
|
|
1168
|
+
total: response.total,
|
|
1169
|
+
results: response.results.map((r) => ({
|
|
1170
|
+
type: r.type,
|
|
1171
|
+
id: r.slug,
|
|
1172
|
+
namespace: r.namespace,
|
|
1173
|
+
name: r.name,
|
|
1174
|
+
description: r.description,
|
|
1175
|
+
install: `decantr get ${r.type} ${r.slug}`
|
|
1176
|
+
}))
|
|
1177
|
+
};
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
return { error: `Search failed: ${e.message}` };
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
case "decantr_resolve_pattern": {
|
|
1183
|
+
const err = validateStringArg(args, "id");
|
|
1184
|
+
if (err) return { error: err };
|
|
1185
|
+
const namespace = args.namespace || "@official";
|
|
1186
|
+
try {
|
|
1187
|
+
const pattern = await apiClient.getPattern(namespace, args.id);
|
|
1188
|
+
const result = { found: true, ...pattern };
|
|
1189
|
+
if (args.preset && typeof args.preset === "string") {
|
|
1190
|
+
const preset = resolvePatternPreset(pattern, args.preset);
|
|
1191
|
+
if (preset) result.resolvedPreset = preset;
|
|
1192
|
+
}
|
|
1193
|
+
return result;
|
|
1194
|
+
} catch {
|
|
1195
|
+
return { found: false, message: `Pattern "${args.id}" not found in ${namespace}.` };
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
case "decantr_resolve_archetype": {
|
|
1199
|
+
const err = validateStringArg(args, "id");
|
|
1200
|
+
if (err) return { error: err };
|
|
1201
|
+
const namespace = args.namespace || "@official";
|
|
1202
|
+
try {
|
|
1203
|
+
const archetype = await apiClient.getArchetype(namespace, args.id);
|
|
1204
|
+
return { found: true, ...archetype };
|
|
1205
|
+
} catch {
|
|
1206
|
+
return { found: false, message: `Archetype "${args.id}" not found in ${namespace}.` };
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
case "decantr_resolve_recipe": {
|
|
1210
|
+
const err = validateStringArg(args, "id");
|
|
1211
|
+
if (err) return { error: err };
|
|
1212
|
+
const namespace = args.namespace || "@official";
|
|
1213
|
+
try {
|
|
1214
|
+
const recipe = await apiClient.getRecipe(namespace, args.id);
|
|
1215
|
+
return { found: true, ...recipe };
|
|
1216
|
+
} catch {
|
|
1217
|
+
return { found: false, message: `Recipe "${args.id}" not found in ${namespace}.` };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
case "decantr_resolve_blueprint": {
|
|
1221
|
+
const err = validateStringArg(args, "id");
|
|
1222
|
+
if (err) return { error: err };
|
|
1223
|
+
const namespace = args.namespace || "@official";
|
|
1224
|
+
try {
|
|
1225
|
+
const blueprint = await apiClient.getBlueprint(namespace, args.id);
|
|
1226
|
+
let topology = null;
|
|
1227
|
+
const composeEntries = blueprint.compose || blueprint.data?.compose;
|
|
1228
|
+
if (composeEntries && Array.isArray(composeEntries) && composeEntries.length > 0) {
|
|
1229
|
+
const zoneInputs = [];
|
|
1230
|
+
const archetypePromises = composeEntries.map(async (entry) => {
|
|
1231
|
+
const arcId = typeof entry === "string" ? entry : entry.archetype;
|
|
1232
|
+
try {
|
|
1233
|
+
const arch = await apiClient.getContent("archetypes", namespace, arcId);
|
|
1234
|
+
const archData = arch.data || arch;
|
|
1235
|
+
const explicitRole = typeof entry === "object" ? entry.role : void 0;
|
|
1236
|
+
zoneInputs.push({
|
|
1237
|
+
archetypeId: arcId,
|
|
1238
|
+
role: explicitRole || archData.role || "auxiliary",
|
|
1239
|
+
shell: archData.pages?.[0]?.shell || "sidebar-main",
|
|
1240
|
+
features: archData.features || [],
|
|
1241
|
+
description: archData.description || ""
|
|
1242
|
+
});
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
await Promise.all(archetypePromises);
|
|
1247
|
+
if (zoneInputs.length > 0) {
|
|
1248
|
+
const zones = deriveZones(zoneInputs);
|
|
1249
|
+
const transitions = deriveTransitions(zones);
|
|
1250
|
+
const primaryArchetype = zoneInputs.find((z) => z.role === "primary");
|
|
1251
|
+
topology = {
|
|
1252
|
+
zones: zones.map((z) => ({
|
|
1253
|
+
role: z.role,
|
|
1254
|
+
archetypes: z.archetypes,
|
|
1255
|
+
shell: z.shell,
|
|
1256
|
+
features: z.features,
|
|
1257
|
+
purpose: z.descriptions.join(" ")
|
|
1258
|
+
})),
|
|
1259
|
+
transitions,
|
|
1260
|
+
entryPoints: {
|
|
1261
|
+
anonymous: "/",
|
|
1262
|
+
authenticated: primaryArchetype ? `/${primaryArchetype.archetypeId}` : "/home"
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return { found: true, ...blueprint, ...topology ? { topology } : {} };
|
|
1268
|
+
} catch {
|
|
1269
|
+
return { found: false, message: `Blueprint "${args.id}" not found in ${namespace}.` };
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
case "decantr_suggest_patterns": {
|
|
1273
|
+
const err = validateStringArg(args, "description");
|
|
1274
|
+
if (err) return { error: err };
|
|
1275
|
+
const desc = args.description.toLowerCase();
|
|
1276
|
+
try {
|
|
1277
|
+
const patternsResponse = await apiClient.listContent("patterns", {
|
|
1278
|
+
namespace: "@official",
|
|
1279
|
+
limit: 100
|
|
1280
|
+
});
|
|
1281
|
+
const suggestions = [];
|
|
1282
|
+
for (const p of patternsResponse.items) {
|
|
1283
|
+
const searchable = [
|
|
1284
|
+
p.name || "",
|
|
1285
|
+
p.description || "",
|
|
1286
|
+
...p.components || [],
|
|
1287
|
+
...p.tags || []
|
|
1288
|
+
].join(" ").toLowerCase();
|
|
1289
|
+
let score = 0;
|
|
1290
|
+
const words = desc.split(/\s+/);
|
|
1291
|
+
for (const word of words) {
|
|
1292
|
+
if (word.length < 3) continue;
|
|
1293
|
+
if (searchable.includes(word)) score += 10;
|
|
1294
|
+
}
|
|
1295
|
+
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(p.id)) score += 20;
|
|
1296
|
+
if (desc.includes("metric") && p.id === "kpi-grid") score += 15;
|
|
1297
|
+
if (desc.includes("chart") && p.id === "chart-grid") score += 15;
|
|
1298
|
+
if (desc.includes("table") && p.id === "data-table") score += 15;
|
|
1299
|
+
if (desc.includes("form") && p.id === "form-sections") score += 15;
|
|
1300
|
+
if (desc.includes("setting") && p.id === "form-sections") score += 15;
|
|
1301
|
+
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(p.id)) score += 20;
|
|
1302
|
+
if (desc.includes("hero") && p.id === "hero") score += 20;
|
|
1303
|
+
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(p.id)) score += 15;
|
|
1304
|
+
if (desc.includes("product") && p.id === "card-grid") score += 15;
|
|
1305
|
+
if (desc.includes("feed") && p.id === "activity-feed") score += 15;
|
|
1306
|
+
if (desc.includes("filter") && p.id === "filter-bar") score += 15;
|
|
1307
|
+
if (desc.includes("search") && p.id === "filter-bar") score += 10;
|
|
1308
|
+
if (score > 0) {
|
|
1309
|
+
const preset = p.presets ? Object.values(p.presets)[0] : null;
|
|
1310
|
+
suggestions.push({
|
|
1311
|
+
id: p.id,
|
|
1312
|
+
score,
|
|
1313
|
+
name: p.name || p.id,
|
|
1314
|
+
description: p.description || "",
|
|
1315
|
+
components: p.components || [],
|
|
1316
|
+
layout: preset?.layout ? preset.layout.layout : "grid"
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
suggestions.sort((a, b) => b.score - a.score);
|
|
1321
|
+
return {
|
|
1322
|
+
query: args.description,
|
|
1323
|
+
suggestions: suggestions.slice(0, 5),
|
|
1324
|
+
total: suggestions.length
|
|
1325
|
+
};
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
return { error: `Could not fetch patterns: ${e.message}` };
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
case "decantr_check_drift": {
|
|
1331
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
1332
|
+
let essence;
|
|
1333
|
+
try {
|
|
1334
|
+
essence = JSON.parse(await readFile2(essencePath, "utf-8"));
|
|
1335
|
+
} catch (e) {
|
|
1336
|
+
return { error: `Could not read essence: ${e.message}` };
|
|
1337
|
+
}
|
|
1338
|
+
const validation = validateEssence(essence);
|
|
1339
|
+
if (!validation.valid) {
|
|
1340
|
+
return { drifted: true, reason: "invalid_essence", errors: validation.errors };
|
|
1341
|
+
}
|
|
1342
|
+
const violations = [];
|
|
1343
|
+
if (args.theme_used && typeof args.theme_used === "string") {
|
|
1344
|
+
let expectedStyle;
|
|
1345
|
+
if (isV32(essence)) {
|
|
1346
|
+
expectedStyle = essence.dna.theme.style;
|
|
1347
|
+
} else {
|
|
1348
|
+
const expectedTheme = essence.theme;
|
|
1349
|
+
expectedStyle = expectedTheme?.style;
|
|
1350
|
+
}
|
|
1351
|
+
if (expectedStyle && args.theme_used !== expectedStyle) {
|
|
1352
|
+
violations.push({
|
|
1353
|
+
rule: "theme-match",
|
|
1354
|
+
severity: "critical",
|
|
1355
|
+
message: `Theme drift: code uses "${args.theme_used}" but Essence specifies "${expectedStyle}". Do not switch themes.`,
|
|
1356
|
+
...isV32(essence) ? { layer: "dna", autoFixable: false } : {}
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (args.page_id && typeof args.page_id === "string") {
|
|
1361
|
+
let pages;
|
|
1362
|
+
if (isV32(essence)) {
|
|
1363
|
+
pages = essence.blueprint.pages;
|
|
1364
|
+
} else {
|
|
1365
|
+
pages = essence.structure || [];
|
|
1366
|
+
}
|
|
1367
|
+
if (!pages.find((p) => p.id === args.page_id)) {
|
|
1368
|
+
violations.push({
|
|
1369
|
+
rule: "page-exists",
|
|
1370
|
+
severity: "critical",
|
|
1371
|
+
message: `Page "${args.page_id}" not found in Essence structure. Add it to the Essence before generating code for it.`,
|
|
1372
|
+
...isV32(essence) ? {
|
|
1373
|
+
layer: "blueprint",
|
|
1374
|
+
autoFixable: true,
|
|
1375
|
+
autoFix: { type: "add_page", patch: { id: args.page_id } }
|
|
1376
|
+
} : {}
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (args.components_used && Array.isArray(args.components_used) && args.page_id && typeof args.page_id === "string") {
|
|
1381
|
+
let pages;
|
|
1382
|
+
if (isV32(essence)) {
|
|
1383
|
+
pages = essence.blueprint.pages;
|
|
1384
|
+
} else {
|
|
1385
|
+
pages = essence.structure || [];
|
|
1386
|
+
}
|
|
1387
|
+
const page = pages.find((p) => p.id === args.page_id);
|
|
1388
|
+
if (page && page.layout) {
|
|
1389
|
+
const expectedPatterns = /* @__PURE__ */ new Set();
|
|
1390
|
+
for (const item of page.layout) {
|
|
1391
|
+
if (typeof item === "string") {
|
|
1392
|
+
expectedPatterns.add(item);
|
|
1393
|
+
} else if (typeof item === "object" && item !== null && "pattern" in item) {
|
|
1394
|
+
expectedPatterns.add(item.pattern);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const componentsUsed = args.components_used;
|
|
1398
|
+
const unmatchedComponents = [];
|
|
1399
|
+
for (const comp of componentsUsed) {
|
|
1400
|
+
const compLower = comp.toLowerCase().replace(/[_\s]/g, "-");
|
|
1401
|
+
let matched = false;
|
|
1402
|
+
for (const pattern of expectedPatterns) {
|
|
1403
|
+
const patternLower = pattern.toLowerCase();
|
|
1404
|
+
if (compLower.includes(patternLower) || patternLower.includes(compLower) || fuzzyScore(compLower, patternLower) >= 60) {
|
|
1405
|
+
matched = true;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (!matched) {
|
|
1410
|
+
unmatchedComponents.push(comp);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
if (unmatchedComponents.length > 0) {
|
|
1414
|
+
violations.push({
|
|
1415
|
+
rule: "component-pattern-match",
|
|
1416
|
+
severity: "warning",
|
|
1417
|
+
message: `Components [${unmatchedComponents.join(", ")}] do not match any pattern in page "${args.page_id}" layout. Expected patterns: [${[...expectedPatterns].join(", ")}].`,
|
|
1418
|
+
...isV32(essence) ? { layer: "blueprint", autoFixable: false } : {}
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
try {
|
|
1424
|
+
const guardViolations = evaluateGuard(essence, {
|
|
1425
|
+
pageId: args.page_id
|
|
1426
|
+
});
|
|
1427
|
+
for (const gv of guardViolations) {
|
|
1428
|
+
violations.push({
|
|
1429
|
+
rule: gv.rule || "guard",
|
|
1430
|
+
severity: gv.severity || "warning",
|
|
1431
|
+
message: gv.message || "Guard violation",
|
|
1432
|
+
...gv.layer ? { layer: gv.layer } : {},
|
|
1433
|
+
...gv.autoFixable !== void 0 ? { autoFixable: gv.autoFixable } : {},
|
|
1434
|
+
...gv.autoFix ? { autoFix: gv.autoFix } : {}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
} catch {
|
|
1438
|
+
}
|
|
1439
|
+
if (isV32(essence)) {
|
|
1440
|
+
const dnaViolations = violations.filter((v) => v.layer === "dna");
|
|
1441
|
+
const blueprintDrift = violations.filter((v) => v.layer === "blueprint");
|
|
1442
|
+
const other = violations.filter((v) => !v.layer);
|
|
1443
|
+
return {
|
|
1444
|
+
drifted: violations.length > 0,
|
|
1445
|
+
dna_violations: dnaViolations,
|
|
1446
|
+
blueprint_drift: blueprintDrift,
|
|
1447
|
+
other_violations: other,
|
|
1448
|
+
checkedAgainst: essencePath
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
drifted: violations.length > 0,
|
|
1453
|
+
violations,
|
|
1454
|
+
checkedAgainst: essencePath
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
case "decantr_create_essence": {
|
|
1458
|
+
const err = validateStringArg(args, "description");
|
|
1459
|
+
if (err) return { error: err };
|
|
1460
|
+
const desc = args.description.toLowerCase();
|
|
1461
|
+
const framework = args.framework || "react";
|
|
1462
|
+
const archetypeScores = [];
|
|
1463
|
+
const archetypeIds = [
|
|
1464
|
+
"saas-dashboard",
|
|
1465
|
+
"ecommerce",
|
|
1466
|
+
"portfolio",
|
|
1467
|
+
"content-site",
|
|
1468
|
+
"financial-dashboard",
|
|
1469
|
+
"cloud-platform",
|
|
1470
|
+
"gaming-platform",
|
|
1471
|
+
"ecommerce-admin",
|
|
1472
|
+
"workbench"
|
|
1473
|
+
];
|
|
1474
|
+
for (const id of archetypeIds) {
|
|
1475
|
+
let score = 0;
|
|
1476
|
+
if (desc.includes("dashboard") && id.includes("dashboard")) score += 20;
|
|
1477
|
+
if (desc.includes("saas") && id.includes("saas")) score += 20;
|
|
1478
|
+
if (desc.includes("ecommerce") && id.includes("ecommerce")) score += 20;
|
|
1479
|
+
if (desc.includes("shop") && id.includes("ecommerce")) score += 15;
|
|
1480
|
+
if (desc.includes("portfolio") && id.includes("portfolio")) score += 20;
|
|
1481
|
+
if (desc.includes("blog") && id.includes("content")) score += 15;
|
|
1482
|
+
if (desc.includes("content") && id.includes("content")) score += 15;
|
|
1483
|
+
if (desc.includes("finance") && id.includes("financial")) score += 20;
|
|
1484
|
+
if (desc.includes("cloud") && id.includes("cloud")) score += 15;
|
|
1485
|
+
if (desc.includes("game") && id.includes("gaming")) score += 15;
|
|
1486
|
+
if (desc.includes("admin") && id.includes("admin")) score += 15;
|
|
1487
|
+
if (desc.includes("analytics") && id.includes("dashboard")) score += 10;
|
|
1488
|
+
if (desc.includes("tool") && id === "workbench") score += 10;
|
|
1489
|
+
if (score > 0) archetypeScores.push({ id, score });
|
|
1490
|
+
}
|
|
1491
|
+
archetypeScores.sort((a, b) => b.score - a.score);
|
|
1492
|
+
const bestMatch = archetypeScores[0]?.id || "saas-dashboard";
|
|
1493
|
+
let pages;
|
|
1494
|
+
let features = [];
|
|
1495
|
+
try {
|
|
1496
|
+
const archetype = await apiClient.getArchetype("@official", bestMatch);
|
|
1497
|
+
pages = archetype.pages;
|
|
1498
|
+
features = archetype.features || [];
|
|
1499
|
+
} catch {
|
|
1500
|
+
}
|
|
1501
|
+
const rawPages = pages || [{ id: "home", shell: "full-bleed", default_layout: ["hero"] }];
|
|
1502
|
+
const defaultShell = rawPages[0]?.shell || "sidebar-main";
|
|
1503
|
+
const essence = {
|
|
1504
|
+
version: "3.0.0",
|
|
1505
|
+
dna: {
|
|
1506
|
+
theme: {
|
|
1507
|
+
style: "auradecantism",
|
|
1508
|
+
mode: "dark",
|
|
1509
|
+
recipe: "auradecantism",
|
|
1510
|
+
shape: "rounded"
|
|
1511
|
+
},
|
|
1512
|
+
spacing: {
|
|
1513
|
+
base_unit: 4,
|
|
1514
|
+
scale: "linear",
|
|
1515
|
+
density: "comfortable",
|
|
1516
|
+
content_gap: "4"
|
|
1517
|
+
},
|
|
1518
|
+
typography: {
|
|
1519
|
+
scale: "modular",
|
|
1520
|
+
heading_weight: 600,
|
|
1521
|
+
body_weight: 400
|
|
1522
|
+
},
|
|
1523
|
+
color: {
|
|
1524
|
+
palette: "semantic",
|
|
1525
|
+
accent_count: 1,
|
|
1526
|
+
cvd_preference: "auto"
|
|
1527
|
+
},
|
|
1528
|
+
radius: {
|
|
1529
|
+
philosophy: "rounded",
|
|
1530
|
+
base: 8
|
|
1531
|
+
},
|
|
1532
|
+
elevation: {
|
|
1533
|
+
system: "layered",
|
|
1534
|
+
max_levels: 3
|
|
1535
|
+
},
|
|
1536
|
+
motion: {
|
|
1537
|
+
preference: "subtle",
|
|
1538
|
+
duration_scale: 1,
|
|
1539
|
+
reduce_motion: true
|
|
1540
|
+
},
|
|
1541
|
+
accessibility: {
|
|
1542
|
+
wcag_level: "AA",
|
|
1543
|
+
focus_visible: true,
|
|
1544
|
+
skip_nav: true
|
|
1545
|
+
},
|
|
1546
|
+
personality: ["professional"]
|
|
1547
|
+
},
|
|
1548
|
+
blueprint: {
|
|
1549
|
+
shell: defaultShell,
|
|
1550
|
+
pages: rawPages.map((p) => ({
|
|
1551
|
+
id: p.id,
|
|
1552
|
+
...p.shell !== defaultShell ? { shell_override: p.shell } : {},
|
|
1553
|
+
layout: p.default_layout || []
|
|
1554
|
+
})),
|
|
1555
|
+
features
|
|
1556
|
+
},
|
|
1557
|
+
meta: {
|
|
1558
|
+
archetype: bestMatch,
|
|
1559
|
+
target: framework,
|
|
1560
|
+
platform: { type: "spa", routing: "hash" },
|
|
1561
|
+
guard: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" }
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
return {
|
|
1565
|
+
essence,
|
|
1566
|
+
archetype: bestMatch,
|
|
1567
|
+
format: "v3",
|
|
1568
|
+
instructions: `Save this as decantr.essence.json in your project root. Review the dna (design tokens), blueprint (pages/features), and meta (project config) sections and adjust to match your needs. The guard rules will validate your code against this spec.`,
|
|
1569
|
+
_generated: {
|
|
1570
|
+
matched_archetype: bestMatch,
|
|
1571
|
+
confidence: archetypeScores[0]?.score || 0,
|
|
1572
|
+
alternatives: archetypeScores.slice(1, 4).map((a) => a.id),
|
|
1573
|
+
description: args.description
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
case "decantr_accept_drift": {
|
|
1578
|
+
const violations = args.violations;
|
|
1579
|
+
const resolution = args.resolution;
|
|
1580
|
+
if (!violations || !Array.isArray(violations) || violations.length === 0) {
|
|
1581
|
+
return { error: 'Required parameter "violations" must be a non-empty array.' };
|
|
1582
|
+
}
|
|
1583
|
+
if (!resolution || !["accept", "accept_scoped", "reject", "defer"].includes(resolution)) {
|
|
1584
|
+
return { error: 'Required parameter "resolution" must be one of: accept, accept_scoped, reject, defer.' };
|
|
1585
|
+
}
|
|
1586
|
+
const hasDnaViolation = violations.some((v) => {
|
|
1587
|
+
const rule = v.rule;
|
|
1588
|
+
return ["style", "recipe", "density", "theme-mode", "accessibility", "theme-match"].includes(rule);
|
|
1589
|
+
});
|
|
1590
|
+
if (hasDnaViolation && resolution !== "reject" && resolution !== "defer" && !args.confirm_dna) {
|
|
1591
|
+
return {
|
|
1592
|
+
error: "DNA-layer violations detected. Set confirm_dna: true to accept changes to design axioms (theme, style, density, etc.).",
|
|
1593
|
+
requires_confirmation: true,
|
|
1594
|
+
dna_rules_affected: violations.filter(
|
|
1595
|
+
(v) => ["style", "recipe", "density", "theme-mode", "accessibility", "theme-match"].includes(v.rule)
|
|
1596
|
+
).map((v) => v.rule)
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
if (resolution === "reject") {
|
|
1600
|
+
return {
|
|
1601
|
+
status: "rejected",
|
|
1602
|
+
message: "Violations rejected. No changes made. Revert the code to match the essence spec.",
|
|
1603
|
+
violations_count: violations.length
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
if (resolution === "defer") {
|
|
1607
|
+
const projectRoot = args.path ? dirname2(args.path) : void 0;
|
|
1608
|
+
const existingLog = await readDriftLog(projectRoot);
|
|
1609
|
+
const newEntries = violations.map((v) => ({
|
|
1610
|
+
rule: v.rule,
|
|
1611
|
+
page_id: v.page_id,
|
|
1612
|
+
details: v.details,
|
|
1613
|
+
resolution: "deferred",
|
|
1614
|
+
scope: args.scope || void 0,
|
|
1615
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1616
|
+
}));
|
|
1617
|
+
const updatedLog = [...existingLog, ...newEntries];
|
|
1618
|
+
const logPath = await writeDriftLog(updatedLog, projectRoot);
|
|
1619
|
+
return {
|
|
1620
|
+
status: "deferred",
|
|
1621
|
+
message: `${violations.length} violation(s) deferred to drift log.`,
|
|
1622
|
+
log_path: logPath,
|
|
1623
|
+
total_deferred: updatedLog.length
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
try {
|
|
1627
|
+
const { essence, path } = await mutateEssenceFile(args.path, (v3) => {
|
|
1628
|
+
for (const v of violations) {
|
|
1629
|
+
applyDriftAcceptance(v3, v, resolution, args.scope);
|
|
1630
|
+
}
|
|
1631
|
+
return v3;
|
|
1632
|
+
});
|
|
1633
|
+
return {
|
|
1634
|
+
status: resolution === "accept_scoped" ? "accepted_scoped" : "accepted",
|
|
1635
|
+
message: `${violations.length} violation(s) resolved. Essence updated.`,
|
|
1636
|
+
path,
|
|
1637
|
+
scope: resolution === "accept_scoped" ? args.scope || "unscoped" : void 0
|
|
1638
|
+
};
|
|
1639
|
+
} catch (e) {
|
|
1640
|
+
return { error: `Failed to update essence: ${e.message}` };
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
case "decantr_update_essence": {
|
|
1644
|
+
const operation = args.operation;
|
|
1645
|
+
const payload = args.payload;
|
|
1646
|
+
if (!operation) {
|
|
1647
|
+
return { error: 'Required parameter "operation" is missing.' };
|
|
1648
|
+
}
|
|
1649
|
+
if (!payload || typeof payload !== "object") {
|
|
1650
|
+
return { error: 'Required parameter "payload" must be an object.' };
|
|
1651
|
+
}
|
|
1652
|
+
const validOps = ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"];
|
|
1653
|
+
if (!validOps.includes(operation)) {
|
|
1654
|
+
return { error: `Invalid operation "${operation}". Must be one of: ${validOps.join(", ")}` };
|
|
1655
|
+
}
|
|
1656
|
+
try {
|
|
1657
|
+
const { essence, path } = await mutateEssenceFile(args.path, (v3) => {
|
|
1658
|
+
return applyEssenceUpdate(v3, operation, payload);
|
|
1659
|
+
});
|
|
1660
|
+
return {
|
|
1661
|
+
status: "updated",
|
|
1662
|
+
operation,
|
|
1663
|
+
path,
|
|
1664
|
+
summary: describeUpdate(operation, payload)
|
|
1665
|
+
};
|
|
1666
|
+
} catch (e) {
|
|
1667
|
+
return { error: `Failed to update essence: ${e.message}` };
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
case "decantr_get_section_context": {
|
|
1671
|
+
const err = validateStringArg(args, "section_id");
|
|
1672
|
+
if (err) return { error: err };
|
|
1673
|
+
const sectionId = args.section_id;
|
|
1674
|
+
let essence;
|
|
1675
|
+
try {
|
|
1676
|
+
const result = await readEssenceFile();
|
|
1677
|
+
essence = result.essence;
|
|
1678
|
+
} catch {
|
|
1679
|
+
return { error: "No valid essence file found. Run decantr init first." };
|
|
1680
|
+
}
|
|
1681
|
+
if (!isV32(essence)) {
|
|
1682
|
+
return { error: "Section context requires a v3 essence file. Run decantr migrate first." };
|
|
1683
|
+
}
|
|
1684
|
+
const sections = essence.blueprint.sections || [];
|
|
1685
|
+
const section = sections.find((s) => s.id === sectionId);
|
|
1686
|
+
if (!section) {
|
|
1687
|
+
return {
|
|
1688
|
+
error: `Section "${sectionId}" not found.`,
|
|
1689
|
+
available_sections: sections.map((s) => ({ id: s.id, role: s.role, pages: s.pages.length }))
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
const contextPath = join2(process.cwd(), ".decantr", "context", `section-${sectionId}.md`);
|
|
1693
|
+
if (existsSync(contextPath)) {
|
|
1694
|
+
return {
|
|
1695
|
+
section_id: sectionId,
|
|
1696
|
+
role: section.role,
|
|
1697
|
+
shell: section.shell,
|
|
1698
|
+
features: section.features,
|
|
1699
|
+
pages: section.pages.map((p) => ({ id: p.id, route: p.route, layout: p.layout })),
|
|
1700
|
+
context: readFileSync(contextPath, "utf-8")
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
section_id: sectionId,
|
|
1705
|
+
role: section.role,
|
|
1706
|
+
shell: section.shell,
|
|
1707
|
+
features: section.features,
|
|
1708
|
+
description: section.description,
|
|
1709
|
+
pages: section.pages.map((p) => ({ id: p.id, route: p.route, layout: p.layout })),
|
|
1710
|
+
note: "Section context file not found. Run decantr refresh to generate it."
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
case "decantr_component_api": {
|
|
1714
|
+
const componentName = args.component;
|
|
1715
|
+
const category = args.category;
|
|
1716
|
+
if (!componentName) {
|
|
1717
|
+
return { error: 'The "component" argument is required.' };
|
|
1718
|
+
}
|
|
1719
|
+
if (componentName === "list") {
|
|
1720
|
+
const grouped = getComponentsByCategory(category);
|
|
1721
|
+
const lines2 = ["# @decantr/ui Components\n"];
|
|
1722
|
+
for (const [cat, entries] of Object.entries(grouped)) {
|
|
1723
|
+
lines2.push(`## ${cat}`);
|
|
1724
|
+
for (const entry2 of entries) {
|
|
1725
|
+
lines2.push(`- **${entry2.name}** \u2014 ${entry2.description}`);
|
|
1726
|
+
}
|
|
1727
|
+
lines2.push("");
|
|
1728
|
+
}
|
|
1729
|
+
return { content: lines2.join("\n"), total: Object.keys(COMPONENT_MANIFEST).length };
|
|
1730
|
+
}
|
|
1731
|
+
const entry = COMPONENT_MANIFEST[componentName] || Object.values(COMPONENT_MANIFEST).find(
|
|
1732
|
+
(e) => e.name.toLowerCase() === componentName.toLowerCase()
|
|
1733
|
+
);
|
|
1734
|
+
if (!entry) {
|
|
1735
|
+
const candidates = Object.keys(COMPONENT_MANIFEST);
|
|
1736
|
+
const scored = candidates.map((name2) => ({ name: name2, score: fuzzyScore(componentName.toLowerCase(), name2.toLowerCase()) })).filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, 5);
|
|
1737
|
+
return {
|
|
1738
|
+
error: `Component "${componentName}" not found in manifest.`,
|
|
1739
|
+
suggestions: scored.map((s) => s.name),
|
|
1740
|
+
hint: 'Use component: "list" to see all available components.'
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
const lines = [
|
|
1744
|
+
`# ${entry.name}`,
|
|
1745
|
+
`**Category:** ${entry.category}`,
|
|
1746
|
+
"",
|
|
1747
|
+
entry.description,
|
|
1748
|
+
"",
|
|
1749
|
+
"## Props",
|
|
1750
|
+
"",
|
|
1751
|
+
"| Prop | Type | Required | Default | Description |",
|
|
1752
|
+
"|------|------|----------|---------|-------------|"
|
|
1753
|
+
];
|
|
1754
|
+
for (const p of entry.props) {
|
|
1755
|
+
const req = p.required ? "Yes" : "No";
|
|
1756
|
+
const def = p.default || "\u2014";
|
|
1757
|
+
const escapedType = p.type.replace(/\|/g, "\\|");
|
|
1758
|
+
lines.push(`| \`${p.name}\` | \`${escapedType}\` | ${req} | ${def} | ${p.description} |`);
|
|
1759
|
+
}
|
|
1760
|
+
lines.push("");
|
|
1761
|
+
lines.push("## Usage");
|
|
1762
|
+
lines.push("");
|
|
1763
|
+
lines.push("```ts");
|
|
1764
|
+
lines.push(entry.usage);
|
|
1765
|
+
lines.push("```");
|
|
1766
|
+
if (entry.subComponents?.length) {
|
|
1767
|
+
lines.push("");
|
|
1768
|
+
lines.push(`**Sub-components:** ${entry.subComponents.join(", ")}`);
|
|
1769
|
+
}
|
|
1770
|
+
if (entry.relatedComponents?.length) {
|
|
1771
|
+
lines.push("");
|
|
1772
|
+
lines.push(`**Related:** ${entry.relatedComponents.join(", ")}`);
|
|
1773
|
+
}
|
|
1774
|
+
return { content: lines.join("\n") };
|
|
1775
|
+
}
|
|
1776
|
+
default:
|
|
1777
|
+
return { error: `Unknown tool: ${name}` };
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function applyDriftAcceptance(essence, violation, resolution, scope) {
|
|
1781
|
+
switch (violation.rule) {
|
|
1782
|
+
case "theme-match":
|
|
1783
|
+
case "style": {
|
|
1784
|
+
if (violation.details) {
|
|
1785
|
+
essence.dna.theme.style = violation.details;
|
|
1786
|
+
}
|
|
1787
|
+
break;
|
|
1788
|
+
}
|
|
1789
|
+
case "page-exists":
|
|
1790
|
+
case "structure": {
|
|
1791
|
+
if (violation.page_id) {
|
|
1792
|
+
const existing = essence.blueprint.pages.find((p) => p.id === violation.page_id);
|
|
1793
|
+
if (!existing) {
|
|
1794
|
+
essence.blueprint.pages.push({
|
|
1795
|
+
id: violation.page_id,
|
|
1796
|
+
layout: []
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
case "layout": {
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
case "recipe": {
|
|
1806
|
+
if (violation.details) {
|
|
1807
|
+
essence.dna.theme.recipe = violation.details;
|
|
1808
|
+
}
|
|
1809
|
+
break;
|
|
1810
|
+
}
|
|
1811
|
+
case "density": {
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
default:
|
|
1815
|
+
break;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
function applyEssenceUpdate(essence, operation, payload) {
|
|
1819
|
+
switch (operation) {
|
|
1820
|
+
case "add_page": {
|
|
1821
|
+
const id = payload.id;
|
|
1822
|
+
if (!id) throw new Error('Payload must include "id" for add_page.');
|
|
1823
|
+
const existing = essence.blueprint.pages.find((p) => p.id === id);
|
|
1824
|
+
if (existing) throw new Error(`Page "${id}" already exists.`);
|
|
1825
|
+
essence.blueprint.pages.push({
|
|
1826
|
+
id,
|
|
1827
|
+
layout: payload.layout || [],
|
|
1828
|
+
...payload.shell_override ? { shell_override: payload.shell_override } : {},
|
|
1829
|
+
...payload.surface ? { surface: payload.surface } : {}
|
|
1830
|
+
});
|
|
1831
|
+
break;
|
|
1832
|
+
}
|
|
1833
|
+
case "remove_page": {
|
|
1834
|
+
const id = payload.id;
|
|
1835
|
+
if (!id) throw new Error('Payload must include "id" for remove_page.');
|
|
1836
|
+
const idx = essence.blueprint.pages.findIndex((p) => p.id === id);
|
|
1837
|
+
if (idx === -1) throw new Error(`Page "${id}" not found.`);
|
|
1838
|
+
essence.blueprint.pages.splice(idx, 1);
|
|
1839
|
+
break;
|
|
1840
|
+
}
|
|
1841
|
+
case "update_page_layout": {
|
|
1842
|
+
const id = payload.id;
|
|
1843
|
+
const layout = payload.layout;
|
|
1844
|
+
if (!id) throw new Error('Payload must include "id" for update_page_layout.');
|
|
1845
|
+
if (!layout || !Array.isArray(layout)) throw new Error('Payload must include "layout" array for update_page_layout.');
|
|
1846
|
+
const page = essence.blueprint.pages.find((p) => p.id === id);
|
|
1847
|
+
if (!page) throw new Error(`Page "${id}" not found.`);
|
|
1848
|
+
page.layout = layout;
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
case "update_dna": {
|
|
1852
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1853
|
+
if (key in essence.dna && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1854
|
+
essence.dna[key] = {
|
|
1855
|
+
...essence.dna[key],
|
|
1856
|
+
...value
|
|
1857
|
+
};
|
|
1858
|
+
} else {
|
|
1859
|
+
essence.dna[key] = value;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1864
|
+
case "update_blueprint": {
|
|
1865
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1866
|
+
if (key === "pages") continue;
|
|
1867
|
+
essence.blueprint[key] = value;
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
case "add_feature": {
|
|
1872
|
+
const feature = payload.feature;
|
|
1873
|
+
if (!feature) throw new Error('Payload must include "feature" for add_feature.');
|
|
1874
|
+
if (!essence.blueprint.features.includes(feature)) {
|
|
1875
|
+
essence.blueprint.features.push(feature);
|
|
1876
|
+
}
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1879
|
+
case "remove_feature": {
|
|
1880
|
+
const feature = payload.feature;
|
|
1881
|
+
if (!feature) throw new Error('Payload must include "feature" for remove_feature.');
|
|
1882
|
+
const idx = essence.blueprint.features.indexOf(feature);
|
|
1883
|
+
if (idx === -1) throw new Error(`Feature "${feature}" not found.`);
|
|
1884
|
+
essence.blueprint.features.splice(idx, 1);
|
|
1885
|
+
break;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return essence;
|
|
1889
|
+
}
|
|
1890
|
+
function describeUpdate(operation, payload) {
|
|
1891
|
+
switch (operation) {
|
|
1892
|
+
case "add_page":
|
|
1893
|
+
return `Added page "${payload.id}".`;
|
|
1894
|
+
case "remove_page":
|
|
1895
|
+
return `Removed page "${payload.id}".`;
|
|
1896
|
+
case "update_page_layout":
|
|
1897
|
+
return `Updated layout for page "${payload.id}".`;
|
|
1898
|
+
case "update_dna":
|
|
1899
|
+
return `Updated DNA: ${Object.keys(payload).join(", ")}.`;
|
|
1900
|
+
case "update_blueprint":
|
|
1901
|
+
return `Updated blueprint: ${Object.keys(payload).join(", ")}.`;
|
|
1902
|
+
case "add_feature":
|
|
1903
|
+
return `Added feature "${payload.feature}".`;
|
|
1904
|
+
case "remove_feature":
|
|
1905
|
+
return `Removed feature "${payload.feature}".`;
|
|
1906
|
+
default:
|
|
1907
|
+
return `Performed ${operation}.`;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// src/index.ts
|
|
1912
|
+
var VERSION = "0.2.0";
|
|
1913
|
+
var server = new Server(
|
|
1914
|
+
{ name: "decantr", version: VERSION },
|
|
1915
|
+
{ capabilities: { tools: {} } }
|
|
1916
|
+
);
|
|
1917
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1918
|
+
return { tools: TOOLS };
|
|
1919
|
+
});
|
|
1920
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1921
|
+
const { name, arguments: args } = request.params;
|
|
1922
|
+
try {
|
|
1923
|
+
const result = await handleTool(name, args ?? {});
|
|
1924
|
+
const response = {
|
|
1925
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1926
|
+
};
|
|
1927
|
+
if (result && typeof result === "object" && "error" in result) {
|
|
1928
|
+
response.isError = true;
|
|
1929
|
+
}
|
|
1930
|
+
return response;
|
|
1931
|
+
} catch (err) {
|
|
1932
|
+
return {
|
|
1933
|
+
content: [{ type: "text", text: JSON.stringify({ error: err.message }) }],
|
|
1934
|
+
isError: true
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
var transport = new StdioServerTransport();
|
|
1939
|
+
await server.connect(transport);
|