@hobui/viui-cli 0.0.6 → 0.0.7

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 (240) hide show
  1. package/README.md +138 -139
  2. package/dist/adapters/adapter-registry.d.ts +12 -0
  3. package/dist/adapters/adapter-registry.d.ts.map +1 -0
  4. package/dist/adapters/adapter-registry.js +49 -0
  5. package/dist/adapters/adapter-types.d.ts +20 -0
  6. package/dist/adapters/adapter-types.d.ts.map +1 -0
  7. package/dist/adapters/adapter-types.js +1 -0
  8. package/dist/adapters/aider-adapter.d.ts +3 -0
  9. package/dist/adapters/aider-adapter.d.ts.map +1 -0
  10. package/dist/adapters/aider-adapter.js +8 -0
  11. package/dist/adapters/claude-adapter.d.ts +3 -0
  12. package/dist/adapters/claude-adapter.d.ts.map +1 -0
  13. package/dist/adapters/claude-adapter.js +15 -0
  14. package/dist/adapters/cline-adapter.d.ts +3 -0
  15. package/dist/adapters/cline-adapter.d.ts.map +1 -0
  16. package/dist/adapters/cline-adapter.js +8 -0
  17. package/dist/adapters/copilot-adapter.d.ts +5 -0
  18. package/dist/adapters/copilot-adapter.d.ts.map +1 -0
  19. package/dist/adapters/copilot-adapter.js +20 -0
  20. package/dist/adapters/cursor-adapter.d.ts +3 -0
  21. package/dist/adapters/cursor-adapter.d.ts.map +1 -0
  22. package/dist/adapters/cursor-adapter.js +18 -0
  23. package/dist/adapters/external/bolt-adapter.d.ts +3 -0
  24. package/dist/adapters/external/bolt-adapter.d.ts.map +1 -0
  25. package/dist/adapters/external/bolt-adapter.js +15 -0
  26. package/dist/adapters/external/chatgpt-adapter.d.ts +3 -0
  27. package/dist/adapters/external/chatgpt-adapter.d.ts.map +1 -0
  28. package/dist/adapters/external/chatgpt-adapter.js +14 -0
  29. package/dist/adapters/external/external-adapter-base.d.ts +15 -0
  30. package/dist/adapters/external/external-adapter-base.d.ts.map +1 -0
  31. package/dist/adapters/external/external-adapter-base.js +92 -0
  32. package/dist/adapters/external/gemini-adapter.d.ts +3 -0
  33. package/dist/adapters/external/gemini-adapter.d.ts.map +1 -0
  34. package/dist/adapters/external/gemini-adapter.js +14 -0
  35. package/dist/adapters/external/lovable-adapter.d.ts +3 -0
  36. package/dist/adapters/external/lovable-adapter.d.ts.map +1 -0
  37. package/dist/adapters/external/lovable-adapter.js +14 -0
  38. package/dist/adapters/external/v0-adapter.d.ts +3 -0
  39. package/dist/adapters/external/v0-adapter.d.ts.map +1 -0
  40. package/dist/adapters/external/v0-adapter.js +15 -0
  41. package/dist/adapters/windsurf-adapter.d.ts +3 -0
  42. package/dist/adapters/windsurf-adapter.d.ts.map +1 -0
  43. package/dist/adapters/windsurf-adapter.js +23 -0
  44. package/dist/assets/plugins/viui-conf/apply-theme-body.ts +23 -4
  45. package/dist/assets/plugins/viui-conf/defaults/README.md +2 -0
  46. package/dist/assets/plugins/viui-conf/defaults/app-bar.ts +1 -1
  47. package/dist/assets/plugins/viui-conf/defaults/buttons.ts +1 -1
  48. package/dist/assets/plugins/viui-conf/defaults/by-theme/minimalist-2.ts +1 -1
  49. package/dist/assets/plugins/viui-conf/defaults/cards.ts +1 -1
  50. package/dist/assets/plugins/viui-conf/defaults/expansion-panels.ts +16 -0
  51. package/dist/assets/plugins/viui-conf/defaults/index.ts +3 -0
  52. package/dist/assets/plugins/viui-conf/defaults/inputs.ts +11 -1
  53. package/dist/assets/plugins/viui-conf/design-tokens.ts +135 -0
  54. package/dist/assets/plugins/viui-conf/theme-base.ts +1 -1
  55. package/dist/assets/plugins/viui-conf/v-dark.ts +3 -5
  56. package/dist/assets/plugins/viui-conf/v-light.ts +3 -5
  57. package/dist/assets/plugins/vuetify.ts +36 -0
  58. package/dist/assets/prompt-data/components.json +106 -0
  59. package/dist/assets/prompt-data/tokens.json +83 -0
  60. package/dist/assets/themes/_bento-grid.scss +8 -0
  61. package/dist/assets/themes/_glassmorphism.scss +8 -0
  62. package/dist/assets/themes/_material.scss +8 -0
  63. package/dist/assets/themes/_minimalist-2.scss +375 -0
  64. package/dist/assets/themes/_minimalist.scss +9 -0
  65. package/dist/assets/themes/_neo-brutalism.scss +199 -0
  66. package/dist/assets/themes/bento-grid.scss +4 -0
  67. package/dist/assets/themes/glassmorphism.scss +4 -0
  68. package/dist/assets/themes/index.scss +11 -0
  69. package/dist/assets/themes/material.scss +4 -0
  70. package/dist/assets/themes/minimalist-2.scss +5 -0
  71. package/dist/assets/themes/minimalist.scss +4 -0
  72. package/dist/assets/themes/neo-brutalism.scss +5 -0
  73. package/dist/assets/viui-themes/_neo-brutalism.scss +70 -152
  74. package/dist/cli-paths.d.ts +7 -0
  75. package/dist/cli-paths.d.ts.map +1 -0
  76. package/dist/cli-paths.js +19 -0
  77. package/dist/cli.js +28 -578
  78. package/dist/cli.legacy.d.ts +3 -0
  79. package/dist/cli.legacy.d.ts.map +1 -0
  80. package/dist/cli.legacy.js +597 -0
  81. package/dist/commands/audit.d.ts +3 -0
  82. package/dist/commands/audit.d.ts.map +1 -0
  83. package/dist/commands/audit.js +152 -0
  84. package/dist/commands/config/config-export.d.ts +6 -0
  85. package/dist/commands/config/config-export.d.ts.map +1 -0
  86. package/dist/commands/config/config-export.js +23 -0
  87. package/dist/commands/config/config-health.d.ts +6 -0
  88. package/dist/commands/config/config-health.d.ts.map +1 -0
  89. package/dist/commands/config/config-health.js +42 -0
  90. package/dist/commands/config/config-import.d.ts +6 -0
  91. package/dist/commands/config/config-import.d.ts.map +1 -0
  92. package/dist/commands/config/config-import.js +63 -0
  93. package/dist/commands/config/config-rollback.d.ts +6 -0
  94. package/dist/commands/config/config-rollback.d.ts.map +1 -0
  95. package/dist/commands/config/config-rollback.js +47 -0
  96. package/dist/commands/config/config-setup.d.ts +6 -0
  97. package/dist/commands/config/config-setup.d.ts.map +1 -0
  98. package/dist/commands/config/config-setup.js +103 -0
  99. package/dist/commands/config/config-status.d.ts +6 -0
  100. package/dist/commands/config/config-status.d.ts.map +1 -0
  101. package/dist/commands/config/config-status.js +42 -0
  102. package/dist/commands/config/config-uninstall.d.ts +6 -0
  103. package/dist/commands/config/config-uninstall.d.ts.map +1 -0
  104. package/dist/commands/config/config-uninstall.js +74 -0
  105. package/dist/commands/config.d.ts +6 -0
  106. package/dist/commands/config.d.ts.map +1 -0
  107. package/dist/commands/config.js +19 -0
  108. package/dist/commands/docs.d.ts +3 -0
  109. package/dist/commands/docs.d.ts.map +1 -0
  110. package/dist/commands/docs.js +17 -0
  111. package/dist/commands/doctor.d.ts +3 -0
  112. package/dist/commands/doctor.d.ts.map +1 -0
  113. package/dist/commands/doctor.js +93 -0
  114. package/dist/commands/init.d.ts +3 -0
  115. package/dist/commands/init.d.ts.map +1 -0
  116. package/dist/commands/init.js +183 -0
  117. package/dist/commands/sync.d.ts +3 -0
  118. package/dist/commands/sync.d.ts.map +1 -0
  119. package/dist/commands/sync.js +73 -0
  120. package/dist/commands/theme.d.ts +3 -0
  121. package/dist/commands/theme.d.ts.map +1 -0
  122. package/dist/commands/theme.js +86 -0
  123. package/dist/commands/update.d.ts +3 -0
  124. package/dist/commands/update.d.ts.map +1 -0
  125. package/dist/commands/update.js +97 -0
  126. package/dist/prompts/prompt-builder.d.ts +4 -0
  127. package/dist/prompts/prompt-builder.d.ts.map +1 -0
  128. package/dist/prompts/prompt-builder.js +18 -0
  129. package/dist/prompts/prompt-data-loader.d.ts +11 -0
  130. package/dist/prompts/prompt-data-loader.d.ts.map +1 -0
  131. package/dist/prompts/prompt-data-loader.js +15 -0
  132. package/dist/prompts/prompt-sections/section-code-examples.d.ts +2 -0
  133. package/dist/prompts/prompt-sections/section-code-examples.d.ts.map +1 -0
  134. package/dist/prompts/prompt-sections/section-code-examples.js +36 -0
  135. package/dist/prompts/prompt-sections/section-color-tokens.d.ts +2 -0
  136. package/dist/prompts/prompt-sections/section-color-tokens.d.ts.map +1 -0
  137. package/dist/prompts/prompt-sections/section-color-tokens.js +19 -0
  138. package/dist/prompts/prompt-sections/section-component-map.d.ts +3 -0
  139. package/dist/prompts/prompt-sections/section-component-map.d.ts.map +1 -0
  140. package/dist/prompts/prompt-sections/section-component-map.js +12 -0
  141. package/dist/prompts/prompt-sections/section-typography-spacing.d.ts +2 -0
  142. package/dist/prompts/prompt-sections/section-typography-spacing.d.ts.map +1 -0
  143. package/dist/prompts/prompt-sections/section-typography-spacing.js +29 -0
  144. package/dist/services/backup-service.d.ts +7 -0
  145. package/dist/services/backup-service.d.ts.map +1 -0
  146. package/dist/services/backup-service.js +54 -0
  147. package/dist/services/config-service.d.ts +17 -0
  148. package/dist/services/config-service.d.ts.map +1 -0
  149. package/dist/services/config-service.js +64 -0
  150. package/dist/services/diff-engine.d.ts +13 -0
  151. package/dist/services/diff-engine.d.ts.map +1 -0
  152. package/dist/services/diff-engine.js +59 -0
  153. package/dist/services/ide-detector.d.ts +9 -0
  154. package/dist/services/ide-detector.d.ts.map +1 -0
  155. package/dist/services/ide-detector.js +113 -0
  156. package/dist/services/ide-detector.spec.d.ts +2 -0
  157. package/dist/services/ide-detector.spec.d.ts.map +1 -0
  158. package/dist/services/ide-detector.spec.js +108 -0
  159. package/dist/services/lock-file-service.d.ts +15 -0
  160. package/dist/services/lock-file-service.d.ts.map +1 -0
  161. package/dist/services/lock-file-service.js +74 -0
  162. package/dist/services/mcp-config-reader.d.ts +11 -0
  163. package/dist/services/mcp-config-reader.d.ts.map +1 -0
  164. package/dist/services/mcp-config-reader.js +40 -0
  165. package/dist/services/mcp-config-reader.spec.d.ts +2 -0
  166. package/dist/services/mcp-config-reader.spec.d.ts.map +1 -0
  167. package/dist/services/mcp-config-reader.spec.js +125 -0
  168. package/dist/services/mcp-config-writer.d.ts +11 -0
  169. package/dist/services/mcp-config-writer.d.ts.map +1 -0
  170. package/dist/services/mcp-config-writer.js +98 -0
  171. package/dist/services/mcp-config-writer.spec.d.ts +2 -0
  172. package/dist/services/mcp-config-writer.spec.d.ts.map +1 -0
  173. package/dist/services/mcp-config-writer.spec.js +162 -0
  174. package/dist/services/merge-engine.d.ts +12 -0
  175. package/dist/services/merge-engine.d.ts.map +1 -0
  176. package/dist/services/merge-engine.js +54 -0
  177. package/dist/services/vuetify-scaffold-service.d.ts +5 -0
  178. package/dist/services/vuetify-scaffold-service.d.ts.map +1 -0
  179. package/dist/services/vuetify-scaffold-service.js +67 -0
  180. package/dist/templates/vuetify-plugin.d.ts +90 -0
  181. package/dist/templates/vuetify-plugin.d.ts.map +1 -0
  182. package/dist/templates/vuetify-plugin.js +33 -0
  183. package/dist/types/command-types.d.ts +15 -0
  184. package/dist/types/command-types.d.ts.map +1 -0
  185. package/dist/types/command-types.js +2 -0
  186. package/dist/types/config-types.d.ts +29 -0
  187. package/dist/types/config-types.d.ts.map +1 -0
  188. package/dist/types/config-types.js +10 -0
  189. package/dist/types/ide-types.d.ts +29 -0
  190. package/dist/types/ide-types.d.ts.map +1 -0
  191. package/dist/types/ide-types.js +4 -0
  192. package/dist/types/lock-file-types.d.ts +27 -0
  193. package/dist/types/lock-file-types.d.ts.map +1 -0
  194. package/dist/types/lock-file-types.js +2 -0
  195. package/dist/utils/diff-display.d.ts +18 -0
  196. package/dist/utils/diff-display.d.ts.map +1 -0
  197. package/dist/utils/diff-display.js +61 -0
  198. package/dist/utils/fs-safe.d.ts +9 -0
  199. package/dist/utils/fs-safe.d.ts.map +1 -0
  200. package/dist/utils/fs-safe.js +44 -0
  201. package/dist/utils/logger.d.ts +14 -0
  202. package/dist/utils/logger.d.ts.map +1 -0
  203. package/dist/utils/logger.js +28 -0
  204. package/dist/utils/open-browser.d.ts +3 -0
  205. package/dist/utils/open-browser.d.ts.map +1 -0
  206. package/dist/utils/open-browser.js +13 -0
  207. package/package.json +11 -6
  208. package/dist/assets/cursor/.design-system-version +0 -1
  209. package/dist/assets/cursor/commands/audit-accessibility.md +0 -25
  210. package/dist/assets/cursor/commands/audit-ui.md +0 -35
  211. package/dist/assets/cursor/commands/component.md +0 -18
  212. package/dist/assets/cursor/commands/fix-storybook.md +0 -24
  213. package/dist/assets/cursor/commands/generate-component-from-figma.md +0 -26
  214. package/dist/assets/cursor/commands/generate-page-from-figma.md +0 -26
  215. package/dist/assets/cursor/plans/DESIGN_SYSTEM_PLAN.md +0 -177
  216. package/dist/assets/cursor/plans/PLANS_INDEX.md +0 -35
  217. package/dist/assets/cursor/rules/accessibility-contrast.mdc +0 -38
  218. package/dist/assets/cursor/rules/bem-class-style.mdc +0 -107
  219. package/dist/assets/cursor/rules/component-naming.mdc +0 -57
  220. package/dist/assets/cursor/rules/design-system-component-library.mdc +0 -59
  221. package/dist/assets/cursor/rules/design-system-workflow.mdc +0 -48
  222. package/dist/assets/cursor/rules/figma-mapping.mdc +0 -37
  223. package/dist/assets/cursor/rules/icons.mdc +0 -42
  224. package/dist/assets/cursor/rules/project-structure.mdc +0 -137
  225. package/dist/assets/cursor/rules/storybook-component-template.mdc +0 -103
  226. package/dist/assets/cursor/rules/storybook.mdc +0 -68
  227. package/dist/assets/cursor/rules/tokens.mdc +0 -32
  228. package/dist/assets/cursor/rules/viui-themes.mdc +0 -53
  229. package/dist/assets/cursor/rules/vuetify-layout.mdc +0 -52
  230. package/dist/assets/cursor/skills/accessibility.md +0 -75
  231. package/dist/assets/cursor/skills/design-system-thinking.md +0 -40
  232. package/dist/assets/cursor/skills/figma-interpretation.md +0 -38
  233. package/dist/assets/cursor/skills/vue-vuetify-design-system-architect.md +0 -60
  234. package/dist/assets/cursor/sync-manifest.json +0 -6
  235. package/dist/assets/viui-themes/bento-grid-global.scss +0 -5
  236. package/dist/assets/viui-themes/glassmorphism-global.scss +0 -5
  237. package/dist/assets/viui-themes/material-global.scss +0 -5
  238. package/dist/assets/viui-themes/minimalist-2-global.scss +0 -5
  239. package/dist/assets/viui-themes/minimalist-global.scss +0 -5
  240. package/dist/assets/viui-themes/neo-brutalism-global.scss +0 -5
