@xiaou66/vite-plugin-vue-mcp-next 1.0.0 → 1.0.2

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 CHANGED
@@ -43,14 +43,18 @@ SSE: http://localhost:<vite-port>/__mcp/sse
43
43
  Streamable HTTP: http://localhost:<vite-port>/__mcp/mcp
44
44
  ```
45
45
 
46
- 启动 Vite dev server 后,插件会自动写入常见 AI 客户端的项目级 MCP 配置,服务名默认是 `vue-mcp-next`。自动配置只会在缺少同名 server 条目时新增配置;如果用户已经配置了 `vue-mcp-next`,插件不会重复写入或覆盖原配置。
46
+ 启动 Vite dev server 后,插件会按项目中已经存在的客户端入口自动写入项目级 MCP 配置,服务名默认是 `vite-mcp-next`。自动配置只会在缺少同名 server 条目时新增配置;如果用户已经配置了历史默认名 `vue-mcp-next`,插件会把它迁移为 `vite-mcp-next` 并保留原配置内容。
47
47
 
48
- | 客户端 | 自动配置文件 | 默认端点 |
49
- | ----------- | -------------------- | --------------- |
50
- | Cursor | `.cursor/mcp.json` | SSE |
51
- | Codex | `.codex/config.toml` | Streamable HTTP |
52
- | Claude Code | `.mcp.json` | SSE |
53
- | Trae | `.trae/mcp.json` | SSE |
48
+ 默认自动探测规则如下:
49
+
50
+ | 客户端 | 自动探测入口 | 自动配置文件 | 默认端点 |
51
+ | ----------- | ------------ | -------------------- | --------------- |
52
+ | Cursor | `.cursor/` | `.cursor/mcp.json` | SSE |
53
+ | Codex | `.codex/` | `.codex/config.toml` | Streamable HTTP |
54
+ | Claude Code | `.mcp.json` | `.mcp.json` | SSE |
55
+ | Trae | `.trae/` | `.trae/mcp.json` | SSE |
56
+
57
+ 如果项目中没有对应入口,默认不会创建该客户端配置。需要强制创建时,可以在 `mcpClients` 中显式设置对应客户端为 `true`;需要禁用时显式设置为 `false`。
54
58
 
55
59
  实际端口以启动日志中的 `MCP: SSE server is running at ...` 和 `MCP: Streamable HTTP server is running at ...` 为准。
56
60
 
@@ -63,7 +67,7 @@ Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
63
67
  ```json
