@mindfiredigital/ignix-lite-mcp 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +108 -0
  2. package/dist/server.js +187 -1954
  3. package/dist/server.js.map +1 -1
  4. package/dist/utils/check-api.js +2 -0
  5. package/dist/utils/check-api.js.map +1 -1
  6. package/package.json +14 -6
  7. package/.turbo/turbo-build.log +0 -25
  8. package/CHANGELOG.md +0 -7
  9. package/dist/manifests/accordion.json +0 -61
  10. package/dist/manifests/alert.json +0 -69
  11. package/dist/manifests/avatar.json +0 -75
  12. package/dist/manifests/badge.json +0 -74
  13. package/dist/manifests/breadcrumb.json +0 -87
  14. package/dist/manifests/button.json +0 -85
  15. package/dist/manifests/card.json +0 -91
  16. package/dist/manifests/checkbox.json +0 -122
  17. package/dist/manifests/codeblock.json +0 -63
  18. package/dist/manifests/combobox.json +0 -33
  19. package/dist/manifests/dialog.json +0 -64
  20. package/dist/manifests/divider.json +0 -47
  21. package/dist/manifests/dropdown.json +0 -105
  22. package/dist/manifests/form.json +0 -81
  23. package/dist/manifests/grid.json +0 -143
  24. package/dist/manifests/input.json +0 -99
  25. package/dist/manifests/meter.json +0 -103
  26. package/dist/manifests/navigation.json +0 -70
  27. package/dist/manifests/progress.json +0 -88
  28. package/dist/manifests/radio.json +0 -121
  29. package/dist/manifests/select.json +0 -109
  30. package/dist/manifests/skeleton.json +0 -101
  31. package/dist/manifests/tab.json +0 -88
  32. package/dist/manifests/table.json +0 -92
  33. package/dist/manifests/textarea.json +0 -117
  34. package/dist/manifests/toast.json +0 -157
  35. package/dist/manifests/tooltip.json +0 -115
  36. package/dist/vector-index.json +0 -14015
  37. package/src/context/api-context.ts +0 -14
  38. package/src/global.d.ts +0 -15
  39. package/src/manifests/accordion.json +0 -61
  40. package/src/manifests/alert.json +0 -69
  41. package/src/manifests/avatar.json +0 -75
  42. package/src/manifests/badge.json +0 -74
  43. package/src/manifests/breadcrumb.json +0 -87
  44. package/src/manifests/button.json +0 -85
  45. package/src/manifests/card.json +0 -91
  46. package/src/manifests/checkbox.json +0 -122
  47. package/src/manifests/codeblock.json +0 -63
  48. package/src/manifests/combobox.json +0 -33
  49. package/src/manifests/dialog.json +0 -64
  50. package/src/manifests/divider.json +0 -47
  51. package/src/manifests/dropdown.json +0 -105
  52. package/src/manifests/form.json +0 -81
  53. package/src/manifests/grid.json +0 -143
  54. package/src/manifests/index.ts +0 -45
  55. package/src/manifests/input.json +0 -99
  56. package/src/manifests/meter.json +0 -103
  57. package/src/manifests/navigation.json +0 -70
  58. package/src/manifests/progress.json +0 -88
  59. package/src/manifests/radio.json +0 -121
  60. package/src/manifests/select.json +0 -109
  61. package/src/manifests/skeleton.json +0 -101
  62. package/src/manifests/tab.json +0 -88
  63. package/src/manifests/table.json +0 -92
  64. package/src/manifests/textarea.json +0 -117
  65. package/src/manifests/toast.json +0 -157
  66. package/src/manifests/tooltip.json +0 -115
  67. package/src/server.ts +0 -201
  68. package/src/tools/build-index.ts +0 -55
  69. package/src/tools/check-a11y.ts +0 -106
  70. package/src/tools/embedder.ts +0 -18
  71. package/src/tools/generate-theme.ts +0 -42
  72. package/src/tools/get-emmet.ts +0 -64
  73. package/src/tools/get-manifests.ts +0 -55
  74. package/src/tools/intent-engine.ts +0 -197
  75. package/src/tools/list-components.ts +0 -20
  76. package/src/tools/search-index.ts +0 -66
  77. package/src/tools/theme-palette.ts +0 -65
  78. package/src/tools/theme-tokens.ts +0 -176
  79. package/src/tools/validator.ts +0 -367
  80. package/src/types.ts +0 -63
  81. package/src/utils/a11y-rules.ts +0 -873
  82. package/src/utils/a11y-types.ts +0 -15
  83. package/src/utils/check-api.ts +0 -13
  84. package/src/utils/cosine.ts +0 -15
  85. package/src/utils/emmet-helpers.ts +0 -171
  86. package/src/utils/intent-helpers.ts +0 -66
  87. package/src/utils/intent-parser.ts +0 -186
  88. package/src/utils/tokenizer.ts +0 -7
  89. package/tsconfig.json +0 -14
  90. package/tsup.config.ts +0 -13
