🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@wot-ui/cli

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wot-ui/cli - npm Package Compare versions

Comparing version
1.0.4
to
1.0.5-beta.1
+282
dist/server-B4R7zC43.mjs
import { _ as version, c as toComponentSummary, g as name, h as loadMetadataFile, i as lintProject, l as toDemoSummary, m as resolveVersion, n as getCliUpdateStatus, o as findComponent, s as listComponents } from "./update-check-YZ9xD4u9.mjs";
import process from "node:process";
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
import * as z from "zod/v4";
//#region src/mcp/prompts.ts
const WOT_EXPERT_PROMPT = [
"You are a wot-ui expert assistant.",
"Use wot_status when the user asks about tool health, updates, or unexpected missing metadata.",
"Always query component metadata before generating code.",
"Prefer using wot_list, wot_info, wot_doc, and wot_token before writing UI code.",
"Assume only wot-ui v2 is supported by this server."
].join(" ");
const WOT_PAGE_GENERATOR_PROMPT = [
"Generate wot-ui pages by first collecting every relevant component API and CSS variable.",
"Prefer existing wd-* components and documented props over ad-hoc custom markup.",
"When theme customization is involved, inspect CSS variables with wot_token first."
].join(" ");
//#endregion
//#region src/mcp/tools.ts
function jsonText(value) {
return JSON.stringify(value, null, 2);
}
function compactJsonText(value) {
return JSON.stringify(value);
}
function registerMcpTools(server, options = {}) {
server.registerTool("wot_status", {
description: "Get wot-ui MCP server and CLI update status.",
inputSchema: z.object({}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async () => {
const update = await getCliUpdateStatus({
currentVersion: version,
packageName: name,
...options.updateCheckOptions
});
return { content: [{
type: "text",
text: jsonText({
server: {
name: "wot-ui",
version
},
cli: update
})
}] };
});
server.registerTool("wot_list", {
description: "List available wot-ui components.",
inputSchema: z.object({ version: z.string().optional() }),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1 }) => {
return { content: [{
type: "text",
text: compactJsonText({ components: listComponents(version$1).map(toComponentSummary) })
}] };
});
server.registerTool("wot_info", {
description: "Get props, events, slots, and CSS variables for a component.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText(result)
}] };
});
server.registerTool("wot_doc", {
description: "Get component markdown documentation.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result?.doc) return {
isError: true,
content: [{
type: "text",
text: `Documentation not found: ${component}`
}]
};
return { content: [{
type: "text",
text: result.doc
}] };
});
server.registerTool("wot_demo", {
description: "Get component demo code or list demos.",
inputSchema: z.object({
component: z.string(),
demo: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, demo, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
if (!demo) return { content: [{
type: "text",
text: jsonText({ demos: (result.demos ?? []).map(toDemoSummary) })
}] };
const matched = result.demos?.find((item) => item.name.toLowerCase() === demo.toLowerCase());
if (!matched) return {
isError: true,
content: [{
type: "text",
text: `Demo not found: ${demo}`
}]
};
return { content: [{
type: "text",
text: jsonText(matched)
}] };
});
server.registerTool("wot_token", {
description: "Get component CSS variables.",
inputSchema: z.object({
component: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
if (!component) return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1).map((item) => ({
name: item.name,
cssVars: item.cssVars
})) })
}] };
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText({
name: result.name,
cssVars: result.cssVars
})
}] };
});
server.registerTool("wot_changelog", {
description: "Get changelog entries for the supported v2 dataset.",
inputSchema: z.object({
version: z.string().optional(),
component: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1, component }) => {
return { content: [{
type: "text",
text: jsonText({ entries: (loadMetadataFile(resolveVersion(version$1)).changelog ?? []).filter((entry) => {
const versionMatches = version$1 ? entry.version === version$1 || `v${entry.version}` === version$1 : true;
const componentMatches = component ? (entry.components ?? []).some((item) => item.toLowerCase() === component.toLowerCase()) : true;
return versionMatches && componentMatches;
}) })
}] };
});
server.registerTool("wot_lint", {
description: "Lint a local project for wot-ui related issues.",
inputSchema: z.object({
dir: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async ({ dir, version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText(lintProject(dir ?? process.cwd(), version$1))
}] };
});
}
//#endregion
//#region src/mcp/server.ts
async function startMcpServer() {
const server = new McpServer({
name: "wot-ui",
version
}, {
instructions: "Use wot-ui component tools before generating UI code. Only wot-ui v2 metadata is available in this server.",
capabilities: { logging: {} }
});
registerMcpTools(server);
getCliUpdateStatus({
currentVersion: version,
packageName: name
}).catch(() => {});
server.registerPrompt("wot-expert", { description: "General wot-ui expert workflow." }, async () => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: WOT_EXPERT_PROMPT
}
}] }));
server.registerPrompt("wot-page-generator", {
description: "Workflow for generating a wot-ui page.",
argsSchema: z.object({ goal: z.string().optional() })
}, async ({ goal }) => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: goal ? `${WOT_PAGE_GENERATOR_PROMPT} Goal: ${goal}` : WOT_PAGE_GENERATOR_PROMPT
}
}] }));
const transport = new StdioServerTransport();
await server.connect(transport);
const shutdown = async () => {
await server.close();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
//#endregion
export { startMcpServer };
import process from "node:process";
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";
import { gunzipSync } from "node:zlib";
import { parse } from "@vue/compiler-sfc";
//#region package.json
var name = "@wot-ui/cli";
var version = "1.0.5-beta.1";
//#endregion
//#region src/data/loader.ts
const currentDir = dirname(fileURLToPath(import.meta.url));
function resolveDataDir() {
const candidates = [
join(currentDir, "..", "data"),
join(currentDir, "..", "..", "data"),
join(currentDir, "data")
];
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
throw new Error("Unable to locate bundled data directory");
}
const dataDir = resolveDataDir();
function readJsonFile(baseName) {
const jsonPath = join(dataDir, `${baseName}.json`);
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
const gzipPath = join(dataDir, `${baseName}.json.gz`);
if (existsSync(gzipPath)) {
const compressed = readFileSync(gzipPath);
return JSON.parse(gunzipSync(compressed).toString("utf8"));
}
throw new Error(`Data file not found for ${baseName}`);
}
function loadVersionsFile() {
return readJsonFile("versions");
}
function loadMetadataFile(versionKey) {
return readJsonFile(versionKey);
}
//#endregion
//#region src/data/version.ts
/** Strip semver range operators (^, ~, >=, >, <=, <, =, whitespace). */
function stripRange(ver) {
return ver.replace(/[\^~>=<\s]/g, "");
}
/**
* Returns all stable version strings for major key 'v2',
* sorted ascending by semver.
*/
function stableV2Versions() {
const map = loadVersionsFile().v2 ?? {};
return Object.values(map).filter((v) => !v.includes("-")).sort((a, b) => {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
});
}
/**
* Auto-detect the wot-ui version to use.
*
* Priority:
* 1. --version flag (flagVersion arg)
* 2. node_modules/@wot-ui/ui/package.json in cwd
* 3. package.json dependencies[@wot-ui/ui] in cwd
* 4. Fallback to latest stable version from versions.json
*/
function detectVersion(flagVersion, cwd) {
const dir = cwd ?? process.cwd();
if (flagVersion) return {
version: flagVersion,
source: "flag"
};
const nmPath = join(dir, "node_modules", "@wot-ui", "ui", "package.json");
if (existsSync(nmPath)) try {
const pkg = JSON.parse(readFileSync(nmPath, "utf8"));
if (pkg.version) return {
version: pkg.version,
source: "node_modules"
};
} catch {}
const pkgPath = join(dir, "package.json");
if (existsSync(pkgPath)) try {
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
const depVersion = pkg.dependencies?.["@wot-ui/ui"] ?? pkg.devDependencies?.["@wot-ui/ui"] ?? pkg.peerDependencies?.["@wot-ui/ui"];
if (depVersion) return {
version: stripRange(depVersion),
source: "package.json"
};
} catch {}
return {
version: stableV2Versions().at(-1) ?? "2.0.0",
source: "fallback"
};
}
/**
* Resolve a version string (from detectVersion or CLI flag) to a data file key.
*
* Examples:
* undefined / 'v2' → 'v2' (major alias, data/v2.json)
* 'latest' → 'v2.0.4' (latest stable snapshot)
* '2.0' → 'v2.0.4' (minor → lookup in versions.json)
* '2.0.4' → 'v2.0.4' (exact patch)
* '2.0.0-alpha.5' → 'v2.0.0-alpha.5' (pre-release exact)
*/
function resolveVersion(requested) {
if (!requested || requested === "v2") return "v2";
const normalized = requested.trim();
if (normalized === "latest") {
const latest = stableV2Versions().at(-1);
if (!latest) return "v2";
return `v${latest}`;
}
const map = loadVersionsFile().v2 ?? {};
if (/^\d+\.\d+$/.test(normalized)) {
const patch = map[normalized];
if (!patch) throw new Error(`Unsupported wot-ui version: ${requested}`);
return `v${patch}`;
}
if (/^\d+\.\d+\.\d+/.test(normalized)) {
if (normalized.split(".")[0] !== "2") throw new Error(`Unsupported wot-ui version: ${requested}`);
return `v${normalized}`;
}
throw new Error(`Unsupported wot-ui version: ${requested}`);
}
//#endregion
//#region src/utils/terminal.ts
const ANSI = {
cyan: ["\x1B[36m", "\x1B[39m"],
dim: ["\x1B[2m", "\x1B[22m"],
green: ["\x1B[32m", "\x1B[39m"],
red: ["\x1B[31m", "\x1B[39m"],
yellow: ["\x1B[33m", "\x1B[39m"]
};
function supportsColor(options = {}) {
const env = options.env ?? process.env;
if (!(options.isTty ?? process.stderr.isTTY)) return false;
if ("NO_COLOR" in env || env.FORCE_COLOR === "0" || env.TERM === "dumb") return false;
return true;
}
function writeStderrLine(message) {
process.stderr.write(`${message}\n`);
}
function formatLogMessage(level, message, options = {}) {
const color = createColorizer(options);
return `${color.dim("[wot]")} ${styleLevel(level, message, color)}`;
}
function formatStatusLabel(status, options = {}) {
const normalized = status.toUpperCase();
const color = createColorizer(options);
if (status === "ok" || status === "pass") return color.green(normalized);
if (status === "warn" || status === "warning") return color.yellow(normalized);
return color.red(normalized);
}
function formatCommand(command, options = {}) {
return createColorizer(options).cyan(command);
}
function formatUpdateNotice(status, options = {}) {
const color = createColorizer(options);
const currentVersion = color.dim(status.currentVersion);
const latestVersion = color.green(status.latestVersion ?? "unknown");
return [
formatLogMessage("update", "Update available", options),
`${color.dim("[wot]")} ${status.packageName} ${currentVersion} -> ${latestVersion}`,
`${color.dim("[wot]")} Run: ${formatCommand(status.command, options)}`
].join("\n");
}
function createColorizer(options) {
const enabled = supportsColor(options);
return {
cyan: (value) => applyAnsi(value, ANSI.cyan, enabled),
dim: (value) => applyAnsi(value, ANSI.dim, enabled),
green: (value) => applyAnsi(value, ANSI.green, enabled),
red: (value) => applyAnsi(value, ANSI.red, enabled),
yellow: (value) => applyAnsi(value, ANSI.yellow, enabled)
};
}
function styleLevel(level, message, color) {
if (level === "error") return color.red(message);
if (level === "success") return color.green(message);
if (level === "warn" || level === "update") return color.yellow(message);
if (level === "hint") return color.cyan(message);
return message;
}
function applyAnsi(value, code, enabled) {
return enabled ? `${code[0]}${value}${code[1]}` : value;
}
//#endregion
//#region src/data/metadata.ts
function loadResolvedMetadata(version$1) {
return loadMetadataFile(resolveVersion(version$1));
}
function listComponents(version$1) {
return loadResolvedMetadata(version$1).components;
}
function filterComponents(components, keyword) {
const normalized = keyword?.trim().toLowerCase();
if (!normalized) return components;
return components.filter((component) => {
return [
component.name,
component.nameZh,
component.tag,
component.category,
component.description,
component.descriptionZh
].some((value) => value.toLowerCase().includes(normalized));
});
}
function toComponentSummary(component) {
return {
name: component.name,
nameZh: component.nameZh,
tag: component.tag,
category: component.category,
description: component.descriptionZh || component.description,
since: component.since
};
}
function toDemoSummary(demo) {
return {
name: demo.name,
title: demo.title,
description: demo.description
};
}
function findComponent(name$1, version$1) {
const normalized = name$1.trim().toLowerCase();
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
}
//#endregion
//#region src/utils/files.ts
const DEFAULT_IGNORES = new Set([
".git",
".idea",
".output",
".turbo",
".vscode",
"dist",
"build",
"coverage",
"node_modules"
]);
function walkFiles(rootDir, extensions) {
const results = [];
function visit(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (DEFAULT_IGNORES.has(entry.name)) continue;
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
visit(fullPath);
continue;
}
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
}
}
visit(rootDir);
return results;
}
function safeRelative(rootDir, filePath) {
return relative(rootDir, filePath) || ".";
}
//#endregion
//#region src/utils/scanner.ts
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
function getLineNumber(source, index) {
return source.slice(0, index).split("\n").length;
}
function collectTemplateTags(content) {
const counts = /* @__PURE__ */ new Map();
for (const match of content.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
return counts;
}
function collectImports(scriptContent) {
const imports = /* @__PURE__ */ new Set();
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
return [...imports];
}
function analyzeUsage(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
const usageMap = /* @__PURE__ */ new Map();
const imports = /* @__PURE__ */ new Set();
for (const file of files) {
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
const template = parsed.descriptor.template?.content ?? "";
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
for (const item of collectImports(script)) imports.add(item);
for (const [tag, count] of collectTemplateTags(template)) {
const known = knownByTag.get(tag);
const key = known?.name ?? tag;
const existing = usageMap.get(key);
if (existing) {
existing.count += count;
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
continue;
}
usageMap.set(key, {
name: known?.name ?? tag,
tag,
count,
files: [safeRelative(dir, file)]
});
}
}
return {
scannedFiles: files.length,
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
imports: [...imports].sort()
};
}
function lintProject(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const issues = [];
for (const file of files) {
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
for (const match of template.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
if (!findComponent(tag, version$1)) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "unknown-component",
severity: "warning",
message: `Unknown wot-ui component tag: ${tag}`
});
}
for (const match of template.matchAll(BUTTON_RE)) {
const attrs = (match[1] ?? match[3] ?? "").trim();
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "button-content",
severity: "warning",
message: "wd-button should include visible text content or an icon attribute."
});
const component = findComponent("wd-button", version$1);
for (const prop of component?.props ?? []) {
if (!prop.deprecated) continue;
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "deprecated-prop",
severity: "warning",
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
});
}
}
}
return {
scannedFiles: files.length,
issues
};
}
//#endregion
//#region src/utils/update-check.ts
const DEFAULT_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
const DEFAULT_TIMEOUT_MS = 1500;
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
function compareSemver(a, b) {
const parsedA = parseSemver(a);
const parsedB = parseSemver(b);
if (!parsedA || !parsedB) return 0;
for (const index of [
0,
1,
2
]) {
const diff = parsedA[index] - parsedB[index];
if (diff !== 0) return diff > 0 ? 1 : -1;
}
return comparePrerelease(parsedA[3], parsedB[3]);
}
function shouldCheckForCliUpdate(args = process.argv, env = process.env, isTty = process.stderr.isTTY) {
if (!isTty) return false;
if (isUpdateCheckDisabled(env) || isTruthyEnv(env.CI) || env.NODE_ENV === "test") return false;
const userArgs = args.slice(2);
if (userArgs.some((arg) => arg === "-V" || arg === "-h" || arg === "--help")) return false;
const command = userArgs.find((arg) => !arg.startsWith("-"));
return command !== "mcp" && command !== "help";
}
function checkForCliUpdate(options) {
const env = options.env ?? process.env;
const args = options.args ?? process.argv;
const stderr = options.stderr ?? process.stderr;
const isTty = options.isTty ?? process.stderr.isTTY;
if (!shouldCheckForCliUpdate(args, env, isTty)) return;
try {
const status = getCachedCliUpdateStatus(options);
if (status.updateAvailable && status.latestVersion) stderr.write(`${formatUpdateNotice(status, {
env,
isTty
})}\n`);
} catch {}
}
function getCachedCliUpdateStatus(options) {
const env = options.env ?? process.env;
const baseStatus = createBaseStatus(options, env);
if (baseStatus.disabled) return {
...baseStatus,
cached: false,
updateAvailable: false
};
const now = options.now ?? Date.now();
const cached = readCache(options.cacheFile ?? getDefaultCacheFile(env));
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
const latestVersion = cacheIsFresh ? cached.latestVersion : void 0;
return {
...baseStatus,
cached: cacheIsFresh,
checkedAt: cacheIsFresh ? cached.checkedAt : void 0,
latestVersion,
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
};
}
async function getCliUpdateStatus(options) {
const env = options.env ?? process.env;
const baseStatus = createBaseStatus(options, env);
if (baseStatus.disabled) return {
...baseStatus,
cached: false,
updateAvailable: false
};
const now = options.now ?? Date.now();
const cacheFile = options.cacheFile ?? getDefaultCacheFile(env);
const cached = readCache(cacheFile);
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
const result = cacheIsFresh ? cached : await fetchAndCacheLatestVersion(options, cacheFile, now);
const latestVersion = result.latestVersion;
return {
...baseStatus,
cached: cacheIsFresh,
checkedAt: result.checkedAt,
latestVersion,
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
};
}
function createBaseStatus(options, env) {
return {
command: `npm install -g ${options.packageName}`,
currentVersion: options.currentVersion,
disabled: isUpdateCheckDisabled(env),
packageName: options.packageName
};
}
function parseSemver(version$1) {
const match = version$1.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/);
if (!match) return void 0;
return [
Number(match[1]),
Number(match[2]),
Number(match[3]),
match[4]
];
}
function comparePrerelease(a, b) {
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
const identifiersA = a.split(".");
const identifiersB = b.split(".");
const length = Math.max(identifiersA.length, identifiersB.length);
for (let index = 0; index < length; index++) {
const identifierA = identifiersA[index];
const identifierB = identifiersB[index];
if (identifierA === void 0) return -1;
if (identifierB === void 0) return 1;
if (identifierA === identifierB) continue;
const numberA = parseNumericIdentifier(identifierA);
const numberB = parseNumericIdentifier(identifierB);
if (numberA !== void 0 && numberB !== void 0) return numberA > numberB ? 1 : -1;
if (numberA !== void 0) return -1;
if (numberB !== void 0) return 1;
return identifierA > identifierB ? 1 : -1;
}
return 0;
}
function parseNumericIdentifier(identifier) {
if (!/^(?:0|[1-9]\d*)$/.test(identifier)) return void 0;
return Number(identifier);
}
function isTruthyEnv(value) {
return !!value && value !== "0" && value !== "false";
}
function isUpdateCheckDisabled(env) {
return isTruthyEnv(env.WOT_DISABLE_UPDATE_CHECK) || isTruthyEnv(env.NO_UPDATE_NOTIFIER);
}
function getDefaultCacheFile(env) {
return join(env.XDG_CACHE_HOME ? join(env.XDG_CACHE_HOME, "open-wot") : join(homedir(), ".cache", "open-wot"), "update-check.json");
}
function readCache(cacheFile) {
if (!existsSync(cacheFile)) return void 0;
let cache;
try {
cache = JSON.parse(readFileSync(cacheFile, "utf8"));
} catch {
return;
}
if (!cache || typeof cache !== "object" || typeof cache.checkedAt !== "number") return void 0;
return {
checkedAt: cache.checkedAt,
latestVersion: typeof cache.latestVersion === "string" ? cache.latestVersion : void 0
};
}
async function fetchAndCacheLatestVersion(options, cacheFile, now) {
let latestVersion;
try {
latestVersion = await fetchLatestVersion(options.packageName, options.registry ?? options.env?.npm_config_registry ?? DEFAULT_REGISTRY, options.fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
} catch {
latestVersion = void 0;
}
const cache = {
checkedAt: now,
latestVersion
};
writeCache(cacheFile, cache);
return cache;
}
async function fetchLatestVersion(packageName, registry, fetchFn, timeoutMs) {
const request = fetchFn ?? globalThis.fetch;
if (typeof request !== "function") return void 0;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await request(`${registry.replace(/\/+$/, "")}/${encodePackageName(packageName)}/latest`, {
headers: {
"accept": "application/json",
"user-agent": `${packageName} update-check`
},
signal: controller.signal
});
if (!response.ok) return void 0;
const json = await response.json();
if (isRegistryLatestResponse(json)) return json.version;
} finally {
clearTimeout(timeout);
}
}
function encodePackageName(packageName) {
if (!packageName.startsWith("@")) return encodeURIComponent(packageName);
const [scope, name$1] = packageName.split("/");
return `${scope}%2f${name$1}`;
}
function isRegistryLatestResponse(value) {
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
}
function writeCache(cacheFile, cache) {
try {
mkdirSync(dirname(cacheFile), { recursive: true });
writeFileSync(cacheFile, `${JSON.stringify(cache, null, 2)}\n`);
} catch {}
}
//#endregion
export { version as _, filterComponents as a, toComponentSummary as c, formatStatusLabel as d, writeStderrLine as f, name as g, loadMetadataFile as h, lintProject as i, toDemoSummary as l, resolveVersion as m, getCliUpdateStatus as n, findComponent as o, detectVersion as p, analyzeUsage as r, listComponents as s, checkForCliUpdate as t, formatLogMessage as u };
# Wot UI CLI Overview
本文件根据本仓库 README 整理,目标是让 Agent 快速理解 `@wot-ui/cli` 的能力边界、命令分组、MCP 接入方式、开发调试路径与数据来源。
## Package Identity
- 包名:`@wot-ui/cli`
- 仓库:open-wot
- 可执行命令:`wot`
- 核心定位:wot-ui 的 AI 工具链仓库,提供 CLI、MCP Server、离线组件知识库与数据提取脚本。
## Repository Positioning
- 面向 wot-ui v2 的组件知识查询工具。
- 面向本地项目的组件使用分析与 lint 工具。
- 面向 AI 客户端的 MCP stdio 服务。
- 面向仓库维护者的数据提取与同步工作流。
## Core Capabilities
### Component Knowledge
- `list`:列出可用组件。
- `info <Component>`:查看 props、events、slots、CSS 变量。
- `doc <Component>`:输出组件 markdown 文档。
- `demo <Component> [name]`:查看 demo 列表或指定 demo 源码。
- `token [Component]`:查看组件 CSS 变量与默认值。
- `changelog [version] [component]`:查看版本更新记录。
### Project Analysis
- `doctor [dir]`:检查项目依赖、运行环境与基础集成情况。
- `usage [dir]`:统计 `.vue` 文件中的 `wd-*` 使用情况。
- `lint [dir]`:检查未知组件、空按钮等规则。
### MCP Server
- `mcp`:启动 MCP stdio server。
## Typical User Flows
### Query Component Knowledge Through CLI
常用顺序:
1. `wot list`
2. `wot info Button`
3. `wot demo Button basic`
4. `wot doc Button`
5. `wot token Button`
### Analyze A Local Project
常用顺序:
1. `wot doctor ./my-project`
2. `wot usage ./my-project`
3. `wot lint ./my-project`
### Run MCP In A Client
推荐自动接入:
```bash
wot agent init --client cursor
wot agent status --client cursor
wot agent doctor --client cursor
```
只管理 MCP 时使用:
```bash
wot mcp init --client cursor
wot mcp print --client cursor
wot mcp status --client cursor
wot mcp doctor --client cursor
wot mcp remove --client cursor
```
所有写操作支持 `--dry-run`;Agent 或 CI 非交互执行时显式传入 `--yes`。
典型配置:
```json
{
"mcpServers": {
"wot-ui": {
"command": "npx",
"args": ["-y", "@wot-ui/cli", "mcp"]
}
}
}
```
当前 README 明确列出的 MCP tools 有:
- `wot_list`
- `wot_info`
- `wot_doc`
- `wot_demo`
- `wot_token`
- `wot_changelog`
- `wot_lint`
## Common Flags
多数查询命令支持:
- `--format text`
- `--format json`
- `--version v2`
## Install And Run
### Global Install
```bash
npm install -g @wot-ui/cli
```
安装后直接用 `wot`。
### Source Mode In This Repo
```bash
pnpm exec tsx src/index.ts list
pnpm exec tsx src/index.ts info Button
pnpm exec tsx src/index.ts mcp
```
适合本地调试源码,不依赖全局安装。
### Built Artifact Mode
```bash
pnpm build
node dist/index.mjs list
```
适合验证构建产物行为。
## MCP Operational Notes
- `wot mcp` 走 stdio。
- 终端里没有交互输出通常是正常现象。
- 若要调试 tool 与 prompt 调用过程,建议配合 MCP Inspector 或编辑器内置 MCP 客户端。
## Data Source And Extraction
当前版本聚焦 `wot-ui v2`。
离线数据主要提取自上游 `wot-ui/wot-ui` 的:
- `docs/component/*.md`
- `docs/guide/changelog.md`
- `src/uni_modules/wot-ui/components/*/index.scss`
重新生成数据有两种方式:
### Use A Local Wot UI Repo
```bash
pnpm extract:cli --wot-dir ../wot-ui --output data/v2.json
```
### Clone Latest Upstream And Extract
```bash
pnpm extract:clone
```
## Repo Layout
- `src`:CLI、MCP 与项目分析源码。
- `data`:离线组件元数据。
- `scripts`:提取脚本。
- `skills`:面向 Agent 的技能说明。
- `test`:根包测试。
## Local Development Commands
### Environment
- Node.js `>= 20`
- pnpm `10.x`
### Install
```bash
pnpm install
```
### Common Validation
```bash
pnpm lint
pnpm test:all
pnpm build:all
pnpm typecheck:all
```
### Package-Level Commands
```bash
pnpm build
pnpm test
pnpm typecheck
```
## Agent Guidance
- 如果用户问的是命令怎么用,按“命令组 + 示例命令 + 输出用途”来回答。
- 如果用户问的是仓库维护或调试,优先给本仓库中的真实命令和目录。
- 如果用户问的是组件本身怎么写页面,不要停留在 CLI 层,应切换到 `wot-ui-v2` skill。
- 不要把 `wot` 命令和 `@wot-ui/ui` 组件库 API 混为一谈。
---
name: wot-ui-cli
description: '回答、使用、调试 @wot-ui/cli 时使用。关键词:wot、@wot-ui/cli、CLI、MCP、doctor、usage、lint、list、info、doc、demo、token、changelog、extract、wot mcp。适用于命令查询、参数说明、MCP 接入、本地调试、数据提取与 open-wot 仓库维护。'
argument-hint: '命令名、参数、MCP 场景、调试问题或数据提取需求'
---
# Wot UI CLI Skill
这个 skill 用于让 Agent 在处理 `@wot-ui/cli` 本身相关的问题时,优先基于本仓库 README 与实际命令能力回答,而不是把它误当成纯组件库文档。
## 适用场景
- 用户询问 `wot` 命令怎么用。
- 用户需要区分 `list`、`info`、`doc`、`demo`、`token`、`changelog`、`doctor`、`usage`、`lint`、`mcp`、`extract` 的用途。
- 用户要接入 MCP Server,或需要 `wot mcp` 的配置与调试方法。
- 用户要在本仓库中调试 `@wot-ui/cli`、验证构建产物、重新提取数据。
- 用户的问题本质上是 open-wot 仓库维护问题,而不是单纯的 wot-ui 组件使用问题。
## 适用范围
- 关注对象是 `@wot-ui/cli` 这个工具包,以及仓库 `open-wot` 的开发维护流程。
- 重点覆盖命令能力、通用参数、MCP、离线数据来源、提取流程、本地调试和发布包边界。
- 如果任务是生成 `wd-*` 页面代码、解释组件 props 或给出主题定制方案,应优先使用 `wot-ui-v2` skill。
## 推荐流程
1. 先确认用户是在问 CLI 工具本身,还是在借 CLI 查询组件知识。
2. 如果是命令使用问题,优先按命令类别回答:组件知识、项目分析、MCP、数据提取、仓库开发。
3. 如果是仓库维护问题,优先给出本仓库里的实际调试命令,而不是泛泛而谈。
4. 如果涉及组件内容本身,可引导或切换到 `wot-ui-v2` skill。
## 命令分组
### 组件知识查询
- `wot list`
- `wot info <Component>`
- `wot doc <Component>`
- `wot demo <Component> [name]`
- `wot token [Component]`
- `wot changelog [version] [component]`
### 项目分析
- `wot doctor [dir]`
- `wot usage [dir]`
- `wot lint [dir]`
### MCP
- `wot mcp`
### 数据提取与仓库维护
- `pnpm extract:cli --wot-dir ../wot-ui --output data/v2.json`
- `pnpm extract:clone`
- `pnpm exec tsx src/index.ts <command>`
- `pnpm build`
- `node dist/index.mjs <command>`
## 工作规则
- 包名是 `@wot-ui/cli`,实际可执行命令是 `wot`。
- 回答命令问题时,优先用仓库 README 中已承诺的行为和参数,不臆造未声明子命令。
- 回答本地调试问题时,优先给源码入口:`pnpm exec tsx src/index.ts ...`。
- 回答构建产物问题时,再给 `node dist/index.mjs ...`。
- 回答 MCP 问题时,要说明 `wot mcp` 走 stdio,终端无交互输出通常是正常现象。
- 回答提取逻辑问题时,要说明数据主要来自上游 `wot-ui/wot-ui` 的 markdown 与 SCSS 源码。
- 当用户问的是组件知识但入口是 CLI,也要保留“这是通过 CLI 查询组件知识”这一层语义。
## 参考资料
- [Wot UI CLI 概览](./references/overview.md)
# Wot UI V2 Overview
本文件根据 wot-ui v2 的 `llms-full.txt` 与本仓库现有 CLI 工作流提炼,目标是帮助 Agent 快速掌握适合生成代码与回答问题的高价值知识,而不是逐字复制官方文档。
## Product Positioning
- Wot UI v2 是面向 `uni-app` 的 `Vue 3 + TypeScript` 组件库。
- 覆盖微信小程序、支付宝小程序、钉钉小程序、H5、APP 等平台。
- 组件命名统一为 `wd-*`。
- 组件库强调 AI 友好、主题定制、暗黑模式、国际化与跨端一致性。
## Installation And Integration
- npm 安装:`pnpm add @wot-ui/ui`
- 使用前需要安装 `sass`。
- `uni_modules` 安装模式天然支持 easycom 自动引入。
- npm 安装模式通常需要配置 vite resolver 或 easycom。
- CLI 项目在 npm 模式下可在 `tsconfig.json` 中加入 `@wot-ui/ui/global` 以增强全局组件类型提示。
## Import Rules
- npm 安装项目:组合式函数、类型和工具优先从 `@wot-ui/ui` 导入。
- `uni_modules` 安装项目:文档中的 `@/uni_modules/wot-ui` 路径通常可直接使用。
- 官方文档示例很多基于 `uni_modules` 路径,回答时要按用户项目实际安装方式转换。
## High Value Conventions
- 反馈类组件不能依赖全局挂载。页面内通常需要显式写出 `wd-toast`、`wd-dialog`、`wd-notify`、`wd-image-preview`、`wd-video-preview` 等实例。
- `useToast`、`useDialog`、`useNotify`、`useQueue` 等 hooks 基于 `provide/inject`,应在 `setup` 中调用。
- 页面内如果存在多个 `wd-dialog` 或 `wd-toast`,需要通过 `selector` 区分,否则可能出现实例冲突或重复弹出。
- 自定义组件中如果要覆盖 wot-ui 内部样式,小程序环境通常需要把组件配置为 `styleIsolation: 'shared'`。
- 在 `Popup`、`ActionSheet`、`DropDownItem` 等延迟渲染弹层里使用 `Slider`、`Tabs` 等依赖尺寸计算的组件时,打开后应调用实例方法重新初始化,例如 `initSlider()` 或 `updateLineStyle()`。
## Theme And Styling
- 主题定制优先走 CSS 变量。
- Design Token 分三层:基础变量、语义变量、组件变量。
- 局部或全局主题可通过 `wd-config-provider` 的 `theme` 和 `theme-vars` 控制。
- 深色模式通过 `wd-config-provider theme="dark"` 开启。
- 更推荐覆盖语义变量或组件变量,不推荐优先依赖深层 class 选择器覆盖。
## Common UI Patterns
- 表单场景优先组合 `wd-form`、`wd-form-item`、`wd-input`、`wd-textarea`、`wd-picker`、`wd-calendar`、`wd-select-picker`。
- 弹层类场景优先使用 `wd-popup`、`wd-dialog`、`wd-action-sheet`、`wd-tooltip`、`wd-popover`。
- 反馈类场景优先使用 `useToast`、`useDialog`、`useNotify`,不要直接手写临时弹层。
- 列表和展示类场景优先考虑 `wd-cell`、`wd-card`、`wd-tag`、`wd-badge`、`wd-empty`、`wd-loadmore`、`wd-skeleton`。
- 导航与布局类场景优先考虑 `wd-navbar`、`wd-tabs`、`wd-tabbar`、`wd-sidebar`、`wd-row`、`wd-col`、`wd-gap`。
## Interaction Patterns
- 大量组件采用 `v-model` 或 `v-model:visible` 控制状态。
- 表单和选择类组件普遍提供 `confirm`、`change`、`close` 等事件。
- 反馈组件常通过 hook 返回方法对象,例如 `toast.success()`、`dialog.confirm()`。
- `Popover`、`Tooltip`、`SwipeAction` 等场景常与 `useQueue().closeOutside()` 配合,实现点击外部关闭。
## Component Selection Hints
- 需要主操作按钮时用 `wd-button`,不要先写原生 `button`。
- 需要列表入口、设置页、表单容器时优先用 `wd-cell` 和 `wd-cell-group`。
- 需要轻量提示时优先用 `useToast`;需要确认交互时优先用 `useDialog`。
- 需要单选或多选弹层时优先用 `wd-select-picker`、`wd-picker`、`wd-cascader`。
- 需要统一主题或暗黑模式时优先用 `wd-config-provider`。
## Common Pitfalls
- `Toast`、`Dialog` 等函数式调用没有效果,先检查页面里是否声明了对应组件实例。
- 同一个页面里多个无 `selector` 的反馈组件可能相互干扰。
- npm 模式使用国际化时,开发态可能需要在 Vite 的 `optimizeDeps.exclude` 中排除 `@wot-ui/ui`。
- 文档里的导入路径和项目实际安装方式不一致时,回答要主动修正。
- 在弹层中直接渲染依赖尺寸测量的组件时,初始化时机往往比 API 本身更关键。
## AI Response Heuristics
- 回答基础用法时,优先给最小可运行模板,再补充常用 props。
- 生成页面时,优先给完整的 `template + script setup + style` 结构。
- 如果项目已使用 wot-ui,不要建议换用其他 UI 库。
- 如果 wot-ui 已有现成组件,就不要用原生结构重复造轮子。
- 如果用户只问某个组件,优先给该组件最常见 3 到 5 个用法,不要把整份文档全部展开。
## Repo-Specific Workflow
- 在本仓库中,优先使用 `wot list`、`wot info`、`wot doc`、`wot demo`、`wot token` 获取组件知识。
- 当仓库数据与线上文档不一致时,以用户目标为准,并明确指出仓库离线数据可能需要重新提取。
- 若要补数据或修提取逻辑,关注 `scripts/extract.ts`、`data/v2.json`、`src/data/*` 与相应命令实现。
---
name: wot-ui-v2
description: '回答、生成、重构、排查 wot-ui v2 相关代码时使用。关键词:wot-ui、uni-app、Vue3、wd-、ConfigProvider、useToast、useDialog、Form、Popup、theme、llms-full。适用于组件选型、API 查询、示例页面生成、主题定制、常见坑排查。'
argument-hint: '组件名、页面场景、问题描述或主题定制需求'
---
# Wot UI V2 Skill
这个 skill 用于让 Agent 在处理 wot-ui v2 相关任务时,优先采用组件库既有能力、遵守 uni-app 场景限制,并结合本仓库提供的 `wot` CLI 查询离线知识。
## 适用场景
- 用户询问某个 `wd-*` 组件的基础用法、属性、事件、插槽或样式变量。
- 需要生成或重构 `uni-app + Vue 3 + TypeScript` 的 wot-ui 页面或组件代码。
- 需要在 `ConfigProvider`、主题变量、暗黑模式、国际化、反馈类 hooks、表单等场景下给出正确做法。
- 需要排查文档中常见的 `Toast`、`Dialog`、`Popup`、`Tabs`、`Slider`、样式覆盖问题。
- 用户只是泛化地提到主题定制,但还没有明确要求按“单文件主题 SCSS + App.vue 只 `@use`”的结构生成时。
## 推荐流程
1. 先用本仓库 CLI 或 MCP 工具查组件知识。
2. 再根据项目实际安装方式决定导入路径与集成方式。
3. 优先复用现成的 `wd-*` 组件、hooks、主题变量与组合模式,不要退化成原生标签堆砌。
4. 如果问题涉及约束或坑位,再查阅 [参考知识](./references/overview.md)。
5. 如果用户明确要求生成 `src/themes/styles/{主题名}.scss` 单文件主题,并把挂载逻辑收进主题文件,优先切换到 `create-wot-ui-theme` skill。
## 查询顺序
1. `wot list` 找组件名。
2. `wot info <Component>` 看 props、events、slots、CSS 变量。
3. `wot demo <Component>` 看 demo 名称或具体 demo 代码。
4. `wot doc <Component>` 看完整 markdown 文档。
5. `wot token <Component>` 看主题变量。
## 工作规则
- 默认把 wot-ui 视为 `uni-app + Vue 3 + TypeScript` 组件库。
- 写页面时优先输出 `script setup` 风格。
- 反馈类能力如 `useToast`、`useDialog`、`useNotify`、`useImagePreview`、`useVideoPreview`,除了 hook 调用外,通常还需要页面内显式声明对应组件实例。
- 文档里经常出现 `@/uni_modules/wot-ui` 导入路径;如果用户项目采用 npm 安装,应切换成 `@wot-ui/ui`。
- 主题定制优先走 `ConfigProvider` 和 CSS 变量,不优先建议深度覆盖内部类名。
- 生成代码时尽量沿用组件库文档里的命名和交互模式,例如 `v-model:visible`、`before-confirm`、`confirm`、`change`、`custom-class`、`custom-style`。
## 参考资料
- [Wot UI V2 概览](./references/overview.md)
+6
-2
{
"name": "@wot-ui/cli",
"type": "module",
"version": "1.0.4",
"version": "1.0.5-beta.1",
"description": "面向 wot-ui 的 CLI、MCP 与数据提取工具集",

@@ -28,3 +28,5 @@ "license": "MIT",

"data",
"dist"
"dist",
"skills/wot-ui-cli",
"skills/wot-ui-v2"
],

