docsui-cli 0.0.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +279 -0
  2. package/dist/commands/add.d.ts +5 -0
  3. package/dist/commands/add.js +254 -0
  4. package/dist/commands/doctor.d.ts +5 -0
  5. package/dist/commands/doctor.js +250 -0
  6. package/dist/commands/init.d.ts +5 -0
  7. package/dist/commands/init.js +548 -0
  8. package/dist/commands/list.d.ts +5 -0
  9. package/dist/commands/list.js +84 -0
  10. package/dist/commands/mcp.d.ts +3 -0
  11. package/dist/commands/mcp.js +1562 -0
  12. package/dist/commands/new.d.ts +5 -0
  13. package/dist/commands/new.js +113 -0
  14. package/dist/commands/remove.d.ts +5 -0
  15. package/dist/commands/remove.js +134 -0
  16. package/dist/commands/save.d.ts +5 -0
  17. package/dist/commands/save.js +60 -0
  18. package/dist/commands/update.d.ts +5 -0
  19. package/dist/commands/update.js +247 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +88 -0
  22. package/dist/latex-to-primitives.d.ts +59 -0
  23. package/dist/latex-to-primitives.js +1019 -0
  24. package/dist/lib/component-registry.d.ts +33 -0
  25. package/dist/lib/component-registry.js +843 -0
  26. package/dist/lib/css-tokens.d.ts +8 -0
  27. package/dist/lib/css-tokens.js +294 -0
  28. package/dist/metadata.d.ts +1 -0
  29. package/dist/metadata.js +4 -0
  30. package/dist/symbol-map.d.ts +30 -0
  31. package/dist/symbol-map.js +1607 -0
  32. package/dist/utils/detect-structure.d.ts +16 -0
  33. package/dist/utils/detect-structure.js +58 -0
  34. package/dist/utils/fetch-component.d.ts +13 -0
  35. package/dist/utils/fetch-component.js +81 -0
  36. package/dist/utils/get-config.d.ts +14 -0
  37. package/dist/utils/get-config.js +19 -0
  38. package/dist/utils/install-deps.d.ts +3 -0
  39. package/dist/utils/install-deps.js +23 -0
  40. package/dist/utils/save-mdx-page.d.ts +35 -0
  41. package/dist/utils/save-mdx-page.js +44 -0
  42. package/dist/utils/scan-mdx.d.ts +20 -0
  43. package/dist/utils/scan-mdx.js +106 -0
  44. package/dist/utils/telemetry.d.ts +3 -0
  45. package/dist/utils/telemetry.js +42 -0
  46. package/dist/utils/write-component.d.ts +7 -0
  47. package/dist/utils/write-component.js +25 -0
  48. package/dist/webview-bundle.js +3478 -0
  49. package/package.json +94 -0