@@ -1,197 +0,0 @@
1
- import { searchIndex } from './search-index.js'
2
- import { getIntentEntries, scoreEntry } from '../utils/intent-parser.js'
3
- import { getTokenCount } from '../utils/tokenizer.js'
4
- import { tokenise } from '../utils/intent-helpers.js'
5
- import {
6
- expandEmmet,
7
- extractComponents,
8
- interpolateEmmet
9
- } from '../utils/emmet-helpers.js'
10
-
11
- import type { IntentEntry } from '../utils/intent-parser.js'
12
-
13
- // ─── Layer 1: api-full.txt INTENTS lookup ───────────────────────────────────
14
-
15
- type IntentMatch = {
16
- name: string
17
- emmet: string
18
- score: number
19
- source: 'intent-table' | 'vector-index'
20
- }
21
-
22
- const LAYER1_THRESHOLD = 8 // minimum score to trust an intent-table hit
23
-
24
- function searchIntentTable(description: string): IntentMatch | null {
25
- const entries = getIntentEntries()
26
- if (entries.length === 0) return null
27
-
28
- const queryWords = tokenise(description)
29
-
30
- // Find all matches meeting the threshold
31
- const candidates: {
32
- entry: IntentEntry
33
- score: number
34
- density: number
35
- components: string[]
36
- }[] = []
37
- let bestSingle: (IntentMatch & { density: number }) | null = null
38
-
39
- for (const entry of entries) {
40
- const { score, density } = scoreEntry(entry, queryWords)
41
- if (score >= LAYER1_THRESHOLD) {
42
- const components = extractComponents(entry.emmet)
43
- candidates.push({ entry, score, density, components })
44
-
45
- const isBetter =
46
- !bestSingle ||
47
- score > bestSingle.score ||
48
- (score === bestSingle.score && density > bestSingle.density)
49
- if (isBetter) {
50
- bestSingle = {
51
- name: entry.name,
52
- emmet: entry.emmet,
53
- score,
54
- density,
55
- source: 'intent-table'
56
- }
57
- }
58
- }
59
- }
60
-
61
- // If a single template matches with exceptionally high score, trust it directly
62
- if (bestSingle && bestSingle.score >= 18) {
63
- return bestSingle
64
- }
65
-
66
- // Check if the user explicitly requested stitching using conjunction words (and, plus, +, comma)
67
- const isStitchRequested =
68
- /\b(and|plus|\+)\b/i.test(description) || description.includes(',')
69
-
70
- // Otherwise, check if we can stitch multiple distinct components together (synthesis)
71
- if (isStitchRequested && candidates.length >= 2) {
72
- // Sort candidates by score (descending)
73
- candidates.sort((a, b) => b.score - a.score || b.density - a.density)
74
-
75
- const selected: typeof candidates = []
76
- const usedComponents = new Set<string>()
77
-
78
- for (const c of candidates) {
79
- // Check if this candidate is disjoint from the already selected components
80
- const hasOverlap = c.components.some((comp) => usedComponents.has(comp))
81
- if (!hasOverlap) {
82
- selected.push(c)
83
- c.components.forEach((comp) => usedComponents.add(comp))
84
- }
85
- }
86
-
87
- // If we gathered at least 2 distinct templates, stitch them!
88
- if (selected.length >= 2) {
89
- // Re-sort selected in the order they appear in the user description to look natural
90
- selected.sort((a, b) => {
91
- const indexA = description
92
- .toLowerCase()
93
- .indexOf(tokenise(a.entry.name)[0])
94
- const indexB = description
95
- .toLowerCase()
96
- .indexOf(tokenise(b.entry.name)[0])
97
- return indexA - indexB
98
- })
99
-
100
- const combinedEmmet = selected.map((s) => s.entry.emmet).join('+')
101
- const totalScore = selected.reduce((sum, s) => sum + s.score, 0)
102
-
103
- return {
104
- name: selected.map((s) => s.entry.name).join(' and '),
105
- emmet: combinedEmmet,
106
- score: totalScore,
107
- source: 'intent-table'
108
- }
109
- }
110
- }
111
-
112
- return bestSingle
113
- }
114
-
115
- // ─── Layer 2: vector-index fallback ─────────────────────────────────────────
116
-
117
- function searchVectorLayer(description: string): IntentMatch | null {
118
- const results = searchIndex(description)
119
- if (results.length === 0) return null
120
- const top = results[0]
121
- return {
122
- name: top.name,
123
- emmet: top.emmet,
124
- score: top.score,
125
- source: 'vector-index'
126
- }
127
- }
128
-
129
- // ─── Public: howToBuild ──────────────────────────────────────────────────────
130
-
131
- export async function howToBuild(description: string) {
132
- let cleanDesc = description.trim()
133
- if (cleanDesc.startsWith('{') && cleanDesc.endsWith('}')) {
134
- try {
135
- const parsed = JSON.parse(cleanDesc)
136
- if (parsed && typeof parsed.description === 'string') {
137
- cleanDesc = parsed.description
138
- } else if (parsed && typeof parsed.text === 'string') {
139
- cleanDesc = parsed.text
140
- }
141
- } catch {
142
- // Ignore parsing errors, keep original string
143
- }
144
- }
145
-
146
- // Layer 1 — fast, deterministic, hand-crafted intent table
147
- let match = searchIntentTable(cleanDesc)
148
-
149
- // Layer 2 — vector index fallback for novel / unknown queries
150
- if (!match) {
151
- match = searchVectorLayer(cleanDesc)
152
- }
153
-
154
- if (!match) {
155
- return {
156
- content: [
157
- {
158
- type: 'text',
159
- text: JSON.stringify({
160
- emmet: '',
161
- html: '',
162
- components_used: [],
163
- confidence: 0,
164
- tokens: 0,
165
- tokens_used: 15,
166
- source: 'no-match',
167
- suggestion:
168
- 'Call list_components() to see all available components, or try a different description.'
169
- })
170
- }
171
- ]
172
- }
173
- }
174
-
175
- // Apply dynamic interpolation to customize labels, types, and colors on the fly
176
- const emmet = interpolateEmmet(match.emmet, cleanDesc)
177
- const components_used = extractComponents(emmet)
178
-
179
- const confidence = Math.min(1, match.score / 40)
180
-
181
- return {
182
- content: [
183
- {
184
- type: 'text',
185
- text: JSON.stringify({
186
- emmet,
187
- html: expandEmmet(emmet),
188
- components_used,
189
- confidence: Number(confidence.toFixed(2)),
190
- tokens: getTokenCount(emmet),
191
- tokens_used: 15,
192
- source: match.source // tells caller which layer matched
193
- })
194
- }
195
- ]
196
- }
197
- }
@@ -1,20 +0,0 @@
1
- import { manifests } from '../manifests/index.js'
2
-
3
- import type { MCPResponse } from '../types.js'
4
-
5
- export function listComponents(): MCPResponse {
6
- const components = Object.keys(manifests).sort()
7
-
8
- return {
9
- content: [
10
- {
11
- type: 'text',
12
- text: JSON.stringify({
13
- components,
14
- count: components.length,
15
- tokens_used: components.length
16
- })
17
- }
18
- ]
19
- }
20
- }
@@ -1,66 +0,0 @@
1
- import path from 'path'
2
- import { readFileSync, existsSync } from 'fs'
3
- import { fileURLToPath } from 'url'
4
- import { embedText } from './embedder.js'
5
- import { cosineSimilarity } from '../utils/cosine.js'
6
-
7
- type IndexItem = {
8
- name: string
9
- emmet: string
10
- searchable: string
11
- embedding: number[]
12
- }
13
-
14
- const __filename = fileURLToPath(import.meta.url)
15
- const __dirname = path.dirname(__filename)
16
-
17
- let _index: IndexItem[] | null = null
18
-
19
- function loadIndex(): IndexItem[] {
20
- if (_index) return _index
21
- const indexPath = path.resolve(__dirname, '../../dist/vector-index.json')
22
- if (!existsSync(indexPath)) {
23
- console.warn(
24
- `[search-index] dist/vector-index.json not found at: ${indexPath} - run pnpm build:index`
25
- )
26
- _index = []
27
- return _index
28
- }
29
- try {
30
- _index = JSON.parse(readFileSync(indexPath, 'utf8'))
31
- } catch (err) {
32
- console.error(`[search-index] Failed to load index from ${indexPath}:`, err)
33
- _index = []
34
- }
35
- return _index!
36
- }
37
-
38
- export function searchIndex(description: string) {
39
- const index = loadIndex()
40
- const queryEmbedding = embedText(description)
41
-
42
- const words = description.toLowerCase().split(/\s+/)
43
- const ranked = index
44
- .map((item) => {
45
- const similarity = cosineSimilarity(queryEmbedding, item.embedding)
46
- let boost = 0
47
- const searchable = item.searchable.toLowerCase()
48
-
49
- words.forEach((word) => {
50
- if (item.name.toLowerCase() === word) {
51
- boost += 2
52
- } else if (item.name.toLowerCase().includes(word)) {
53
- boost += 1
54
- }
55
- if (searchable.includes(word)) {
56
- boost += 0.3
57
- }
58
- })
59
- return {
60
- ...item,
61
- score: similarity + boost
62
- }
63
- })
64
- .sort((a, b) => b.score - a.score)
65
- return ranked.slice(0, 3)
66
- }
@@ -1,65 +0,0 @@
1
- export interface ColorEntry {
2
- primary: string
3
- contrast: string
4
- }
5
-
6
- export const PALETTE: Record<string, ColorEntry> = {
7
- red: { primary: '#ef4444', contrast: '#ffffff' },
8
- orange: { primary: '#f97316', contrast: '#ffffff' },
9
- amber: { primary: '#f59e0b', contrast: '#111827' },
10
- yellow: { primary: '#eab308', contrast: '#111827' },
11
- lime: { primary: '#84cc16', contrast: '#111827' },
12
- green: { primary: '#22c55e', contrast: '#ffffff' },
13
- emerald: { primary: '#10b981', contrast: '#ffffff' },
14
- teal: { primary: '#14b8a6', contrast: '#ffffff' },
15
- cyan: { primary: '#06b6d4', contrast: '#111827' },
16
- sky: { primary: '#0ea5e9', contrast: '#111827' },
17
- blue: { primary: '#3b82f6', contrast: '#ffffff' },
18
- indigo: { primary: '#6366f1', contrast: '#ffffff' },
19
- violet: { primary: '#8b5cf6', contrast: '#ffffff' },
20
- purple: { primary: '#a855f7', contrast: '#ffffff' },
21
- fuchsia: { primary: '#d946ef', contrast: '#ffffff' },
22
- pink: { primary: '#ec4899', contrast: '#ffffff' },
23
- rose: { primary: '#f43f5e', contrast: '#ffffff' },
24
- cyberpunk: { primary: '#ec4899', contrast: '#ffffff' },
25
- neon: { primary: '#d946ef', contrast: '#ffffff' },
26
- eco: { primary: '#10b981', contrast: '#ffffff' },
27
- forest: { primary: '#0f766e', contrast: '#ffffff' },
28
- coffee: { primary: '#78350f', contrast: '#ffffff' },
29
- ocean: { primary: '#0284c7', contrast: '#ffffff' },
30
- sunset: { primary: '#f97316', contrast: '#ffffff' },
31
- midnight: { primary: '#3730a3', contrast: '#ffffff' },
32
- lavender: { primary: '#8b5cf6', contrast: '#ffffff' },
33
- coral: { primary: '#f43f5e', contrast: '#ffffff' },
34
- slate: { primary: '#64748b', contrast: '#ffffff' },
35
- gold: { primary: '#d97706', contrast: '#111827' },
36
- silver: { primary: '#94a3b8', contrast: '#111827' }
37
- }
38
-
39
- export function expandHex(hex: string): string {
40
- const h = hex.replace('#', '')
41
- return h.length === 3 ? '#' + h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : hex
42
- }
43
-
44
- export function contrastFor(hex6: string): string {
45
- const h = hex6.replace('#', '')
46
- const r = parseInt(h.substring(0, 2), 16)
47
- const g = parseInt(h.substring(2, 4), 16)
48
- const b = parseInt(h.substring(4, 6), 16)
49
- return (r * 299 + g * 587 + b * 114) / 1000 > 165 ? '#111827' : '#ffffff'
50
- }
51
-
52
- export function resolveColor(query: string): ColorEntry {
53
- const hexMatch = query.match(/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/)
54
- if (hexMatch) {
55
- const primary = expandHex(hexMatch[0])
56
- return { primary, contrast: contrastFor(primary) }
57
- }
58
-
59
- const sorted = Object.keys(PALETTE).sort((a, b) => b.length - a.length)
60
- for (const name of sorted) {
61
- if (query.includes(name)) return PALETTE[name]
62
- }
63
-
64
- return { primary: '#6366f1', contrast: '#ffffff' }
65
- }
@@ -1,176 +0,0 @@
1
- import { resolveColor } from './theme-palette.js'
2
-
3
- export interface ThemeTokens {
4
- primary: string
5
- primaryHover: string
6
- primaryContrast: string
7
- primaryBg: string
8
-
9
- danger: string
10
- dangerText: string
11
- dangerBg: string
12
-
13
- warning: string
14
- warningBg: string
15
-
16
- success: string
17
- successBg: string
18
-
19
- infoBg: string
20
- neutral: string
21
- divider: string
22
-
23
- surface: string
24
- surfaceRaised: string
25
-
26
- text: string
27
- textMuted: string
28
- border: string
29
-
30
- radius: string
31
- radiusLg: string
32
- badgeRadius: string
33
-
34
- skeletonBg: string
35
- skeletonShimmer: string
36
-
37
- gradientStart: string
38
- gradientEnd: string
39
- gradientBgEnd: string
40
-
41
- isDark: boolean
42
- resolvedPrimary: string
43
- resolvedContrast: string
44
- }
45
-
46
- export function resolveTokens(query: string): ThemeTokens {
47
- const { primary, contrast } = resolveColor(query)
48
-
49
- const isDark =
50
- query.includes('dark') ||
51
- query.includes('night') ||
52
- query.includes('midnight') ||
53
- query.includes('cyberpunk') ||
54
- query.includes('dim')
55
-
56
- const sharp =
57
- query.includes('sharp') ||
58
- query.includes('flat') ||
59
- query.includes('square')
60
- const round =
61
- query.includes('round') || query.includes('pill') || query.includes('soft')
62
-
63
- return {
64
- resolvedPrimary: primary,
65
- resolvedContrast: contrast,
66
- isDark,
67
-
68
- primary,
69
- primaryHover: isDark
70
- ? 'color-mix(in srgb, var(--ix-primary) 75%, white)'
71
- : 'color-mix(in srgb, var(--ix-primary) 85%, black)',
72
- primaryContrast: isDark
73
- ? 'color-mix(in srgb, var(--ix-primary) 40%, white)'
74
- : contrast,
75
- primaryBg: isDark
76
- ? 'color-mix(in srgb, var(--ix-primary) 20%, #0f172a)'
77
- : 'color-mix(in srgb, var(--ix-primary) 15%, white)',
78
-
79
- danger: '#ef4444',
80
- dangerText: isDark ? '#f87171' : '#ef4444',
81
- dangerBg: isDark ? '#3f1d1d' : '#fee2e2',
82
-
83
- warning: isDark ? '#fbbf24' : '#f59e0b',
84
- warningBg: isDark ? '#3f2e05' : '#fef3c7',
85
-
86
- success: '#22c55e',
87
- successBg: isDark ? '#052e16' : '#dcfce7',
88
-
89
- infoBg: isDark ? '#1e293b' : '#f3f4f6',
90
- neutral: isDark ? '#9ca3af' : '#6b7280',
91
- divider: isDark ? '#475569' : '#9ca3af',
92
-
93
- surface: isDark ? '#0f172a' : '#f9fafb',
94
- surfaceRaised: isDark ? '#1e293b' : '#ffffff',
95
-
96
- text: isDark ? '#f9fafb' : '#111827',
97
- textMuted: isDark ? '#94a3b8' : '#6b7280',
98
- border: isDark ? '#334155' : '#e5e7eb',
99
-
100
- radius: sharp ? '0px' : round ? '0.75rem' : '0.375rem',
101
- radiusLg: sharp ? '0px' : round ? '1rem' : '0.5rem',
102
- badgeRadius: '999px',
103
-
104
- skeletonBg: isDark ? '#1e293b' : '#e5e7eb',
105
- skeletonShimmer: isDark ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.6)',
106
-
107
- gradientStart: primary,
108
- gradientEnd: '#fffbd5',
109
- gradientBgEnd: isDark ? '#aba67a' : '#d7d4bc'
110
- }
111
- }
112
-
113
- export function buildCss(t: ThemeTokens): string {
114
- return `:root {
115
- --ix-primary: ${t.primary};
116
- --ix-primary-hover: ${t.primaryHover};
117
- --ix-primary-contrast: ${t.primaryContrast};
118
-
119
- --ix-danger: ${t.danger};
120
- --ix-danger-text: ${t.dangerText};
121
-
122
- --ix-warning: ${t.warning};
123
- --ix-success: ${t.success};
124
- --ix-neutral: ${t.neutral};
125
- --ix-ghost: transparent;
126
-
127
- --ix-primary-bg: ${t.primaryBg};
128
- --ix-danger-bg: ${t.dangerBg};
129
- --ix-warning-bg: ${t.warningBg};
130
- --ix-success-bg: ${t.successBg};
131
- --ix-info-bg: ${t.infoBg};
132
-
133
- --ix-on-danger: #ffffff;
134
- --ix-on-warning: #000000;
135
- --ix-on-success: #ffffff;
136
- --ix-on-primary: ${t.resolvedContrast};
137
-
138
- --ix-divider: ${t.divider};
139
-
140
- --ix-surface: ${t.surface};
141
- --ix-surface-raised: ${t.surfaceRaised};
142
-
143
- --ix-text: ${t.text};
144
- --ix-text-muted: ${t.textMuted};
145
-
146
- --ix-border: ${t.border};
147
- --ix-focus: 2px solid var(--ix-primary);
148
-
149
- --ix-font: 'Segoe UI Variable', 'Segoe UI', 'Roboto', 'Noto Sans', system-ui, sans-serif;
150
- --ix-font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
151
-
152
- --ix-size-xs: 0.75rem;
153
- --ix-size-sm: 0.875rem;
154
- --ix-size-md: 1rem;
155
- --ix-size-lg: 1.125rem;
156
- --ix-size-xl: 1.25rem;
157
-
158
- --ix-line-height: 1.5;
159
-
160
- --ix-space-sm: 0.5rem;
161
- --ix-space-md: 1rem;
162
- --ix-space-lg: 1.5rem;
163
-
164
- --ix-radius: ${t.radius};
165
- --ix-radius-lg: ${t.radiusLg};
166
- --ix-badge-radius: ${t.badgeRadius};
167
-
168
- --ix-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
169
-
170
- --ix-gradient: linear-gradient(to right, ${t.gradientStart}, ${t.gradientEnd});
171
- --ix-gradient-bg: linear-gradient(to right, ${t.gradientStart}, ${t.gradientBgEnd});
172
-
173
- --ix-skeleton-bg: ${t.skeletonBg};
174
- --ix-skeleton-shimmer: ${t.skeletonShimmer};
175
- }`
176
- }