@@ -39,2 +41,4 @@ "engines": {

"commander": "^14.0.0",
"jsonc-parser": "^3.3.1",
"smol-toml": "^1.7.0",
"zod": "^4.1.12"

@@ -41,0 +45,0 @@ },

+146
-7

@@ -16,3 +16,4 @@ # Open Wot

- 项目分析:`doctor`、`usage`、`lint`
- MCP Server:`wot mcp`
- Agent 接入:`wot agent init` 自动配置 MCP、内置 Skill 与 Agent Instructions
- MCP 生命周期:`wot mcp`(默认启动 Server)、`wot mcp serve/list/init/status/doctor/remove/print`
- 元数据提取:从 `wot-ui/wot-ui` 源码生成本地 `v2.json`

@@ -22,8 +23,30 @@

推荐全局安装 CLI:
```bash
npm install -g @wot-ui/cli
npm install -g @wot-ui/cli@latest
```
安装完成后可直接使用 `wot` 命令。
也可以使用其他包管理器:
```bash
pnpm add -g @wot-ui/cli@latest
bun add -g @wot-ui/cli@latest
```
安装完成后可直接使用 `wot`:
```bash
wot list
```
如果只想临时运行,也可以使用包执行器,不会修改项目依赖:
```bash
pnpm dlx @wot-ui/cli@latest list
npx -y @wot-ui/cli@latest list
yarn dlx @wot-ui/cli@latest list
bunx @wot-ui/cli@latest list
```
`wot` 在交互式终端启动时会自动检查 `@wot-ui/cli` 是否有新版本。检查结果最多缓存 24 小时,提示只写入 stderr,不会污染 `--format json` 的 stdout;CI 和非交互式环境会自动跳过。`wot mcp` 不会在启动时输出更新提示,MCP 客户端可通过 `wot_status` tool 查询 CLI 更新状态。若需要关闭检查,可设置 `WOT_DISABLE_UPDATE_CHECK=1` 或 `NO_UPDATE_NOTIFIER=1`。