@@ -0,0 +1,11 @@
1
+ import type { PromptConfig } from '../types/config-types.js';
2
+ export interface ComponentMapping {
3
+ vi: string;
4
+ vuetify: string;
5
+ }
6
+ export interface PromptData {
7
+ tokens: Record<string, unknown>;
8
+ components: ComponentMapping[];
9
+ }
10
+ export declare function loadPromptData(promptConfig?: PromptConfig): PromptData;
11
+ //# sourceMappingURL=prompt-data-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-data-loader.d.ts","sourceRoot":"","sources":["../../src/prompts/prompt-data-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AAED,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,UAAU,CAetE"}
@@ -0,0 +1,15 @@
1
+ import path from 'node:path';
2
+ import { readJsonSafe } from '../utils/fs-safe.js';
3
+ import { getAssetsDir } from '../cli-paths.js';
4
+ export function loadPromptData(promptConfig) {
5
+ const assetsDir = getAssetsDir();
6
+ const tokens = readJsonSafe(path.join(assetsDir, 'prompt-data', 'tokens.json')) ?? {};
7
+ let components = readJsonSafe(path.join(assetsDir, 'prompt-data', 'components.json')) ?? [];
8
+ // Merge consumer overrides
9
+ if (promptConfig?.componentMap) {
10
+ const overrides = Object.entries(promptConfig.componentMap)
11
+ .map(([vuetify, vi]) => ({ vi, vuetify }));
12
+ components = [...components, ...overrides];
13
+ }
14
+ return { tokens, components };
15
+ }
@@ -0,0 +1,2 @@
1
+ export declare function buildCodeExamplesSection(): string;
2
+ //# sourceMappingURL=section-code-examples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-code-examples.d.ts","sourceRoot":"","sources":["../../../src/prompts/prompt-sections/section-code-examples.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,IAAI,MAAM,CAmCjD"}
@@ -0,0 +1,36 @@
1
+ export function buildCodeExamplesSection() {
2
+ return `## Code Examples
3
+
4
+ ### WRONG — Do NOT do this (when Vi* wrapper exists):
5
+ \`\`\`vue
6
+ <template>
7
+ <v-btn color="#1565C0" class="px-4">Click</v-btn>
8
+ <v-text-field variant="outlined" />
9
+ </template>
10
+ \`\`\`
11
+
12
+ ### CORRECT — Do this instead:
13
+ \`\`\`vue
14
+ <template>
15
+ <ViButton color="primary" class="px-md">Click</ViButton>
16
+ <ViInput variant="outlined" />
17
+ <!-- v-icon has no Vi* wrapper — use directly -->
18
+ <v-icon icon="mdi-check" />
19
+ </template>
20
+ <style scoped>
21
+ .px-md { padding-inline: var(--inet-space-md); }
22
+ </style>
23
+ \`\`\`
24
+
25
+ ## Icons
26
+
27
+ Use \`@tabler/icons-vue\` as primary icon set:
28
+ \`\`\`vue
29
+ <script setup>
30
+ import { IconHome, IconSettings } from '@tabler/icons-vue'
31
+ </script>
32
+ <template>
33
+ <ViButton prepend-icon><IconHome :size="20" /></ViButton>
34
+ </template>
35
+ \`\`\``;
36
+ }
@@ -0,0 +1,2 @@
1
+ export declare function buildColorTokensSection(tokens: Record<string, unknown>): string;
2
+ //# sourceMappingURL=section-color-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-color-tokens.d.ts","sourceRoot":"","sources":["../../../src/prompts/prompt-sections/section-color-tokens.ts"],"names":[],"mappings":"AAAA,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmB/E"}
@@ -0,0 +1,19 @@
1
+ export function buildColorTokensSection(tokens) {
2
+ const colors = tokens.colors;
3
+ if (!colors)
4
+ return '';
5
+ let table = '## Design Tokens — Colors\n\n'
6
+ + 'Use CSS variables, never hardcode color values.\n'
7
+ + 'For chart color arrays, prefer Vuetify theme colors (`rgb(var(--v-theme-error))`, '
8
+ + '`rgb(var(--v-theme-success))`) or token CSS vars over raw hex.\n\n'
9
+ + '| Token | CSS Variable | Default |\n'
10
+ + '|---|---|---|\n';
11
+ for (const [group, entries] of Object.entries(colors)) {
12
+ for (const [name, info] of Object.entries(entries)) {
13
+ if (!info?.css || !info?.value)
14
+ continue;
15
+ table += `| ${group}/${name} | \`var(${info.css})\` | \`${info.value}\` |\n`;
16
+ }
17
+ }
18
+ return table;
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { ComponentMapping } from '../prompt-data-loader.js';
2
+ export declare function buildComponentMapSection(components: ComponentMapping[]): string;
3
+ //# sourceMappingURL=section-component-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-component-map.d.ts","sourceRoot":"","sources":["../../../src/prompts/prompt-sections/section-component-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAa/E"}
@@ -0,0 +1,12 @@
1
+ export function buildComponentMapSection(components) {
2
+ const header = '## Component Mapping\n\n'
3
+ + 'For components listed below, **always** use the Vi* wrapper instead of raw Vuetify.\n'
4
+ + 'Vuetify components NOT in this table (e.g. `v-icon`, `v-progress-circular`, `v-progress-linear`, `v-tooltip`) '
5
+ + 'do not have Vi* wrappers yet — use them directly.\n\n'
6
+ + '| Vuetify (DO NOT use) | ViUI (USE THIS) |\n'
7
+ + '|---|---|\n';
8
+ const rows = components
9
+ .map(c => `| \`<${c.vuetify}>\` | \`<${c.vi}>\` |`)
10
+ .join('\n');
11
+ return header + rows;
12
+ }
@@ -0,0 +1,2 @@
1
+ export declare function buildTypographySpacingSection(tokens: Record<string, unknown>): string;
2
+ //# sourceMappingURL=section-typography-spacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-typography-spacing.d.ts","sourceRoot":"","sources":["../../../src/prompts/prompt-sections/section-typography-spacing.ts"],"names":[],"mappings":"AAAA,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiCrF"}
@@ -0,0 +1,29 @@
1
+ export function buildTypographySpacingSection(tokens) {
2
+ const spacing = tokens.spacing;
3
+ const shape = tokens.shape;
4
+ const typo = tokens.typography;
5
+ let md = '## Typography & Spacing\n\n';
6
+ if (typo) {
7
+ md += `**Font:** \`${typo.fontFamily}\`\n\n`;
8
+ md += '| Scale | Size |\n|---|---|\n';
9
+ for (const [name, size] of Object.entries(typo.scale)) {
10
+ md += `| ${name} | ${size} |\n`;
11
+ }
12
+ md += '\n';
13
+ }
14
+ if (spacing) {
15
+ md += `**Spacing system:** ${spacing.system}\n\n`;
16
+ md += '| Token | Value |\n|---|---|\n';
17
+ for (const [name, val] of Object.entries(spacing.values)) {
18
+ md += `| ${name} | ${val} |\n`;
19
+ }
20
+ md += '\n';
21
+ }
22
+ if (shape) {
23
+ md += '**Border radius:**\n\n| Token | Value |\n|---|---|\n';
24
+ for (const [name, val] of Object.entries(shape.borderRadius)) {
25
+ md += `| ${name} | ${val} |\n`;
26
+ }
27
+ }
28
+ return md;
29
+ }
@@ -0,0 +1,7 @@
1
+ /** Backup a file or directory to .viui-backup/ (keeps 1 version only) */
2
+ export declare function createBackup(cwd: string, relativePath: string): void;
3
+ /** Restore a file or directory from .viui-backup/ */
4
+ export declare function restoreBackup(cwd: string, relativePath: string): boolean;
5
+ /** Check if backup directory exists */
6
+ export declare function hasBackup(cwd: string): boolean;
7
+ //# sourceMappingURL=backup-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-service.d.ts","sourceRoot":"","sources":["../../src/services/backup-service.ts"],"names":[],"mappings":"AAYA,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAqBpE;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAqBxE;AAED,uCAAuC;AACvC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C"}
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { copyDirRecursive } from '../utils/fs-safe.js';
4
+ import { logger } from '../utils/logger.js';
5
+ const BACKUP_DIR = '.viui-backup';
6
+ /** Get backup directory path */
7
+ function getBackupDir(cwd) {
8
+ return path.join(cwd, BACKUP_DIR);
9
+ }
10
+ /** Backup a file or directory to .viui-backup/ (keeps 1 version only) */
11
+ export function createBackup(cwd, relativePath) {
12
+ const source = path.join(cwd, relativePath);
13
+ if (!fs.existsSync(source))
14
+ return;
15
+ const backupDir = getBackupDir(cwd);
16
+ const backupTarget = path.join(backupDir, relativePath);
17
+ // Remove previous backup of this path
18
+ if (fs.existsSync(backupTarget)) {
19
+ fs.rmSync(backupTarget, { recursive: true });
20
+ }
21
+ fs.mkdirSync(path.dirname(backupTarget), { recursive: true });
22
+ if (fs.statSync(source).isDirectory()) {
23
+ copyDirRecursive(source, backupTarget);
24
+ }
25
+ else {
26
+ fs.copyFileSync(source, backupTarget);
27
+ }
28
+ logger.dim(`Backed up ${relativePath}`);
29
+ }
30
+ /** Restore a file or directory from .viui-backup/ */
31
+ export function restoreBackup(cwd, relativePath) {
32
+ const backupDir = getBackupDir(cwd);
33
+ const backupSource = path.join(backupDir, relativePath);
34
+ const target = path.join(cwd, relativePath);
35
+ if (!fs.existsSync(backupSource)) {
36
+ logger.warn(`No backup found for ${relativePath}`);
37
+ return false;
38
+ }
39
+ fs.mkdirSync(path.dirname(target), { recursive: true });
40
+ if (fs.statSync(backupSource).isDirectory()) {
41
+ if (fs.existsSync(target))
42
+ fs.rmSync(target, { recursive: true });
43
+ copyDirRecursive(backupSource, target);
44
+ }
45
+ else {
46
+ fs.copyFileSync(backupSource, target);
47
+ }
48
+ logger.success(`Restored ${relativePath} from backup`);
49
+ return true;
50
+ }
51
+ /** Check if backup directory exists */
52
+ export function hasBackup(cwd) {
53
+ return fs.existsSync(getBackupDir(cwd));
54
+ }
@@ -0,0 +1,17 @@
1
+ import type { ViuiConfig } from '../types/config-types.js';
2
+ /** Default config values */
3
+ export declare const DEFAULT_CONFIG: ViuiConfig;
4
+ /** Find viui.config.ts in project root */
5
+ export declare function findConfigPath(cwd: string): string;
6
+ /** Check if viui.config.ts exists */
7
+ export declare function configExists(cwd: string): boolean;
8
+ /**
9
+ * Read viui.config.ts by extracting the default export object.
10
+ * Simple regex parse — avoids needing tsx runtime for config reading.
11
+ */
12
+ export declare function readConfig(cwd: string): ViuiConfig | null;
13
+ /** Generate viui.config.ts content from config object */
14
+ export declare function generateConfigContent(config: ViuiConfig): string;
15
+ /** Write viui.config.ts to project root */
16
+ export declare function writeConfig(cwd: string, config: ViuiConfig): void;
17
+ //# sourceMappingURL=config-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../../src/services/config-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAK1D,4BAA4B;AAC5B,eAAO,MAAM,cAAc,EAAE,UAM5B,CAAA;AAED,0CAA0C;AAC1C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,qCAAqC;AACrC,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAoBzD;AAED,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAWhE;AAED,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAIjE"}
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from '../utils/logger.js';
4
+ const CONFIG_FILENAME = 'viui.config.ts';
5
+ /** Default config values */
6
+ export const DEFAULT_CONFIG = {
7
+ theme: 'neo-brutalism',
8
+ tokens: '@hobui/tokens',
9
+ styleImport: 'scss',
10
+ outputDir: 'src/assets/styles/viui/',
11
+ vuetifyIntegration: true,
12
+ };
13
+ /** Find viui.config.ts in project root */
14
+ export function findConfigPath(cwd) {
15
+ return path.join(cwd, CONFIG_FILENAME);
16
+ }
17
+ /** Check if viui.config.ts exists */
18
+ export function configExists(cwd) {
19
+ return fs.existsSync(findConfigPath(cwd));
20
+ }
21
+ /**
22
+ * Read viui.config.ts by extracting the default export object.
23
+ * Simple regex parse — avoids needing tsx runtime for config reading.
24
+ */
25
+ export function readConfig(cwd) {
26
+ const configPath = findConfigPath(cwd);
27
+ if (!fs.existsSync(configPath))
28
+ return null;
29
+ try {
30
+ const raw = fs.readFileSync(configPath, 'utf8');
31
+ // Strip comments before regex parsing to avoid matching values inside comments
32
+ const code = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
33
+ // Extract values from the config file via regex
34
+ const theme = code.match(/theme:\s*['"]([^'"]+)['"]/)?.[1] ?? DEFAULT_CONFIG.theme;
35
+ const tokens = code.match(/tokens:\s*['"]([^'"]+)['"]/)?.[1] ?? DEFAULT_CONFIG.tokens;
36
+ const styleImport = code.match(/styleImport:\s*['"]([^'"]+)['"]/)?.[1] ?? DEFAULT_CONFIG.styleImport;
37
+ const outputDir = code.match(/outputDir:\s*['"]([^'"]+)['"]/)?.[1] ?? DEFAULT_CONFIG.outputDir;
38
+ const vuetifyIntegration = code.includes('vuetifyIntegration: true');
39
+ return { theme, tokens, styleImport, outputDir, vuetifyIntegration };
40
+ }
41
+ catch (err) {
42
+ logger.error(`Failed to parse ${CONFIG_FILENAME}: ${err.message}`);
43
+ return null;
44
+ }
45
+ }
46
+ /** Generate viui.config.ts content from config object */
47
+ export function generateConfigContent(config) {
48
+ return `import type { ViuiConfig } from '@hobui/viui-cli'
49
+
50
+ export default {
51
+ theme: '${config.theme}',
52
+ tokens: '${config.tokens}',
53
+ styleImport: '${config.styleImport}',
54
+ outputDir: '${config.outputDir}',
55
+ vuetifyIntegration: ${config.vuetifyIntegration},
56
+ } satisfies ViuiConfig
57
+ `;
58
+ }
59
+ /** Write viui.config.ts to project root */
60
+ export function writeConfig(cwd, config) {
61
+ const configPath = findConfigPath(cwd);
62
+ fs.writeFileSync(configPath, generateConfigContent(config), 'utf8');
63
+ logger.success(`Created ${CONFIG_FILENAME}`);
64
+ }
@@ -0,0 +1,13 @@
1
+ /** Result of comparing two directories */
2
+ export interface DiffResult {
3
+ added: string[];
4
+ modified: string[];
5
+ removed: string[];
6
+ }
7
+ /** Compare two file contents, return true if different */
8
+ export declare function filesAreDifferent(fileA: string, fileB: string): boolean;
9
+ /** Compare two directories and return added/modified/removed file lists */
10
+ export declare function diffDirectories(sourceDir: string, targetDir: string): DiffResult;
11
+ /** Read file content, return empty string if not found */
12
+ export declare function readFileOrEmpty(filePath: string): string;
13
+ //# sourceMappingURL=diff-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/diff-engine.ts"],"names":[],"mappings":"AAGA,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvE;AAiBD,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CA4BhF;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMxD"}
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /** Compare two file contents, return true if different */
4
+ export function filesAreDifferent(fileA, fileB) {
5
+ if (!fs.existsSync(fileA) || !fs.existsSync(fileB))
6
+ return true;
7
+ const a = fs.readFileSync(fileA, 'utf8');
8
+ const b = fs.readFileSync(fileB, 'utf8');
9
+ return a !== b;
10
+ }
11
+ /** List all files recursively in a directory (relative paths) */
12
+ function listFiles(dir, prefix = '') {
13
+ if (!fs.existsSync(dir))
14
+ return [];
15
+ const results = [];
16
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
17
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
18
+ if (entry.isDirectory()) {
19
+ results.push(...listFiles(path.join(dir, entry.name), rel));
20
+ }
21
+ else {
22
+ results.push(rel);
23
+ }
24
+ }
25
+ return results;
26
+ }
27
+ /** Compare two directories and return added/modified/removed file lists */
28
+ export function diffDirectories(sourceDir, targetDir) {
29
+ const sourceFiles = new Set(listFiles(sourceDir));
30
+ const targetFiles = new Set(listFiles(targetDir));
31
+ const added = [];
32
+ const modified = [];
33
+ const removed = [];
34
+ // Files in source but not in target = added
35
+ for (const file of sourceFiles) {
36
+ if (!targetFiles.has(file)) {
37
+ added.push(file);
38
+ }
39
+ else if (filesAreDifferent(path.join(sourceDir, file), path.join(targetDir, file))) {
40
+ modified.push(file);
41
+ }
42
+ }
43
+ // Files in target but not in source = removed
44
+ for (const file of targetFiles) {
45
+ if (!sourceFiles.has(file)) {
46
+ removed.push(file);
47
+ }
48
+ }
49
+ return { added, modified, removed };
50
+ }
51
+ /** Read file content, return empty string if not found */
52
+ export function readFileOrEmpty(filePath) {
53
+ try {
54
+ return fs.readFileSync(filePath, 'utf8');
55
+ }
56
+ catch {
57
+ return '';
58
+ }
59
+ }
@@ -0,0 +1,9 @@
1
+ import type { IdeInfo } from '../types/ide-types.js';
2
+ /**
3
+ * Phát hiện tất cả IDE trong project root và hệ thống.
4
+ * Luôn trả về danh sách đầy đủ 6 IDE với trạng thái detected.
5
+ */
6
+ export declare function detectIdes(projectRoot: string): IdeInfo[];
7
+ /** Lấy chỉ các IDE được phát hiện */
8
+ export declare function getDetectedIdes(projectRoot: string): IdeInfo[];
9
+ //# sourceMappingURL=ide-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-detector.d.ts","sourceRoot":"","sources":["../../src/services/ide-detector.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAkD3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,EAAE,CAmDzD;AAED,qCAAqC;AACrC,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,EAAE,CAE9D"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * ide-detector.ts — Phát hiện IDE được cài đặt dựa trên thư mục config và binary trong PATH
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ import { execSync } from 'node:child_process';
8
+ /** Kiểm tra binary có tồn tại trong PATH không */
9
+ function hasBinary(bin) {
10
+ if (!/^[a-zA-Z0-9_-]+$/.test(bin))
11
+ return false;
12
+ try {
13
+ const cmd = process.platform === 'win32' ? `where ${bin}` : `which ${bin}`;
14
+ execSync(cmd, { stdio: 'ignore' });
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /** Kiểm tra thư mục có tồn tại không */
22
+ function hasDir(root, dirName) {
23
+ return fs.existsSync(path.join(root, dirName));
24
+ }
25
+ /** Kiểm tra file tồn tại không */
26
+ function hasFile(root, fileName) {
27
+ return fs.existsSync(path.join(root, fileName));
28
+ }
29
+ /** Resolve global config path theo OS cho từng IDE */
30
+ function resolveGlobalConfigPath(ideId) {
31
+ const home = homedir();
32
+ switch (ideId) {
33
+ case 'vscode': {
34
+ if (process.platform === 'win32')
35
+ return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Code', 'User', 'mcp.json');
36
+ if (process.platform === 'darwin')
37
+ return path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
38
+ return path.join(home, '.config', 'Code', 'User', 'mcp.json');
39
+ }
40
+ case 'cursor':
41
+ return path.join(home, '.cursor', 'mcp.json');
42
+ case 'claude':
43
+ return path.join(home, '.claude.json');
44
+ case 'windsurf':
45
+ return path.join(home, '.codeium', 'windsurf', 'mcp_config.json');
46
+ case 'gemini':
47
+ return path.join(home, '.gemini', 'settings.json');
48
+ case 'trae':
49
+ return null;
50
+ default:
51
+ return null;
52
+ }
53
+ }
54
+ /**
55
+ * Phát hiện tất cả IDE trong project root và hệ thống.
56
+ * Luôn trả về danh sách đầy đủ 6 IDE với trạng thái detected.
57
+ */
58
+ export function detectIdes(projectRoot) {
59
+ return [
60
+ {
61
+ id: 'vscode',
62
+ name: 'VS Code',
63
+ detected: hasDir(projectRoot, '.vscode') || hasBinary('code'),
64
+ configPath: path.join(projectRoot, '.vscode', 'mcp.json'),
65
+ globalConfigPath: resolveGlobalConfigPath('vscode'),
66
+ configFormat: 'vscode',
67
+ },
68
+ {
69
+ id: 'cursor',
70
+ name: 'Cursor',
71
+ detected: hasDir(projectRoot, '.cursor') || hasBinary('cursor'),
72
+ configPath: path.join(projectRoot, '.cursor', 'mcp.json'),
73
+ globalConfigPath: resolveGlobalConfigPath('cursor'),
74
+ configFormat: 'standard',
75
+ },
76
+ {
77
+ id: 'claude',
78
+ name: 'Claude Code',
79
+ detected: hasFile(projectRoot, '.mcp.json') || hasDir(projectRoot, '.claude'),
80
+ configPath: path.join(projectRoot, '.mcp.json'),
81
+ globalConfigPath: resolveGlobalConfigPath('claude'),
82
+ configFormat: 'standard',
83
+ },
84
+ {
85
+ id: 'windsurf',
86
+ name: 'Windsurf',
87
+ detected: hasDir(projectRoot, '.windsurf'),
88
+ configPath: path.join(projectRoot, '.windsurf', 'mcp.json'),
89
+ globalConfigPath: resolveGlobalConfigPath('windsurf'),
90
+ configFormat: 'standard',
91
+ },
92
+ {
93
+ id: 'trae',
94
+ name: 'Trae',
95
+ detected: hasDir(projectRoot, '.trae'),
96
+ configPath: path.join(projectRoot, '.trae', 'mcp.json'),
97
+ globalConfigPath: resolveGlobalConfigPath('trae'),
98
+ configFormat: 'standard',
99
+ },
100
+ {
101
+ id: 'gemini',
102
+ name: 'Gemini',
103
+ detected: hasDir(homedir(), '.gemini') || hasBinary('gemini'),
104
+ configPath: '',
105
+ globalConfigPath: resolveGlobalConfigPath('gemini'),
106
+ configFormat: 'gemini',
107
+ },
108
+ ];
109
+ }
110
+ /** Lấy chỉ các IDE được phát hiện */
111
+ export function getDetectedIdes(projectRoot) {
112
+ return detectIdes(projectRoot).filter(ide => ide.detected);
113
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ide-detector.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-detector.spec.d.ts","sourceRoot":"","sources":["../../src/services/ide-detector.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { detectIdes, getDetectedIdes } from './ide-detector.js';
6
+ // ── Helpers ──
7
+ const tmpDir = path.join(os.tmpdir(), '.tmp-test-detector');
8
+ beforeEach(() => {
9
+ fs.mkdirSync(tmpDir, { recursive: true });
10
+ });
11
+ afterEach(() => {
12
+ fs.rmSync(tmpDir, { recursive: true, force: true });
13
+ });
14
+ // ── Tests ──
15
+ describe('detectIdes', () => {
16
+ it('returns exactly 6 IDE entries', () => {
17
+ const ides = detectIdes(tmpDir);
18
+ expect(ides).toHaveLength(6);
19
+ });
20
+ it('returns all expected IDE ids', () => {
21
+ const ids = detectIdes(tmpDir).map(ide => ide.id);
22
+ expect(ids).toEqual(['vscode', 'cursor', 'claude', 'windsurf', 'trae', 'gemini']);
23
+ });
24
+ it('sets correct configFormat per IDE', () => {
25
+ const formats = Object.fromEntries(detectIdes(tmpDir).map(ide => [ide.id, ide.configFormat]));
26
+ expect(formats.vscode).toBe('vscode');
27
+ expect(formats.gemini).toBe('gemini');
28
+ expect(formats.cursor).toBe('standard');
29
+ expect(formats.claude).toBe('standard');
30
+ expect(formats.windsurf).toBe('standard');
31
+ expect(formats.trae).toBe('standard');
32
+ });
33
+ it('marks vscode detected when .vscode/ directory exists', () => {
34
+ fs.mkdirSync(path.join(tmpDir, '.vscode'), { recursive: true });
35
+ const ides = detectIdes(tmpDir);
36
+ const vscode = ides.find(i => i.id === 'vscode');
37
+ expect(vscode.detected).toBe(true);
38
+ });
39
+ it('marks claude detected when .mcp.json exists', () => {
40
+ fs.writeFileSync(path.join(tmpDir, '.mcp.json'), '{}', 'utf8');
41
+ const ides = detectIdes(tmpDir);
42
+ const claude = ides.find(i => i.id === 'claude');
43
+ expect(claude.detected).toBe(true);
44
+ });
45
+ it('marks claude detected when .claude/ directory exists', () => {
46
+ fs.mkdirSync(path.join(tmpDir, '.claude'), { recursive: true });
47
+ const ides = detectIdes(tmpDir);
48
+ const claude = ides.find(i => i.id === 'claude');
49
+ expect(claude.detected).toBe(true);
50
+ });
51
+ it('marks cursor detected when .cursor/ directory exists', () => {
52
+ fs.mkdirSync(path.join(tmpDir, '.cursor'), { recursive: true });
53
+ const ides = detectIdes(tmpDir);
54
+ const cursor = ides.find(i => i.id === 'cursor');
55
+ expect(cursor.detected).toBe(true);
56
+ });
57
+ it('marks windsurf detected when .windsurf/ directory exists', () => {
58
+ fs.mkdirSync(path.join(tmpDir, '.windsurf'), { recursive: true });
59
+ const ides = detectIdes(tmpDir);
60
+ const windsurf = ides.find(i => i.id === 'windsurf');
61
+ expect(windsurf.detected).toBe(true);
62
+ });
63
+ it('resolves configPath relative to projectRoot', () => {
64
+ const ides = detectIdes(tmpDir);
65
+ const cursor = ides.find(i => i.id === 'cursor');
66
+ expect(cursor.configPath).toBe(path.join(tmpDir, '.cursor', 'mcp.json'));
67
+ });
68
+ it('claude configPath is .mcp.json in project root', () => {
69
+ const ides = detectIdes(tmpDir);
70
+ const claude = ides.find(i => i.id === 'claude');
71
+ expect(claude.configPath).toBe(path.join(tmpDir, '.mcp.json'));
72
+ });
73
+ it('each IDE has a non-empty name', () => {
74
+ for (const ide of detectIdes(tmpDir)) {
75
+ expect(ide.name.length).toBeGreaterThan(0);
76
+ }
77
+ });
78
+ it('gemini has empty configPath (global only)', () => {
79
+ const ides = detectIdes(tmpDir);
80
+ const gemini = ides.find(i => i.id === 'gemini');
81
+ expect(gemini.configPath).toBe('');
82
+ });
83
+ });
84
+ describe('getDetectedIdes', () => {
85
+ it('returns only detected IDEs', () => {
86
+ fs.mkdirSync(path.join(tmpDir, '.vscode'), { recursive: true });
87
+ fs.mkdirSync(path.join(tmpDir, '.cursor'), { recursive: true });
88
+ const detected = getDetectedIdes(tmpDir);
89
+ for (const ide of detected) {
90
+ expect(ide.detected).toBe(true);
91
+ }
92
+ });
93
+ it('returns empty when no IDE marker exists', () => {
94
+ // tmpDir is clean — no IDE dirs or files
95
+ // Note: some IDEs may be detected via binary in PATH (e.g., `code`, `cursor`)
96
+ // so we just verify all returned items have detected=true
97
+ const detected = getDetectedIdes(tmpDir);
98
+ for (const ide of detected) {
99
+ expect(ide.detected).toBe(true);
100
+ }
101
+ });
102
+ it('includes IDE detected by both dir and binary', () => {
103
+ fs.mkdirSync(path.join(tmpDir, '.vscode'), { recursive: true });
104
+ const detected = getDetectedIdes(tmpDir);
105
+ const vscode = detected.find(i => i.id === 'vscode');
106
+ expect(vscode).toBeDefined();
107
+ });
108
+ });