mcp-react-toolkit 1.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.
Files changed (228) hide show
  1. package/CONTRIBUTING.md +157 -0
  2. package/HOW_IT_WORKS.md +270 -0
  3. package/README.md +259 -0
  4. package/demo/legacy-app/src/App.jsx +12 -0
  5. package/demo/legacy-app/src/components/Dashboard.jsx +51 -0
  6. package/demo/legacy-app/src/components/UserCard.jsx +32 -0
  7. package/demo/legacy-app/src/hooks/useUsers.js +38 -0
  8. package/demo/legacy-app/src/utils/api.js +30 -0
  9. package/glama.json +4 -0
  10. package/package.json +39 -0
  11. package/tools/accessibility-checker/build/index.d.ts +3 -0
  12. package/tools/accessibility-checker/build/index.d.ts.map +1 -0
  13. package/tools/accessibility-checker/build/index.js +112 -0
  14. package/tools/accessibility-checker/build/index.js.map +1 -0
  15. package/tools/accessibility-checker/build/rules.d.ts +22 -0
  16. package/tools/accessibility-checker/build/rules.d.ts.map +1 -0
  17. package/tools/accessibility-checker/build/rules.js +244 -0
  18. package/tools/accessibility-checker/build/rules.js.map +1 -0
  19. package/tools/accessibility-checker/build/rules.test.d.ts +2 -0
  20. package/tools/accessibility-checker/build/rules.test.d.ts.map +1 -0
  21. package/tools/accessibility-checker/build/rules.test.js.map +1 -0
  22. package/tools/accessibility-checker/package.json +20 -0
  23. package/tools/code-modernizer/build/index.d.ts +3 -0
  24. package/tools/code-modernizer/build/index.d.ts.map +1 -0
  25. package/tools/code-modernizer/build/index.js +58 -0
  26. package/tools/code-modernizer/build/index.js.map +1 -0
  27. package/tools/code-modernizer/build/tools/01-convert-to-typescript.d.ts +3 -0
  28. package/tools/code-modernizer/build/tools/01-convert-to-typescript.d.ts.map +1 -0
  29. package/tools/code-modernizer/build/tools/01-convert-to-typescript.js +110 -0
  30. package/tools/code-modernizer/build/tools/01-convert-to-typescript.js.map +1 -0
  31. package/tools/code-modernizer/build/types.d.ts +57 -0
  32. package/tools/code-modernizer/build/types.d.ts.map +1 -0
  33. package/tools/code-modernizer/build/types.js +5 -0
  34. package/tools/code-modernizer/build/types.js.map +1 -0
  35. package/tools/code-modernizer/build/utils/ast-parser.d.ts +6 -0
  36. package/tools/code-modernizer/build/utils/ast-parser.d.ts.map +1 -0
  37. package/tools/code-modernizer/build/utils/ast-parser.js +177 -0
  38. package/tools/code-modernizer/build/utils/ast-parser.js.map +1 -0
  39. package/tools/code-modernizer/build/utils/file-ops.d.ts +8 -0
  40. package/tools/code-modernizer/build/utils/file-ops.d.ts.map +1 -0
  41. package/tools/code-modernizer/build/utils/file-ops.js +63 -0
  42. package/tools/code-modernizer/build/utils/file-ops.js.map +1 -0
  43. package/tools/code-modernizer/build/utils/file-ops.test.d.ts +2 -0
  44. package/tools/code-modernizer/build/utils/file-ops.test.d.ts.map +1 -0
  45. package/tools/code-modernizer/build/utils/file-ops.test.js.map +1 -0
  46. package/tools/code-modernizer/build/utils/type-generator.d.ts +4 -0
  47. package/tools/code-modernizer/build/utils/type-generator.d.ts.map +1 -0
  48. package/tools/code-modernizer/build/utils/type-generator.js +37 -0
  49. package/tools/code-modernizer/build/utils/type-generator.js.map +1 -0
  50. package/tools/code-modernizer/package.json +23 -0
  51. package/tools/component-factory/build/index.d.ts +3 -0
  52. package/tools/component-factory/build/index.d.ts.map +1 -0
  53. package/tools/component-factory/build/index.js +534 -0
  54. package/tools/component-factory/build/index.js.map +1 -0
  55. package/tools/component-factory/build/utils.d.ts +6 -0
  56. package/tools/component-factory/build/utils.d.ts.map +1 -0
  57. package/tools/component-factory/build/utils.js +11 -0
  58. package/tools/component-factory/build/utils.js.map +1 -0
  59. package/tools/component-factory/build/utils.test.d.ts +2 -0
  60. package/tools/component-factory/build/utils.test.d.ts.map +1 -0
  61. package/tools/component-factory/build/utils.test.js.map +1 -0
  62. package/tools/component-factory/package.json +20 -0
  63. package/tools/component-factory/templates/accordion.tsx +57 -0
  64. package/tools/component-factory/templates/alert.tsx +59 -0
  65. package/tools/component-factory/templates/aspect-ratio.tsx +8 -0
  66. package/tools/component-factory/templates/avatar.tsx +51 -0
  67. package/tools/component-factory/templates/badge.tsx +37 -0
  68. package/tools/component-factory/templates/breadcrumb.tsx +116 -0
  69. package/tools/component-factory/templates/button.tsx +57 -0
  70. package/tools/component-factory/templates/calendar.tsx +66 -0
  71. package/tools/component-factory/templates/card.tsx +80 -0
  72. package/tools/component-factory/templates/checkbox.tsx +31 -0
  73. package/tools/component-factory/templates/collapsible.tsx +11 -0
  74. package/tools/component-factory/templates/command.tsx +150 -0
  75. package/tools/component-factory/templates/context-menu.tsx +199 -0
  76. package/tools/component-factory/templates/dialog.tsx +123 -0
  77. package/tools/component-factory/templates/drawer.tsx +118 -0
  78. package/tools/component-factory/templates/dropdown-menu.tsx +201 -0
  79. package/tools/component-factory/templates/form.tsx +178 -0
  80. package/tools/component-factory/templates/hover-card.tsx +29 -0
  81. package/tools/component-factory/templates/input-otp.tsx +71 -0
  82. package/tools/component-factory/templates/input.tsx +23 -0
  83. package/tools/component-factory/templates/label.tsx +27 -0
  84. package/tools/component-factory/templates/menubar.tsx +236 -0
  85. package/tools/component-factory/templates/navigation-menu.tsx +128 -0
  86. package/tools/component-factory/templates/pagination.tsx +120 -0
  87. package/tools/component-factory/templates/popover.tsx +31 -0
  88. package/tools/component-factory/templates/progress.tsx +28 -0
  89. package/tools/component-factory/templates/radio-group.tsx +44 -0
  90. package/tools/component-factory/templates/scroll-area.tsx +48 -0
  91. package/tools/component-factory/templates/select.tsx +159 -0
  92. package/tools/component-factory/templates/separator.tsx +32 -0
  93. package/tools/component-factory/templates/sheet.tsx +140 -0
  94. package/tools/component-factory/templates/skeleton.tsx +15 -0
  95. package/tools/component-factory/templates/slider.tsx +28 -0
  96. package/tools/component-factory/templates/sonner.tsx +31 -0
  97. package/tools/component-factory/templates/switch.tsx +29 -0
  98. package/tools/component-factory/templates/table.tsx +117 -0
  99. package/tools/component-factory/templates/tabs.tsx +56 -0
  100. package/tools/component-factory/templates/textarea.tsx +22 -0
  101. package/tools/component-factory/templates/toggle-group.tsx +61 -0
  102. package/tools/component-factory/templates/toggle.tsx +45 -0
  103. package/tools/component-factory/templates/tooltip.tsx +30 -0
  104. package/tools/dep-auditor/build/index.d.ts +18 -0
  105. package/tools/dep-auditor/build/index.d.ts.map +1 -0
  106. package/tools/dep-auditor/build/index.js +247 -0
  107. package/tools/dep-auditor/build/index.js.map +1 -0
  108. package/tools/dep-auditor/build/index.test.d.ts +2 -0
  109. package/tools/dep-auditor/build/index.test.d.ts.map +1 -0
  110. package/tools/dep-auditor/build/index.test.js.map +1 -0
  111. package/tools/dep-auditor/package.json +20 -0
  112. package/tools/generate-tests/build/analyzer.d.ts +31 -0
  113. package/tools/generate-tests/build/analyzer.d.ts.map +1 -0
  114. package/tools/generate-tests/build/analyzer.js +105 -0
  115. package/tools/generate-tests/build/analyzer.js.map +1 -0
  116. package/tools/generate-tests/build/analyzer.test.d.ts +2 -0
  117. package/tools/generate-tests/build/analyzer.test.d.ts.map +1 -0
  118. package/tools/generate-tests/build/analyzer.test.js.map +1 -0
  119. package/tools/generate-tests/build/generators.d.ts +6 -0
  120. package/tools/generate-tests/build/generators.d.ts.map +1 -0
  121. package/tools/generate-tests/build/generators.js +161 -0
  122. package/tools/generate-tests/build/generators.js.map +1 -0
  123. package/tools/generate-tests/build/index.d.ts +3 -0
  124. package/tools/generate-tests/build/index.d.ts.map +1 -0
  125. package/tools/generate-tests/build/index.js +148 -0
  126. package/tools/generate-tests/build/index.js.map +1 -0
  127. package/tools/generate-tests/package.json +20 -0
  128. package/tools/json-viewer/build/index.d.ts +3 -0
  129. package/tools/json-viewer/build/index.d.ts.map +1 -0
  130. package/tools/json-viewer/build/index.js +282 -0
  131. package/tools/json-viewer/build/index.js.map +1 -0
  132. package/tools/json-viewer/build/utils.d.ts +5 -0
  133. package/tools/json-viewer/build/utils.d.ts.map +1 -0
  134. package/tools/json-viewer/build/utils.js +40 -0
  135. package/tools/json-viewer/build/utils.js.map +1 -0
  136. package/tools/json-viewer/build/utils.test.d.ts +2 -0
  137. package/tools/json-viewer/build/utils.test.d.ts.map +1 -0
  138. package/tools/json-viewer/build/utils.test.js.map +1 -0
  139. package/tools/json-viewer/package.json +20 -0
  140. package/tools/monorepo-manager/build/index.d.ts +3 -0
  141. package/tools/monorepo-manager/build/index.d.ts.map +1 -0
  142. package/tools/monorepo-manager/build/index.js +318 -0
  143. package/tools/monorepo-manager/build/index.js.map +1 -0
  144. package/tools/monorepo-manager/build/types.d.ts +17 -0
  145. package/tools/monorepo-manager/build/types.d.ts.map +1 -0
  146. package/tools/monorepo-manager/build/types.js +2 -0
  147. package/tools/monorepo-manager/build/types.js.map +1 -0
  148. package/tools/monorepo-manager/build/utils.d.ts +9 -0
  149. package/tools/monorepo-manager/build/utils.d.ts.map +1 -0
  150. package/tools/monorepo-manager/build/utils.js +135 -0
  151. package/tools/monorepo-manager/build/utils.js.map +1 -0
  152. package/tools/monorepo-manager/build/utils.test.d.ts +2 -0
  153. package/tools/monorepo-manager/build/utils.test.d.ts.map +1 -0
  154. package/tools/monorepo-manager/build/utils.test.js.map +1 -0
  155. package/tools/monorepo-manager/package.json +20 -0
  156. package/tools/quality-pipeline/build/index.d.ts +3 -0
  157. package/tools/quality-pipeline/build/index.d.ts.map +1 -0
  158. package/tools/quality-pipeline/build/index.js +538 -0
  159. package/tools/quality-pipeline/build/index.js.map +1 -0
  160. package/tools/quality-pipeline/build/utils.d.ts +9 -0
  161. package/tools/quality-pipeline/build/utils.d.ts.map +1 -0
  162. package/tools/quality-pipeline/build/utils.js +15 -0
  163. package/tools/quality-pipeline/build/utils.js.map +1 -0
  164. package/tools/quality-pipeline/build/utils.test.d.ts +2 -0
  165. package/tools/quality-pipeline/build/utils.test.d.ts.map +1 -0
  166. package/tools/quality-pipeline/build/utils.test.js.map +1 -0
  167. package/tools/quality-pipeline/package.json +20 -0
  168. package/tools/shared/build/McpServerBase.d.ts +18 -0
  169. package/tools/shared/build/McpServerBase.d.ts.map +1 -0
  170. package/tools/shared/build/McpServerBase.js +74 -0
  171. package/tools/shared/build/McpServerBase.js.map +1 -0
  172. package/tools/shared/build/ToolRegistry.d.ts +9 -0
  173. package/tools/shared/build/ToolRegistry.d.ts.map +1 -0
  174. package/tools/shared/build/ToolRegistry.js +22 -0
  175. package/tools/shared/build/ToolRegistry.js.map +1 -0
  176. package/tools/shared/build/index.d.ts +4 -0
  177. package/tools/shared/build/index.d.ts.map +1 -0
  178. package/tools/shared/build/index.js +4 -0
  179. package/tools/shared/build/index.js.map +1 -0
  180. package/tools/shared/build/types.d.ts +36 -0
  181. package/tools/shared/build/types.d.ts.map +1 -0
  182. package/tools/shared/build/types.js +5 -0
  183. package/tools/shared/build/types.js.map +1 -0
  184. package/tools/shared/package.json +23 -0
  185. package/tools/typescript-enforcer/build/index.d.ts +3 -0
  186. package/tools/typescript-enforcer/build/index.d.ts.map +1 -0
  187. package/tools/typescript-enforcer/build/index.js +155 -0
  188. package/tools/typescript-enforcer/build/index.js.map +1 -0
  189. package/tools/typescript-enforcer/build/rules/branded-types.d.ts +3 -0
  190. package/tools/typescript-enforcer/build/rules/branded-types.d.ts.map +1 -0
  191. package/tools/typescript-enforcer/build/rules/branded-types.js +4 -0
  192. package/tools/typescript-enforcer/build/rules/branded-types.js.map +1 -0
  193. package/tools/typescript-enforcer/build/rules/discriminated-unions.d.ts +3 -0
  194. package/tools/typescript-enforcer/build/rules/discriminated-unions.d.ts.map +1 -0
  195. package/tools/typescript-enforcer/build/rules/discriminated-unions.js +4 -0
  196. package/tools/typescript-enforcer/build/rules/discriminated-unions.js.map +1 -0
  197. package/tools/typescript-enforcer/build/rules/generics.d.ts +3 -0
  198. package/tools/typescript-enforcer/build/rules/generics.d.ts.map +1 -0
  199. package/tools/typescript-enforcer/build/rules/generics.js +182 -0
  200. package/tools/typescript-enforcer/build/rules/generics.js.map +1 -0
  201. package/tools/typescript-enforcer/build/rules/modifiers.d.ts +3 -0
  202. package/tools/typescript-enforcer/build/rules/modifiers.d.ts.map +1 -0
  203. package/tools/typescript-enforcer/build/rules/modifiers.js +214 -0
  204. package/tools/typescript-enforcer/build/rules/modifiers.js.map +1 -0
  205. package/tools/typescript-enforcer/build/rules/no-any.d.ts +3 -0
  206. package/tools/typescript-enforcer/build/rules/no-any.d.ts.map +1 -0
  207. package/tools/typescript-enforcer/build/rules/no-any.js +138 -0
  208. package/tools/typescript-enforcer/build/rules/no-any.js.map +1 -0
  209. package/tools/typescript-enforcer/build/rules/type-guards.d.ts +3 -0
  210. package/tools/typescript-enforcer/build/rules/type-guards.d.ts.map +1 -0
  211. package/tools/typescript-enforcer/build/rules/type-guards.js +176 -0
  212. package/tools/typescript-enforcer/build/rules/type-guards.js.map +1 -0
  213. package/tools/typescript-enforcer/build/rules/utility-types.d.ts +3 -0
  214. package/tools/typescript-enforcer/build/rules/utility-types.d.ts.map +1 -0
  215. package/tools/typescript-enforcer/build/rules/utility-types.js +101 -0
  216. package/tools/typescript-enforcer/build/rules/utility-types.js.map +1 -0
  217. package/tools/typescript-enforcer/build/scanner.d.ts +4 -0
  218. package/tools/typescript-enforcer/build/scanner.d.ts.map +1 -0
  219. package/tools/typescript-enforcer/build/scanner.js +114 -0
  220. package/tools/typescript-enforcer/build/scanner.js.map +1 -0
  221. package/tools/typescript-enforcer/build/scanner.test.d.ts +2 -0
  222. package/tools/typescript-enforcer/build/scanner.test.d.ts.map +1 -0
  223. package/tools/typescript-enforcer/build/scanner.test.js.map +1 -0
  224. package/tools/typescript-enforcer/build/types.d.ts +55 -0
  225. package/tools/typescript-enforcer/build/types.d.ts.map +1 -0
  226. package/tools/typescript-enforcer/build/types.js +2 -0
  227. package/tools/typescript-enforcer/build/types.js.map +1 -0
  228. package/tools/typescript-enforcer/package.json +20 -0