@@ -41,2 +64,3 @@

wot list
wot list button
wot info Button

@@ -50,2 +74,4 @@ wot demo Button basic

wot lint ./my-project
wot agent list
wot agent init --client cursor
wot mcp

@@ -58,3 +84,3 @@ ```

- `wot list [keyword]`:列出可用的 wot-ui 组件,支持按名称过滤
- `wot list [keyword]`:列出可用的 wot-ui 组件,支持按名称、中文名、标签、分类和描述过滤
- `wot info <component>`:查看组件 props、events、slots、CSS 变量

@@ -72,2 +98,62 @@ - `wot doc <component>`:输出组件 markdown 文档

### Agent 接入
#### 复制给 AI,一键接入
将下面这段提示词复制到 Claude Code、Cursor、VS Code 或 Codex,让当前 AI Agent 自动完成接入:
```text
请在当前项目根目录接入 wot-ui 的 AI 开发能力,并直接执行所需命令:
1. 确认 Node.js 版本不低于 20。
2. 执行 `npm install -g @wot-ui/cli@latest` 安装或更新全局 CLI。不要使用 sudo;如果全局安装因权限受限而失败,改用 `npx -y @wot-ui/cli@latest` 运行后续命令,并在结果中说明。
3. 识别你当前所在的 AI 客户端,并使用对应的 client id:Claude Code 使用 claude,Cursor 使用 cursor,VS Code 使用 vscode,Codex 使用 codex。只配置当前客户端;如果无法确定,请先询问我,不要猜测。
4. 执行 `wot agent init --client <client-id> --scope project --with mcp,skill,instructions --yes` 完成项目级接入;不要手动覆盖现有配置。若上一步回退到 npx,则用 `npx -y @wot-ui/cli@latest` 代替 `wot`。
5. 执行 `wot agent doctor --client <client-id> --scope project --with mcp,skill,instructions` 检查配置、MCP handshake、Skill 和 Instructions;使用与上一步相同的 CLI 执行方式。
6. 最后告诉我:CLI 安装结果、识别到的客户端、修改了哪些文件、doctor 检查结果,以及是否需要我重启客户端或批准项目 MCP。
请保留项目中已有的 MCP Server 和用户内容;如果命令失败,不要绕过安全检查,说明具体原因和建议的处理方式。
```
这段提示词会优先安装全局 `wot` 命令,并在权限受限时安全回退到 `npx`。生成的 MCP 配置、安装的 Skill 和 Instructions 会持久保留在项目中;`agent init` 是幂等操作,重复执行不会重复添加配置。
也可以手动执行:
```bash
wot agent list
wot agent init --client cursor
wot agent status --client cursor
wot agent doctor --client cursor
wot agent remove --client cursor
```
`agent init` 默认同时安装三项能力:
- 在客户端项目配置中注册 `wot-ui` MCP Server
- 安装仓库内置的 `wot-ui-v2` Skill
- 在 `AGENTS.md` 或 `CLAUDE.md` 中维护 open-wot 自己拥有的 Instructions 区块
当前 npm 发布包只随附以下两个 Skill:
| Skill | 主要用途 | 随 npm 发布 | `agent init` 默认安装 |
| --- | --- | --- | --- |
| `wot-ui-v2` | 组件选型、API 查询、页面生成与组件问题排查 | 是 | 是 |
| `wot-ui-cli` | CLI、MCP、数据提取与 open-wot 仓库维护 | 是 | 否 |
可通过 `--with` 限制能力范围:
```bash
wot agent init --client codex --with mcp
wot agent init --client claude --with skill,instructions
wot agent status --client claude --with skill,instructions
wot agent doctor --client claude --with skill,instructions
wot agent init --client cursor --dry-run
```
`status` 和 `doctor` 只检查 `--with` 选中的能力;未选择 MCP 时,`doctor` 不会启动 MCP Server。
`init` 和 `remove` 支持 `--dry-run`。写操作在交互式终端中会请求确认;脚本和 CI 必须显式传入 `--yes`。重复执行是幂等的,删除操作只移除 `wot-ui` MCP 条目、未修改的内置 Skill 文件和 open-wot 托管 Instructions 区块。
若 Instructions 中的 open-wot 标记残缺、顺序错误或重复,CLI 会拒绝修改,避免误删用户内容。
### 通用参数

@@ -94,2 +180,38 @@

`wot mcp` 保留默认启动 stdio Server 的行为;在脚本或文档中也可以使用语义更明确的 `wot mcp serve`。
推荐使用 CLI 自动配置:
```bash
wot mcp list
wot mcp init --client cursor
wot mcp status --client cursor
wot mcp doctor --client cursor
```
支持的客户端和 project scope 配置位置:
| Client | 配置文件 | 根字段 |
| --- | --- | --- |
| Claude Code | `.mcp.json` | `mcpServers` |
| Cursor | `.cursor/mcp.json` | `mcpServers` |
| VS Code | `.vscode/mcp.json` | `servers` |
| Codex | `.codex/config.toml` | `mcp_servers.wot-ui` |
Claude Code、Cursor 和 Codex 同时支持 `--scope user`;VS Code 当前使用 project scope。管理命令支持 `--format json`,`init`、`status`、`doctor`、`remove` 和 `print` 支持通过 `--pin [version]` 固定生成配置中的 `@wot-ui/cli` 版本;只有会写文件的 `init` 和 `remove` 支持 `--dry-run`。
dry-run 和 JSON 结果只输出托管配置节点的安全预览,不输出配置文件完整内容或其他 Server 的环境变量。写操作还可以使用 `--client all` 一次处理所有支持当前 scope 的客户端。
Codex adapter 会在写入前后验证完整 TOML。若现有配置使用 `[mcp_servers.wot-ui.env]` 等外部嵌套子表,CLI 会拒绝自动接管或删除;请先将自定义字段迁移到主 Server 定义,再重新执行命令。
`doctor` 分三层检查配置、MCP handshake 和客户端注册状态。Handshake 会验证 Server 名称以及 `wot_status`、`wot_list` 核心工具;Claude Code 和 Codex 会进一步调用客户端 CLI 查询注册状态。Cursor、VS Code 没有稳定查询接口时会显示 `server-ready`,提示用户重启客户端并在 MCP 面板确认,而不会将它描述为客户端已经就绪。需要用户批准或信任项目时退出码为 `2`,配置或 handshake 失败时退出码为 `1`。
只需要查看配置而不写文件时:
```bash
wot mcp print --client vscode
```
也可以手动配置。推荐使用无需全局安装的 `npx` 方式:
将以下配置加入支持 MCP 的客户端:

@@ -101,2 +223,15 @@

"wot-ui": {
"command": "npx",
"args": ["-y", "@wot-ui/cli", "mcp"]
}
}
}
```
如果已经全局安装 CLI,并且 AI 客户端可以从 `PATH` 中找到 `wot`,也可以使用:
```json
{
"mcpServers": {
"wot-ui": {
"command": "wot",

@@ -109,2 +244,4 @@ "args": ["mcp"]

桌面应用不一定会继承终端中的 npm 全局 `PATH`,因此默认配置和 `wot mcp init` 仍使用兼容性更好的 `npx` 方式。
当前 MCP Server 提供以下 tools:

@@ -115,6 +252,6 @@

| `wot_status` | 查看 MCP Server 与 `@wot-ui/cli` 状态,包括当前版本、是否有 CLI 更新及更新命令。 | 无 |
| `wot_list` | 列出当前离线知识库中的 wot-ui 组件元数据,适合在生成页面前发现可用组件。 | `version` |
| `wot_list` | 列出当前离线知识库中的组件摘要,不包含完整文档、API 与 demo 源码,适合在生成页面前发现可用组件。 | `version` |
| `wot_info` | 查询单个组件的 props、events、slots、CSS 变量等结构化信息。 | `component`, `version` |
| `wot_doc` | 获取单个组件的完整 markdown 文档,适合需要阅读用法细节或限制说明时调用。 | `component`, `version` |
| `wot_demo` | 获取组件 demo 列表,或按 demo 名称获取指定示例源码。 | `component`, `demo`, `version` |
| `wot_demo` | 获取不含源码的 demo 摘要列表;指定 demo 名称时获取完整示例源码。 | `component`, `demo`, `version` |
| `wot_token` | 查询组件 CSS 变量;不传组件名时返回所有组件的 CSS 变量摘要。 | `component`, `version` |

@@ -126,2 +263,4 @@ | `wot_changelog` | 查询 wot-ui v2 离线数据中的更新记录,可按版本或组件过滤。 | `version`, `component` |

为控制 Agent 上下文占用,MCP 的 `wot_list` 只返回 `name`、`nameZh`、`tag`、`category`、`description` 和 `since`;需要组件 API、文档或示例源码时,再调用 `wot_info`、`wot_doc` 或带具体 demo 名称的 `wot_demo`。CLI 的 `list --format json` 与 `demo --format json` 继续保留原有详细结构,避免影响已有脚本。
## 数据来源

@@ -219,3 +358,3 @@

- `.github/workflows/sync.yml`:每日 02:00 UTC 自动检测 `@wot-ui/ui` 最新版本,有更新时拉取全量多版本快照并创建同步 PR;也可手动触发单版本提取
- `.github/workflows/release.yml`:`v*` tag 触发自动发布 `@wot-ui/cli` 到 npm(发布前自动运行 `pnpm compress && pnpm build`,npm 包只含 `.json.gz`)
- `.github/workflows/release.yml`:`v*` tag 触发自动发布 `@wot-ui/cli` 到 npm;`prepublishOnly` 依次执行 `pnpm build` 和 `pnpm compress`,发布包携带压缩后的组件数据及 `versions.json` 版本索引
- `.github/workflows/coverage-upload.yml`:`v*` tag 触发,上传测试覆盖率到 Codecov

@@ -222,0 +361,0 @@

import { a as findComponent, d as resolveVersion, f as loadMetadataFile, i as lintProject, m as version, n as getCliUpdateStatus, o as listComponents, p as name } from "./update-check-DlG1c3ZB.mjs";
import process from "node:process";
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
import * as z from "zod/v4";
//#region src/mcp/prompts.ts
const WOT_EXPERT_PROMPT = [
"You are a wot-ui expert assistant.",
"Use wot_status when the user asks about tool health, updates, or unexpected missing metadata.",
"Always query component metadata before generating code.",
"Prefer using wot_list, wot_info, wot_doc, and wot_token before writing UI code.",
"Assume only wot-ui v2 is supported by this server."
].join(" ");
const WOT_PAGE_GENERATOR_PROMPT = [
"Generate wot-ui pages by first collecting every relevant component API and CSS variable.",
"Prefer existing wd-* components and documented props over ad-hoc custom markup.",
"When theme customization is involved, inspect CSS variables with wot_token first."
].join(" ");
//#endregion
//#region src/mcp/tools.ts
function jsonText(value) {
return JSON.stringify(value, null, 2);
}
function registerMcpTools(server, options = {}) {
server.registerTool("wot_status", {
description: "Get wot-ui MCP server and CLI update status.",
inputSchema: z.object({}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async () => {
const update = await getCliUpdateStatus({
currentVersion: version,
packageName: name,
...options.updateCheckOptions
});
return { content: [{
type: "text",
text: jsonText({
server: {
name: "wot-ui",
version
},
cli: update
})
}] };
});
server.registerTool("wot_list", {
description: "List available wot-ui components.",
inputSchema: z.object({ version: z.string().optional() }),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1) })
}] };
});
server.registerTool("wot_info", {
description: "Get props, events, slots, and CSS variables for a component.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText(result)
}] };
});
server.registerTool("wot_doc", {
description: "Get component markdown documentation.",
inputSchema: z.object({
component: z.string(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result?.doc) return {
isError: true,
content: [{
type: "text",
text: `Documentation not found: ${component}`
}]
};
return { content: [{
type: "text",
text: result.doc
}] };
});
server.registerTool("wot_demo", {
description: "Get component demo code or list demos.",
inputSchema: z.object({
component: z.string(),
demo: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, demo, version: version$1 }) => {
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
if (!demo) return { content: [{
type: "text",
text: jsonText({ demos: result.demos ?? [] })
}] };
const matched = result.demos?.find((item) => item.name.toLowerCase() === demo.toLowerCase());
if (!matched) return {
isError: true,
content: [{
type: "text",
text: `Demo not found: ${demo}`
}]
};
return { content: [{
type: "text",
text: jsonText(matched)
}] };
});
server.registerTool("wot_token", {
description: "Get component CSS variables.",
inputSchema: z.object({
component: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ component, version: version$1 }) => {
if (!component) return { content: [{
type: "text",
text: jsonText({ components: listComponents(version$1).map((item) => ({
name: item.name,
cssVars: item.cssVars
})) })
}] };
const result = findComponent(component, version$1);
if (!result) return {
isError: true,
content: [{
type: "text",
text: `Component not found: ${component}`
}]
};
return { content: [{
type: "text",
text: jsonText({
name: result.name,
cssVars: result.cssVars
})
}] };
});
server.registerTool("wot_changelog", {
description: "Get changelog entries for the supported v2 dataset.",
inputSchema: z.object({
version: z.string().optional(),
component: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
}, async ({ version: version$1, component }) => {
return { content: [{
type: "text",
text: jsonText({ entries: (loadMetadataFile(resolveVersion(version$1)).changelog ?? []).filter((entry) => {
const versionMatches = version$1 ? entry.version === version$1 || `v${entry.version}` === version$1 : true;
const componentMatches = component ? (entry.components ?? []).some((item) => item.toLowerCase() === component.toLowerCase()) : true;
return versionMatches && componentMatches;
}) })
}] };
});
server.registerTool("wot_lint", {
description: "Lint a local project for wot-ui related issues.",
inputSchema: z.object({
dir: z.string().optional(),
version: z.string().optional()
}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
}, async ({ dir, version: version$1 }) => {
return { content: [{
type: "text",
text: jsonText(lintProject(dir ?? process.cwd(), version$1))
}] };
});
}
//#endregion
//#region src/mcp/server.ts
async function startMcpServer() {
const server = new McpServer({
name: "wot-ui",
version
}, {
instructions: "Use wot-ui component tools before generating UI code. Only wot-ui v2 metadata is available in this server.",
capabilities: { logging: {} }
});
registerMcpTools(server);
getCliUpdateStatus({
currentVersion: version,
packageName: name
}).catch(() => {});
server.registerPrompt("wot-expert", { description: "General wot-ui expert workflow." }, async () => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: WOT_EXPERT_PROMPT
}
}] }));
server.registerPrompt("wot-page-generator", {
description: "Workflow for generating a wot-ui page.",
argsSchema: z.object({ goal: z.string().optional() })
}, async ({ goal }) => ({ messages: [{
role: "assistant",
content: {
type: "text",
text: goal ? `${WOT_PAGE_GENERATOR_PROMPT} Goal: ${goal}` : WOT_PAGE_GENERATOR_PROMPT
}
}] }));
const transport = new StdioServerTransport();
await server.connect(transport);
const shutdown = async () => {
await server.close();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
//#endregion
export { startMcpServer };
import process from "node:process";
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { gunzipSync } from "node:zlib";
import { parse } from "@vue/compiler-sfc";
import { homedir } from "node:os";
//#region package.json
var name = "@wot-ui/cli";
var version = "1.0.4";
//#endregion
//#region src/data/loader.ts
const currentDir = dirname(fileURLToPath(import.meta.url));
function resolveDataDir() {
const candidates = [
join(currentDir, "..", "data"),
join(currentDir, "..", "..", "data"),
join(currentDir, "data")
];
for (const candidate of candidates) if (existsSync(join(candidate, "versions.json")) || existsSync(join(candidate, "versions.json.gz"))) return candidate;
throw new Error("Unable to locate bundled data directory");
}
const dataDir = resolveDataDir();
function readJsonFile(baseName) {
const jsonPath = join(dataDir, `${baseName}.json`);
if (existsSync(jsonPath)) return JSON.parse(readFileSync(jsonPath, "utf8"));
const gzipPath = join(dataDir, `${baseName}.json.gz`);
if (existsSync(gzipPath)) {
const compressed = readFileSync(gzipPath);
return JSON.parse(gunzipSync(compressed).toString("utf8"));
}
throw new Error(`Data file not found for ${baseName}`);
}
function loadVersionsFile() {
return readJsonFile("versions");
}
function loadMetadataFile(versionKey) {
return readJsonFile(versionKey);
}
//#endregion
//#region src/data/version.ts
/** Strip semver range operators (^, ~, >=, >, <=, <, =, whitespace). */
function stripRange(ver) {
return ver.replace(/[\^~>=<\s]/g, "");
}
/**
* Returns all stable version strings for major key 'v2',
* sorted ascending by semver.
*/
function stableV2Versions() {
const map = loadVersionsFile().v2 ?? {};
return Object.values(map).filter((v) => !v.includes("-")).sort((a, b) => {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
});
}
/**
* Auto-detect the wot-ui version to use.
*
* Priority:
* 1. --version flag (flagVersion arg)
* 2. node_modules/@wot-ui/ui/package.json in cwd
* 3. package.json dependencies[@wot-ui/ui] in cwd
* 4. Fallback to latest stable version from versions.json
*/
function detectVersion(flagVersion, cwd) {
const dir = cwd ?? process.cwd();
if (flagVersion) return {
version: flagVersion,
source: "flag"
};
const nmPath = join(dir, "node_modules", "@wot-ui", "ui", "package.json");
if (existsSync(nmPath)) try {
const pkg = JSON.parse(readFileSync(nmPath, "utf8"));
if (pkg.version) return {
version: pkg.version,
source: "node_modules"
};
} catch {}
const pkgPath = join(dir, "package.json");
if (existsSync(pkgPath)) try {
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
const depVersion = pkg.dependencies?.["@wot-ui/ui"] ?? pkg.devDependencies?.["@wot-ui/ui"] ?? pkg.peerDependencies?.["@wot-ui/ui"];
if (depVersion) return {
version: stripRange(depVersion),
source: "package.json"
};
} catch {}
return {
version: stableV2Versions().at(-1) ?? "2.0.0",
source: "fallback"
};
}
/**
* Resolve a version string (from detectVersion or CLI flag) to a data file key.
*
* Examples:
* undefined / 'v2' → 'v2' (major alias, data/v2.json)
* 'latest' → 'v2.0.4' (latest stable snapshot)
* '2.0' → 'v2.0.4' (minor → lookup in versions.json)
* '2.0.4' → 'v2.0.4' (exact patch)
* '2.0.0-alpha.5' → 'v2.0.0-alpha.5' (pre-release exact)
*/
function resolveVersion(requested) {
if (!requested || requested === "v2") return "v2";
const normalized = requested.trim();
if (normalized === "latest") {
const latest = stableV2Versions().at(-1);
if (!latest) return "v2";
return `v${latest}`;
}
const map = loadVersionsFile().v2 ?? {};
if (/^\d+\.\d+$/.test(normalized)) {
const patch = map[normalized];
if (!patch) throw new Error(`Unsupported wot-ui version: ${requested}`);
return `v${patch}`;
}
if (/^\d+\.\d+\.\d+/.test(normalized)) {
if (normalized.split(".")[0] !== "2") throw new Error(`Unsupported wot-ui version: ${requested}`);
return `v${normalized}`;
}
throw new Error(`Unsupported wot-ui version: ${requested}`);
}
//#endregion
//#region src/utils/terminal.ts
const ANSI = {
cyan: ["\x1B[36m", "\x1B[39m"],
dim: ["\x1B[2m", "\x1B[22m"],
green: ["\x1B[32m", "\x1B[39m"],
red: ["\x1B[31m", "\x1B[39m"],
yellow: ["\x1B[33m", "\x1B[39m"]
};
function supportsColor(options = {}) {
const env = options.env ?? process.env;
if (!(options.isTty ?? process.stderr.isTTY)) return false;
if ("NO_COLOR" in env || env.FORCE_COLOR === "0" || env.TERM === "dumb") return false;
return true;
}
function writeStderrLine(message) {
process.stderr.write(`${message}\n`);
}
function formatLogMessage(level, message, options = {}) {
const color = createColorizer(options);
return `${color.dim("[wot]")} ${styleLevel(level, message, color)}`;
}
function formatStatusLabel(status, options = {}) {
const normalized = status.toUpperCase();
const color = createColorizer(options);
if (status === "ok" || status === "pass") return color.green(normalized);
if (status === "warn" || status === "warning") return color.yellow(normalized);
return color.red(normalized);
}
function formatCommand(command, options = {}) {
return createColorizer(options).cyan(command);
}
function formatUpdateNotice(status, options = {}) {
const color = createColorizer(options);
const currentVersion = color.dim(status.currentVersion);
const latestVersion = color.green(status.latestVersion ?? "unknown");
return [
formatLogMessage("update", "Update available", options),
`${color.dim("[wot]")} ${status.packageName} ${currentVersion} -> ${latestVersion}`,
`${color.dim("[wot]")} Run: ${formatCommand(status.command, options)}`
].join("\n");
}
function createColorizer(options) {
const enabled = supportsColor(options);
return {
cyan: (value) => applyAnsi(value, ANSI.cyan, enabled),
dim: (value) => applyAnsi(value, ANSI.dim, enabled),
green: (value) => applyAnsi(value, ANSI.green, enabled),
red: (value) => applyAnsi(value, ANSI.red, enabled),
yellow: (value) => applyAnsi(value, ANSI.yellow, enabled)
};
}
function styleLevel(level, message, color) {
if (level === "error") return color.red(message);
if (level === "success") return color.green(message);
if (level === "warn" || level === "update") return color.yellow(message);
if (level === "hint") return color.cyan(message);
return message;
}
function applyAnsi(value, code, enabled) {
return enabled ? `${code[0]}${value}${code[1]}` : value;
}
//#endregion
//#region src/data/metadata.ts
function loadResolvedMetadata(version$1) {
return loadMetadataFile(resolveVersion(version$1));
}
function listComponents(version$1) {
return loadResolvedMetadata(version$1).components;
}
function findComponent(name$1, version$1) {
const normalized = name$1.trim().toLowerCase();
return listComponents(version$1).find((component) => component.name.toLowerCase() === normalized || component.tag.toLowerCase() === normalized);
}
//#endregion
//#region src/utils/files.ts
const DEFAULT_IGNORES = new Set([
".git",
".idea",
".output",
".turbo",
".vscode",
"dist",
"build",
"coverage",
"node_modules"
]);
function walkFiles(rootDir, extensions) {
const results = [];
function visit(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (DEFAULT_IGNORES.has(entry.name)) continue;
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
visit(fullPath);
continue;
}
if (extensions.some((extension) => entry.name.endsWith(extension))) results.push(fullPath);
}
}
visit(rootDir);
return results;
}
function safeRelative(rootDir, filePath) {
return relative(rootDir, filePath) || ".";
}
//#endregion
//#region src/utils/scanner.ts
const IMPORT_RE = /from\s+['"]([^'"]*wot[^'"]*)['"]/g;
const TAG_RE = /<\s*(wd-[a-z0-9-]+)/gi;
const BUTTON_RE = /<wd-button\b([^>]*)>([\s\S]*?)<\/wd-button>|<wd-button\b([^>]*)\/>/gi;
function getLineNumber(source, index) {
return source.slice(0, index).split("\n").length;
}
function collectTemplateTags(content) {
const counts = /* @__PURE__ */ new Map();
for (const match of content.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
return counts;
}
function collectImports(scriptContent) {
const imports = /* @__PURE__ */ new Set();
for (const match of scriptContent.matchAll(IMPORT_RE)) if (match[1]) imports.add(match[1]);
return [...imports];
}
function analyzeUsage(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const knownByTag = new Map(listComponents(version$1).map((component) => [component.tag.toLowerCase(), component]));
const usageMap = /* @__PURE__ */ new Map();
const imports = /* @__PURE__ */ new Set();
for (const file of files) {
const parsed = parse(readFileSync(file, "utf8"), { filename: file });
const template = parsed.descriptor.template?.content ?? "";
const script = [parsed.descriptor.script?.content ?? "", parsed.descriptor.scriptSetup?.content ?? ""].filter(Boolean).join("\n");
for (const item of collectImports(script)) imports.add(item);
for (const [tag, count] of collectTemplateTags(template)) {
const known = knownByTag.get(tag);
const key = known?.name ?? tag;
const existing = usageMap.get(key);
if (existing) {
existing.count += count;
if (!existing.files.includes(safeRelative(dir, file))) existing.files.push(safeRelative(dir, file));
continue;
}
usageMap.set(key, {
name: known?.name ?? tag,
tag,
count,
files: [safeRelative(dir, file)]
});
}
}
return {
scannedFiles: files.length,
components: [...usageMap.values()].sort((left, right) => right.count - left.count || left.name.localeCompare(right.name)),
imports: [...imports].sort()
};
}
function lintProject(targetDir, version$1) {
const dir = resolve(targetDir);
const files = walkFiles(dir, [".vue"]);
const issues = [];
for (const file of files) {
const template = parse(readFileSync(file, "utf8"), { filename: file }).descriptor.template?.content ?? "";
for (const match of template.matchAll(TAG_RE)) {
const tag = match[1]?.toLowerCase();
if (!tag) continue;
if (!findComponent(tag, version$1)) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "unknown-component",
severity: "warning",
message: `Unknown wot-ui component tag: ${tag}`
});
}
for (const match of template.matchAll(BUTTON_RE)) {
const attrs = (match[1] ?? match[3] ?? "").trim();
const body = (match[2] ?? "").replace(/<[^>]+>/g, "").trim();
if (!/\bicon\s*=/.test(attrs) && !body) issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "button-content",
severity: "warning",
message: "wd-button should include visible text content or an icon attribute."
});
const component = findComponent("wd-button", version$1);
for (const prop of component?.props ?? []) {
if (!prop.deprecated) continue;
if (!(/* @__PURE__ */ new RegExp(`\\b${prop.name}\\b`)).test(attrs)) continue;
issues.push({
file: safeRelative(dir, file),
line: getLineNumber(template, match.index ?? 0),
rule: "deprecated-prop",
severity: "warning",
message: prop.replacement ? `Deprecated prop ${prop.name} detected on wd-button. Use ${prop.replacement} instead.` : `Deprecated prop ${prop.name} detected on wd-button.`
});
}
}
}
return {
scannedFiles: files.length,
issues
};
}
//#endregion
//#region src/utils/update-check.ts
const DEFAULT_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
const DEFAULT_TIMEOUT_MS = 1500;
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
function compareSemver(a, b) {
const parsedA = parseSemver(a);
const parsedB = parseSemver(b);
if (!parsedA || !parsedB) return 0;
for (const index of [
0,
1,
2
]) {
const diff = parsedA[index] - parsedB[index];
if (diff !== 0) return diff > 0 ? 1 : -1;
}
return comparePrerelease(parsedA[3], parsedB[3]);
}
function shouldCheckForCliUpdate(args = process.argv, env = process.env, isTty = process.stderr.isTTY) {
if (!isTty) return false;
if (isUpdateCheckDisabled(env) || isTruthyEnv(env.CI) || env.NODE_ENV === "test") return false;
const userArgs = args.slice(2);
if (userArgs.some((arg) => arg === "-V" || arg === "-h" || arg === "--help")) return false;
const command = userArgs.find((arg) => !arg.startsWith("-"));
return command !== "mcp" && command !== "help";
}
function checkForCliUpdate(options) {
const env = options.env ?? process.env;
const args = options.args ?? process.argv;
const stderr = options.stderr ?? process.stderr;
const isTty = options.isTty ?? process.stderr.isTTY;
if (!shouldCheckForCliUpdate(args, env, isTty)) return;
try {
const status = getCachedCliUpdateStatus(options);
if (status.updateAvailable && status.latestVersion) stderr.write(`${formatUpdateNotice(status, {
env,
isTty
})}\n`);
} catch {}
}
function getCachedCliUpdateStatus(options) {
const env = options.env ?? process.env;
const baseStatus = createBaseStatus(options, env);
if (baseStatus.disabled) return {
...baseStatus,
cached: false,
updateAvailable: false
};
const now = options.now ?? Date.now();
const cached = readCache(options.cacheFile ?? getDefaultCacheFile(env));
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
const latestVersion = cacheIsFresh ? cached.latestVersion : void 0;
return {
...baseStatus,
cached: cacheIsFresh,
checkedAt: cacheIsFresh ? cached.checkedAt : void 0,
latestVersion,
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
};
}
async function getCliUpdateStatus(options) {
const env = options.env ?? process.env;
const baseStatus = createBaseStatus(options, env);
if (baseStatus.disabled) return {
...baseStatus,
cached: false,
updateAvailable: false
};
const now = options.now ?? Date.now();
const cacheFile = options.cacheFile ?? getDefaultCacheFile(env);
const cached = readCache(cacheFile);
const intervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
const cacheIsFresh = !!cached && now - cached.checkedAt < intervalMs;
const result = cacheIsFresh ? cached : await fetchAndCacheLatestVersion(options, cacheFile, now);
const latestVersion = result.latestVersion;
return {
...baseStatus,
cached: cacheIsFresh,
checkedAt: result.checkedAt,
latestVersion,
updateAvailable: !!latestVersion && compareSemver(latestVersion, options.currentVersion) > 0
};
}
function createBaseStatus(options, env) {
return {
command: `npm install -g ${options.packageName}`,
currentVersion: options.currentVersion,
disabled: isUpdateCheckDisabled(env),
packageName: options.packageName
};
}
function parseSemver(version$1) {
const match = version$1.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/);
if (!match) return void 0;
return [
Number(match[1]),
Number(match[2]),
Number(match[3]),
match[4]
];
}
function comparePrerelease(a, b) {
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
const identifiersA = a.split(".");
const identifiersB = b.split(".");
const length = Math.max(identifiersA.length, identifiersB.length);
for (let index = 0; index < length; index++) {
const identifierA = identifiersA[index];
const identifierB = identifiersB[index];
if (identifierA === void 0) return -1;
if (identifierB === void 0) return 1;
if (identifierA === identifierB) continue;
const numberA = parseNumericIdentifier(identifierA);
const numberB = parseNumericIdentifier(identifierB);
if (numberA !== void 0 && numberB !== void 0) return numberA > numberB ? 1 : -1;
if (numberA !== void 0) return -1;
if (numberB !== void 0) return 1;
return identifierA > identifierB ? 1 : -1;
}
return 0;
}
function parseNumericIdentifier(identifier) {
if (!/^(?:0|[1-9]\d*)$/.test(identifier)) return void 0;
return Number(identifier);
}
function isTruthyEnv(value) {
return !!value && value !== "0" && value !== "false";
}
function isUpdateCheckDisabled(env) {
return isTruthyEnv(env.WOT_DISABLE_UPDATE_CHECK) || isTruthyEnv(env.NO_UPDATE_NOTIFIER);
}
function getDefaultCacheFile(env) {
return join(env.XDG_CACHE_HOME ? join(env.XDG_CACHE_HOME, "open-wot") : join(homedir(), ".cache", "open-wot"), "update-check.json");
}
function readCache(cacheFile) {
if (!existsSync(cacheFile)) return void 0;
let cache;
try {
cache = JSON.parse(readFileSync(cacheFile, "utf8"));
} catch {
return;
}
if (!cache || typeof cache !== "object" || typeof cache.checkedAt !== "number") return void 0;
return {
checkedAt: cache.checkedAt,
latestVersion: typeof cache.latestVersion === "string" ? cache.latestVersion : void 0
};
}
async function fetchAndCacheLatestVersion(options, cacheFile, now) {
let latestVersion;
try {
latestVersion = await fetchLatestVersion(options.packageName, options.registry ?? options.env?.npm_config_registry ?? DEFAULT_REGISTRY, options.fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
} catch {
latestVersion = void 0;
}
const cache = {
checkedAt: now,
latestVersion
};
writeCache(cacheFile, cache);
return cache;
}
async function fetchLatestVersion(packageName, registry, fetchFn, timeoutMs) {
const request = fetchFn ?? globalThis.fetch;
if (typeof request !== "function") return void 0;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await request(`${registry.replace(/\/+$/, "")}/${encodePackageName(packageName)}/latest`, {
headers: {
"accept": "application/json",
"user-agent": `${packageName} update-check`
},
signal: controller.signal
});
if (!response.ok) return void 0;
const json = await response.json();
if (isRegistryLatestResponse(json)) return json.version;
} finally {
clearTimeout(timeout);
}
}
function encodePackageName(packageName) {
if (!packageName.startsWith("@")) return encodeURIComponent(packageName);
const [scope, name$1] = packageName.split("/");
return `${scope}%2f${name$1}`;
}
function isRegistryLatestResponse(value) {
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
}
function writeCache(cacheFile, cache) {
try {
mkdirSync(dirname(cacheFile), { recursive: true });
writeFileSync(cacheFile, `${JSON.stringify(cache, null, 2)}\n`);
} catch {}
}
//#endregion
export { findComponent as a, formatStatusLabel as c, resolveVersion as d, loadMetadataFile as f, lintProject as i, writeStderrLine as l, version as m, getCliUpdateStatus as n, listComponents as o, name as p, analyzeUsage as r, formatLogMessage as s, checkForCliUpdate as t, detectVersion as u };

Sorry, the diff of this file is too big to display