timepicker-ui-mcp 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -32
- package/build/content.js +122 -71
- package/build/data.js +62 -12
- package/build/generated/css-classes.json +393 -67
- package/build/generated/css-variables.json +1187 -772
- package/build/generated/events.json +456 -409
- package/build/generated/meta.json +5 -0
- package/build/generated/migration-v5.json +190 -0
- package/build/generated/options.json +962 -823
- package/build/generated/themes.json +1412 -1412
- package/build/generated/troubleshooting.json +234 -0
- package/build/generated.js +9 -1
- package/build/index.js +571 -358
- package/build/search.js +117 -0
- package/build/snippet.js +7 -1
- package/build/theme.js +40 -17
- package/package.json +6 -5
package/build/index.js
CHANGED
|
@@ -1,382 +1,595 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { McpServer, completable } from '@modelcontextprotocol/server';
|
|
6
|
+
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
4
7
|
import { z } from 'zod';
|
|
5
|
-
import { API_METHODS, MODES, MODE_NAMES,
|
|
6
|
-
import { CSS_CLASSES, CSS_VARS, EVENTS, OPTION_GROUPS, OPTION_GROUP_NAMES, THEME_VARS, } from './generated.js';
|
|
7
|
-
import { FRAMEWORK_NAMES, FRAMEWORK_RECIPES, IO_CONTRACT, MIGRATION_V3_TO_V4, MIGRATION_V43_TO_V44, MIGRATION_V44_TO_V45,
|
|
8
|
+
import { API_METHODS, DOCS, LIBRARY_VERSION, MODES, MODE_NAMES, PLUGINS, PLUGIN_NAMES, SITE_URL, THEMES, THEME_NAMES, docsUrl, optionGroupDocs, } from './data.js';
|
|
9
|
+
import { CSS_CLASSES, CSS_VARS, CSS_VAR_GROUPS, EVENTS, MIGRATION_V5, OPTION_GROUPS, OPTION_GROUP_NAMES, THEME_VARS, TROUBLESHOOTING, } from './generated.js';
|
|
10
|
+
import { FRAMEWORK_NAMES, FRAMEWORK_RECIPES, IO_CONTRACT, MIGRATION_V3_TO_V4, MIGRATION_V43_TO_V44, MIGRATION_V44_TO_V45, MIGRATION_V45_TO_V5, } from './content.js';
|
|
11
|
+
import { searchReference } from './search.js';
|
|
8
12
|
import { buildSnippet, modeRequiresWheel } from './snippet.js';
|
|
9
|
-
import { buildCustomTheme } from './theme.js';
|
|
13
|
+
import { CLASS_NAME, buildCustomTheme } from './theme.js';
|
|
10
14
|
import { parseTime, validateDisabledTime } from './validate.js';
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
|
|
16
|
+
const INSTRUCTIONS = [
|
|
17
|
+
`Reference data for timepicker-ui ${LIBRARY_VERSION}, a zero-dependency time picker (npm package timepicker-ui).`,
|
|
18
|
+
'Answer timepicker-ui questions from these tools rather than from memory.',
|
|
19
|
+
'Options are grouped since 4.0: clock, ui, labels, behavior, callbacks, timezone, range, wheel, clearBehavior.',
|
|
20
|
+
'Items marked deprecated still work in 4.x and are removed in 5.0, so suggest their replacement.',
|
|
21
|
+
'Every setup imports timepicker-ui/main.css. The wheel and compact-wheel modes and the range and timezone features need their plugin registered once with PluginRegistry.register().',
|
|
22
|
+
'For custom styling, set --tp- variables under .tp-ui-wrapper.<class> and pass the class in ui.cssClass.',
|
|
23
|
+
'When you are not sure which option, event or CSS variable fits, call search_reference.',
|
|
24
|
+
`Results link to the docs at ${SITE_URL}.`,
|
|
25
|
+
].join(' ');
|
|
26
|
+
const READ_ONLY = {
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
destructiveHint: false,
|
|
29
|
+
idempotentHint: true,
|
|
30
|
+
openWorldHint: false,
|
|
31
|
+
};
|
|
15
32
|
function text(value) {
|
|
16
33
|
const body = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
17
34
|
return { content: [{ type: 'text', text: body }] };
|
|
18
35
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
36
|
+
function fail(message, details = {}) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message, ...details }, null, 2) }],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function withoutDeprecated(options) {
|
|
43
|
+
return options
|
|
44
|
+
.filter((option) => !option.deprecated)
|
|
45
|
+
.map((option) => (option.fields ? { ...option, fields: withoutDeprecated(option.fields) } : option));
|
|
46
|
+
}
|
|
47
|
+
function deprecatedNames(options, path) {
|
|
48
|
+
return options.flatMap((option) => {
|
|
49
|
+
const name = `${path}.${option.name}`;
|
|
50
|
+
if (option.deprecated)
|
|
51
|
+
return [name];
|
|
52
|
+
return option.fields ? deprecatedNames(option.fields, name) : [];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function findOption(group, path) {
|
|
56
|
+
let list = OPTION_GROUPS[group]?.options;
|
|
57
|
+
let found;
|
|
58
|
+
for (const part of path.split('.')) {
|
|
59
|
+
found = list?.find((option) => option.name === part);
|
|
60
|
+
if (!found)
|
|
61
|
+
return undefined;
|
|
62
|
+
list = found.fields;
|
|
63
|
+
}
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
const groupEnum = z.enum(OPTION_GROUP_NAMES);
|
|
67
|
+
const CACHE_HINT = { ttlMs: 3_600_000, cacheScope: 'public' };
|
|
68
|
+
export function createServer() {
|
|
69
|
+
const server = new McpServer({
|
|
70
|
+
name: 'timepicker-ui',
|
|
71
|
+
title: 'timepicker-ui',
|
|
72
|
+
version: packageJson.version,
|
|
73
|
+
description: 'Reference data, snippets and migration help for the timepicker-ui time picker.',
|
|
74
|
+
websiteUrl: SITE_URL,
|
|
75
|
+
}, {
|
|
76
|
+
instructions: INSTRUCTIONS,
|
|
77
|
+
cacheHints: {
|
|
78
|
+
'tools/list': CACHE_HINT,
|
|
79
|
+
'prompts/list': CACHE_HINT,
|
|
80
|
+
'resources/list': CACHE_HINT,
|
|
81
|
+
'resources/read': CACHE_HINT,
|
|
82
|
+
'server/discover': CACHE_HINT,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
server.registerTool('list_themes', {
|
|
86
|
+
title: 'List timepicker-ui themes',
|
|
87
|
+
description: 'Lists the 12 built-in themes with their CSS import paths. Select one with ui.theme. For a custom look use ui.cssClass (see generate_custom_theme).',
|
|
88
|
+
annotations: READ_ONLY,
|
|
89
|
+
}, async () => text({
|
|
90
|
+
themes: THEMES,
|
|
91
|
+
usage: "Import 'timepicker-ui/main.css', then the theme file, and set ui: { theme: '<name>' }.",
|
|
92
|
+
custom: "ui.theme accepts only the built-in names. For your own colors, pass a class in ui.cssClass and set --tp- variables under .tp-ui-wrapper.<class> (generate_custom_theme writes the CSS).",
|
|
93
|
+
docs: DOCS.themes,
|
|
94
|
+
}));
|
|
95
|
+
server.registerTool('list_modes', {
|
|
96
|
+
title: 'List timepicker-ui UI modes',
|
|
97
|
+
description: 'Lists the UI modes (clock, wheel, compact-wheel) and which plugin each needs.',
|
|
98
|
+
annotations: READ_ONLY,
|
|
99
|
+
}, async () => text({ modes: MODES, option: 'ui.mode' }));
|
|
100
|
+
server.registerTool('list_plugins', {
|
|
101
|
+
title: 'List timepicker-ui plugins',
|
|
102
|
+
description: 'Lists the optional plugins (range, timezone, wheel) with import paths and the option that turns each on.',
|
|
103
|
+
annotations: READ_ONLY,
|
|
104
|
+
}, async () => text({
|
|
105
|
+
plugins: PLUGINS,
|
|
106
|
+
register: "Import the plugin and call PluginRegistry.register(Plugin) once, before creating pickers: import { TimepickerUI, PluginRegistry } from 'timepicker-ui'.",
|
|
107
|
+
docs: DOCS.plugins,
|
|
108
|
+
}));
|
|
109
|
+
server.registerTool('list_events', {
|
|
110
|
+
title: 'List timepicker-ui events',
|
|
111
|
+
description: 'Lists the events picker.on(event, handler) can subscribe to, with the payload type and its fields, extracted from source. Internal and deprecated events are left out unless asked for.',
|
|
112
|
+
inputSchema: z.object({
|
|
113
|
+
includeInternal: z
|
|
114
|
+
.boolean()
|
|
115
|
+
.default(false)
|
|
116
|
+
.describe('Also list internal events (open:request, range:minute:commit, animation:*). 5.0 removes them from the event map.'),
|
|
117
|
+
includeDeprecated: z.boolean().default(false).describe('Also list deprecated events such as hide, select:am and select:pm.'),
|
|
118
|
+
}),
|
|
119
|
+
annotations: READ_ONLY,
|
|
120
|
+
}, async (args) => {
|
|
121
|
+
const shown = EVENTS.filter((event) => (args.includeInternal || !event.internal) && (args.includeDeprecated || !event.deprecated));
|
|
122
|
+
const omitted = EVENTS.filter((event) => !shown.includes(event)).map((event) => event.name);
|
|
123
|
+
return text({ events: shown, omitted, docs: DOCS.events });
|
|
124
|
+
});
|
|
125
|
+
server.registerTool('list_api', {
|
|
126
|
+
title: 'List the public API',
|
|
127
|
+
description: 'Lists the TimepickerUI instance and static methods with signatures, deprecations, and the getValue/setValue contract.',
|
|
128
|
+
annotations: READ_ONLY,
|
|
129
|
+
}, async () => text({ methods: API_METHODS, io: IO_CONTRACT, docs: DOCS.methods }));
|
|
130
|
+
server.registerTool('list_css_classes', {
|
|
131
|
+
title: 'List tp-ui- CSS classes',
|
|
132
|
+
description: 'Lists the tp-ui- class names and state modifiers, with an example selector for each styled class. Prefer --tp- variables; class names change in 5.0.',
|
|
133
|
+
annotations: READ_ONLY,
|
|
134
|
+
}, async () => text({ ...CSS_CLASSES, docs: DOCS.cssClasses }));
|
|
135
|
+
server.registerTool('list_css_variables', {
|
|
136
|
+
title: 'List --tp- CSS variables',
|
|
137
|
+
description: 'Lists the --tp- CSS custom properties with group, default value and whether built-in themes override them. Variables no rule reads are left out unless asked for.',
|
|
138
|
+
inputSchema: z.object({
|
|
139
|
+
group: z
|
|
140
|
+
.enum(CSS_VAR_GROUPS)
|
|
141
|
+
.optional()
|
|
142
|
+
.describe('Limit to one group, e.g. Color, Typography, Focus, Wheel.'),
|
|
143
|
+
includeUnread: z
|
|
144
|
+
.boolean()
|
|
145
|
+
.default(false)
|
|
146
|
+
.describe('Also list variables that no rule reads. Setting them changes nothing and 5.0 removes them.'),
|
|
147
|
+
}),
|
|
148
|
+
annotations: READ_ONLY,
|
|
149
|
+
}, async (args) => {
|
|
150
|
+
const inGroup = CSS_VARS.filter((v) => !args.group || v.group === args.group);
|
|
151
|
+
const variables = inGroup.filter((v) => args.includeUnread || v.read);
|
|
77
152
|
return text({
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
variables,
|
|
154
|
+
omittedUnread: inGroup.filter((v) => !variables.includes(v)).map((v) => v.name),
|
|
155
|
+
groups: CSS_VAR_GROUPS,
|
|
156
|
+
docs: DOCS.cssVariables,
|
|
80
157
|
});
|
|
81
|
-
}
|
|
82
|
-
return text({ group: args.group, ...option });
|
|
83
|
-
});
|
|
84
|
-
server.registerTool('get_theme', {
|
|
85
|
-
title: 'Get a built-in theme variable set',
|
|
86
|
-
description: 'Returns the exact --tp- CSS variables a built-in theme overrides (with their values), so you can derive or tweak a custom theme from it.',
|
|
87
|
-
inputSchema: {
|
|
88
|
-
name: z.enum(Object.keys(THEME_VARS)).describe('Built-in theme name.'),
|
|
89
|
-
},
|
|
90
|
-
}, async (args) => text({ theme: args.name, variables: THEME_VARS[args.name] ?? [] }));
|
|
91
|
-
server.registerTool('framework_recipe', {
|
|
92
|
-
title: 'Framework integration recipe',
|
|
93
|
-
description: 'Returns a correct integration recipe (lifecycle + SSR guidance + code) for a given framework: vanilla, react, next, vue, angular, svelte.',
|
|
94
|
-
inputSchema: {
|
|
95
|
-
framework: z.enum(FRAMEWORK_NAMES).describe('Target framework.'),
|
|
96
|
-
},
|
|
97
|
-
}, async (args) => text(FRAMEWORK_RECIPES[args.framework]));
|
|
98
|
-
server.registerTool('migration_guide', {
|
|
99
|
-
title: 'Migration guide',
|
|
100
|
-
description: 'Returns migration guidance between versions. "v3->v4" gives the full flat->grouped field mapping; "v4.3->v4.4" and "v4.4->v4.5" list the non-breaking changes.',
|
|
101
|
-
inputSchema: {
|
|
102
|
-
path: z.enum(['v3->v4', 'v4.3->v4.4', 'v4.4->v4.5']).default('v3->v4').describe('Which migration to return.'),
|
|
103
|
-
},
|
|
104
|
-
}, async (args) => {
|
|
105
|
-
if (args.path === 'v4.3->v4.4')
|
|
106
|
-
return text({ path: 'v4.3->v4.4', changes: MIGRATION_V43_TO_V44 });
|
|
107
|
-
if (args.path === 'v4.4->v4.5')
|
|
108
|
-
return text({ path: 'v4.4->v4.5', changes: MIGRATION_V44_TO_V45 });
|
|
109
|
-
return text({ path: 'v3->v4', ...MIGRATION_V3_TO_V4 });
|
|
110
|
-
});
|
|
111
|
-
server.registerTool('generate_snippet', {
|
|
112
|
-
title: 'Generate a timepicker-ui setup snippet',
|
|
113
|
-
description: 'Generates a ready-to-paste vanilla JS or React snippet wiring up TimepickerUI with the chosen theme, mode, clock type and plugins (CSS imports + plugin registration included). For full framework lifecycle/SSR, use framework_recipe.',
|
|
114
|
-
inputSchema: {
|
|
115
|
-
selector: z.string().default('#time').describe('CSS selector of the target input (vanilla only).'),
|
|
116
|
-
theme: z.enum(THEME_NAMES).default('basic').describe('Theme name.'),
|
|
117
|
-
mode: z.enum(MODE_NAMES).default('clock').describe('UI mode.'),
|
|
118
|
-
clockType: z.enum(['12h', '24h']).default('24h').describe('12-hour or 24-hour clock.'),
|
|
119
|
-
plugins: z
|
|
120
|
-
.array(z.enum(PLUGIN_NAMES))
|
|
121
|
-
.default([])
|
|
122
|
-
.describe('Plugins to import and register.'),
|
|
123
|
-
framework: z.enum(['vanilla', 'react']).default('vanilla').describe('Output flavor.'),
|
|
124
|
-
},
|
|
125
|
-
}, async (args) => {
|
|
126
|
-
const plugins = new Set(args.plugins);
|
|
127
|
-
if (modeRequiresWheel(args.mode))
|
|
128
|
-
plugins.add('wheel');
|
|
129
|
-
const snippet = buildSnippet({
|
|
130
|
-
selector: args.selector,
|
|
131
|
-
theme: args.theme,
|
|
132
|
-
mode: args.mode,
|
|
133
|
-
clockType: args.clockType,
|
|
134
|
-
plugins: [...plugins],
|
|
135
|
-
framework: args.framework,
|
|
136
158
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
158
|
-
server.registerTool('
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
''
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
'
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
'
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
159
|
+
server.registerTool('get_options', {
|
|
160
|
+
title: 'Get grouped options',
|
|
161
|
+
description: 'Returns the grouped options reference (clock, ui, labels, behavior, callbacks, timezone, range, wheel, clearBehavior) with type, default, description and nested fields, extracted from options.d.ts. Deprecated options are left out unless asked for. Pass a group to narrow it.',
|
|
162
|
+
inputSchema: z.object({
|
|
163
|
+
group: groupEnum.optional().describe('Limit to a single option group.'),
|
|
164
|
+
includeDeprecated: z
|
|
165
|
+
.boolean()
|
|
166
|
+
.default(false)
|
|
167
|
+
.describe('Also return deprecated options (old names that still work in 4.x and are removed in 5.0).'),
|
|
168
|
+
}),
|
|
169
|
+
annotations: READ_ONLY,
|
|
170
|
+
}, async (args) => {
|
|
171
|
+
const names = args.group ? [args.group] : OPTION_GROUP_NAMES;
|
|
172
|
+
const groups = Object.fromEntries(names.map((name) => {
|
|
173
|
+
const group = OPTION_GROUPS[name];
|
|
174
|
+
const options = args.includeDeprecated ? group.options : withoutDeprecated(group.options);
|
|
175
|
+
return [name, { ...group, options, docs: optionGroupDocs(name) }];
|
|
176
|
+
}));
|
|
177
|
+
const omitted = args.includeDeprecated ? [] : names.flatMap((name) => deprecatedNames(OPTION_GROUPS[name].options, name));
|
|
178
|
+
return text({ groups, omittedDeprecated: omitted });
|
|
179
|
+
});
|
|
180
|
+
server.registerTool('explain_option', {
|
|
181
|
+
title: 'Explain a single option',
|
|
182
|
+
description: 'Returns the full detail (type, default, description, example, deprecation, nested fields) for one option. Use a dotted name for nested fields, e.g. "disabledTime.ranges".',
|
|
183
|
+
inputSchema: z.object({
|
|
184
|
+
group: groupEnum.describe('Option group.'),
|
|
185
|
+
name: z.string().min(1).describe('Option name within the group, e.g. "showSeconds" or "disabledTime.ranges".'),
|
|
186
|
+
}),
|
|
187
|
+
annotations: READ_ONLY,
|
|
188
|
+
}, async (args) => {
|
|
189
|
+
const path = args.name.startsWith(`${args.group}.`) ? args.name.slice(args.group.length + 1) : args.name;
|
|
190
|
+
const option = findOption(args.group, path);
|
|
191
|
+
if (option)
|
|
192
|
+
return text({ group: args.group, ...option, docs: optionGroupDocs(args.group) });
|
|
193
|
+
const elsewhere = OPTION_GROUP_NAMES.filter((group) => group !== args.group && findOption(group, path)).map((group) => `${group}.${path}`);
|
|
194
|
+
return fail(`Unknown option "${path}" in "${args.group}".`, {
|
|
195
|
+
...(elsewhere.length ? { didYouMean: elsewhere } : {}),
|
|
196
|
+
available: OPTION_GROUPS[args.group].options.map((o) => o.name),
|
|
197
|
+
hint: 'Call search_reference with a keyword when you do not know the exact name.',
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
server.registerTool('get_theme', {
|
|
201
|
+
title: 'Get a built-in theme variable set',
|
|
202
|
+
description: 'Returns the --tp- CSS variables a built-in theme sets, with their values, to start a custom theme from.',
|
|
203
|
+
inputSchema: z.object({
|
|
204
|
+
name: z.enum(Object.keys(THEME_VARS)).describe('Built-in theme name.'),
|
|
205
|
+
}),
|
|
206
|
+
annotations: READ_ONLY,
|
|
207
|
+
}, async (args) => text({
|
|
208
|
+
theme: args.name,
|
|
209
|
+
cssImport: THEMES.find((t) => t.name === args.name)?.cssImport,
|
|
210
|
+
variables: THEME_VARS[args.name] ?? [],
|
|
211
|
+
docs: DOCS.themes,
|
|
212
|
+
}));
|
|
213
|
+
server.registerTool('framework_recipe', {
|
|
214
|
+
title: 'Framework integration recipe',
|
|
215
|
+
description: 'Returns an integration recipe (lifecycle, SSR notes and code) for vanilla, react, react-wrapper (timepicker-ui-react), next, vue, angular or svelte.',
|
|
216
|
+
inputSchema: z.object({
|
|
217
|
+
framework: z.enum(FRAMEWORK_NAMES).describe('Target framework.'),
|
|
218
|
+
}),
|
|
219
|
+
annotations: READ_ONLY,
|
|
220
|
+
}, async (args) => {
|
|
221
|
+
const recipe = FRAMEWORK_RECIPES[args.framework];
|
|
222
|
+
return text({ ...recipe, docs: docsUrl(recipe.docs) });
|
|
223
|
+
});
|
|
224
|
+
server.registerTool('migration_guide', {
|
|
225
|
+
title: 'Migration guide',
|
|
226
|
+
description: 'Returns migration guidance. "v3->v4": flat to grouped option mapping. "v4.3->v4.4" and "v4.4->v4.5": non-breaking changes, with every renamed option. "v4.5->v5": draft list of what 5.0 removes and changes, and the steps that work on 4.5.0 today.',
|
|
227
|
+
inputSchema: z.object({
|
|
228
|
+
path: z
|
|
229
|
+
.enum(['v3->v4', 'v4.3->v4.4', 'v4.4->v4.5', 'v4.5->v5'])
|
|
230
|
+
.default('v3->v4')
|
|
231
|
+
.describe('Which migration to return.'),
|
|
232
|
+
}),
|
|
233
|
+
annotations: READ_ONLY,
|
|
234
|
+
}, async (args) => {
|
|
235
|
+
if (args.path === 'v4.3->v4.4')
|
|
236
|
+
return text({ path: args.path, changes: MIGRATION_V43_TO_V44 });
|
|
237
|
+
if (args.path === 'v4.4->v4.5') {
|
|
238
|
+
return text({ path: args.path, changes: MIGRATION_V44_TO_V45, renamed: MIGRATION_V5.renamed, docs: DOCS.renamed });
|
|
239
|
+
}
|
|
240
|
+
if (args.path === 'v4.5->v5') {
|
|
241
|
+
return text({
|
|
242
|
+
path: args.path,
|
|
243
|
+
...MIGRATION_V45_TO_V5,
|
|
244
|
+
renamed: MIGRATION_V5.renamed,
|
|
245
|
+
removed: MIGRATION_V5.removed,
|
|
246
|
+
docs: DOCS.migrationV5,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return text({ path: args.path, ...MIGRATION_V3_TO_V4, docs: DOCS.migrationV4 });
|
|
250
|
+
});
|
|
251
|
+
server.registerTool('generate_snippet', {
|
|
252
|
+
title: 'Generate a timepicker-ui setup snippet',
|
|
253
|
+
description: 'Generates a ready-to-paste vanilla JS or React snippet wiring up TimepickerUI with the chosen theme, mode, clock type, seconds and plugins (CSS imports and plugin registration included). For full framework lifecycle and SSR notes, use framework_recipe.',
|
|
254
|
+
inputSchema: z.object({
|
|
255
|
+
selector: z.string().default('#time').describe('CSS selector of the target input (vanilla only).'),
|
|
256
|
+
theme: z.enum(THEME_NAMES).default('basic').describe('Theme name.'),
|
|
257
|
+
mode: z.enum(MODE_NAMES).default('clock').describe('UI mode.'),
|
|
258
|
+
clockType: z
|
|
259
|
+
.enum(['12h', '24h', 'auto'])
|
|
260
|
+
.default('24h')
|
|
261
|
+
.describe("12-hour, 24-hour, or 'auto' (from the browser locale)."),
|
|
262
|
+
showSeconds: z.boolean().default(false).describe('Add a seconds field (clock.showSeconds).'),
|
|
263
|
+
plugins: z
|
|
264
|
+
.array(z.enum(PLUGIN_NAMES))
|
|
265
|
+
.default([])
|
|
266
|
+
.describe('Plugins to import and register. wheel is added for the wheel modes.'),
|
|
267
|
+
framework: z.enum(['vanilla', 'react']).default('vanilla').describe('Output flavor.'),
|
|
268
|
+
}),
|
|
269
|
+
annotations: READ_ONLY,
|
|
270
|
+
}, async (args) => {
|
|
271
|
+
const plugins = new Set(args.plugins);
|
|
272
|
+
if (modeRequiresWheel(args.mode))
|
|
273
|
+
plugins.add('wheel');
|
|
274
|
+
return text(buildSnippet({
|
|
275
|
+
selector: args.selector,
|
|
276
|
+
theme: args.theme,
|
|
277
|
+
mode: args.mode,
|
|
278
|
+
clockType: args.clockType,
|
|
279
|
+
showSeconds: args.showSeconds,
|
|
280
|
+
plugins: [...plugins],
|
|
281
|
+
framework: args.framework,
|
|
282
|
+
}));
|
|
283
|
+
});
|
|
284
|
+
server.registerTool('generate_custom_theme', {
|
|
285
|
+
title: 'Generate a custom theme stylesheet',
|
|
286
|
+
description: 'Writes the CSS for a custom theme applied with ui.cssClass: a .tp-ui-wrapper.<name> block of --tp- variables. Pass `overrides` to emit only those, or leave it empty for an editable scaffold of the variables built-in themes change. Unknown variables and variables no rule reads are reported.',
|
|
287
|
+
inputSchema: z.object({
|
|
288
|
+
name: z.string().min(1).describe("Class name for ui.cssClass, e.g. 'ocean'. Letters, digits, - and _."),
|
|
289
|
+
overrides: z
|
|
290
|
+
.record(z.string(), z.string())
|
|
291
|
+
.default({})
|
|
292
|
+
.describe("Map of CSS variables to values, e.g. { 'tp-primary': '#0ea5e9' }."),
|
|
293
|
+
onlyCommon: z
|
|
294
|
+
.boolean()
|
|
295
|
+
.default(true)
|
|
296
|
+
.describe('For the scaffold, include only the variables built-in themes override.'),
|
|
297
|
+
}),
|
|
298
|
+
annotations: READ_ONLY,
|
|
299
|
+
}, async (args) => {
|
|
300
|
+
if (!CLASS_NAME.test(args.name)) {
|
|
301
|
+
return fail(`"${args.name}" is not a valid CSS class name.`, {
|
|
302
|
+
hint: 'Use letters, digits, "-" and "_", starting with a letter or "_", e.g. "ocean" or "brand-dark".',
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const theme = buildCustomTheme({ name: args.name, overrides: args.overrides, onlyCommon: args.onlyCommon });
|
|
306
|
+
const content = [{ type: 'text', text: theme.css }];
|
|
307
|
+
if (theme.warnings.length) {
|
|
308
|
+
content.push({ type: 'text', text: `Notes:\n${theme.warnings.map((w) => `- ${w}`).join('\n')}` });
|
|
309
|
+
}
|
|
310
|
+
return { content };
|
|
311
|
+
});
|
|
312
|
+
server.registerTool('validate_time', {
|
|
313
|
+
title: 'Validate a time string',
|
|
314
|
+
description: 'Validates a time string against a 12h or 24h clock and returns the parsed parts. Accepts "HH:MM", "HH:MM:SS", "h:MM AM/PM" and "h:MM:SS AM/PM".',
|
|
315
|
+
inputSchema: z.object({
|
|
316
|
+
time: z.string().describe('The time string, e.g. "14:30", "14:30:15" or "2:30:15 PM".'),
|
|
317
|
+
clockType: z.enum(['12h', '24h']).default('24h'),
|
|
318
|
+
}),
|
|
319
|
+
annotations: READ_ONLY,
|
|
320
|
+
}, async (args) => text(parseTime(args.time, args.clockType)));
|
|
321
|
+
server.registerTool('validate_disabled_time', {
|
|
322
|
+
title: 'Validate a disabledTime config',
|
|
323
|
+
description: 'Validates a clock.disabledTime config: hours, minutes and seconds lists, and range strings in ranges ("10:00 AM - 12:00 PM", "10:00:30 - 10:05"). Returns per-issue feedback.',
|
|
324
|
+
inputSchema: z.object({
|
|
325
|
+
hours: z.array(z.union([z.string(), z.number()])).optional().describe('Disabled hours.'),
|
|
326
|
+
minutes: z.array(z.union([z.string(), z.number()])).optional().describe('Disabled minutes.'),
|
|
327
|
+
seconds: z
|
|
328
|
+
.array(z.union([z.string(), z.number()]))
|
|
329
|
+
.optional()
|
|
330
|
+
.describe('Disabled seconds (used with clock.showSeconds).'),
|
|
331
|
+
ranges: z
|
|
332
|
+
.union([z.string(), z.array(z.string())])
|
|
333
|
+
.optional()
|
|
334
|
+
.describe('Range string or array, e.g. "10:00 AM - 12:00 PM".'),
|
|
335
|
+
interval: z
|
|
336
|
+
.union([z.string(), z.array(z.string())])
|
|
337
|
+
.optional()
|
|
338
|
+
.describe('Deprecated old name of ranges; ranges wins when both are set.'),
|
|
339
|
+
clockType: z.enum(['12h', '24h']).default('24h'),
|
|
340
|
+
}),
|
|
341
|
+
annotations: READ_ONLY,
|
|
342
|
+
}, async (args) => {
|
|
343
|
+
const result = validateDisabledTime({ hours: args.hours, minutes: args.minutes, seconds: args.seconds, ranges: args.ranges, interval: args.interval }, args.clockType);
|
|
344
|
+
const notes = args.interval !== undefined ? ['interval is deprecated and removed in 5.0. Use ranges.'] : [];
|
|
345
|
+
return text({ ...result, ...(notes.length ? { notes } : {}) });
|
|
346
|
+
});
|
|
347
|
+
server.registerTool('search_reference', {
|
|
348
|
+
title: 'Search the timepicker-ui reference',
|
|
349
|
+
description: 'Keyword search across options (including nested fields), events, CSS variables, CSS classes, methods, themes and troubleshooting entries. Use it when you do not know the exact name. Pass a few keywords, not a sentence, e.g. "disable time range", "focus ring color" or "seconds increment". Deprecated items rank lower and are flagged.',
|
|
350
|
+
inputSchema: z.object({
|
|
351
|
+
query: z.string().min(2).describe('Keywords, e.g. "seconds increment" or "backdrop".'),
|
|
352
|
+
limit: z.number().int().min(1).max(50).default(10).describe('Maximum number of results.'),
|
|
353
|
+
}),
|
|
354
|
+
annotations: READ_ONLY,
|
|
355
|
+
}, async (args) => {
|
|
356
|
+
const results = searchReference(args.query, args.limit);
|
|
357
|
+
return text({
|
|
358
|
+
query: args.query,
|
|
359
|
+
results,
|
|
360
|
+
...(results.length ? {} : { hint: 'No match. Try a shorter or different keyword, or call get_options.' }),
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
server.registerResource('timepicker-ui-reference', 'timepicker-ui://reference', {
|
|
364
|
+
title: 'timepicker-ui options reference',
|
|
365
|
+
description: 'Summary of the grouped options, themes, modes, plugins and events.',
|
|
366
|
+
mimeType: 'text/markdown',
|
|
367
|
+
}, async (uri) => ({
|
|
368
|
+
contents: [
|
|
369
|
+
{
|
|
370
|
+
uri: uri.href,
|
|
371
|
+
mimeType: 'text/markdown',
|
|
318
372
|
text: [
|
|
319
|
-
|
|
320
|
-
'
|
|
321
|
-
'
|
|
322
|
-
'
|
|
323
|
-
|
|
373
|
+
`# timepicker-ui ${LIBRARY_VERSION} - options reference`,
|
|
374
|
+
'',
|
|
375
|
+
'Options are grouped (v4+). Use the `get_options` / `explain_option` tools for full detail. Deprecated names are left out here.',
|
|
376
|
+
'',
|
|
377
|
+
...OPTION_GROUP_NAMES.map((g) => {
|
|
378
|
+
const options = withoutDeprecated(OPTION_GROUPS[g].options);
|
|
379
|
+
return `- \`${g}\` (${options.length} options): ${options.map((o) => o.name).join(', ')}`;
|
|
380
|
+
}),
|
|
381
|
+
'',
|
|
382
|
+
`Themes: ${THEME_NAMES.join(', ')}`,
|
|
383
|
+
`Modes: ${MODE_NAMES.join(', ')} (wheel modes need the wheel plugin)`,
|
|
384
|
+
`Plugins: ${PLUGINS.map((p) => p.name).join(', ')} (registered via PluginRegistry.register)`,
|
|
385
|
+
`Events: ${EVENTS.filter((e) => !e.internal && !e.deprecated).length} (see list_events for payload fields)`,
|
|
386
|
+
'',
|
|
387
|
+
'CSS: always import `timepicker-ui/main.css`, then a `timepicker-ui/theme-<name>.css` for non-basic themes.',
|
|
388
|
+
'',
|
|
389
|
+
`Docs: ${SITE_URL}/docs`,
|
|
390
|
+
].join('\n'),
|
|
324
391
|
},
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
{
|
|
337
|
-
role: 'user',
|
|
338
|
-
content: {
|
|
339
|
-
type: 'text',
|
|
392
|
+
],
|
|
393
|
+
}));
|
|
394
|
+
server.registerResource('timepicker-ui-css-variables', 'timepicker-ui://css-variables', {
|
|
395
|
+
title: 'timepicker-ui CSS variables',
|
|
396
|
+
description: 'Every --tp- CSS custom property with group and default value; flags the ones themes override and the ones no rule reads.',
|
|
397
|
+
mimeType: 'text/markdown',
|
|
398
|
+
}, async (uri) => ({
|
|
399
|
+
contents: [
|
|
400
|
+
{
|
|
401
|
+
uri: uri.href,
|
|
402
|
+
mimeType: 'text/markdown',
|
|
340
403
|
text: [
|
|
341
|
-
|
|
342
|
-
'
|
|
343
|
-
|
|
344
|
-
'
|
|
345
|
-
|
|
404
|
+
'# timepicker-ui CSS variables',
|
|
405
|
+
'',
|
|
406
|
+
`${CSS_VARS.length} variables. **themed**: built-in themes override it. **no effect**: no rule reads it, so setting it changes nothing (removed in 5.0).`,
|
|
407
|
+
'',
|
|
408
|
+
'| Variable | Group | Default | Notes |',
|
|
409
|
+
'| --- | --- | --- | --- |',
|
|
410
|
+
...CSS_VARS.map((v) => {
|
|
411
|
+
const notes = [v.commonlyThemed ? 'themed' : '', v.read ? '' : 'no effect'].filter(Boolean).join(', ');
|
|
412
|
+
return `| \`${v.name}\` | ${v.group} | \`${v.default ?? 'unset'}\` | ${notes} |`;
|
|
413
|
+
}),
|
|
414
|
+
'',
|
|
415
|
+
`Docs: ${DOCS.cssVariables}`,
|
|
416
|
+
].join('\n'),
|
|
346
417
|
},
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
{
|
|
359
|
-
role: 'user',
|
|
360
|
-
content: {
|
|
361
|
-
type: 'text',
|
|
418
|
+
],
|
|
419
|
+
}));
|
|
420
|
+
server.registerResource('timepicker-ui-custom-theme-guide', 'timepicker-ui://custom-theme-guide', {
|
|
421
|
+
title: 'How to add a custom theme',
|
|
422
|
+
description: 'Step-by-step guide for styling timepicker-ui with your own class and --tp- variables.',
|
|
423
|
+
mimeType: 'text/markdown',
|
|
424
|
+
}, async (uri) => ({
|
|
425
|
+
contents: [
|
|
426
|
+
{
|
|
427
|
+
uri: uri.href,
|
|
428
|
+
mimeType: 'text/markdown',
|
|
362
429
|
text: [
|
|
363
|
-
'
|
|
364
|
-
'(path "v3->v4") for the exact field mapping, then rewrite the code:',
|
|
430
|
+
'# Adding a custom timepicker-ui theme',
|
|
365
431
|
'',
|
|
432
|
+
'`ui.theme` selects one of the 12 built-in themes only: the picker writes it to the `data-theme` attribute of the modal, and the TypeScript type is a union of the built-in names.',
|
|
433
|
+
'For your own colors, pass a class in `ui.cssClass` and set `--tp-` variables under it. The picker adds the class to `.tp-ui-wrapper` when it opens.',
|
|
434
|
+
'',
|
|
435
|
+
'## Steps',
|
|
436
|
+
'',
|
|
437
|
+
'1. Pick a class name (e.g. `ocean`). Call `generate_custom_theme` to write the CSS, or `get_theme` to copy a built-in theme as a starting point.',
|
|
438
|
+
'2. Override the variables you need. `list_css_variables` lists them; skip the ones marked as having no effect.',
|
|
439
|
+
"3. Import your stylesheet after `timepicker-ui/main.css` (and after a built-in theme file if you combine them).",
|
|
440
|
+
"4. Pass the class: `ui: { cssClass: 'ocean' }`. It can sit on top of a built-in theme: `ui: { theme: 'dark', cssClass: 'ocean' }`.",
|
|
441
|
+
'',
|
|
442
|
+
'## Example',
|
|
443
|
+
'',
|
|
444
|
+
'```css',
|
|
445
|
+
'.tp-ui-wrapper.ocean {',
|
|
446
|
+
' --tp-bg: #06283d;',
|
|
447
|
+
' --tp-surface: #0a3a52;',
|
|
448
|
+
' --tp-text: #eaf6ff;',
|
|
449
|
+
' --tp-primary: #2ec4f1;',
|
|
450
|
+
' --tp-on-primary: #00131f;',
|
|
451
|
+
'}',
|
|
366
452
|
'```',
|
|
367
|
-
|
|
368
|
-
'
|
|
453
|
+
'',
|
|
454
|
+
'To change a value for every picker, set it on `.tp-ui-wrapper` instead.',
|
|
455
|
+
'',
|
|
456
|
+
'## Material Design 3 note',
|
|
457
|
+
'',
|
|
458
|
+
'Convey selected and active state with fill (state layers, tonal containers) and disabled with reduced opacity, not bold weight or strikethrough. Keep a visible focus ring (`--tp-focus-ring`) with at least 3:1 contrast.',
|
|
459
|
+
'',
|
|
460
|
+
`Docs: ${DOCS.styling}`,
|
|
369
461
|
].join('\n'),
|
|
370
462
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
463
|
+
],
|
|
464
|
+
}));
|
|
465
|
+
server.registerResource('timepicker-ui-troubleshooting', 'timepicker-ui://troubleshooting', {
|
|
466
|
+
title: 'timepicker-ui troubleshooting',
|
|
467
|
+
description: 'Common problems, the fix and the cause, from the docs troubleshooting page.',
|
|
468
|
+
mimeType: 'text/markdown',
|
|
469
|
+
}, async (uri) => ({
|
|
470
|
+
contents: [
|
|
471
|
+
{
|
|
472
|
+
uri: uri.href,
|
|
473
|
+
mimeType: 'text/markdown',
|
|
474
|
+
text: [
|
|
475
|
+
'# timepicker-ui troubleshooting',
|
|
476
|
+
'',
|
|
477
|
+
...TROUBLESHOOTING.flatMap((t) => [
|
|
478
|
+
`## ${t.symptom}`,
|
|
479
|
+
'',
|
|
480
|
+
...t.fix.flatMap((step) => [
|
|
481
|
+
step.text,
|
|
482
|
+
'',
|
|
483
|
+
...(step.code ? ['```' + step.code.language, step.code.source, '```', ''] : []),
|
|
484
|
+
]),
|
|
485
|
+
...(t.why ? [`Why: ${t.why}`, ''] : []),
|
|
486
|
+
`Docs: ${t.docs}`,
|
|
487
|
+
'',
|
|
488
|
+
]),
|
|
489
|
+
].join('\n'),
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
}));
|
|
493
|
+
server.registerPrompt('integrate', {
|
|
494
|
+
title: 'Integrate timepicker-ui',
|
|
495
|
+
description: 'Guide integrating timepicker-ui into a given framework with correct lifecycle and SSR handling.',
|
|
496
|
+
argsSchema: z.object({
|
|
497
|
+
framework: completable(z.string().describe(FRAMEWORK_NAMES.join(' | ')), (value) => FRAMEWORK_NAMES.filter((name) => name.startsWith(value ?? ''))),
|
|
498
|
+
}),
|
|
499
|
+
}, (args) => ({
|
|
500
|
+
messages: [
|
|
501
|
+
{
|
|
502
|
+
role: 'user',
|
|
503
|
+
content: {
|
|
504
|
+
type: 'text',
|
|
505
|
+
text: [
|
|
506
|
+
`Integrate timepicker-ui into a ${args.framework} app.`,
|
|
507
|
+
'Call the `framework_recipe` tool with this framework for the lifecycle and SSR handling,',
|
|
508
|
+
'use `get_options` or `search_reference` for any options I mention, and `list_events` if I need to react to selections.',
|
|
509
|
+
'Always import timepicker-ui/main.css and destroy the instance on unmount.',
|
|
510
|
+
].join(' '),
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
}));
|
|
515
|
+
server.registerPrompt('build-custom-theme', {
|
|
516
|
+
title: 'Build a custom theme',
|
|
517
|
+
description: 'Guide creating a custom timepicker-ui theme from a description or palette.',
|
|
518
|
+
argsSchema: z.object({
|
|
519
|
+
description: z.string().describe('Describe the look or palette, e.g. "dark ocean blue, teal accent".'),
|
|
520
|
+
}),
|
|
521
|
+
}, (args) => ({
|
|
522
|
+
messages: [
|
|
523
|
+
{
|
|
524
|
+
role: 'user',
|
|
525
|
+
content: {
|
|
526
|
+
type: 'text',
|
|
527
|
+
text: [
|
|
528
|
+
`Create a custom timepicker-ui theme: ${args.description}.`,
|
|
529
|
+
'Call `list_css_variables` (group Color first) and optionally `get_theme` for a similar built-in,',
|
|
530
|
+
'then call `generate_custom_theme` with concrete --tp- overrides and show how to pass the class in ui.cssClass.',
|
|
531
|
+
'Follow Material Design 3 (state via fill and opacity, visible focus ring with at least 3:1 contrast).',
|
|
532
|
+
].join(' '),
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
}));
|
|
537
|
+
server.registerPrompt('migrate-v3-to-v4', {
|
|
538
|
+
title: 'Migrate v3 code to v4',
|
|
539
|
+
description: 'Convert v3 flat-options timepicker-ui code to the v4 grouped structure.',
|
|
540
|
+
argsSchema: z.object({
|
|
541
|
+
code: z.string().describe('Paste the v3 timepicker-ui setup code to migrate.'),
|
|
542
|
+
}),
|
|
543
|
+
}, (args) => ({
|
|
544
|
+
messages: [
|
|
545
|
+
{
|
|
546
|
+
role: 'user',
|
|
547
|
+
content: {
|
|
548
|
+
type: 'text',
|
|
549
|
+
text: [
|
|
550
|
+
'Migrate this v3 timepicker-ui code to v4 grouped options. Use the `migration_guide` tool',
|
|
551
|
+
'(path "v3->v4") for the exact field mapping, then rewrite the code:',
|
|
552
|
+
'',
|
|
553
|
+
'```',
|
|
554
|
+
args.code,
|
|
555
|
+
'```',
|
|
556
|
+
].join('\n'),
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
}));
|
|
561
|
+
server.registerPrompt('prepare-for-v5', {
|
|
562
|
+
title: 'Prepare 4.x code for 5.0',
|
|
563
|
+
description: 'Replace deprecated timepicker-ui 4.x options, events and methods with the names that stay in 5.0.',
|
|
564
|
+
argsSchema: z.object({
|
|
565
|
+
code: z.string().describe('Paste the timepicker-ui 4.x code to update.'),
|
|
566
|
+
}),
|
|
567
|
+
}, (args) => ({
|
|
568
|
+
messages: [
|
|
569
|
+
{
|
|
570
|
+
role: 'user',
|
|
571
|
+
content: {
|
|
572
|
+
type: 'text',
|
|
573
|
+
text: [
|
|
574
|
+
'Update this timepicker-ui 4.x code so it uses no deprecated API and is ready for 5.0.',
|
|
575
|
+
'Call the `migration_guide` tool with path "v4.5->v5" for the renamed and removed items,',
|
|
576
|
+
'and `explain_option` when an option is unclear. Keep the behavior the same and list each change you made:',
|
|
577
|
+
'',
|
|
578
|
+
'```',
|
|
579
|
+
args.code,
|
|
580
|
+
'```',
|
|
581
|
+
].join('\n'),
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
}));
|
|
586
|
+
return server;
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
serveStdio(() => createServer());
|
|
377
590
|
process.stderr.write('timepicker-ui MCP server running on stdio\n');
|
|
378
591
|
}
|
|
379
|
-
|
|
592
|
+
catch (error) {
|
|
380
593
|
process.stderr.write(`Fatal error: ${String(error)}\n`);
|
|
381
594
|
process.exit(1);
|
|
382
|
-
}
|
|
595
|
+
}
|