@@ -0,0 +1,161 @@
1
+ import { mockValue } from './analyzer.js';
2
+ export function generateComponentTests(info) {
3
+ const { name, isVoidElement, hasVariants, hasSizes } = info;
4
+ const renderProps = isVoidElement ? 'placeholder="test"' : `>Test Content</${name}>`;
5
+ const getEl = isVoidElement
6
+ ? 'screen.getByPlaceholderText("test")'
7
+ : 'screen.getByText("Test Content")';
8
+ let t = `import { describe, it, expect, vi } from 'vitest'
9
+ import { render, screen, fireEvent } from '@testing-library/react'
10
+ import { ${name} } from './${name}'
11
+
12
+ describe('${name}', () => {
13
+ it('renders without crashing', () => {
14
+ render(<${name} ${renderProps} />)
15
+ expect(${getEl}).toBeInTheDocument()
16
+ })
17
+
18
+ it('applies custom className', () => {
19
+ const { container } = render(<${name} className="custom" ${renderProps} />)
20
+ expect(container.firstChild).toHaveClass('custom')
21
+ })
22
+
23
+ it('forwards ref', () => {
24
+ const ref = { current: null }
25
+ render(<${name} ref={ref} ${renderProps} />)
26
+ expect(ref.current).not.toBeNull()
27
+ })
28
+
29
+ it('spreads extra props via data-testid', () => {
30
+ render(<${name} data-testid="el" ${renderProps} />)
31
+ expect(screen.getByTestId('el')).toBeInTheDocument()
32
+ })
33
+
34
+ it('supports aria-label for accessibility', () => {
35
+ render(<${name} aria-label="label" ${renderProps} />)
36
+ expect(screen.getByLabelText('label')).toBeInTheDocument()
37
+ })
38
+ `;
39
+ if (hasVariants) {
40
+ for (const v of ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']) {
41
+ t += `
42
+ it('renders variant="${v}"', () => {
43
+ const { container } = render(<${name} variant="${v}" ${renderProps} />)
44
+ expect(container.firstChild).toBeInTheDocument()
45
+ })
46
+ `;
47
+ }
48
+ }
49
+ if (hasSizes) {
50
+ for (const s of ['default', 'sm', 'lg', 'icon']) {
51
+ t += `
52
+ it('renders size="${s}"', () => {
53
+ const { container } = render(<${name} size="${s}" ${renderProps} />)
54
+ expect(container.firstChild).toBeInTheDocument()
55
+ })
56
+ `;
57
+ }
58
+ }
59
+ if (isVoidElement) {
60
+ t += `
61
+ it('handles value prop', () => {
62
+ render(<${name} value="hello" readOnly />)
63
+ expect(screen.getByDisplayValue('hello')).toBeInTheDocument()
64
+ })
65
+
66
+ it('handles disabled state', () => {
67
+ render(<${name} disabled placeholder="test" />)
68
+ expect(screen.getByPlaceholderText('test')).toBeDisabled()
69
+ })
70
+
71
+ it('calls onChange', () => {
72
+ const onChange = vi.fn()
73
+ render(<${name} onChange={onChange} placeholder="test" />)
74
+ fireEvent.change(screen.getByPlaceholderText('test'), { target: { value: 'x' } })
75
+ expect(onChange).toHaveBeenCalledTimes(1)
76
+ })
77
+ `;
78
+ }
79
+ else {
80
+ t += `
81
+ it('calls onClick', () => {
82
+ const onClick = vi.fn()
83
+ render(<${name} onClick={onClick}>Click me</${name}>)
84
+ fireEvent.click(screen.getByText('Click me'))
85
+ expect(onClick).toHaveBeenCalledTimes(1)
86
+ })
87
+
88
+ it('renders children', () => {
89
+ render(<${name}><span>Child</span></${name}>)
90
+ expect(screen.getByText('Child')).toBeInTheDocument()
91
+ })
92
+ `;
93
+ }
94
+ t += `})
95
+ `;
96
+ return t;
97
+ }
98
+ export function generateFunctionTests(info) {
99
+ const { name, params, isAsync } = info;
100
+ const args = params.map(p => mockValue(p)).join(', ');
101
+ const call = isAsync ? `await ${name}(${args})` : `${name}(${args})`;
102
+ return `import { describe, it, expect } from 'vitest'
103
+
104
+ describe('${name}', () => {
105
+ it('is defined', () => {
106
+ expect(${name}).toBeDefined()
107
+ })
108
+
109
+ it('returns a value for valid input', ${isAsync ? 'async ' : ''}() => {
110
+ const result = ${call}
111
+ expect(result).toBeDefined()
112
+ })
113
+ })
114
+ `;
115
+ }
116
+ export function generateHookTests(info) {
117
+ const { name, params } = info;
118
+ const args = params.map(p => mockValue(p)).join(', ');
119
+ return `import { describe, it, expect } from 'vitest'
120
+ import { renderHook } from '@testing-library/react'
121
+
122
+ describe('${name}', () => {
123
+ it('returns a value', () => {
124
+ const { result } = renderHook(() => ${name}(${args}))
125
+ expect(result.current).toBeDefined()
126
+ })
127
+
128
+ it('does not throw on unmount', () => {
129
+ const { unmount } = renderHook(() => ${name}(${args}))
130
+ expect(() => unmount()).not.toThrow()
131
+ })
132
+ })
133
+ `;
134
+ }
135
+ export function generateClassTests(info) {
136
+ const { name, methods } = info;
137
+ const methodTests = methods
138
+ .filter(m => m !== 'constructor')
139
+ .map(m => `
140
+ it('${m} is callable', () => {
141
+ expect(typeof instance.${m}).toBe('function')
142
+ })
143
+ `)
144
+ .join('');
145
+ return `import { describe, it, expect, beforeEach } from 'vitest'
146
+ import { ${name} } from './${name.toLowerCase()}'
147
+
148
+ describe('${name}', () => {
149
+ let instance: ${name}
150
+
151
+ beforeEach(() => {
152
+ instance = new ${name}()
153
+ })
154
+
155
+ it('creates an instance', () => {
156
+ expect(instance).toBeInstanceOf(${name})
157
+ })
158
+ ${methodTests}})
159
+ `;
160
+ }
161
+ //# sourceMappingURL=generators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators.js","sourceRoot":"","sources":["../src/generators.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,UAAU,sBAAsB,CAAC,IAAmB;IACxD,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC5D,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kBAAkB,IAAI,GAAG,CAAC;IACrF,MAAM,KAAK,GAAG,aAAa;QACzB,CAAC,CAAC,qCAAqC;QACvC,CAAC,CAAC,kCAAkC,CAAC;IAEvC,IAAI,CAAC,GAAG;;WAEC,IAAI,cAAc,IAAI;;YAErB,IAAI;;cAEF,IAAI,IAAI,WAAW;aACpB,KAAK;;;;oCAIkB,IAAI,uBAAuB,WAAW;;;;;;cAM5D,IAAI,cAAc,WAAW;;;;;cAK7B,IAAI,qBAAqB,WAAW;;;;;cAKpC,IAAI,uBAAuB,WAAW;;;CAGnD,CAAC;IAEA,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;YACpF,CAAC,IAAI;yBACc,CAAC;oCACU,IAAI,aAAa,CAAC,KAAK,WAAW;;;CAGrE,CAAC;QACE,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAChD,CAAC,IAAI;sBACW,CAAC;oCACa,IAAI,UAAU,CAAC,KAAK,WAAW;;;CAGlE,CAAC;QACE,CAAC;IACH,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QAClB,CAAC,IAAI;;cAEK,IAAI;;;;;cAKJ,IAAI;;;;;;cAMJ,IAAI;;;;CAIjB,CAAC;IACA,CAAC;SAAM,CAAC;QACN,CAAC,IAAI;;;cAGK,IAAI,gCAAgC,IAAI;;;;;;cAMxC,IAAI,wBAAwB,IAAI;;;CAG7C,CAAC;IACA,CAAC;IAED,CAAC,IAAI;CACN,CAAC;IACA,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAkB;IACtD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC;IAErE,OAAO;;YAEG,IAAI;;aAEH,IAAI;;;0CAGyB,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;qBAC5C,IAAI;;;;CAIxB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC9C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtD,OAAO;;;YAGG,IAAI;;0CAE0B,IAAI,IAAI,IAAI;;;;;2CAKX,IAAI,IAAI,IAAI;;;;CAItD,CAAC;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAe;IAChD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,WAAW,GAAG,OAAO;SACxB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,aAAa,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACN,CAAC;6BACoB,CAAC;;CAE7B,CAAC;SACG,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO;WACE,IAAI,cAAc,IAAI,CAAC,WAAW,EAAE;;YAEnC,IAAI;kBACE,IAAI;;;qBAGD,IAAI;;;;sCAIa,IAAI;;EAExC,WAAW;CACZ,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import { McpServerBase } from '@mcp-showcase/shared';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { analyzeSource } from './analyzer.js';
6
+ import { generateComponentTests, generateFunctionTests, generateHookTests, generateClassTests } from './generators.js';
7
+ function scanDirectory(dir, exts = ['.ts', '.tsx', '.js', '.jsx']) {
8
+ const files = [];
9
+ if (!fs.existsSync(dir))
10
+ return files;
11
+ const SKIP = new Set(['node_modules', 'build', 'dist', '.next']);
12
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
13
+ const full = path.join(dir, entry.name);
14
+ if (entry.isDirectory()) {
15
+ if (SKIP.has(entry.name))
16
+ continue;
17
+ files.push(...scanDirectory(full, exts));
18
+ }
19
+ else if (exts.some(e => entry.name.endsWith(e)) &&
20
+ !entry.name.includes('.test.') &&
21
+ !entry.name.includes('.spec.') &&
22
+ !entry.name.includes('.stories.') &&
23
+ !entry.name.includes('.types.')) {
24
+ files.push(full);
25
+ }
26
+ }
27
+ return files;
28
+ }
29
+ function testFilePath(sourceFile) {
30
+ const dir = path.dirname(sourceFile);
31
+ const ext = path.extname(sourceFile);
32
+ const base = path.basename(sourceFile, ext);
33
+ const testExt = ext.includes('x') ? '.test.tsx' : '.test.ts';
34
+ return path.join(dir, `${base}${testExt}`);
35
+ }
36
+ class GenerateTestsServer extends McpServerBase {
37
+ constructor() {
38
+ super({ name: 'generate-tests', version: '1.0.0' });
39
+ }
40
+ registerTools() {
41
+ this.addTool('generate_tests', 'Analyze a TypeScript/React source file and generate a Vitest test file for all exported components, functions, hooks, and classes.', {
42
+ type: 'object',
43
+ properties: {
44
+ path: { type: 'string', description: 'Path to source file to generate tests for' },
45
+ outputPath: { type: 'string', description: 'Where to write the test file (defaults to <name>.test.tsx alongside the source)' },
46
+ overwrite: { type: 'boolean', description: 'Overwrite existing test file (default: false)' },
47
+ },
48
+ required: ['path'],
49
+ }, async (args) => {
50
+ const { path: srcPath, outputPath, overwrite = false } = (args ?? {});
51
+ try {
52
+ const resolved = path.resolve(srcPath);
53
+ if (!fs.existsSync(resolved))
54
+ throw new Error(`File not found: ${resolved}`);
55
+ const content = fs.readFileSync(resolved, 'utf-8');
56
+ const analysis = analyzeSource(content);
57
+ const testSections = [];
58
+ for (const c of analysis.components) {
59
+ testSections.push(`// --- ${c.name} ---\n${generateComponentTests(c)}`);
60
+ }
61
+ for (const h of analysis.hooks) {
62
+ testSections.push(`// --- ${h.name} ---\n${generateHookTests(h)}`);
63
+ }
64
+ for (const f of analysis.functions) {
65
+ testSections.push(`// --- ${f.name} ---\n${generateFunctionTests(f)}`);
66
+ }
67
+ for (const cl of analysis.classes) {
68
+ testSections.push(`// --- ${cl.name} ---\n${generateClassTests(cl)}`);
69
+ }
70
+ if (testSections.length === 0) {
71
+ return this.success({ message: 'No exportable symbols found to generate tests for', analysis });
72
+ }
73
+ const testContent = testSections.join('\n\n');
74
+ const dest = outputPath ? path.resolve(outputPath) : testFilePath(resolved);
75
+ if (fs.existsSync(dest) && !overwrite) {
76
+ return this.success({
77
+ message: `Test file already exists: ${dest}. Pass overwrite: true to replace it.`,
78
+ testContent,
79
+ dest,
80
+ });
81
+ }
82
+ fs.writeFileSync(dest, testContent, 'utf-8');
83
+ return this.success({
84
+ message: `Generated ${testSections.length} test suite(s)`,
85
+ dest,
86
+ analysis: {
87
+ components: analysis.components.map(c => c.name),
88
+ hooks: analysis.hooks.map(h => h.name),
89
+ functions: analysis.functions.map(f => f.name),
90
+ classes: analysis.classes.map(c => c.name),
91
+ },
92
+ });
93
+ }
94
+ catch (error) {
95
+ return this.error(error);
96
+ }
97
+ });
98
+ this.addTool('generate_tests_for_directory', 'Scan a directory and generate test files for all source files that do not already have tests.', {
99
+ type: 'object',
100
+ properties: {
101
+ path: { type: 'string', description: 'Directory to scan' },
102
+ overwrite: { type: 'boolean', description: 'Overwrite existing test files (default: false)' },
103
+ },
104
+ required: ['path'],
105
+ }, async (args) => {
106
+ const { path: dirPath, overwrite = false } = (args ?? {});
107
+ try {
108
+ const resolved = path.resolve(dirPath);
109
+ const files = scanDirectory(resolved);
110
+ const generated = [];
111
+ const skipped = [];
112
+ for (const file of files) {
113
+ const content = fs.readFileSync(file, 'utf-8');
114
+ const analysis = analyzeSource(content);
115
+ const hasSymbols = analysis.components.length + analysis.hooks.length + analysis.functions.length + analysis.classes.length > 0;
116
+ if (!hasSymbols)
117
+ continue;
118
+ const dest = testFilePath(file);
119
+ if (fs.existsSync(dest) && !overwrite) {
120
+ skipped.push(path.relative(resolved, file));
121
+ continue;
122
+ }
123
+ const sections = [];
124
+ for (const c of analysis.components)
125
+ sections.push(generateComponentTests(c));
126
+ for (const h of analysis.hooks)
127
+ sections.push(generateHookTests(h));
128
+ for (const f of analysis.functions)
129
+ sections.push(generateFunctionTests(f));
130
+ for (const cl of analysis.classes)
131
+ sections.push(generateClassTests(cl));
132
+ fs.writeFileSync(dest, sections.join('\n\n'), 'utf-8');
133
+ generated.push(path.relative(resolved, dest));
134
+ }
135
+ return this.success({
136
+ summary: `Generated ${generated.length} test files, skipped ${skipped.length} (already exist)`,
137
+ generated,
138
+ skipped,
139
+ });
140
+ }
141
+ catch (error) {
142
+ return this.error(error);
143
+ }
144
+ });
145
+ }
146
+ }
147
+ new GenerateTestsServer().run().catch(console.error);
148
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@mcp-showcase/generate-tests",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./build/index.js",
6
+ "scripts": {
7
+ "build": "tsc && chmod +x build/index.js",
8
+ "dev": "tsc --watch",
9
+ "test": "vitest run"
10
+ },
11
+ "dependencies": {
12
+ "@mcp-showcase/shared": "*",
13
+ "@modelcontextprotocol/sdk": "^1.12.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.0.0",
17
+ "typescript": "^5.0.0",
18
+ "vitest": "^2.0.0"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+ import { McpServerBase } from '@mcp-showcase/shared';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ import { countKeys, getMaxDepth, escapeHtml, generateId } from './utils.js';
7
+ // ============================================================================
8
+ // HELPERS
9
+ // ============================================================================
10
+ const RESPONSES_DIR = path.join(os.homedir(), '.mcp-responses');
11
+ function ensureDir() {
12
+ if (!fs.existsSync(RESPONSES_DIR)) {
13
+ fs.mkdirSync(RESPONSES_DIR, { recursive: true });
14
+ }
15
+ }
16
+ // ============================================================================
17
+ // HTML TEMPLATE
18
+ // ============================================================================
19
+ function generateHtml(label, parsed, timestamp) {
20
+ const compactJson = JSON.stringify(parsed).replace(/<\/script>/gi, '<\\/script>');
21
+ return `<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
26
+ <title>JSON Viewer — ${escapeHtml(label)}</title>
27
+ <style>
28
+ :root {
29
+ --bg: #1e1e2e; --surface: #282840; --surface2: #313150; --border: #44447a;
30
+ --text: #cdd6f4; --text-dim: #8888b0; --key: #89b4fa; --string: #a6e3a1;
31
+ --number: #fab387; --boolean: #cba6f7; --null: #f38ba8; --bracket: #8888b0;
32
+ --search-bg: #f9e2af33;
33
+ }
34
+ [data-theme="light"] {
35
+ --bg: #eff1f5; --surface: #e6e9ef; --surface2: #dce0e8; --border: #bcc0cc;
36
+ --text: #4c4f69; --text-dim: #8c8fa1; --key: #1e66f5; --string: #40a02b;
37
+ --number: #fe640b; --boolean: #8839ef; --null: #d20f39; --bracket: #8c8fa1;
38
+ }
39
+ * { margin: 0; padding: 0; box-sizing: border-box; }
40
+ body { font-family: 'JetBrains Mono', 'Fira Code', monospace; background: var(--bg); color: var(--text); min-height: 100vh; }
41
+ .header { position: sticky; top: 0; z-index: 100; background: var(--surface); border-bottom: 1px solid var(--border); padding: 12px 20px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
42
+ .header h1 { font-size: 14px; font-weight: 600; color: var(--key); }
43
+ .header .meta { font-size: 11px; color: var(--text-dim); }
44
+ .search-box { flex: 1; min-width: 200px; }
45
+ .search-box input { width: 100%; padding: 6px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: inherit; font-size: 12px; outline: none; }
46
+ .search-box input:focus { border-color: var(--key); }
47
+ .btn { padding: 4px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: inherit; font-size: 11px; cursor: pointer; }
48
+ .btn:hover { background: var(--border); }
49
+ .container { padding: 20px; }
50
+ .json-tree { font-size: 13px; line-height: 1.6; }
51
+ .json-line { display: flex; align-items: flex-start; }
52
+ .line-number { color: var(--text-dim); min-width: 40px; text-align: right; padding-right: 12px; user-select: none; font-size: 11px; }
53
+ .json-content { flex: 1; white-space: pre; }
54
+ .json-key { color: var(--key); }
55
+ .json-string { color: var(--string); }
56
+ .json-number { color: var(--number); }
57
+ .json-boolean { color: var(--boolean); }
58
+ .json-null { color: var(--null); }
59
+ .json-bracket { color: var(--bracket); }
60
+ .json-colon, .json-comma { color: var(--text-dim); }
61
+ .collapsible { cursor: pointer; user-select: none; }
62
+ .collapsible::before { content: '▼'; display: inline-block; margin-right: 4px; font-size: 8px; transition: transform 0.15s; color: var(--text-dim); }
63
+ .collapsed::before { transform: rotate(-90deg); }
64
+ .collapsed + .json-summary + .json-children { display: none; }
65
+ .json-children { padding-left: 20px; }
66
+ .json-summary { color: var(--text-dim); font-style: italic; font-size: 11px; }
67
+ .highlight { background: var(--search-bg); border-radius: 2px; }
68
+ .hidden { display: none !important; }
69
+ .copy-toast { position: fixed; bottom: 20px; right: 20px; background: var(--string); color: var(--bg); padding: 8px 16px; border-radius: 6px; font-size: 12px; opacity: 0; transition: opacity 0.3s; z-index: 1000; }
70
+ .copy-toast.show { opacity: 1; }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div class="header">
75
+ <h1>${escapeHtml(label)}</h1>
76
+ <span class="meta">${timestamp}</span>
77
+ <div class="search-box"><input type="text" id="search" placeholder="Search keys or values..." /></div>
78
+ <button class="btn" onclick="toggleTheme()">Theme</button>
79
+ <button class="btn" onclick="copyAll()">Copy All</button>
80
+ <button class="btn" onclick="expandAll()">Expand All</button>
81
+ <button class="btn" onclick="collapseAll()">Collapse All</button>
82
+ </div>
83
+ <div class="container"><div id="treeView" class="json-tree"></div></div>
84
+ <div class="copy-toast" id="copyToast">Copied!</div>
85
+ <script>
86
+ const RAW_JSON = ${compactJson};
87
+ let lineNum = 0;
88
+
89
+ function escapeHtml(s) {
90
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
91
+ }
92
+ function toggleTheme() {
93
+ const b = document.body;
94
+ b.getAttribute('data-theme') === 'light' ? b.removeAttribute('data-theme') : b.setAttribute('data-theme', 'light');
95
+ }
96
+ function showCopyToast() {
97
+ const t = document.getElementById('copyToast');
98
+ t.classList.add('show');
99
+ setTimeout(() => t.classList.remove('show'), 1500);
100
+ }
101
+ function copyAll() { navigator.clipboard.writeText(JSON.stringify(RAW_JSON, null, 2)); showCopyToast(); }
102
+ function expandAll() { document.querySelectorAll('.collapsed').forEach(el => el.classList.remove('collapsed')); }
103
+ function collapseAll() { document.querySelectorAll('.collapsible').forEach(el => el.classList.add('collapsed')); }
104
+
105
+ function renderValue(val, path, depth) {
106
+ if (val === null) return '<span class="json-null">null</span>';
107
+ if (typeof val === 'boolean') return '<span class="json-boolean">' + val + '</span>';
108
+ if (typeof val === 'number') return '<span class="json-number">' + val + '</span>';
109
+ if (typeof val === 'string') {
110
+ const escaped = escapeHtml(val);
111
+ const display = val.length > 200 ? escaped.slice(0, 200) + '...' : escaped;
112
+ return '<span class="json-string">"' + display + '"</span>';
113
+ }
114
+ if (Array.isArray(val)) {
115
+ if (val.length === 0) return '<span class="json-bracket">[]</span>';
116
+ const items = val.map((item, i) => {
117
+ const ln = ++lineNum;
118
+ return '<div class="json-line"><span class="line-number">' + ln + '</span><span class="json-content">' +
119
+ renderValue(item, path + '[' + i + ']', depth + 1) + (i < val.length - 1 ? '<span class="json-comma">,</span>' : '') + '</span></div>';
120
+ }).join('');
121
+ return '<span class="collapsible" onclick="this.classList.toggle(\'collapsed\')"><span class="json-bracket">[</span></span>' +
122
+ '<span class="json-summary"> ' + val.length + ' items</span>' +
123
+ '<div class="json-children">' + items + '</div><span class="json-bracket">]</span>';
124
+ }
125
+ if (typeof val === 'object') {
126
+ const keys = Object.keys(val);
127
+ if (keys.length === 0) return '<span class="json-bracket">{}</span>';
128
+ const items = keys.map((key, i) => {
129
+ const ln = ++lineNum;
130
+ return '<div class="json-line"><span class="line-number">' + ln + '</span><span class="json-content">' +
131
+ '<span class="json-key" data-key="' + escapeHtml(key) + '">"' + escapeHtml(key) + '"</span>' +
132
+ '<span class="json-colon">: </span>' +
133
+ renderValue(val[key], path + '.' + key, depth + 1) + (i < keys.length - 1 ? '<span class="json-comma">,</span>' : '') + '</span></div>';
134
+ }).join('');
135
+ return '<span class="collapsible" onclick="this.classList.toggle(\'collapsed\')"><span class="json-bracket">{</span></span>' +
136
+ '<span class="json-summary"> ' + keys.length + ' keys</span>' +
137
+ '<div class="json-children">' + items + '</div><span class="json-bracket">}</span>';
138
+ }
139
+ return '<span class="json-null">' + escapeHtml(String(val)) + '</span>';
140
+ }
141
+
142
+ let searchTimeout;
143
+ document.getElementById('search').addEventListener('input', function() {
144
+ clearTimeout(searchTimeout);
145
+ searchTimeout = setTimeout(() => {
146
+ document.querySelectorAll('.highlight').forEach(el => el.classList.remove('highlight'));
147
+ const q = this.value.toLowerCase();
148
+ if (!q) return;
149
+ document.querySelectorAll('.json-key,.json-string').forEach(el => {
150
+ if (el.textContent.toLowerCase().includes(q)) el.classList.add('highlight');
151
+ });
152
+ }, 150);
153
+ });
154
+
155
+ const tree = document.getElementById('treeView');
156
+ lineNum = 1;
157
+ tree.innerHTML = '<div class="json-line"><span class="line-number">1</span><span class="json-content">' + renderValue(RAW_JSON, 'root', 0) + '</span></div>';
158
+ </script>
159
+ </body>
160
+ </html>`;
161
+ }
162
+ // ============================================================================
163
+ // SERVER
164
+ // ============================================================================
165
+ class JsonViewerServer extends McpServerBase {
166
+ constructor() {
167
+ super({ name: 'json-viewer', version: '1.0.0' });
168
+ }
169
+ registerTools() {
170
+ this.addTool('view_json', 'Save JSON data and generate an interactive HTML viewer. Opens in browser for easy visualization of complex JSON responses.', {
171
+ type: 'object',
172
+ properties: {
173
+ data: { type: 'string', description: 'JSON string to visualize' },
174
+ label: { type: 'string', description: 'Label for this response (used in filename and title)' },
175
+ open: { type: 'boolean', description: 'Automatically open in browser (default: true)', default: true },
176
+ },
177
+ required: ['data'],
178
+ }, this.handleViewJson.bind(this));
179
+ this.addTool('list_responses', 'List all saved JSON responses with timestamps and metadata.', {
180
+ type: 'object',
181
+ properties: {
182
+ limit: { type: 'number', description: 'Max number of responses to return (default: 20)', default: 20 },
183
+ },
184
+ }, this.handleListResponses.bind(this));
185
+ this.addTool('view_response', 'Re-open a previously saved JSON response by its ID.', {
186
+ type: 'object',
187
+ properties: {
188
+ id: { type: 'string', description: 'Response ID (from list_responses)' },
189
+ },
190
+ required: ['id'],
191
+ }, this.handleViewResponse.bind(this));
192
+ }
193
+ async handleViewJson(args) {
194
+ const { data, label = 'response', open = true } = args;
195
+ try {
196
+ ensureDir();
197
+ let parsed;
198
+ let jsonStr;
199
+ try {
200
+ parsed = typeof data === 'string' ? JSON.parse(data) : data;
201
+ jsonStr = JSON.stringify(parsed, null, 2);
202
+ }
203
+ catch {
204
+ throw new Error('Invalid JSON data provided');
205
+ }
206
+ const id = generateId(label);
207
+ const timestamp = new Date().toISOString();
208
+ const jsonPath = path.join(RESPONSES_DIR, `${id}.json`);
209
+ const htmlPath = path.join(RESPONSES_DIR, `${id}.html`);
210
+ fs.writeFileSync(jsonPath, jsonStr, 'utf-8');
211
+ const html = generateHtml(label, parsed, timestamp);
212
+ fs.writeFileSync(htmlPath, html, 'utf-8');
213
+ const meta = {
214
+ id, label, timestamp, jsonPath, htmlPath,
215
+ sizeBytes: Buffer.byteLength(jsonStr, 'utf-8'),
216
+ keyCount: countKeys(parsed),
217
+ maxDepth: getMaxDepth(parsed),
218
+ };
219
+ fs.writeFileSync(path.join(RESPONSES_DIR, `${id}.meta.json`), JSON.stringify(meta, null, 2), 'utf-8');
220
+ if (open) {
221
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
222
+ const { execSync } = await import('child_process');
223
+ try {
224
+ execSync(`${cmd} "${htmlPath}"`, { stdio: 'ignore' });
225
+ }
226
+ catch { /* silent */ }
227
+ }
228
+ return this.success({
229
+ id, jsonPath, htmlPath,
230
+ message: `JSON saved and viewer generated. Open: ${htmlPath}`,
231
+ stats: { sizeBytes: meta.sizeBytes, keyCount: meta.keyCount, maxDepth: meta.maxDepth },
232
+ });
233
+ }
234
+ catch (error) {
235
+ return this.error(error);
236
+ }
237
+ }
238
+ async handleListResponses(args) {
239
+ const { limit = 20 } = (args || {});
240
+ try {
241
+ ensureDir();
242
+ const files = fs.readdirSync(RESPONSES_DIR)
243
+ .filter(f => f.endsWith('.meta.json'))
244
+ .sort().reverse().slice(0, limit);
245
+ const responses = [];
246
+ for (const file of files) {
247
+ try {
248
+ responses.push(JSON.parse(fs.readFileSync(path.join(RESPONSES_DIR, file), 'utf-8')));
249
+ }
250
+ catch { /* skip corrupted */ }
251
+ }
252
+ return this.success({ total: responses.length, responses });
253
+ }
254
+ catch (error) {
255
+ return this.error(error);
256
+ }
257
+ }
258
+ async handleViewResponse(args) {
259
+ const { id } = args;
260
+ try {
261
+ ensureDir();
262
+ const metaPath = path.join(RESPONSES_DIR, `${id}.meta.json`);
263
+ if (!fs.existsSync(metaPath))
264
+ throw new Error(`Response not found: ${id}`);
265
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
266
+ if (!fs.existsSync(meta.htmlPath))
267
+ throw new Error(`HTML viewer not found for: ${id}`);
268
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
269
+ const { execSync } = await import('child_process');
270
+ try {
271
+ execSync(`${cmd} "${meta.htmlPath}"`, { stdio: 'ignore' });
272
+ }
273
+ catch { /* silent */ }
274
+ return this.success({ id: meta.id, label: meta.label, htmlPath: meta.htmlPath, message: `Opened viewer for: ${meta.label}` });
275
+ }
276
+ catch (error) {
277
+ return this.error(error);
278
+ }
279
+ }
280
+ }
281
+ new JsonViewerServer().run().catch(console.error);
282
+ //# sourceMappingURL=index.js.map