@hobui/viui-cli 0.0.5 → 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.
- package/README.md +138 -139
- package/dist/adapters/adapter-registry.d.ts +12 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +49 -0
- package/dist/adapters/adapter-types.d.ts +20 -0
- package/dist/adapters/adapter-types.d.ts.map +1 -0
- package/dist/adapters/adapter-types.js +1 -0
- package/dist/adapters/aider-adapter.d.ts +3 -0
- package/dist/adapters/aider-adapter.d.ts.map +1 -0
- package/dist/adapters/aider-adapter.js +8 -0
- package/dist/adapters/claude-adapter.d.ts +3 -0
- package/dist/adapters/claude-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-adapter.js +15 -0
- package/dist/adapters/cline-adapter.d.ts +3 -0
- package/dist/adapters/cline-adapter.d.ts.map +1 -0
- package/dist/adapters/cline-adapter.js +8 -0
- package/dist/adapters/copilot-adapter.d.ts +5 -0
- package/dist/adapters/copilot-adapter.d.ts.map +1 -0
- package/dist/adapters/copilot-adapter.js +20 -0
- package/dist/adapters/cursor-adapter.d.ts +3 -0
- package/dist/adapters/cursor-adapter.d.ts.map +1 -0
- package/dist/adapters/cursor-adapter.js +18 -0
- package/dist/adapters/external/bolt-adapter.d.ts +3 -0
- package/dist/adapters/external/bolt-adapter.d.ts.map +1 -0
- package/dist/adapters/external/bolt-adapter.js +15 -0
- package/dist/adapters/external/chatgpt-adapter.d.ts +3 -0
- package/dist/adapters/external/chatgpt-adapter.d.ts.map +1 -0
- package/dist/adapters/external/chatgpt-adapter.js +14 -0
- package/dist/adapters/external/external-adapter-base.d.ts +15 -0
- package/dist/adapters/external/external-adapter-base.d.ts.map +1 -0
- package/dist/adapters/external/external-adapter-base.js +92 -0
- package/dist/adapters/external/gemini-adapter.d.ts +3 -0
- package/dist/adapters/external/gemini-adapter.d.ts.map +1 -0
- package/dist/adapters/external/gemini-adapter.js +14 -0
- package/dist/adapters/external/lovable-adapter.d.ts +3 -0
- package/dist/adapters/external/lovable-adapter.d.ts.map +1 -0
- package/dist/adapters/external/lovable-adapter.js +14 -0
- package/dist/adapters/external/v0-adapter.d.ts +3 -0
- package/dist/adapters/external/v0-adapter.d.ts.map +1 -0
- package/dist/adapters/external/v0-adapter.js +15 -0
- package/dist/adapters/windsurf-adapter.d.ts +3 -0
- package/dist/adapters/windsurf-adapter.d.ts.map +1 -0
- package/dist/adapters/windsurf-adapter.js +23 -0
- package/dist/assets/plugins/viui-conf/apply-theme-body.ts +23 -4
- package/dist/assets/plugins/viui-conf/defaults/README.md +2 -0
- package/dist/assets/plugins/viui-conf/defaults/app-bar.ts +1 -1
- package/dist/assets/plugins/viui-conf/defaults/buttons.ts +1 -1
- package/dist/assets/plugins/viui-conf/defaults/by-theme/minimalist-2.ts +1 -1
- package/dist/assets/plugins/viui-conf/defaults/cards.ts +1 -1
- package/dist/assets/plugins/viui-conf/defaults/expansion-panels.ts +16 -0
- package/dist/assets/plugins/viui-conf/defaults/index.ts +3 -0
- package/dist/assets/plugins/viui-conf/defaults/inputs.ts +11 -1
- package/dist/assets/plugins/viui-conf/design-tokens.ts +135 -0
- package/dist/assets/plugins/viui-conf/theme-base.ts +1 -1
- package/dist/assets/plugins/viui-conf/v-dark.ts +3 -5
- package/dist/assets/plugins/viui-conf/v-light.ts +3 -5
- package/dist/assets/plugins/vuetify.ts +36 -0
- package/dist/assets/prompt-data/components.json +106 -0
- package/dist/assets/prompt-data/tokens.json +83 -0
- package/dist/assets/themes/_bento-grid.scss +8 -0
- package/dist/assets/themes/_glassmorphism.scss +8 -0
- package/dist/assets/themes/_material.scss +8 -0
- package/dist/assets/themes/_minimalist-2.scss +375 -0
- package/dist/assets/themes/_minimalist.scss +9 -0
- package/dist/assets/themes/_neo-brutalism.scss +199 -0
- package/dist/assets/themes/bento-grid.scss +4 -0
- package/dist/assets/themes/glassmorphism.scss +4 -0
- package/dist/assets/themes/index.scss +11 -0
- package/dist/assets/themes/material.scss +4 -0
- package/dist/assets/themes/minimalist-2.scss +5 -0
- package/dist/assets/themes/minimalist.scss +4 -0
- package/dist/assets/themes/neo-brutalism.scss +5 -0
- package/dist/assets/viui-themes/_neo-brutalism.scss +70 -152
- package/dist/cli-paths.d.ts +7 -0
- package/dist/cli-paths.d.ts.map +1 -0
- package/dist/cli-paths.js +19 -0
- package/dist/cli.js +28 -450
- package/dist/cli.legacy.d.ts +3 -0
- package/dist/cli.legacy.d.ts.map +1 -0
- package/dist/cli.legacy.js +597 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +152 -0
- package/dist/commands/config/config-export.d.ts +6 -0
- package/dist/commands/config/config-export.d.ts.map +1 -0
- package/dist/commands/config/config-export.js +23 -0
- package/dist/commands/config/config-health.d.ts +6 -0
- package/dist/commands/config/config-health.d.ts.map +1 -0
- package/dist/commands/config/config-health.js +42 -0
- package/dist/commands/config/config-import.d.ts +6 -0
- package/dist/commands/config/config-import.d.ts.map +1 -0
- package/dist/commands/config/config-import.js +63 -0
- package/dist/commands/config/config-rollback.d.ts +6 -0
- package/dist/commands/config/config-rollback.d.ts.map +1 -0
- package/dist/commands/config/config-rollback.js +47 -0
- package/dist/commands/config/config-setup.d.ts +6 -0
- package/dist/commands/config/config-setup.d.ts.map +1 -0
- package/dist/commands/config/config-setup.js +103 -0
- package/dist/commands/config/config-status.d.ts +6 -0
- package/dist/commands/config/config-status.d.ts.map +1 -0
- package/dist/commands/config/config-status.js +42 -0
- package/dist/commands/config/config-uninstall.d.ts +6 -0
- package/dist/commands/config/config-uninstall.d.ts.map +1 -0
- package/dist/commands/config/config-uninstall.js +74 -0
- package/dist/commands/config.d.ts +6 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +19 -0
- package/dist/commands/docs.d.ts +3 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +17 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +93 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +183 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +73 -0
- package/dist/commands/theme.d.ts +3 -0
- package/dist/commands/theme.d.ts.map +1 -0
- package/dist/commands/theme.js +86 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +97 -0
- package/dist/prompts/prompt-builder.d.ts +4 -0
- package/dist/prompts/prompt-builder.d.ts.map +1 -0
- package/dist/prompts/prompt-builder.js +18 -0
- package/dist/prompts/prompt-data-loader.d.ts +11 -0
- package/dist/prompts/prompt-data-loader.d.ts.map +1 -0
- package/dist/prompts/prompt-data-loader.js +15 -0
- package/dist/prompts/prompt-sections/section-code-examples.d.ts +2 -0
- package/dist/prompts/prompt-sections/section-code-examples.d.ts.map +1 -0
- package/dist/prompts/prompt-sections/section-code-examples.js +36 -0
- package/dist/prompts/prompt-sections/section-color-tokens.d.ts +2 -0
- package/dist/prompts/prompt-sections/section-color-tokens.d.ts.map +1 -0
- package/dist/prompts/prompt-sections/section-color-tokens.js +19 -0
- package/dist/prompts/prompt-sections/section-component-map.d.ts +3 -0
- package/dist/prompts/prompt-sections/section-component-map.d.ts.map +1 -0
- package/dist/prompts/prompt-sections/section-component-map.js +12 -0
- package/dist/prompts/prompt-sections/section-typography-spacing.d.ts +2 -0
- package/dist/prompts/prompt-sections/section-typography-spacing.d.ts.map +1 -0
- package/dist/prompts/prompt-sections/section-typography-spacing.js +29 -0
- package/dist/services/backup-service.d.ts +7 -0
- package/dist/services/backup-service.d.ts.map +1 -0
- package/dist/services/backup-service.js +54 -0
- package/dist/services/config-service.d.ts +17 -0
- package/dist/services/config-service.d.ts.map +1 -0
- package/dist/services/config-service.js +64 -0
- package/dist/services/diff-engine.d.ts +13 -0
- package/dist/services/diff-engine.d.ts.map +1 -0
- package/dist/services/diff-engine.js +59 -0
- package/dist/services/ide-detector.d.ts +9 -0
- package/dist/services/ide-detector.d.ts.map +1 -0
- package/dist/services/ide-detector.js +113 -0
- package/dist/services/ide-detector.spec.d.ts +2 -0
- package/dist/services/ide-detector.spec.d.ts.map +1 -0
- package/dist/services/ide-detector.spec.js +108 -0
- package/dist/services/lock-file-service.d.ts +15 -0
- package/dist/services/lock-file-service.d.ts.map +1 -0
- package/dist/services/lock-file-service.js +74 -0
- package/dist/services/mcp-config-reader.d.ts +11 -0
- package/dist/services/mcp-config-reader.d.ts.map +1 -0
- package/dist/services/mcp-config-reader.js +40 -0
- package/dist/services/mcp-config-reader.spec.d.ts +2 -0
- package/dist/services/mcp-config-reader.spec.d.ts.map +1 -0
- package/dist/services/mcp-config-reader.spec.js +125 -0
- package/dist/services/mcp-config-writer.d.ts +11 -0
- package/dist/services/mcp-config-writer.d.ts.map +1 -0
- package/dist/services/mcp-config-writer.js +98 -0
- package/dist/services/mcp-config-writer.spec.d.ts +2 -0
- package/dist/services/mcp-config-writer.spec.d.ts.map +1 -0
- package/dist/services/mcp-config-writer.spec.js +162 -0
- package/dist/services/merge-engine.d.ts +12 -0
- package/dist/services/merge-engine.d.ts.map +1 -0
- package/dist/services/merge-engine.js +54 -0
- package/dist/services/vuetify-scaffold-service.d.ts +5 -0
- package/dist/services/vuetify-scaffold-service.d.ts.map +1 -0
- package/dist/services/vuetify-scaffold-service.js +67 -0
- package/dist/templates/vuetify-plugin.d.ts +90 -0
- package/dist/templates/vuetify-plugin.d.ts.map +1 -0
- package/dist/templates/vuetify-plugin.js +33 -0
- package/dist/types/command-types.d.ts +15 -0
- package/dist/types/command-types.d.ts.map +1 -0
- package/dist/types/command-types.js +2 -0
- package/dist/types/config-types.d.ts +29 -0
- package/dist/types/config-types.d.ts.map +1 -0
- package/dist/types/config-types.js +10 -0
- package/dist/types/ide-types.d.ts +29 -0
- package/dist/types/ide-types.d.ts.map +1 -0
- package/dist/types/ide-types.js +4 -0
- package/dist/types/lock-file-types.d.ts +27 -0
- package/dist/types/lock-file-types.d.ts.map +1 -0
- package/dist/types/lock-file-types.js +2 -0
- package/dist/utils/diff-display.d.ts +18 -0
- package/dist/utils/diff-display.d.ts.map +1 -0
- package/dist/utils/diff-display.js +61 -0
- package/dist/utils/fs-safe.d.ts +9 -0
- package/dist/utils/fs-safe.d.ts.map +1 -0
- package/dist/utils/fs-safe.js +44 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +28 -0
- package/dist/utils/open-browser.d.ts +3 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +13 -0
- package/package.json +11 -6
- package/dist/assets/cursor/.design-system-version +0 -1
- package/dist/assets/cursor/commands/audit-accessibility.md +0 -25
- package/dist/assets/cursor/commands/audit-ui.md +0 -35
- package/dist/assets/cursor/commands/component.md +0 -18
- package/dist/assets/cursor/commands/fix-storybook.md +0 -24
- package/dist/assets/cursor/commands/generate-component-from-figma.md +0 -26
- package/dist/assets/cursor/commands/generate-page-from-figma.md +0 -26
- package/dist/assets/cursor/plans/DESIGN_SYSTEM_PLAN.md +0 -177
- package/dist/assets/cursor/plans/PLANS_INDEX.md +0 -35
- package/dist/assets/cursor/rules/accessibility-contrast.mdc +0 -38
- package/dist/assets/cursor/rules/bem-class-style.mdc +0 -107
- package/dist/assets/cursor/rules/component-naming.mdc +0 -57
- package/dist/assets/cursor/rules/design-system-component-library.mdc +0 -59
- package/dist/assets/cursor/rules/design-system-workflow.mdc +0 -48
- package/dist/assets/cursor/rules/figma-mapping.mdc +0 -37
- package/dist/assets/cursor/rules/icons.mdc +0 -42
- package/dist/assets/cursor/rules/project-structure.mdc +0 -137
- package/dist/assets/cursor/rules/storybook-component-template.mdc +0 -103
- package/dist/assets/cursor/rules/storybook.mdc +0 -68
- package/dist/assets/cursor/rules/tokens.mdc +0 -32
- package/dist/assets/cursor/rules/viui-themes.mdc +0 -53
- package/dist/assets/cursor/rules/vuetify-layout.mdc +0 -52
- package/dist/assets/cursor/skills/accessibility.md +0 -75
- package/dist/assets/cursor/skills/design-system-thinking.md +0 -40
- package/dist/assets/cursor/skills/figma-interpretation.md +0 -38
- package/dist/assets/cursor/skills/vue-vuetify-design-system-architect.md +0 -60
- package/dist/assets/cursor/sync-manifest.json +0 -6
- package/dist/assets/viui-themes/bento-grid-global.scss +0 -5
- package/dist/assets/viui-themes/glassmorphism-global.scss +0 -5
- package/dist/assets/viui-themes/material-global.scss +0 -5
- package/dist/assets/viui-themes/minimalist-2-global.scss +0 -5
- package/dist/assets/viui-themes/minimalist-global.scss +0 -5
- package/dist/assets/viui-themes/neo-brutalism-global.scss +0 -5
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
import { readConfig } from '../services/config-service.js';
|
|
6
|
+
/** Scan files recursively matching extensions */
|
|
7
|
+
function scanFiles(dir, extensions) {
|
|
8
|
+
if (!fs.existsSync(dir))
|
|
9
|
+
return [];
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
12
|
+
const full = path.join(dir, entry.name);
|
|
13
|
+
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'tokens')
|
|
14
|
+
continue;
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
results.push(...scanFiles(full, extensions));
|
|
17
|
+
}
|
|
18
|
+
else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
19
|
+
results.push(full);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
/** Count occurrences of pattern in file contents */
|
|
25
|
+
function countPattern(files, pattern) {
|
|
26
|
+
let count = 0;
|
|
27
|
+
for (const f of files) {
|
|
28
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
29
|
+
const matches = content.match(pattern);
|
|
30
|
+
if (matches)
|
|
31
|
+
count += matches.length;
|
|
32
|
+
}
|
|
33
|
+
return count;
|
|
34
|
+
}
|
|
35
|
+
/** Exclude token definition files from token coverage scan */
|
|
36
|
+
const TOKEN_DEF_PATTERNS = ['inet-tokens.scss', 'design-tokens.ts', 'inet-design-tokens.ts'];
|
|
37
|
+
/** Token coverage: ratio of var(--*) usage vs hardcoded color/spacing values */
|
|
38
|
+
function auditTokenCoverage(srcDir) {
|
|
39
|
+
const files = scanFiles(srcDir, ['.vue', '.ts', '.scss', '.css'])
|
|
40
|
+
.filter((f) => !TOKEN_DEF_PATTERNS.some((p) => f.endsWith(p)));
|
|
41
|
+
const tokenUsage = countPattern(files, /var\(--[\w-]+\)/g);
|
|
42
|
+
const hardcodedColors = countPattern(files, /#(?:[0-9a-fA-F]{3,8})\b/g);
|
|
43
|
+
const hardcodedSpacing = countPattern(files, /\b\d+(?:\.\d+)?px\b/g);
|
|
44
|
+
const hardcoded = hardcodedColors + hardcodedSpacing;
|
|
45
|
+
const total = tokenUsage + hardcoded;
|
|
46
|
+
const score = total === 0 ? 100 : Math.round((tokenUsage / total) * 100);
|
|
47
|
+
return {
|
|
48
|
+
category: 'Token Coverage',
|
|
49
|
+
score,
|
|
50
|
+
details: `${tokenUsage} token vars, ${hardcoded} hardcoded values`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Component usage: ratio of Vi* vs raw v-* components */
|
|
54
|
+
function auditComponentUsage(srcDir) {
|
|
55
|
+
const files = scanFiles(srcDir, ['.vue']);
|
|
56
|
+
const viUsage = countPattern(files, /<Vi[A-Z]\w+/g);
|
|
57
|
+
const vuetifyDirect = countPattern(files, /<v-(btn|text-field|select|checkbox|card|alert|dialog|data-table|pagination)\b/g);
|
|
58
|
+
const total = viUsage + vuetifyDirect;
|
|
59
|
+
const score = total === 0 ? 100 : Math.round((viUsage / total) * 100);
|
|
60
|
+
return {
|
|
61
|
+
category: 'Component Usage',
|
|
62
|
+
score,
|
|
63
|
+
details: `${viUsage} Vi* components, ${vuetifyDirect} direct Vuetify`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** A11y score: check aria-label on icon buttons */
|
|
67
|
+
function auditA11y(srcDir) {
|
|
68
|
+
const files = scanFiles(srcDir, ['.vue']);
|
|
69
|
+
let iconButtons = 0;
|
|
70
|
+
let withAria = 0;
|
|
71
|
+
for (const f of files) {
|
|
72
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
73
|
+
const iconMatches = content.match(/<(?:v-btn|ViButton)[^>]*icon[^>]*>/gi) ?? [];
|
|
74
|
+
iconButtons += iconMatches.length;
|
|
75
|
+
for (const m of iconMatches) {
|
|
76
|
+
if (/aria-label/i.test(m))
|
|
77
|
+
withAria++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const score = iconButtons === 0 ? 100 : Math.round((withAria / iconButtons) * 100);
|
|
81
|
+
return {
|
|
82
|
+
category: 'Accessibility',
|
|
83
|
+
score,
|
|
84
|
+
details: `${withAria}/${iconButtons} icon buttons with aria-label`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Theme consistency: verify Vuetify theme config references tokens */
|
|
88
|
+
function auditThemeConsistency(cwd) {
|
|
89
|
+
const vuetifyPath = path.join(cwd, 'src', 'plugins', 'vuetify.ts');
|
|
90
|
+
if (!fs.existsSync(vuetifyPath)) {
|
|
91
|
+
return { category: 'Theme Consistency', score: 50, details: 'No vuetify.ts found' };
|
|
92
|
+
}
|
|
93
|
+
const content = fs.readFileSync(vuetifyPath, 'utf8');
|
|
94
|
+
let score = 0;
|
|
95
|
+
const checks = [];
|
|
96
|
+
if (content.includes('viui-conf')) {
|
|
97
|
+
score += 40;
|
|
98
|
+
checks.push('viui-conf imported');
|
|
99
|
+
}
|
|
100
|
+
if (content.includes('apply-theme-body')) {
|
|
101
|
+
score += 30;
|
|
102
|
+
checks.push('theme body applied');
|
|
103
|
+
}
|
|
104
|
+
if (content.includes('v-light') || content.includes('v-dark')) {
|
|
105
|
+
score += 30;
|
|
106
|
+
checks.push('light/dark themes');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
category: 'Theme Consistency',
|
|
110
|
+
score,
|
|
111
|
+
details: checks.length ? checks.join(', ') : 'No token integration found',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function registerAuditCommand(program) {
|
|
115
|
+
program
|
|
116
|
+
.command('audit')
|
|
117
|
+
.description('Run compliance audit and show score /100')
|
|
118
|
+
.option('--json', 'Output as JSON for CI')
|
|
119
|
+
.action((opts) => {
|
|
120
|
+
const cwd = process.cwd();
|
|
121
|
+
const config = readConfig(cwd);
|
|
122
|
+
const srcDir = path.join(cwd, 'src');
|
|
123
|
+
if (!fs.existsSync(srcDir)) {
|
|
124
|
+
logger.error('src/ directory not found.');
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const scores = [
|
|
129
|
+
auditTokenCoverage(srcDir),
|
|
130
|
+
auditComponentUsage(srcDir),
|
|
131
|
+
auditA11y(srcDir),
|
|
132
|
+
auditThemeConsistency(cwd),
|
|
133
|
+
];
|
|
134
|
+
const overall = Math.round(scores.reduce((sum, s) => sum + s.score, 0) / scores.length);
|
|
135
|
+
if (opts.json) {
|
|
136
|
+
console.log(JSON.stringify({ overall, scores }, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
logger.header('viui audit — Compliance Report\n');
|
|
140
|
+
for (const s of scores) {
|
|
141
|
+
const color = s.score >= 80 ? chalk.green : s.score >= 50 ? chalk.yellow : chalk.red;
|
|
142
|
+
console.log(` ${color(`${String(s.score).padStart(3)}/100`)} ${s.category}`);
|
|
143
|
+
console.log(chalk.dim(` ${s.details}`));
|
|
144
|
+
}
|
|
145
|
+
logger.br();
|
|
146
|
+
const overallColor = overall >= 80 ? chalk.green : overall >= 50 ? chalk.yellow : chalk.red;
|
|
147
|
+
console.log(` ${overallColor(`Overall: ${overall}/100`)}`);
|
|
148
|
+
logger.br();
|
|
149
|
+
if (overall < 50)
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-export.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-export.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAIxC,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAuBjE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
2
|
+
import { readMcpConfig } from '../../services/mcp-config-reader.js';
|
|
3
|
+
export function registerExportSubcommand(configCmd) {
|
|
4
|
+
configCmd
|
|
5
|
+
.command('export')
|
|
6
|
+
.description('Xuất cấu hình MCP ra stdout (JSON)')
|
|
7
|
+
.action(() => {
|
|
8
|
+
const ides = detectIdes(process.cwd());
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const ide of ides) {
|
|
11
|
+
const project = readMcpConfig(ide, false);
|
|
12
|
+
const global = ide.globalConfigPath ? readMcpConfig(ide, true) : null;
|
|
13
|
+
if (project.length > 0 || (global && global.length > 0)) {
|
|
14
|
+
result[ide.id] = {
|
|
15
|
+
name: ide.name,
|
|
16
|
+
project: project.length > 0 ? project : null,
|
|
17
|
+
global: global && global.length > 0 ? global : null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-health.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-health.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOxC,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAwCjE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { logger } from '../../utils/logger.js';
|
|
3
|
+
const DEFAULT_REMOTE = 'https://viui.inet.vn/mcp';
|
|
4
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
5
|
+
export function registerHealthSubcommand(configCmd) {
|
|
6
|
+
configCmd
|
|
7
|
+
.command('health')
|
|
8
|
+
.description('Kiểm tra MCP server có hoạt động không')
|
|
9
|
+
.option('--timeout <ms>', 'Timeout (ms)', String(DEFAULT_TIMEOUT))
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
const timeout = parseInt(opts.timeout, 10) || DEFAULT_TIMEOUT;
|
|
12
|
+
const url = DEFAULT_REMOTE;
|
|
13
|
+
logger.info(`Kiểm tra: ${chalk.cyan(url)} (timeout: ${timeout}ms)`);
|
|
14
|
+
logger.br();
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
try {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
19
|
+
const res = await fetch(url, {
|
|
20
|
+
method: 'GET',
|
|
21
|
+
signal: controller.signal,
|
|
22
|
+
});
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
const elapsed = Date.now() - start;
|
|
25
|
+
if (res.ok || res.status === 405) {
|
|
26
|
+
// MCP servers often return 405 for GET (expects POST)
|
|
27
|
+
logger.success(`Server hoạt động — ${elapsed}ms (HTTP ${res.status})`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
logger.warn(`Server phản hồi HTTP ${res.status} — ${elapsed}ms`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const elapsed = Date.now() - start;
|
|
35
|
+
const msg = err.name === 'AbortError'
|
|
36
|
+
? `Timeout sau ${elapsed}ms`
|
|
37
|
+
: err.message;
|
|
38
|
+
logger.error(`Server không phản hồi — ${msg}`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-import.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-import.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAYxC,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CA6DjE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { checkbox } from '@inquirer/prompts';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
5
|
+
import { writeConfig } from '../../services/mcp-config-writer.js';
|
|
6
|
+
export function registerImportSubcommand(configCmd) {
|
|
7
|
+
configCmd
|
|
8
|
+
.command('import <file>')
|
|
9
|
+
.description('Nhập cấu hình MCP từ file JSON')
|
|
10
|
+
.option('--yes', 'Tự động áp dụng không hỏi')
|
|
11
|
+
.action(async (file, opts) => {
|
|
12
|
+
if (!fs.existsSync(file)) {
|
|
13
|
+
logger.error(`File không tồn tại: ${file}`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let importData;
|
|
18
|
+
try {
|
|
19
|
+
importData = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
logger.error('File không phải JSON hợp lệ');
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const ides = detectIdes(process.cwd());
|
|
27
|
+
const importableIdes = ides.filter(ide => importData[ide.id]);
|
|
28
|
+
if (importableIdes.length === 0) {
|
|
29
|
+
logger.warn('File import không chứa config cho IDE nào trên máy.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let selected = importableIdes;
|
|
33
|
+
if (!opts.yes) {
|
|
34
|
+
selected = await checkbox({
|
|
35
|
+
message: 'Chọn IDE để áp dụng config:',
|
|
36
|
+
choices: importableIdes.map(ide => ({
|
|
37
|
+
name: ide.name,
|
|
38
|
+
value: ide,
|
|
39
|
+
checked: true,
|
|
40
|
+
})),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
for (const ide of selected) {
|
|
44
|
+
try {
|
|
45
|
+
const data = importData[ide.id];
|
|
46
|
+
const entries = (data?.project || data?.global);
|
|
47
|
+
if (entries && entries.length > 0) {
|
|
48
|
+
const serverUrl = entries[0].serverUrl;
|
|
49
|
+
const mcpConfig = {
|
|
50
|
+
serverName: ide.id === 'vscode' ? 'viui' : 'inet-viui',
|
|
51
|
+
serverUrl,
|
|
52
|
+
transport: 'http',
|
|
53
|
+
};
|
|
54
|
+
writeConfig(ide, mcpConfig);
|
|
55
|
+
logger.success(`${ide.name}: đã áp dụng config từ file`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger.error(`${ide.name}: lỗi — ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-rollback.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-rollback.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAUxC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CA8CnE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
5
|
+
export function registerRollbackSubcommand(configCmd) {
|
|
6
|
+
configCmd
|
|
7
|
+
.command('rollback')
|
|
8
|
+
.description('Khôi phục cấu hình từ backup (.bak)')
|
|
9
|
+
.option('--global', 'Khôi phục global config')
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
const useGlobal = !!opts.global;
|
|
12
|
+
const ides = detectIdes(process.cwd());
|
|
13
|
+
const backups = ides
|
|
14
|
+
.map(ide => {
|
|
15
|
+
const configPath = useGlobal ? ide.globalConfigPath : ide.configPath;
|
|
16
|
+
const bakPath = configPath ? `${configPath}.bak` : null;
|
|
17
|
+
return { ide, bakPath, exists: bakPath ? fs.existsSync(bakPath) : false };
|
|
18
|
+
})
|
|
19
|
+
.filter(b => b.exists);
|
|
20
|
+
if (backups.length === 0) {
|
|
21
|
+
logger.info('Không tìm thấy file backup nào.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const chosen = await select({
|
|
25
|
+
message: 'Chọn backup để khôi phục:',
|
|
26
|
+
choices: backups.map(b => {
|
|
27
|
+
const stat = fs.statSync(b.bakPath);
|
|
28
|
+
return {
|
|
29
|
+
name: `${b.ide.name} — ${b.bakPath} (${stat.mtime.toLocaleString()})`,
|
|
30
|
+
value: b,
|
|
31
|
+
};
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
const ok = await confirm({
|
|
35
|
+
message: `Khôi phục ${chosen.ide.name} từ backup?`,
|
|
36
|
+
default: true,
|
|
37
|
+
});
|
|
38
|
+
if (!ok)
|
|
39
|
+
return;
|
|
40
|
+
const configPath = useGlobal ? chosen.ide.globalConfigPath : chosen.ide.configPath;
|
|
41
|
+
if (fs.existsSync(configPath)) {
|
|
42
|
+
fs.copyFileSync(configPath, `${configPath}.pre-rollback.bak`);
|
|
43
|
+
}
|
|
44
|
+
fs.copyFileSync(chosen.bakPath, configPath);
|
|
45
|
+
logger.success(`${chosen.ide.name}: đã khôi phục từ backup`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-setup.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-setup.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA+DxC,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CA0DhE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { checkbox, confirm } from '@inquirer/prompts';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
5
|
+
import { generateMcpConfig, buildConfigObject, writeConfig } from '../../services/mcp-config-writer.js';
|
|
6
|
+
import { hasViuiConfig } from '../../services/mcp-config-reader.js';
|
|
7
|
+
/** Post-config instructions per IDE */
|
|
8
|
+
const IDE_INSTRUCTIONS = {
|
|
9
|
+
vscode: 'Nhấn Ctrl+Shift+P → "Reload Window" để áp dụng',
|
|
10
|
+
cursor: 'Khởi động lại Cursor để áp dụng',
|
|
11
|
+
claude: 'MCP sẽ tự động nhận config ở lần chạy tiếp theo',
|
|
12
|
+
windsurf: 'Khởi động lại Windsurf để áp dụng',
|
|
13
|
+
trae: 'Khởi động lại Trae để áp dụng',
|
|
14
|
+
gemini: 'MCP sẽ tự động nhận config ở lần chạy tiếp theo',
|
|
15
|
+
};
|
|
16
|
+
/** Chọn IDE qua @inquirer/prompts checkbox */
|
|
17
|
+
async function promptSelectIdes(ides) {
|
|
18
|
+
const choices = ides
|
|
19
|
+
.filter(ide => ide.detected)
|
|
20
|
+
.map(ide => ({ name: ide.name, value: ide, checked: true }));
|
|
21
|
+
if (choices.length === 0) {
|
|
22
|
+
logger.warn('Không phát hiện IDE nào.');
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return checkbox({ message: 'Chọn IDE để cấu hình MCP:', choices });
|
|
26
|
+
}
|
|
27
|
+
/** Ghi config cho 1 IDE, hỗ trợ dry-run */
|
|
28
|
+
function applyConfig(ide, useRemote, useGlobal, dryRun) {
|
|
29
|
+
try {
|
|
30
|
+
const mcpConfig = generateMcpConfig(ide, useRemote);
|
|
31
|
+
if (dryRun) {
|
|
32
|
+
const targetPath = useGlobal ? ide.globalConfigPath : ide.configPath;
|
|
33
|
+
logger.info(`${ide.name} → ${targetPath}`);
|
|
34
|
+
const preview = buildConfigObject(ide, mcpConfig);
|
|
35
|
+
console.log(chalk.dim(JSON.stringify(preview, null, 2)));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
writeConfig(ide, mcpConfig, useGlobal);
|
|
39
|
+
const targetPath = useGlobal ? ide.globalConfigPath : ide.configPath;
|
|
40
|
+
logger.success(`${ide.name}: đã ghi cấu hình → ${targetPath}`);
|
|
41
|
+
logger.dim(` ${IDE_INSTRUCTIONS[ide.id] || 'Khởi động lại IDE để áp dụng'}`);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logger.error(`${ide.name}: lỗi — ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function registerSetupSubcommand(configCmd) {
|
|
48
|
+
configCmd
|
|
49
|
+
.command('setup', { isDefault: true })
|
|
50
|
+
.description('Cấu hình MCP server cho IDE (mặc định)')
|
|
51
|
+
.option('--remote', 'Dùng server từ xa (mặc định)')
|
|
52
|
+
.option('--local', 'Dùng server cục bộ')
|
|
53
|
+
.option('--yes', 'Tự động cấu hình tất cả IDE đã phát hiện')
|
|
54
|
+
.option('--dry-run', 'Xem trước cấu hình, không ghi file')
|
|
55
|
+
.option('--global', 'Cấu hình global thay vì project-level')
|
|
56
|
+
.action(async (opts) => {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
const useRemote = !opts.local;
|
|
59
|
+
const useGlobal = !!opts.global;
|
|
60
|
+
logger.header('viui config — Cấu hình MCP Server\n');
|
|
61
|
+
if (opts.dryRun)
|
|
62
|
+
logger.warn('Chế độ DRY-RUN — không ghi file\n');
|
|
63
|
+
const serverLabel = useRemote
|
|
64
|
+
? chalk.cyan('https://viui.inet.vn/mcp') + chalk.dim(' (từ xa)')
|
|
65
|
+
: chalk.yellow('http://localhost:3200/mcp') + chalk.dim(' (cục bộ)');
|
|
66
|
+
logger.info(`Server: ${serverLabel}`);
|
|
67
|
+
logger.info(`Scope: ${useGlobal ? 'Global (hệ thống)' : 'Project (thư mục hiện tại)'}`);
|
|
68
|
+
logger.br();
|
|
69
|
+
const ides = detectIdes(cwd);
|
|
70
|
+
const available = useGlobal
|
|
71
|
+
? ides.filter(i => i.globalConfigPath !== null)
|
|
72
|
+
: ides.filter(i => i.configPath !== '');
|
|
73
|
+
let selected;
|
|
74
|
+
if (opts.yes) {
|
|
75
|
+
selected = available.filter(i => i.detected);
|
|
76
|
+
if (selected.length === 0) {
|
|
77
|
+
logger.warn('Không phát hiện IDE nào.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
logger.info(`Tự động cấu hình ${selected.length} IDE...`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
selected = await promptSelectIdes(available);
|
|
84
|
+
if (selected.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const ide of selected) {
|
|
88
|
+
if (hasViuiConfig(ide, useGlobal) && !opts.yes && !opts.dryRun) {
|
|
89
|
+
const overwrite = await confirm({
|
|
90
|
+
message: `${ide.name} đã có cấu hình MCP. Ghi đè?`,
|
|
91
|
+
default: true,
|
|
92
|
+
});
|
|
93
|
+
if (!overwrite)
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
applyConfig(ide, useRemote, useGlobal, !!opts.dryRun);
|
|
97
|
+
}
|
|
98
|
+
logger.br();
|
|
99
|
+
if (!opts.dryRun) {
|
|
100
|
+
logger.dim('Khởi động lại IDE để áp dụng cấu hình MCP.');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-status.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-status.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWxC,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CA4CjE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { logger } from '../../utils/logger.js';
|
|
3
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
4
|
+
import { readMcpConfig } from '../../services/mcp-config-reader.js';
|
|
5
|
+
export function registerStatusSubcommand(configCmd) {
|
|
6
|
+
configCmd
|
|
7
|
+
.command('status')
|
|
8
|
+
.description('Hiển thị trạng thái cấu hình MCP của tất cả IDE')
|
|
9
|
+
.option('--global', 'Hiển thị global config thay vì project-level')
|
|
10
|
+
.option('--json', 'Xuất kết quả dạng JSON')
|
|
11
|
+
.action((opts) => {
|
|
12
|
+
const ides = detectIdes(process.cwd());
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
const result = ides.map(ide => ({
|
|
15
|
+
id: ide.id,
|
|
16
|
+
name: ide.name,
|
|
17
|
+
detected: ide.detected,
|
|
18
|
+
projectConfig: readMcpConfig(ide, false),
|
|
19
|
+
globalConfig: ide.globalConfigPath ? readMcpConfig(ide, true) : null,
|
|
20
|
+
}));
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
logger.header('Trạng thái MCP Config\n');
|
|
25
|
+
for (const ide of ides) {
|
|
26
|
+
const projectEntries = readMcpConfig(ide, false);
|
|
27
|
+
const globalEntries = ide.globalConfigPath ? readMcpConfig(ide, true) : null;
|
|
28
|
+
const projectStatus = projectEntries.length > 0
|
|
29
|
+
? chalk.green('✓') + ' ' + projectEntries[0].serverUrl
|
|
30
|
+
: ide.configPath ? chalk.dim('✗ chưa cấu hình') : chalk.dim('— không hỗ trợ');
|
|
31
|
+
const globalStatus = globalEntries === null
|
|
32
|
+
? chalk.dim('— không hỗ trợ')
|
|
33
|
+
: globalEntries.length > 0
|
|
34
|
+
? chalk.green('✓') + ' ' + globalEntries[0].serverUrl
|
|
35
|
+
: chalk.dim('✗ chưa cấu hình');
|
|
36
|
+
const detected = ide.detected ? chalk.green('●') : chalk.dim('○');
|
|
37
|
+
console.log(` ${detected} ${ide.name.padEnd(14)} Project: ${projectStatus}`);
|
|
38
|
+
console.log(` ${''.padEnd(17)} Global: ${globalStatus}`);
|
|
39
|
+
logger.br();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-uninstall.d.ts","sourceRoot":"","sources":["../../../src/commands/config/config-uninstall.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAuCxC,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CA6CpE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { confirm, checkbox } from '@inquirer/prompts';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { detectIdes } from '../../services/ide-detector.js';
|
|
5
|
+
import { hasViuiConfig } from '../../services/mcp-config-reader.js';
|
|
6
|
+
/** Xóa viui/inet-viui entry khỏi config file */
|
|
7
|
+
function removeViuiEntry(ide, useGlobal) {
|
|
8
|
+
const configPath = useGlobal ? ide.globalConfigPath : ide.configPath;
|
|
9
|
+
if (!configPath || !fs.existsSync(configPath))
|
|
10
|
+
return;
|
|
11
|
+
fs.copyFileSync(configPath, `${configPath}.bak`);
|
|
12
|
+
let data;
|
|
13
|
+
try {
|
|
14
|
+
data = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
logger.error(`${ide.name}: config file corrupt, đã backup tại ${configPath}.bak`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const topKey = ide.configFormat === 'vscode' ? 'servers' : 'mcpServers';
|
|
21
|
+
const servers = data[topKey];
|
|
22
|
+
if (servers) {
|
|
23
|
+
delete servers['viui'];
|
|
24
|
+
delete servers['inet-viui'];
|
|
25
|
+
if (Object.keys(servers).length === 0)
|
|
26
|
+
delete data[topKey];
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(configPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
29
|
+
}
|
|
30
|
+
export function registerUninstallSubcommand(configCmd) {
|
|
31
|
+
configCmd
|
|
32
|
+
.command('uninstall')
|
|
33
|
+
.description('Xóa cấu hình MCP viui khỏi IDE')
|
|
34
|
+
.option('--global', 'Xóa global config thay vì project-level')
|
|
35
|
+
.option('--yes', 'Tự động xóa không hỏi')
|
|
36
|
+
.action(async (opts) => {
|
|
37
|
+
const useGlobal = !!opts.global;
|
|
38
|
+
const ides = detectIdes(process.cwd());
|
|
39
|
+
const configured = ides.filter(ide => hasViuiConfig(ide, useGlobal));
|
|
40
|
+
if (configured.length === 0) {
|
|
41
|
+
logger.info('Không tìm thấy cấu hình MCP viui nào.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let selected;
|
|
45
|
+
if (opts.yes) {
|
|
46
|
+
selected = configured;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
selected = await checkbox({
|
|
50
|
+
message: 'Chọn IDE để xóa cấu hình MCP:',
|
|
51
|
+
choices: configured.map(ide => ({ name: ide.name, value: ide, checked: true })),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (selected.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
if (!opts.yes) {
|
|
57
|
+
const ok = await confirm({
|
|
58
|
+
message: `Xóa cấu hình MCP viui khỏi ${selected.length} IDE?`,
|
|
59
|
+
default: false,
|
|
60
|
+
});
|
|
61
|
+
if (!ok)
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const ide of selected) {
|
|
65
|
+
try {
|
|
66
|
+
removeViuiEntry(ide, useGlobal);
|
|
67
|
+
logger.success(`${ide.name}: đã xóa cấu hình MCP viui`);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
logger.error(`${ide.name}: lỗi — ${err.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AASxC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAY5D"}
|