@@ -0,0 +1,16 @@
1
+ type Framework = "nextjs" | "astro" | "react" | "unknown";
2
+ type MdxPipeline = "contentlayer" | "next-mdx-remote" | "next-mdx" | "astro-mdx" | "mdx-rollup" | "unknown";
3
+ interface ProjectStructure {
4
+ hasSrc: boolean;
5
+ componentsDir: string;
6
+ libDir: string;
7
+ framework: Framework;
8
+ mdxPipeline: MdxPipeline;
9
+ hasTypeScript: boolean;
10
+ hasTailwind: boolean;
11
+ }
12
+ declare function detectProjectStructure(cwd?: string): Promise<ProjectStructure>;
13
+ declare function detectMdxPipeline(cwd: string): Promise<MdxPipeline>;
14
+ declare function detectFramework(cwd: string): Promise<Framework>;
15
+
16
+ export { type Framework, type MdxPipeline, type ProjectStructure, detectFramework, detectMdxPipeline, detectProjectStructure };
@@ -0,0 +1,58 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ async function detectProjectStructure(cwd = process.cwd()) {
4
+ const srcExists = await fs.pathExists(path.join(cwd, "src"));
5
+ const framework = await detectFramework(cwd);
6
+ const mdxPipeline = await detectMdxPipeline(cwd);
7
+ const hasTypeScript = await fs.pathExists(path.join(cwd, "tsconfig.json"));
8
+ const hasTailwind = await fs.pathExists(path.join(cwd, "tailwind.config.js")) || await fs.pathExists(path.join(cwd, "tailwind.config.ts")) || await fs.pathExists(path.join(cwd, "tailwind.config.mjs"));
9
+ if (framework === "astro" || srcExists) {
10
+ return {
11
+ hasSrc: true,
12
+ componentsDir: "src/components/docsui",
13
+ libDir: "src/lib",
14
+ framework,
15
+ mdxPipeline,
16
+ hasTypeScript,
17
+ hasTailwind
18
+ };
19
+ }
20
+ return {
21
+ hasSrc: false,
22
+ componentsDir: "components/docsui",
23
+ libDir: "lib",
24
+ framework,
25
+ mdxPipeline,
26
+ hasTypeScript,
27
+ hasTailwind
28
+ };
29
+ }
30
+ async function detectMdxPipeline(cwd) {
31
+ try {
32
+ const pkg = await fs.readJSON(path.join(cwd, "package.json"));
33
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
34
+ if ("contentlayer" in deps || "next-contentlayer" in deps || "contentlayer2" in deps || "next-contentlayer2" in deps)
35
+ return "contentlayer";
36
+ if ("next-mdx-remote" in deps) return "next-mdx-remote";
37
+ if ("@next/mdx" in deps) return "next-mdx";
38
+ if ("@astrojs/mdx" in deps) return "astro-mdx";
39
+ if ("@mdx-js/rollup" in deps) return "mdx-rollup";
40
+ } catch {
41
+ }
42
+ return "unknown";
43
+ }
44
+ async function detectFramework(cwd) {
45
+ const pkgPath = path.join(cwd, "package.json");
46
+ if (!await fs.pathExists(pkgPath)) return "unknown";
47
+ const pkg = await fs.readJSON(pkgPath);
48
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
49
+ if ("next" in deps) return "nextjs";
50
+ if ("astro" in deps) return "astro";
51
+ if ("react" in deps || "react-dom" in deps) return "react";
52
+ return "unknown";
53
+ }
54
+ export {
55
+ detectFramework,
56
+ detectMdxPipeline,
57
+ detectProjectStructure
58
+ };
@@ -0,0 +1,13 @@
1
+ interface ComponentData {
2
+ name: string;
3
+ files: Array<{
4
+ path: string;
5
+ content: string;
6
+ integrity?: string;
7
+ }>;
8
+ dependencies?: string[];
9
+ registryDependencies?: string[];
10
+ }
11
+ declare function fetchComponent(name: string): Promise<ComponentData>;
12
+
13
+ export { type ComponentData, fetchComponent };
@@ -0,0 +1,81 @@
1
+ import axios from "axios";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { readFileSync } from "fs";
5
+ import { createHash } from "crypto";
6
+ import { fileURLToPath } from "url";
7
+ const REGISTRY_BASE = "https://raw.githubusercontent.com/suryaravikumar-space/mdx-ui";
8
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
9
+ function getCliVersion() {
10
+ try {
11
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
12
+ const pkg = JSON.parse(
13
+ readFileSync(path.join(__dirname2, "../../package.json"), "utf-8")
14
+ );
15
+ const v = pkg.version;
16
+ return typeof v === "string" && SEMVER_RE.test(v) ? v : null;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ const CLI_VERSION = getCliVersion();
22
+ function verifyIntegrity(data) {
23
+ for (const file of data.files) {
24
+ if (!file.integrity) continue;
25
+ const [algo, expected] = file.integrity.split("-");
26
+ if (algo !== "sha256") continue;
27
+ const actual = createHash("sha256").update(file.content).digest("base64");
28
+ if (actual !== expected) {
29
+ throw new Error(
30
+ `Integrity check failed for "${file.path}" in component "${data.name}". The registry may be compromised. Aborting.`
31
+ );
32
+ }
33
+ }
34
+ }
35
+ async function fetchComponent(name) {
36
+ try {
37
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
38
+ const possiblePaths = [
39
+ path.join(__dirname2, "../../../../registry/mdx", `${name}.json`),
40
+ path.join(__dirname2, "../../../registry/mdx", `${name}.json`),
41
+ path.join(__dirname2, "../../registry/mdx", `${name}.json`)
42
+ ];
43
+ for (const registryPath of possiblePaths) {
44
+ if (await fs.pathExists(registryPath)) {
45
+ return await fs.readJSON(registryPath);
46
+ }
47
+ }
48
+ } catch {
49
+ }
50
+ const candidates = [];
51
+ if (CLI_VERSION) {
52
+ candidates.push(
53
+ `${REGISTRY_BASE}/v${CLI_VERSION}/registry/mdx/${name}.json`
54
+ );
55
+ }
56
+ candidates.push(`${REGISTRY_BASE}/main/registry/mdx/${name}.json`);
57
+ for (let i = 0; i < candidates.length; i++) {
58
+ const url = candidates[i];
59
+ const isLast = i === candidates.length - 1;
60
+ try {
61
+ const response = await axios.get(url);
62
+ const data = response.data;
63
+ verifyIntegrity(data);
64
+ return data;
65
+ } catch (error) {
66
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
67
+ if (!isLast) continue;
68
+ throw new Error(
69
+ `Component "${name}" not found. Run: npx docsui-cli@latest list`
70
+ );
71
+ }
72
+ throw new Error(
73
+ `Could not fetch "${name}" \u2014 check your internet connection`
74
+ );
75
+ }
76
+ }
77
+ throw new Error(`Could not fetch "${name}" \u2014 check your internet connection`);
78
+ }
79
+ export {
80
+ fetchComponent
81
+ };
@@ -0,0 +1,14 @@
1
+ import { Framework, MdxPipeline } from './detect-structure.js';
2
+
3
+ interface Config {
4
+ componentsDir: string;
5
+ typescript: boolean;
6
+ tailwind: boolean;
7
+ framework?: Framework;
8
+ mdxPipeline?: MdxPipeline;
9
+ cssFile?: string;
10
+ tailwindConfig?: string;
11
+ }
12
+ declare function getConfig(): Promise<Config | null>;
13
+
14
+ export { type Config, getConfig };
@@ -0,0 +1,19 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ async function getConfig() {
4
+ const cwd = process.cwd();
5
+ const configPath = path.join(cwd, "docsui.json");
6
+ try {
7
+ const exists = await fs.pathExists(configPath);
8
+ if (!exists) {
9
+ return null;
10
+ }
11
+ const config = await fs.readJSON(configPath);
12
+ return config;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ export {
18
+ getConfig
19
+ };
@@ -0,0 +1,3 @@
1
+ declare function installDependencies(deps: string[]): Promise<void>;
2
+
3
+ export { installDependencies };
@@ -0,0 +1,23 @@
1
+ import { execa } from "execa";
2
+ async function installDependencies(deps) {
3
+ if (deps.length === 0) return;
4
+ const packageManager = await detectPackageManager();
5
+ const args = packageManager === "npm" ? ["install", ...deps] : ["add", ...deps];
6
+ await execa(packageManager, args, {
7
+ cwd: process.cwd()
8
+ });
9
+ }
10
+ async function detectPackageManager() {
11
+ const { stdout } = await execa("which", ["pnpm"], {
12
+ reject: false
13
+ });
14
+ if (stdout) return "pnpm";
15
+ const { stdout: yarnStdout } = await execa("which", ["yarn"], {
16
+ reject: false
17
+ });
18
+ if (yarnStdout) return "yarn";
19
+ return "npm";
20
+ }
21
+ export {
22
+ installDependencies
23
+ };
@@ -0,0 +1,35 @@
1
+ interface SaveMdxPageOptions {
2
+ /** The MDX string to write */
3
+ content: string;
4
+ /** Root output folder — e.g. "content", "docs", "src/content" */
5
+ outDir: string;
6
+ /**
7
+ * Ordered path segments.
8
+ * Every item except the last becomes a folder.
9
+ * The last item becomes the filename (`.ai.mdx` is appended automatically).
10
+ *
11
+ * @example
12
+ * path: ["2025", "data-structures", "binary-search-trees", "introduction"]
13
+ * // writes to: <outDir>/2025/data-structures/binary-search-trees/introduction.ai.mdx
14
+ *
15
+ * @example
16
+ * path: ["lecture-1"]
17
+ * // writes to: <outDir>/lecture-1.ai.mdx
18
+ */
19
+ path: string[];
20
+ }
21
+ interface SaveMdxPageResult {
22
+ /** Absolute path of the written file */
23
+ filePath: string;
24
+ /** Path relative to process.cwd() */
25
+ relativePath: string;
26
+ }
27
+ /**
28
+ * Write an MDX string to a fully dynamic folder structure.
29
+ *
30
+ * No assumptions about segment names, year, subject, or depth.
31
+ * The caller owns the structure entirely via the `path` array.
32
+ */
33
+ declare function saveMdxPage(options: SaveMdxPageOptions): Promise<SaveMdxPageResult>;
34
+
35
+ export { type SaveMdxPageOptions, type SaveMdxPageResult, saveMdxPage };
@@ -0,0 +1,44 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ async function saveMdxPage(options) {
4
+ const { content, outDir, path: segments } = options;
5
+ if (!content || content.trim() === "") {
6
+ throw new Error("saveMdxPage: content must not be empty");
7
+ }
8
+ if (!outDir || outDir.trim() === "") {
9
+ throw new Error("saveMdxPage: outDir must not be empty");
10
+ }
11
+ if (!segments || segments.length === 0) {
12
+ throw new Error("saveMdxPage: path must have at least one segment");
13
+ }
14
+ for (const segment of segments) {
15
+ if (!segment || segment.trim() === "") {
16
+ throw new Error("saveMdxPage: path segments must not be empty strings");
17
+ }
18
+ if (path.isAbsolute(segment)) {
19
+ throw new Error(
20
+ `saveMdxPage: path segment "${segment}" must not be absolute`
21
+ );
22
+ }
23
+ if (segment.includes("..")) {
24
+ throw new Error(
25
+ `saveMdxPage: path segment "${segment}" must not contain ".."`
26
+ );
27
+ }
28
+ }
29
+ const folders = segments.slice(0, -1);
30
+ const rawName = segments[segments.length - 1];
31
+ const baseName = rawName.replace(/\.[^.]+$/, "");
32
+ const fileName = `${baseName}.ai.mdx`;
33
+ const cwd = process.cwd();
34
+ const absoluteOutDir = path.isAbsolute(outDir) ? outDir : path.join(cwd, outDir);
35
+ const targetDir = path.join(absoluteOutDir, ...folders);
36
+ const filePath = path.join(targetDir, fileName);
37
+ await fs.ensureDir(targetDir);
38
+ await fs.writeFile(filePath, content, "utf-8");
39
+ const relativePath = path.relative(cwd, filePath);
40
+ return { filePath, relativePath };
41
+ }
42
+ export {
43
+ saveMdxPage
44
+ };
@@ -0,0 +1,20 @@
1
+ declare function extractComponentNames(content: string): Set<string>;
2
+ declare function extractRegistered(mdxComponentsSource: string): Set<string>;
3
+ interface ScanResult {
4
+ file: string;
5
+ missing: Array<{
6
+ name: string;
7
+ installAs: string;
8
+ }>;
9
+ }
10
+ /**
11
+ * Scan all .mdx files under cwd for unregistered components.
12
+ * Returns results and prints warnings to the console.
13
+ */
14
+ declare function scanMdxComponents(cwd: string): Promise<ScanResult[]>;
15
+ /**
16
+ * Print scan results as warnings with install commands.
17
+ */
18
+ declare function printScanWarnings(results: ScanResult[], pm?: string): void;
19
+
20
+ export { type ScanResult, extractComponentNames, extractRegistered, printScanWarnings, scanMdxComponents };
@@ -0,0 +1,106 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { glob } from "glob";
5
+ function extractComponentNames(content) {
6
+ const names = /* @__PURE__ */ new Set();
7
+ const tagRe = /<([A-Z][a-zA-Z0-9]*)/g;
8
+ const stripped = content.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "");
9
+ let m;
10
+ while ((m = tagRe.exec(stripped)) !== null) {
11
+ names.add(m[1]);
12
+ }
13
+ return names;
14
+ }
15
+ function extractRegistered(mdxComponentsSource) {
16
+ const names = /* @__PURE__ */ new Set();
17
+ const importRe = /import\s*\{([^}]+)\}/g;
18
+ let m;
19
+ while ((m = importRe.exec(mdxComponentsSource)) !== null) {
20
+ m[1].split(",").forEach((s) => {
21
+ const name = s.trim().split(/\s+as\s+/).pop()?.trim();
22
+ if (name && /^[A-Z]/.test(name)) names.add(name);
23
+ });
24
+ }
25
+ const mapRe = /:\s*([A-Z][a-zA-Z0-9]*)/g;
26
+ while ((m = mapRe.exec(mdxComponentsSource)) !== null) {
27
+ names.add(m[1]);
28
+ }
29
+ return names;
30
+ }
31
+ const IGNORE = /* @__PURE__ */ new Set([
32
+ "Fragment",
33
+ "React",
34
+ "StrictMode",
35
+ "Suspense",
36
+ "ErrorBoundary"
37
+ ]);
38
+ async function scanMdxComponents(cwd) {
39
+ const candidates = [
40
+ "src/components/docsui/mdx-components.tsx",
41
+ "src/components/mdx-components.tsx",
42
+ "components/docsui/mdx-components.tsx",
43
+ "components/mdx-components.tsx"
44
+ ];
45
+ let mdxComponentsPath = "";
46
+ for (const c of candidates) {
47
+ const full = path.join(cwd, c);
48
+ if (fs.existsSync(full)) {
49
+ mdxComponentsPath = full;
50
+ break;
51
+ }
52
+ }
53
+ const registered = mdxComponentsPath ? extractRegistered(fs.readFileSync(mdxComponentsPath, "utf-8")) : /* @__PURE__ */ new Set();
54
+ const mdxFiles = await glob("**/*.mdx", {
55
+ cwd,
56
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.next/**"],
57
+ absolute: true
58
+ });
59
+ if (mdxFiles.length === 0) return [];
60
+ const results = [];
61
+ for (const file of mdxFiles) {
62
+ const content = fs.readFileSync(file, "utf-8");
63
+ const used = extractComponentNames(content);
64
+ const missing = [];
65
+ for (const name of used) {
66
+ if (IGNORE.has(name)) continue;
67
+ if (registered.has(name)) continue;
68
+ const installAs = name.replace(/([A-Z])/g, (m, c, i) => i === 0 ? c : `-${c}`).toLowerCase();
69
+ missing.push({ name, installAs });
70
+ }
71
+ if (missing.length > 0) {
72
+ results.push({ file: path.relative(cwd, file), missing });
73
+ }
74
+ }
75
+ return results;
76
+ }
77
+ function printScanWarnings(results, pm = "pnpm") {
78
+ if (results.length === 0) return;
79
+ const allMissing = /* @__PURE__ */ new Map();
80
+ for (const r of results) {
81
+ for (const m of r.missing) allMissing.set(m.name, m.installAs);
82
+ }
83
+ console.log("");
84
+ console.log(chalk.yellow("\u26A0 Unregistered components found in MDX files:\n"));
85
+ for (const { file, missing } of results) {
86
+ for (const { name } of missing) {
87
+ console.log(
88
+ ` ${chalk.dim(file)} \u2192 ${chalk.cyan(`<${name}>`)} is not installed`
89
+ );
90
+ }
91
+ }
92
+ console.log("");
93
+ console.log(chalk.bold("Fix:"));
94
+ const dlx = pm === "pnpm" ? "pnpm dlx" : pm === "yarn" ? "yarn dlx" : "npx";
95
+ const installNames = [...allMissing.values()].join(" ");
96
+ console.log(
97
+ ` ${chalk.green(`${dlx} docsui add ${installNames}`)}`
98
+ );
99
+ console.log("");
100
+ }
101
+ export {
102
+ extractComponentNames,
103
+ extractRegistered,
104
+ printScanWarnings,
105
+ scanMdxComponents
106
+ };
@@ -0,0 +1,3 @@
1
+ declare function ping(event: string, data?: Record<string, unknown>): void;
2
+
3
+ export { ping };
@@ -0,0 +1,42 @@
1
+ import { execSync } from "child_process";
2
+ import { createHash } from "crypto";
3
+ import os from "os";
4
+ import axios from "axios";
5
+ const POSTHOG_API_KEY = "phc_zNLMNdcFKJwLJqD6NK8uQwqSwXYYQfm6oAnga7sLQt6b";
6
+ const POSTHOG_ENDPOINT = "https://app.posthog.com/capture/";
7
+ const OWN_REPO = "suryaravikumar-space/mdx-ui";
8
+ function getDistinctId() {
9
+ const raw = `${os.hostname()}:${os.userInfo().username}`;
10
+ return createHash("sha256").update(raw).digest("hex").slice(0, 16);
11
+ }
12
+ function isOptedOut() {
13
+ return process.env.MDX_UI_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1";
14
+ }
15
+ function isOwnMachine() {
16
+ try {
17
+ const remote = execSync("git remote get-url origin 2>/dev/null", {
18
+ timeout: 1500,
19
+ stdio: ["pipe", "pipe", "pipe"]
20
+ }).toString().trim();
21
+ return remote.includes(OWN_REPO);
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+ function ping(event, data) {
27
+ if (isOptedOut() || isOwnMachine()) return;
28
+ axios.post(
29
+ POSTHOG_ENDPOINT,
30
+ {
31
+ api_key: POSTHOG_API_KEY,
32
+ event,
33
+ distinct_id: getDistinctId(),
34
+ properties: { ...data, $lib: "docsui-cli" }
35
+ },
36
+ { timeout: 3e3 }
37
+ ).catch(() => {
38
+ });
39
+ }
40
+ export {
41
+ ping
42
+ };
@@ -0,0 +1,7 @@
1
+ import { Config } from './get-config.js';
2
+ import { ComponentData } from './fetch-component.js';
3
+ import './detect-structure.js';
4
+
5
+ declare function writeComponent(component: ComponentData, config: Config): Promise<void>;
6
+
7
+ export { writeComponent };
@@ -0,0 +1,25 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ function resolveFilePath(filePath, componentsDir, cwd) {
4
+ if (filePath.startsWith("lib/")) {
5
+ const libRoot = componentsDir.startsWith("src/") ? path.join(cwd, "src") : cwd;
6
+ return path.join(libRoot, filePath);
7
+ }
8
+ return path.join(cwd, componentsDir, filePath);
9
+ }
10
+ async function writeComponent(component, config) {
11
+ const cwd = process.cwd();
12
+ const framework = config.framework ?? "unknown";
13
+ for (const file of component.files) {
14
+ const filePath = resolveFilePath(file.path, config.componentsDir, cwd);
15
+ await fs.ensureDir(path.dirname(filePath));
16
+ let content = file.content;
17
+ if (framework === "react") {
18
+ content = content.replace(/^["']use client["']\n\n?/m, "");
19
+ }
20
+ await fs.writeFile(filePath, content, "utf-8");
21
+ }
22
+ }
23
+ export {
24
+ writeComponent
25
+ };