64
68
  {
65
69
  "mcpServers": {
66
- "vue-mcp-next": {
70
+ "vite-mcp-next": {
67
71
  "type": "sse",
68
72
  "url": "http://localhost:5173/__mcp/sse"
69
73
  }
@@ -74,14 +78,48 @@ Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
74
78
  Codex 使用 TOML 配置:
75
79
 
76
80
  ```toml
77
- [mcp_servers.vue-mcp-next]
81
+ [mcp_servers.vite-mcp-next]
78
82
  url = "http://localhost:5173/__mcp/mcp"
79
83
  ```
80
84
 
81
85
  `5173` 是示例端口。若 Vite 使用了其他端口,请替换为启动日志中打印的 MCP 地址。
82
86
 
87
+ ### 自动复制 AI 使用指南
88
+
89
+ 插件随 npm 包发布一份通用 Skill 文件:`skills/vite-mcp-next/SKILL.md`。Vite dev server 启动时,插件会在检测到项目中已经存在对应 AI 工具入口后,把这份静态文件复制到对应工具目录。插件不会在运行时拼接或生成 Skill 正文,包内 `skills/vite-mcp-next/SKILL.md` 是唯一内容来源。
90
+
91
+ | 客户端 | 探测入口 | 自动复制目标 | 说明 |
92
+ | ----------- | ---------- | ---------------------------------------------- | ------------------------------------- |
93
+ | Codex | `.codex/` | `.codex/skills/vite-mcp-next/SKILL.md` | 从包内 `skills/vite-mcp-next/SKILL.md` 复制 |
94
+ | Claude Code | `.claude/` | `.claude/skills/vite-mcp-next/SKILL.md` | 从包内 `skills/vite-mcp-next/SKILL.md` 复制 |
95
+ | Cursor | `.cursor/` | `.cursor/rules/vite-mcp-next.mdc` | 从同一份 Skill 文件复制,作为项目规则参考 |
96
+ | Trae | `.trae/` | 不自动复制 | 当前只自动写 MCP 配置,Rule 路径未作为首版稳定能力 |
97
+
98
+ 使用指南覆盖以下工具顺序:
99
+
100
+ 1. `list_pages`:先确认页面、runtime 和 CDP target
101
+ 2. `get_dom_tree` / `query_dom`:检查 DOM 结构
102
+ 3. `take_screenshot`:做视觉验证
103
+ 4. `get_console_logs`:排查 Console 报错
104
+ 5. `get_network_requests` / `get_network_request_detail`:排查接口请求
105
+ 6. `get_component_tree` / `get_component_state` / `get_router_info` / `get_pinia_tree` / `get_pinia_state`:检查 Vue 语义状态
106
+
107
+ 如果不希望插件复制任何 AI 使用指南,可以关闭:
108
+
109
+ ```ts
110
+ vueMcpNext({
111
+ skill: {
112
+ autoConfig: false
113
+ }
114
+ })
115
+ ```
116
+
117
+ 自动复制只会更新带有插件生成标记的文件;如果目标文件已经存在但不是插件生成的内容,插件会跳过并保留用户文件。没有使用某个 AI 工具时,对应入口目录不存在,插件不会创建该工具目录。
118
+
83
119
  ## 完整配置
84
120
 
121
+ `mcpClients` 中的布尔值保留为开关语义:默认解析后为 `true`,但写入阶段会先做项目入口自动探测;用户在配置中显式写出 `true` 时表示强制创建对应客户端配置。
122
+
85
123
  ```ts
86
124
  vueMcpNext({
87
125
  mcpPath: '/__mcp',
@@ -93,7 +131,10 @@ vueMcpNext({
93
131
  codex: true,
94
132
  claudeCode: true,
95
133
  trae: true,
96
- serverName: 'vue-mcp-next'
134
+ serverName: 'vite-mcp-next'
135
+ },
136
+ skill: {
137
+ autoConfig: true
97
138
  },
98
139
  appendTo: undefined,
99
140
  runtime: {
@@ -147,8 +188,9 @@ vueMcpNext({
147
188
  | `mcpPath` | `string` | `'/__mcp'` | MCP 服务挂载路径,实际 SSE 地址是 `${mcpPath}/sse`,Streamable HTTP 地址是 `${mcpPath}/mcp` |
148
189
  | `host` | `string` | `'localhost'` | 打印 MCP 地址和写入 MCP 客户端配置时使用的 host |
149
190
  | `printUrl` | `boolean` | `true` | 是否在 Vite 启动日志中打印 MCP SSE 地址 |
150
- | `mcpClients` | `{ cursor?: boolean; codex?: boolean; claudeCode?: boolean; trae?: boolean; serverName?: string }` | 全部启用 | 是否自动写入 Cursor、Codex、Claude Code、Trae 的项目级 MCP 配置 |
151
- | `updateCursorMcpJson` | `boolean \| { enabled: boolean; serverName?: string }` | `true` | 兼容旧配置,建议新项目使用 `mcpClients` |
191
+ | `mcpClients` | `{ cursor?: boolean; codex?: boolean; claudeCode?: boolean; trae?: boolean; serverName?: string }` | 自动探测 | 是否写入 Cursor、Codex、Claude Code、Trae 的项目级 MCP 配置;默认只处理项目中已有入口,显式 `true` 强制创建 |
192
+ | `updateCursorMcpJson` | `boolean \| { enabled: boolean; serverName?: string }` | 自动探测 | 兼容旧配置;默认只在 `.cursor` 已存在时写入,建议新项目使用 `mcpClients` |
193
+ | `skill` | `{ autoConfig?: boolean }` | `{ autoConfig: true }` | 是否在检测到 Codex、Claude Code、Cursor 项目入口时自动复制包内 AI 使用指南 |
152
194
  | `appendTo` | `string \| RegExp` | `undefined` | 非 HTML 入口注入点。配置后会在匹配入口模块前追加 runtime import |
153
195
  | `screenshot` | `ScreenshotOptions` | CDP 优先,snapdom 降级 | 页面截图配置,控制真截图、DOM 降级截图、体积上限和 snapdom 扩展 |
154
196
  | `screenshot.type` | `'path' \| 'base64'` | `'path'` | 项目级控制截图返回文件路径还是 base64 数据 |
package/dist/index.cjs CHANGED
@@ -81,7 +81,8 @@ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-
81
81
  var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
82
82
  var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
83
83
  var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
84
- var DEFAULT_MCP_CLIENT_SERVER_NAME = "vue-mcp-next";
84
+ var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
85
+ var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
85
86
  var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
86
87
  var DEFAULT_OPTIONS = {
87
88
  mcpPath: DEFAULT_MCP_PATH,
@@ -98,6 +99,9 @@ var DEFAULT_OPTIONS = {
98
99
  trae: true,
99
100
  serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
100
101
  },
102
+ skill: {
103
+ autoConfig: true
104
+ },
101
105
  runtime: {
102
106
  mode: "auto",
103
107
  evaluate: {
@@ -155,6 +159,10 @@ function mergeOptions(options = {}) {
155
159
  ...options,
156
160
  updateCursorMcpJson: cursorConfig,
157
161
  mcpClients,
162
+ skill: {
163
+ ...DEFAULT_OPTIONS.skill,
164
+ ...options.skill
165
+ },
158
166
  runtime: {
159
167
  ...DEFAULT_OPTIONS.runtime,
160
168
  ...options.runtime,
@@ -1183,13 +1191,13 @@ function registerVueTools(server, ctx) {
1183
1191
  valueType: import_zod7.z.enum(["string", "number", "boolean", "object", "array"])
1184
1192
  }
1185
1193
  },
1186
- ({ componentName, path: path6, value, valueType }) => {
1194
+ ({ componentName, path: path8, value, valueType }) => {
1187
1195
  if (!ctx.rpcServer) {
1188
1196
  return vueBridgeUnavailable();
1189
1197
  }
1190
1198
  void ctx.rpcServer.editComponentState({
1191
1199
  componentName,
1192
- path: path6,
1200
+ path: path8,
1193
1201
  value,
1194
1202
  valueType
1195
1203
  });
@@ -1771,6 +1779,7 @@ function getPluginPath(plugin) {
1771
1779
  }
1772
1780
 
1773
1781
  // src/plugin/mcpClientConfig/index.ts
1782
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1774
1783
  var import_node_path6 = __toESM(require("path"), 1);
1775
1784
 
1776
1785
  // src/plugin/mcpClientConfig/codexConfig.ts
@@ -1790,10 +1799,18 @@ async function updateCodexMcpClientConfig(options) {
1790
1799
  }
1791
1800
  function replaceOrAppendOwnedBlock(current, options) {
1792
1801
  const block = createCodexServerBlock(options);
1793
- const matcher = createOwnedBlockMatcher(options.serverName);
1802
+ const matcher = createServerTableMatcher(options.serverName);
1794
1803
  if (matcher.test(current)) {
1795
1804
  return ensureTrailingNewline(current);
1796
1805
  }
1806
+ const legacyServerName = options.legacyServerNames?.find(
1807
+ (serverName) => createServerTableMatcher(serverName).test(current)
1808
+ );
1809
+ if (legacyServerName) {
1810
+ return ensureTrailingNewline(
1811
+ renameServerTableHeaders(current, legacyServerName, options.serverName)
1812
+ );
1813
+ }
1797
1814
  const separator = current.trim() ? "\n\n" : "";
1798
1815
  return `${trimEndNewline(current)}${separator}${block}`;
1799
1816
  }
@@ -1802,25 +1819,47 @@ function createCodexServerBlock(options) {
1802
1819
  url = ${quoteTomlString(options.mcpUrl)}
1803
1820
  `;
1804
1821
  }
1805
- function createOwnedBlockMatcher(serverName) {
1822
+ function createServerTableMatcher(serverName) {
1806
1823
  const plainHeader = escapeRegExp(`[mcp_servers.${serverName}]`);
1824
+ const plainChildHeader = escapeRegExp(`[mcp_servers.${serverName}.`);
1807
1825
  const quotedHeader = escapeRegExp(
1808
1826
  `[mcp_servers.${quoteTomlKey(serverName)}]`
1809
1827
  );
1828
+ const quotedChildHeader = escapeRegExp(
1829
+ `[mcp_servers.${quoteTomlKey(serverName)}.`
1830
+ );
1810
1831
  return new RegExp(
1811
- `(?:^|\\n)(?:${plainHeader}|${quotedHeader})\\n[\\s\\S]*?(?=\\n\\[|$)`
1832
+ `(?:^|\\n)(?:${plainHeader}|${plainChildHeader}|${quotedHeader}|${quotedChildHeader})`
1812
1833
  );
1813
1834
  }
1814
1835
  function createTableHeader(serverName) {
1815
- const key = /^[A-Za-z0-9_-]+$/.test(serverName) ? serverName : quoteTomlKey(serverName);
1836
+ const key = createTableKey(serverName);
1816
1837
  return `[mcp_servers.${key}]`;
1817
1838
  }
1839
+ function createTableKey(serverName) {
1840
+ return /^[A-Za-z0-9_-]+$/.test(serverName) ? serverName : quoteTomlKey(serverName);
1841
+ }
1818
1842
  function quoteTomlKey(value) {
1819
1843
  return quoteTomlString(value);
1820
1844
  }
1821
1845
  function quoteTomlString(value) {
1822
1846
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1823
1847
  }
1848
+ function renameServerTableHeaders(current, fromServerName, toServerName) {
1849
+ return current.split("\n").map((line) => renameServerTableHeader(line, fromServerName, toServerName)).join("\n");
1850
+ }
1851
+ function renameServerTableHeader(line, fromServerName, toServerName) {
1852
+ const toKey = createTableKey(toServerName);
1853
+ const fromKeys = [fromServerName, quoteTomlKey(fromServerName)];
1854
+ for (const fromKey of fromKeys) {
1855
+ const prefix = `[mcp_servers.${fromKey}`;
1856
+ const nextChar = line[prefix.length];
1857
+ if (line.startsWith(prefix) && (nextChar === "]" || nextChar === ".")) {
1858
+ return `[mcp_servers.${toKey}${line.slice(prefix.length)}`;
1859
+ }
1860
+ }
1861
+ return line;
1862
+ }
1824
1863
  async function readOptionalTextFile(filePath) {
1825
1864
  try {
1826
1865
  return await import_promises2.default.readFile(filePath, "utf-8");
@@ -1862,18 +1901,38 @@ async function updateJsonMcpClientConfig(options) {
1862
1901
  if (Object.hasOwn(mcpServers, options.serverName)) {
1863
1902
  return;
1864
1903
  }
1904
+ const migratedMcpServers = renameLegacyServer(mcpServers, options);
1905
+ if (migratedMcpServers) {
1906
+ config.mcpServers = migratedMcpServers;
1907
+ await writeJsonConfig(options.configPath, config);
1908
+ return;
1909
+ }
1865
1910
  mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
1866
1911
  config.mcpServers = mcpServers;
1867
- await import_promises3.default.mkdir(import_node_path5.default.dirname(options.configPath), { recursive: true });
1868
- await import_promises3.default.writeFile(
1869
- options.configPath,
1870
- `${JSON.stringify(config, null, 2)}
1871
- `
1872
- );
1912
+ await writeJsonConfig(options.configPath, config);
1873
1913
  } catch (error) {
1874
1914
  warnConfigFailure(options, formatError2(error));
1875
1915
  }
1876
1916
  }
1917
+ function renameLegacyServer(mcpServers, options) {
1918
+ const legacyServerName = options.legacyServerNames?.find(
1919
+ (name) => Object.hasOwn(mcpServers, name)
1920
+ );
1921
+ if (!legacyServerName) {
1922
+ return void 0;
1923
+ }
1924
+ return Object.fromEntries(
1925
+ Object.entries(mcpServers).map(([serverName, serverConfig]) => [
1926
+ serverName === legacyServerName ? options.serverName : serverName,
1927
+ serverConfig
1928
+ ])
1929
+ );
1930
+ }
1931
+ async function writeJsonConfig(configPath, config) {
1932
+ await import_promises3.default.mkdir(import_node_path5.default.dirname(configPath), { recursive: true });
1933
+ await import_promises3.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
1934
+ `);
1935
+ }
1877
1936
  async function readJsonConfig(configPath) {
1878
1937
  const raw = await readOptionalTextFile2(configPath);
1879
1938
  if (!raw.trim()) {
@@ -1907,50 +1966,271 @@ function isNodeError2(error) {
1907
1966
  }
1908
1967
 
1909
1968
  // src/plugin/mcpClientConfig/index.ts
1910
- async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options) {
1969
+ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options, userOptions = {}) {
1911
1970
  const serverName = options.serverName;
1912
- const jobs = [];
1913
- if (options.cursor) {
1914
- jobs.push(
1915
- updateJsonMcpClientConfig({
1971
+ const legacyServerNames = getLegacyServerNames(serverName);
1972
+ const descriptors = [
1973
+ {
1974
+ root,
1975
+ clientName: "cursor",
1976
+ enabled: options.cursor,
1977
+ entryPath: import_node_path6.default.join(root, ".cursor"),
1978
+ entryKind: "directory",
1979
+ userOptions,
1980
+ createJob: () => updateJsonMcpClientConfig({
1916
1981
  clientName: "Cursor",
1917
1982
  configPath: import_node_path6.default.join(root, ".cursor", "mcp.json"),
1918
1983
  mcpUrl: sseUrl,
1919
- serverName
1984
+ serverName,
1985
+ legacyServerNames
1920
1986
  })
1921
- );
1922
- }
1923
- if (options.codex) {
1924
- jobs.push(
1925
- updateCodexMcpClientConfig({
1987
+ },
1988
+ {
1989
+ root,
1990
+ clientName: "codex",
1991
+ enabled: options.codex,
1992
+ entryPath: import_node_path6.default.join(root, ".codex"),
1993
+ entryKind: "directory",
1994
+ userOptions,
1995
+ createJob: () => updateCodexMcpClientConfig({
1926
1996
  configPath: import_node_path6.default.join(root, ".codex", "config.toml"),
1927
1997
  mcpUrl: streamableHttpUrl,
1928
- serverName
1998
+ serverName,
1999
+ legacyServerNames
1929
2000
  })
1930
- );
1931
- }
1932
- if (options.claudeCode) {
1933
- jobs.push(
1934
- updateJsonMcpClientConfig({
2001
+ },
2002
+ {
2003
+ root,
2004
+ clientName: "claudeCode",
2005
+ enabled: options.claudeCode,
2006
+ entryPath: import_node_path6.default.join(root, ".mcp.json"),
2007
+ entryKind: "file",
2008
+ userOptions,
2009
+ createJob: () => updateJsonMcpClientConfig({
1935
2010
  clientName: "Claude Code",
1936
2011
  configPath: import_node_path6.default.join(root, ".mcp.json"),
1937
2012
  mcpUrl: sseUrl,
1938
- serverName
2013
+ serverName,
2014
+ legacyServerNames
1939
2015
  })
1940
- );
1941
- }
1942
- if (options.trae) {
1943
- jobs.push(
1944
- updateJsonMcpClientConfig({
2016
+ },
2017
+ {
2018
+ root,
2019
+ clientName: "trae",
2020
+ enabled: options.trae,
2021
+ entryPath: import_node_path6.default.join(root, ".trae"),
2022
+ entryKind: "directory",
2023
+ userOptions,
2024
+ createJob: () => updateJsonMcpClientConfig({
1945
2025
  clientName: "Trae",
1946
2026
  configPath: import_node_path6.default.join(root, ".trae", "mcp.json"),
1947
2027
  mcpUrl: sseUrl,
1948
- serverName
2028
+ serverName,
2029
+ legacyServerNames
1949
2030
  })
2031
+ }
2032
+ ];
2033
+ const jobs = await createClientConfigJobs(descriptors);
2034
+ await Promise.all(jobs);
2035
+ }
2036
+ async function createClientConfigJobs(descriptors) {
2037
+ const jobs = [];
2038
+ for (const descriptor of descriptors) {
2039
+ if (await shouldUpdateClientConfig(descriptor)) {
2040
+ jobs.push(descriptor.createJob());
2041
+ }
2042
+ }
2043
+ return jobs;
2044
+ }
2045
+ function getLegacyServerNames(serverName) {
2046
+ return serverName === DEFAULT_MCP_CLIENT_SERVER_NAME ? LEGACY_MCP_CLIENT_SERVER_NAMES : [];
2047
+ }
2048
+ async function shouldUpdateClientConfig(options) {
2049
+ if (!options.enabled) {
2050
+ return false;
2051
+ }
2052
+ if (isClientExplicitlyConfigured(options.clientName, options.userOptions)) {
2053
+ return true;
2054
+ }
2055
+ return hasExpectedEntry(options.entryPath, options.entryKind);
2056
+ }
2057
+ function isClientExplicitlyConfigured(clientName, userOptions) {
2058
+ if (Object.hasOwn(userOptions.mcpClients ?? {}, clientName)) {
2059
+ return true;
2060
+ }
2061
+ return clientName === "cursor" && userOptions.updateCursorMcpJson !== void 0;
2062
+ }
2063
+ async function hasExpectedEntry(entryPath, entryKind) {
2064
+ try {
2065
+ const stat = await import_promises4.default.stat(entryPath);
2066
+ return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
2067
+ } catch (error) {
2068
+ if (isNodeError3(error) && error.code === "ENOENT") {
2069
+ return false;
2070
+ }
2071
+ console.warn(
2072
+ `[vite-plugin-vue-mcp-next] Failed to inspect MCP client entry at ${entryPath}: ${formatError3(error)}`
2073
+ );
2074
+ return false;
2075
+ }
2076
+ }
2077
+ function formatError3(error) {
2078
+ return error instanceof Error ? error.message : String(error);
2079
+ }
2080
+ function isNodeError3(error) {
2081
+ return error instanceof Error && "code" in error;
2082
+ }
2083
+
2084
+ // src/plugin/skillConfig/index.ts
2085
+ var import_promises6 = __toESM(require("fs/promises"), 1);
2086
+ var import_node_path8 = __toESM(require("path"), 1);
2087
+
2088
+ // src/plugin/skillConfig/writers.ts
2089
+ var import_promises5 = __toESM(require("fs/promises"), 1);
2090
+ var import_node_path7 = __toESM(require("path"), 1);
2091
+ var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
2092
+ async function writeGeneratedTextFile(options) {
2093
+ try {
2094
+ const current = await readOptionalTextFile3(options.filePath);
2095
+ if (current && !current.includes(GENERATED_SKILL_CONFIG_MARKER)) {
2096
+ console.warn(
2097
+ `[vite-plugin-vue-mcp-next] Skipped ${options.targetName} at ${options.filePath}: file is not generated by this plugin`
2098
+ );
2099
+ return;
2100
+ }
2101
+ await import_promises5.default.mkdir(import_node_path7.default.dirname(options.filePath), { recursive: true });
2102
+ await import_promises5.default.writeFile(options.filePath, options.content);
2103
+ } catch (error) {
2104
+ console.warn(
2105
+ `[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
1950
2106
  );
1951
2107
  }
2108
+ }
2109
+ async function readOptionalTextFile3(filePath) {
2110
+ try {
2111
+ return await import_promises5.default.readFile(filePath, "utf-8");
2112
+ } catch (error) {
2113
+ if (isNodeError4(error) && error.code === "ENOENT") {
2114
+ return "";
2115
+ }
2116
+ throw error;
2117
+ }
2118
+ }
2119
+ function formatError4(error) {
2120
+ return error instanceof Error ? error.message : String(error);
2121
+ }
2122
+ function isNodeError4(error) {
2123
+ return error instanceof Error && "code" in error;
2124
+ }
2125
+
2126
+ // src/plugin/skillConfig/index.ts
2127
+ var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
2128
+ var PACKAGED_SKILL_PATH = import_node_path8.default.join("skills", "vite-mcp-next", "SKILL.md");
2129
+ async function updateSkillConfigs(root, options) {
2130
+ if (!options.autoConfig) {
2131
+ return;
2132
+ }
2133
+ const descriptors = createSkillConfigDescriptors(root);
2134
+ const skillContent = await safelyReadPackagedSkillContent(root);
2135
+ if (!skillContent) {
2136
+ return;
2137
+ }
2138
+ const jobs = await createSkillConfigJobs(descriptors, skillContent);
1952
2139
  await Promise.all(jobs);
1953
2140
  }
2141
+ function createSkillConfigDescriptors(root) {
2142
+ return [
2143
+ {
2144
+ entryPath: import_node_path8.default.join(root, ".codex"),
2145
+ filePath: import_node_path8.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
2146
+ targetName: "Codex skill"
2147
+ },
2148
+ {
2149
+ entryPath: import_node_path8.default.join(root, ".claude"),
2150
+ filePath: import_node_path8.default.join(
2151
+ root,
2152
+ ".claude",
2153
+ "skills",
2154
+ "vite-mcp-next",
2155
+ "SKILL.md"
2156
+ ),
2157
+ targetName: "Claude Code skill"
2158
+ },
2159
+ {
2160
+ entryPath: import_node_path8.default.join(root, ".cursor"),
2161
+ filePath: import_node_path8.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
2162
+ targetName: "Cursor rule"
2163
+ }
2164
+ ];
2165
+ }
2166
+ async function createSkillConfigJobs(descriptors, content) {
2167
+ const jobs = [];
2168
+ for (const descriptor of descriptors) {
2169
+ if (await hasDirectoryEntry(descriptor.entryPath)) {
2170
+ jobs.push(
2171
+ writeGeneratedTextFile({
2172
+ filePath: descriptor.filePath,
2173
+ content,
2174
+ targetName: descriptor.targetName
2175
+ })
2176
+ );
2177
+ }
2178
+ }
2179
+ return jobs;
2180
+ }
2181
+ async function readPackagedSkillContent(root) {
2182
+ const candidates = getPackagedSkillCandidates(root);
2183
+ for (const candidate of candidates) {
2184
+ try {
2185
+ return await import_promises6.default.readFile(candidate, "utf-8");
2186
+ } catch (error) {
2187
+ if (isNodeError5(error) && error.code === "ENOENT") {
2188
+ continue;
2189
+ }
2190
+ throw error;
2191
+ }
2192
+ }
2193
+ throw new Error(
2194
+ `Cannot find packaged vite-mcp-next skill at ${candidates.join(", ")}`
2195
+ );
2196
+ }
2197
+ async function safelyReadPackagedSkillContent(root) {
2198
+ try {
2199
+ return await readPackagedSkillContent(root);
2200
+ } catch (error) {
2201
+ console.warn(
2202
+ `[vite-plugin-vue-mcp-next] Failed to read packaged AI skill: ${formatError5(error)}`
2203
+ );
2204
+ return void 0;
2205
+ }
2206
+ }
2207
+ function getPackagedSkillCandidates(root) {
2208
+ return [
2209
+ import_node_path8.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2210
+ import_node_path8.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2211
+ import_node_path8.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
2212
+ ];
2213
+ }
2214
+ async function hasDirectoryEntry(entryPath) {
2215
+ try {
2216
+ const stat = await import_promises6.default.stat(entryPath);
2217
+ return stat.isDirectory();
2218
+ } catch (error) {
2219
+ if (isNodeError5(error) && error.code === "ENOENT") {
2220
+ return false;
2221
+ }
2222
+ console.warn(
2223
+ `[vite-plugin-vue-mcp-next] Failed to inspect skill config entry at ${entryPath}: ${formatError5(error)}`
2224
+ );
2225
+ return false;
2226
+ }
2227
+ }
2228
+ function formatError5(error) {
2229
+ return error instanceof Error ? error.message : String(error);
2230
+ }
2231
+ function isNodeError5(error) {
2232
+ return error instanceof Error && "code" in error;
2233
+ }
1954
2234
 
1955
2235
  // src/plugin/createPlugin.ts
1956
2236
  var import_vite_dev_rpc = require("vite-dev-rpc");
@@ -2020,8 +2300,10 @@ function vueMcpNext(userOptions = {}) {
2020
2300
  root,
2021
2301
  mcpSseUrl,
2022
2302
  mcpStreamableHttpUrl,
2023
- options.mcpClients
2303
+ options.mcpClients,
2304
+ userOptions
2024
2305
  );
2306
+ await updateSkillConfigs(root, options.skill);
2025
2307
  if (options.printUrl) {
2026
2308
  setTimeout(() => {
2027
2309
  console.log(` \u279C MCP: SSE server is running at ${mcpSseUrl}`);