@thallylabs/mcp 0.8.1 → 0.10.0

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/dist/index.js CHANGED
@@ -11,258 +11,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
  import { z } from "zod";
12
12
 
13
13
  // src/lib/scaffold.ts
14
- import { existsSync, mkdirSync, readdirSync, writeFileSync, readFileSync, cpSync } from "fs";
15
- import { resolve, join } from "path";
16
- import { execSync } from "child_process";
17
- import { Readable, pipeline } from "stream";
18
- import { promisify } from "util";
19
- import tar from "tar";
20
- var pipelineAsync = promisify(pipeline);
21
- var MCP_TEMPLATE_REPOSITORY = "thallylabs/docs";
22
- var TARBALL_URL = `https://codeload.github.com/${MCP_TEMPLATE_REPOSITORY}/tar.gz/main`;
23
- var MCP_EXCLUDE_PATHS = [
24
- "/cli/",
25
- "/packages/",
26
- "/node_modules/",
27
- "/.git/",
28
- "/thally-track.yml",
29
- "/CODEOWNERS",
30
- "/CLAUDE.md",
31
- "/notes/"
32
- ];
33
- function shouldIncludeMcpTemplatePath(path) {
34
- return !MCP_EXCLUDE_PATHS.some((excluded) => path.includes(excluded));
35
- }
36
- var STARTER_PAGES = {
37
- "introduction.mdx": `---
38
- title: Introduction
39
- description: Welcome to {NAME} documentation.
40
- ---
41
-
42
- ## Welcome
43
-
44
- This is the home page of your **{NAME}** documentation site, powered by [Thally](https://github.com/thallylabs/thally).
45
-
46
- Get started by editing this file at \`src/content/introduction.mdx\`.
47
- `,
48
- "quickstart.mdx": `---
49
- title: Quickstart
50
- description: Get up and running with {NAME} in under 5 minutes.
51
- ---
52
-
53
- ## Installation
54
-
55
- \`\`\`bash
56
- npm install {SLUG}
57
- \`\`\`
58
-
59
- ## Basic usage
60
-
61
- \`\`\`ts
62
- import { create } from '{SLUG}'
63
-
64
- const client = create({ apiKey: 'your-api-key' })
65
- \`\`\`
66
-
67
- That's it \u2014 you're ready to go!
68
- `
69
- };
70
- function buildStarterDocsJson({
71
- enableAiChat,
72
- repoUrl,
73
- i18nLocales
74
- }) {
75
- const config = {};
76
- if (enableAiChat) {
77
- config.ai = { chat: true };
78
- }
79
- if (repoUrl) {
80
- config.navbar = {
81
- links: [{ label: "GitHub", href: repoUrl, type: "github" }],
82
- primary: { label: "Get started", href: "/quickstart" }
83
- };
84
- }
85
- if (i18nLocales && i18nLocales.length > 0) {
86
- config.i18n = {
87
- defaultLocale: "en",
88
- locales: [{ code: "en", label: "English" }, ...i18nLocales]
89
- };
90
- }
91
- config.tabs = [
92
- {
93
- tab: "Overview",
94
- groups: [{ group: "Getting Started", pages: ["introduction", "quickstart"] }]
95
- },
96
- { tab: "API Reference", api: { source: "openapi.yaml" } },
97
- { tab: "Changelog", href: "/changelog" }
98
- ];
99
- return JSON.stringify(config, null, 2) + "\n";
100
- }
101
- function slugify(name) {
102
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
103
- }
104
- function run(cmd, cwd) {
105
- execSync(cmd, { cwd, stdio: "inherit" });
106
- }
107
- async function downloadTemplate(targetDir) {
108
- const response = await fetch(TARBALL_URL);
109
- if (!response.ok) {
110
- throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
111
- }
112
- if (!response.body) {
113
- throw new Error("Response body is empty");
114
- }
115
- const nodeStream = Readable.fromWeb(response.body);
116
- await pipelineAsync(
117
- nodeStream,
118
- tar.extract({
119
- cwd: targetDir,
120
- strip: 1,
121
- filter: shouldIncludeMcpTemplatePath
122
- })
123
- );
124
- }
125
- function writeStarterContent(targetDir, projectName, slug, enableAiChat = true, repoUrl = "", i18nLocales) {
126
- const contentDir = join(targetDir, "src", "content");
127
- if (existsSync(contentDir)) {
128
- const entries = readdirSync(contentDir);
129
- for (const entry of entries) {
130
- execSync(`rm -rf "${join(contentDir, entry)}"`);
131
- }
132
- } else {
133
- mkdirSync(contentDir, { recursive: true });
134
- }
135
- for (const [filename, template] of Object.entries(STARTER_PAGES)) {
136
- const content = template.replace(/\{NAME\}/g, projectName).replace(/\{SLUG\}/g, slug);
137
- writeFileSync(join(contentDir, filename), content, "utf8");
138
- }
139
- writeFileSync(
140
- join(targetDir, "docs.json"),
141
- buildStarterDocsJson({ enableAiChat, repoUrl: repoUrl || void 0, i18nLocales }),
142
- "utf8"
143
- );
144
- }
145
- function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
146
- const siteFile = join(targetDir, "src", "data", "site.ts");
147
- if (!existsSync(siteFile)) return;
148
- let source = readFileSync(siteFile, "utf8");
149
- source = source.replace(/name:\s*'[^']*'/, `name: '${projectName.replace(/'/g, "\\'")}'`);
150
- source = source.replace(
151
- /description:\s*\n\s*'[^']*'/,
152
- `description:
153
- '${description.replace(/'/g, "\\'")}'`
154
- );
155
- source = source.replace(
156
- /const brandPreset:\s*BrandPresetKey\s*=\s*'[^']*'/,
157
- `const brandPreset: BrandPresetKey = '${brandPreset}'`
158
- );
159
- if (repoUrl) {
160
- source = source.replace(/repoUrl:\s*'[^']*'/, `repoUrl: '${repoUrl}'`);
161
- source = source.replace(
162
- /\{\s*label:\s*'GitHub',\s*href:\s*'[^']*'\s*\}/,
163
- `{ label: 'GitHub', href: '${repoUrl}' }`
164
- );
165
- source = source.replace(
166
- /\{\s*label:\s*'Support',\s*href:\s*'[^']*'\s*\}/,
167
- `{ label: 'Support', href: '${repoUrl}/issues/new' }`
168
- );
169
- }
170
- writeFileSync(siteFile, source, "utf8");
171
- }
172
- function patchTopBarNavigation(targetDir) {
173
- const filePath = join(targetDir, "src", "components", "layout", "top-bar.tsx");
174
- if (!existsSync(filePath)) return;
175
- const source = readFileSync(filePath, "utf8");
176
- if (!source.includes("target={isExternal ? '_blank' : undefined}")) return;
177
- const patched = source.replace(
178
- /if \(collection\.href\) \{\n const isExternal[^\n]+\n return \(\n <a[\s\S]*?<\/a>\n \)\n \}/,
179
- `if (collection.href) {
180
- const isExternal = /^https?:\\/\\//.test(collection.href)
181
- if (isExternal) {
182
- return (
183
- <a
184
- key={collection.id}
185
- href={collection.href}
186
- target="_blank"
187
- rel="noreferrer"
188
- className={baseClasses}
189
- >
190
- {collection.label}
191
- </a>
192
- )
193
- }
194
- return (
195
- <Link
196
- key={collection.id}
197
- href={collection.href}
198
- className={baseClasses}
199
- >
200
- {collection.label}
201
- </Link>
202
- )
203
- }`
204
- );
205
- writeFileSync(filePath, patched, "utf8");
206
- }
207
- function patchApiReferenceGuard(targetDir) {
208
- const filePath = join(targetDir, "src", "data", "api-reference.ts");
209
- if (!existsSync(filePath)) return;
210
- let source = readFileSync(filePath, "utf8");
211
- source = source.replace(
212
- /export async function buildApiNavigation\([^)]*\)[^{]*\{\n/,
213
- (match) => `${match} if (apiReferenceConfig.specs.length === 0) return []
214
- `
215
- );
216
- writeFileSync(filePath, source, "utf8");
217
- }
218
- function patchOpenApiFetch(targetDir) {
219
- const filePath = join(targetDir, "src", "lib", "openapi", "fetch.ts");
220
- if (!existsSync(filePath)) return;
221
- let source = readFileSync(filePath, "utf8");
222
- source = source.replace(
223
- /const absolutePath = path\.isAbsolute\(filePath\) \? filePath : path\.resolve\(process\.cwd\(\), filePath\)/,
224
- `const absolutePath = filePath.startsWith('/')
225
- ? path.resolve(process.cwd(), 'public', filePath.slice(1))
226
- : path.resolve(process.cwd(), filePath)`
227
- );
228
- writeFileSync(filePath, source, "utf8");
229
- }
230
- function updateEnvExample(targetDir) {
231
- const envFile = join(targetDir, ".env.example");
232
- if (existsSync(envFile)) {
233
- const envLocal = join(targetDir, ".env.local");
234
- if (!existsSync(envLocal)) cpSync(envFile, envLocal);
235
- }
236
- }
237
- function installDeps(targetDir) {
238
- run("npm install", targetDir);
239
- }
240
- function initGit(targetDir) {
241
- try {
242
- run("git init", targetDir);
243
- run("git add -A", targetDir);
244
- run('git commit -m "Initial commit from create-thally-docs"', targetDir);
245
- } catch {
246
- }
247
- }
14
+ import {
15
+ STABLE_SCAFFOLD_RELEASE,
16
+ STARTER_REPOSITORY,
17
+ scaffold as scaffoldProject
18
+ } from "create-thally-docs/scaffold";
19
+ var MCP_STARTER_COMMIT_SHA = STABLE_SCAFFOLD_RELEASE.source.commitSha;
20
+ var MCP_SCAFFOLD_RELEASE_ID = STABLE_SCAFFOLD_RELEASE.id;
248
21
  async function scaffold(options) {
249
- const { projectDir, projectName, description, brandPreset, repoUrl, doInstall, enableAiChat = true, i18nLocales } = options;
250
- const targetDir = resolve(projectDir);
251
- if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
252
- throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
253
- }
254
- mkdirSync(targetDir, { recursive: true });
255
- const slug = slugify(projectName);
256
- await downloadTemplate(targetDir);
257
- writeStarterContent(targetDir, projectName, slug, enableAiChat, repoUrl, i18nLocales);
258
- updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl);
259
- patchApiReferenceGuard(targetDir);
260
- patchTopBarNavigation(targetDir);
261
- patchOpenApiFetch(targetDir);
262
- updateEnvExample(targetDir);
263
- if (doInstall) installDeps(targetDir);
264
- initGit(targetDir);
265
- return { projectDir: targetDir };
22
+ return scaffoldProject(options);
266
23
  }
267
24
 
268
25
  // src/tools/create-project.ts
@@ -274,7 +31,7 @@ var createProjectSchema = z.object({
274
31
  repoUrl: z.string().optional().describe("GitHub repository URL (optional)"),
275
32
  install: z.boolean().optional().default(true).describe("Whether to run npm install after scaffolding"),
276
33
  enableAiChat: z.boolean().optional().default(true).describe("Enable AI chat in docs.json (default true)"),
277
- i18nLocales: z.array(z.object({ code: z.string(), label: z.string() })).optional().describe('Secondary locales to enable (e.g. [{code:"es",label:"Espa\xF1ol"}])')
34
+ i18nLocales: z.array(z.object({ code: z.string(), label: z.string() })).optional().describe('Additional documentation locales to include alongside English (e.g. [{code:"fr",label:"Fran\xE7ais"}])')
278
35
  });
279
36
  async function handleCreateProject(input) {
280
37
  const { projectDir, brandPreset = "primary", install = true } = input;
@@ -313,20 +70,20 @@ async function handleCreateProject(input) {
313
70
 
314
71
  // src/tools/add-page.ts
315
72
  import { z as z2 } from "zod";
316
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
317
- import { join as join3, dirname } from "path";
73
+ import { existsSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
74
+ import { join as join2, dirname } from "path";
318
75
 
319
76
  // src/lib/docs-json.ts
320
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
321
- import { join as join2 } from "path";
77
+ import { readFileSync, writeFileSync } from "fs";
78
+ import { join } from "path";
322
79
  function readDocsJson(projectDir) {
323
- const docsPath = join2(projectDir, "docs.json");
324
- const raw = readFileSync2(docsPath, "utf8");
80
+ const docsPath = join(projectDir, "docs.json");
81
+ const raw = readFileSync(docsPath, "utf8");
325
82
  return JSON.parse(raw);
326
83
  }
327
84
  function writeDocsJson(projectDir, config) {
328
- const docsPath = join2(projectDir, "docs.json");
329
- writeFileSync2(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
85
+ const docsPath = join(projectDir, "docs.json");
86
+ writeFileSync(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
330
87
  }
331
88
 
332
89
  // src/lib/page-echo.ts
@@ -378,11 +135,11 @@ async function handleAddPage(input) {
378
135
  `Invalid pageId "${pageId}". Use only alphanumeric characters, hyphens, and slashes. Do not include .mdx extension.`
379
136
  );
380
137
  }
381
- const mdxPath = join3(projectDir, "src", "content", `${pageId}.mdx`);
382
- if (existsSync2(mdxPath)) {
138
+ const mdxPath = join2(projectDir, "src", "content", `${pageId}.mdx`);
139
+ if (existsSync(mdxPath)) {
383
140
  throw new Error(`Page already exists at: ${mdxPath}`);
384
141
  }
385
- mkdirSync2(dirname(mdxPath), { recursive: true });
142
+ mkdirSync(dirname(mdxPath), { recursive: true });
386
143
  const frontmatterLines = [`title: ${title}`];
387
144
  if (description) {
388
145
  frontmatterLines.push(`description: ${description}`);
@@ -396,7 +153,7 @@ ${frontmatterLines.join("\n")}
396
153
 
397
154
  ${bodyContent}
398
155
  `;
399
- writeFileSync3(mdxPath, mdxContent, "utf8");
156
+ writeFileSync2(mdxPath, mdxContent, "utf8");
400
157
  const config = readDocsJson(projectDir);
401
158
  let targetTab = config.tabs[0];
402
159
  if (input.tab) {
@@ -509,8 +266,8 @@ async function handleListPages(input) {
509
266
 
510
267
  // src/tools/update-page.ts
511
268
  import { z as z5 } from "zod";
512
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
513
- import { join as join4 } from "path";
269
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
270
+ import { join as join3 } from "path";
514
271
 
515
272
  // src/lib/frontmatter.ts
516
273
  import matter from "gray-matter";
@@ -538,11 +295,11 @@ var updatePageSchema = z5.object({
538
295
  });
539
296
  function findPageFile(projectDir, pageId) {
540
297
  const candidates = [
541
- join4(projectDir, "src", "content", `${pageId}.mdx`),
542
- join4(projectDir, "src", "content", `${pageId}/index.mdx`)
298
+ join3(projectDir, "src", "content", `${pageId}.mdx`),
299
+ join3(projectDir, "src", "content", `${pageId}/index.mdx`)
543
300
  ];
544
301
  for (const candidate of candidates) {
545
- if (existsSync3(candidate)) return candidate;
302
+ if (existsSync2(candidate)) return candidate;
546
303
  }
547
304
  return null;
548
305
  }
@@ -556,7 +313,7 @@ async function handleUpdatePage(input) {
556
313
  src/content/${pageId}/index.mdx`
557
314
  );
558
315
  }
559
- const raw = readFileSync3(filePath, "utf8");
316
+ const raw = readFileSync2(filePath, "utf8");
560
317
  const parsed = parseFrontmatter(raw);
561
318
  const newFm = { ...parsed.data };
562
319
  if (input.title !== void 0) newFm["title"] = input.title;
@@ -566,7 +323,7 @@ async function handleUpdatePage(input) {
566
323
  }
567
324
  const newBody = input.content !== void 0 ? stripEchoedPageHeader(input.content, pageId) : parsed.content;
568
325
  const newContent = stringifyFrontmatter(newBody.trim(), newFm);
569
- writeFileSync4(filePath, newContent, "utf8");
326
+ writeFileSync3(filePath, newContent, "utf8");
570
327
  return [
571
328
  `\u2705 Page updated: ${filePath}`,
572
329
  ` pageId: ${pageId}`,
@@ -622,8 +379,8 @@ async function handleImportDocs(input) {
622
379
 
623
380
  // src/tools/search-docs.ts
624
381
  import { z as z7 } from "zod";
625
- import { readdirSync as readdirSync2, statSync, readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
626
- import { join as join5, relative, extname } from "path";
382
+ import { readdirSync, statSync, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
383
+ import { join as join4, relative, extname } from "path";
627
384
  var searchDocsSchema = z7.object({
628
385
  projectDir: z7.string().describe("Path to the Thally project root"),
629
386
  query: z7.string().describe("Search query"),
@@ -632,12 +389,12 @@ var searchDocsSchema = z7.object({
632
389
  function scanMdxFiles(dir, results) {
633
390
  let entries;
634
391
  try {
635
- entries = readdirSync2(dir);
392
+ entries = readdirSync(dir);
636
393
  } catch {
637
394
  return;
638
395
  }
639
396
  for (const entry of entries) {
640
- const fullPath = join5(dir, entry);
397
+ const fullPath = join4(dir, entry);
641
398
  try {
642
399
  const stat = statSync(fullPath);
643
400
  if (stat.isDirectory()) {
@@ -655,7 +412,7 @@ function scoreFiles(files, contentDir, query) {
655
412
  for (const filePath of files) {
656
413
  let raw;
657
414
  try {
658
- raw = readFileSync4(filePath, "utf8");
415
+ raw = readFileSync3(filePath, "utf8");
659
416
  } catch {
660
417
  continue;
661
418
  }
@@ -680,8 +437,8 @@ function scoreFiles(files, contentDir, query) {
680
437
  }
681
438
  async function handleSearchDocs(input) {
682
439
  const { projectDir, query, limit = 5 } = input;
683
- const contentDir = join5(projectDir, "src", "content");
684
- if (!existsSync4(contentDir)) {
440
+ const contentDir = join4(projectDir, "src", "content");
441
+ if (!existsSync3(contentDir)) {
685
442
  throw new Error(`Content directory not found: ${contentDir}`);
686
443
  }
687
444
  const files = [];
@@ -776,22 +533,22 @@ async function handleAgentReadiness(input) {
776
533
 
777
534
  // src/tools/read-page.ts
778
535
  import { z as z10 } from "zod";
779
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
780
- import { join as join6 } from "path";
536
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
537
+ import { join as join5 } from "path";
781
538
  var readPageSchema = z10.object({
782
539
  projectDir: z10.string().describe("Path to the Thally project root"),
783
540
  pageId: z10.string().describe('Page ID, e.g. "guides/authentication"')
784
541
  });
785
542
  async function handleReadPage(input) {
786
543
  const { projectDir, pageId } = input;
787
- const contentDir = join6(projectDir, "src", "content");
544
+ const contentDir = join5(projectDir, "src", "content");
788
545
  const candidates = [
789
- join6(contentDir, `${pageId}.mdx`),
790
- join6(contentDir, `${pageId}/index.mdx`)
546
+ join5(contentDir, `${pageId}.mdx`),
547
+ join5(contentDir, `${pageId}/index.mdx`)
791
548
  ];
792
549
  let filePath = null;
793
550
  for (const c of candidates) {
794
- if (existsSync5(c)) {
551
+ if (existsSync4(c)) {
795
552
  filePath = c;
796
553
  break;
797
554
  }
@@ -799,7 +556,7 @@ async function handleReadPage(input) {
799
556
  if (!filePath) {
800
557
  throw new Error(`Page not found: "${pageId}". No file at src/content/${pageId}.mdx`);
801
558
  }
802
- const raw = readFileSync5(filePath, "utf8");
559
+ const raw = readFileSync4(filePath, "utf8");
803
560
  const { data, content } = parseFrontmatter(raw);
804
561
  const title = data.title ?? pageId;
805
562
  const description = data.description ?? "";
@@ -811,8 +568,8 @@ async function handleReadPage(input) {
811
568
 
812
569
  // src/tools/get-context.ts
813
570
  import { z as z11 } from "zod";
814
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
815
- import { join as join7 } from "path";
571
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
572
+ import { join as join6 } from "path";
816
573
  var getContextSchema = z11.object({
817
574
  projectDir: z11.string().describe("Path to the Thally project root"),
818
575
  topic: z11.string().describe("Topic or question to find relevant docs for"),
@@ -820,8 +577,8 @@ var getContextSchema = z11.object({
820
577
  });
821
578
  async function handleGetContext(input) {
822
579
  const { projectDir, topic, maxTokens = 4e3 } = input;
823
- const contentDir = join7(projectDir, "src", "content");
824
- if (!existsSync6(contentDir)) {
580
+ const contentDir = join6(projectDir, "src", "content");
581
+ if (!existsSync5(contentDir)) {
825
582
  throw new Error(`Content directory not found: ${contentDir}`);
826
583
  }
827
584
  const files = [];
@@ -835,13 +592,13 @@ async function handleGetContext(input) {
835
592
  const sections = [];
836
593
  for (const result of scored) {
837
594
  const candidates = [
838
- join7(contentDir, `${result.pageId}.mdx`),
839
- join7(contentDir, `${result.pageId}/index.mdx`)
595
+ join6(contentDir, `${result.pageId}.mdx`),
596
+ join6(contentDir, `${result.pageId}/index.mdx`)
840
597
  ];
841
598
  let content = "";
842
599
  for (const c of candidates) {
843
- if (existsSync6(c)) {
844
- const raw = readFileSync6(c, "utf8");
600
+ if (existsSync5(c)) {
601
+ const raw = readFileSync5(c, "utf8");
845
602
  const { content: body } = parseFrontmatter(raw);
846
603
  content = body.trim();
847
604
  break;
@@ -866,8 +623,8 @@ async function handleGetContext(input) {
866
623
 
867
624
  // src/tools/lint-project.ts
868
625
  import { z as z12 } from "zod";
869
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
870
- import { join as join8 } from "path";
626
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
627
+ import { join as join7 } from "path";
871
628
  var lintProjectSchema = z12.object({
872
629
  projectDir: z12.string().describe("Path to the Thally project root"),
873
630
  fix: z12.boolean().optional().default(false).describe("Auto-fix issues where possible (adds orphan pages to nav)")
@@ -898,9 +655,9 @@ function addOrphanToNav(projectDir, pageId) {
898
655
  }
899
656
  async function handleLintProject(input) {
900
657
  const { projectDir, fix = false } = input;
901
- const contentDir = join8(projectDir, "src", "content");
658
+ const contentDir = join7(projectDir, "src", "content");
902
659
  const issues = [];
903
- if (!existsSync7(join8(projectDir, "docs.json"))) {
660
+ if (!existsSync6(join7(projectDir, "docs.json"))) {
904
661
  throw new Error(`Not a Thally project: docs.json not found in ${projectDir}`);
905
662
  }
906
663
  const config = readDocsJson(projectDir);
@@ -919,10 +676,10 @@ async function handleLintProject(input) {
919
676
  }
920
677
  for (const pageId of navPageIds) {
921
678
  const candidates = [
922
- join8(contentDir, `${pageId}.mdx`),
923
- join8(contentDir, `${pageId}/index.mdx`)
679
+ join7(contentDir, `${pageId}.mdx`),
680
+ join7(contentDir, `${pageId}/index.mdx`)
924
681
  ];
925
- if (!candidates.some((c) => existsSync7(c))) {
682
+ if (!candidates.some((c) => existsSync6(c))) {
926
683
  issues.push({
927
684
  severity: "error",
928
685
  message: `"${pageId}" is in docs.json but has no MDX file`,
@@ -931,7 +688,7 @@ async function handleLintProject(input) {
931
688
  }
932
689
  }
933
690
  const allFiles = [];
934
- if (existsSync7(contentDir)) {
691
+ if (existsSync6(contentDir)) {
935
692
  scanMdxFiles(contentDir, allFiles);
936
693
  }
937
694
  const fixedOrphans = [];
@@ -949,7 +706,7 @@ async function handleLintProject(input) {
949
706
  let data = {};
950
707
  let content = "";
951
708
  try {
952
- const raw = readFileSync7(filePath, "utf8");
709
+ const raw = readFileSync6(filePath, "utf8");
953
710
  const parsed = parseFrontmatter(raw);
954
711
  data = parsed.data;
955
712
  content = parsed.content;
@@ -1006,8 +763,8 @@ async function handleLintProject(input) {
1006
763
 
1007
764
  // src/tools/translate-docs.ts
1008
765
  import { z as z13 } from "zod";
1009
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1010
- import { join as join9, dirname as dirname2 } from "path";
766
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
767
+ import { join as join8, dirname as dirname2 } from "path";
1011
768
  import Anthropic from "@anthropic-ai/sdk";
1012
769
  import pLimit from "p-limit";
1013
770
  var translateDocsSchema = z13.object({
@@ -1019,8 +776,8 @@ var translateDocsSchema = z13.object({
1019
776
  model: z13.string().optional().default("claude-sonnet-4-6").describe("Claude model to use for translation")
1020
777
  });
1021
778
  function readDocsJson2(projectDir) {
1022
- const docsPath = join9(projectDir, "docs.json");
1023
- const raw = readFileSync8(docsPath, "utf8");
779
+ const docsPath = join8(projectDir, "docs.json");
780
+ const raw = readFileSync7(docsPath, "utf8");
1024
781
  return JSON.parse(raw);
1025
782
  }
1026
783
  function collectPageIds(pages) {
@@ -1062,12 +819,12 @@ function getAllPageIds(config) {
1062
819
  return { ids, hrefOnlyPages };
1063
820
  }
1064
821
  function findSourceFile(projectDir, pageId) {
1065
- const contentRoot = join9(projectDir, "src", "content");
822
+ const contentRoot = join8(projectDir, "src", "content");
1066
823
  const candidates = [
1067
- join9(contentRoot, `${pageId}.mdx`),
1068
- join9(contentRoot, `${pageId}/index.mdx`)
824
+ join8(contentRoot, `${pageId}.mdx`),
825
+ join8(contentRoot, `${pageId}/index.mdx`)
1069
826
  ];
1070
- return candidates.find((p) => existsSync8(p)) ?? null;
827
+ return candidates.find((p) => existsSync7(p)) ?? null;
1071
828
  }
1072
829
  var TRANSLATION_SYSTEM_PROMPT = `You are a professional documentation translator. You will receive an MDX documentation file and translate it into the target language.
1073
830
 
@@ -1119,7 +876,7 @@ async function handleTranslateDocs(input) {
1119
876
  }
1120
877
  const { ids: allPageIds, hrefOnlyPages } = getAllPageIds(config);
1121
878
  const targetPageIds = pages ?? allPageIds;
1122
- const contentRoot = join9(projectDir, "src", "content");
879
+ const contentRoot = join8(projectDir, "src", "content");
1123
880
  const toTranslate = [];
1124
881
  const skipped = [];
1125
882
  for (const pageId of targetPageIds) {
@@ -1129,8 +886,8 @@ async function handleTranslateDocs(input) {
1129
886
  continue;
1130
887
  }
1131
888
  const relativeFromContent = sourceFile.slice(contentRoot.length + 1);
1132
- const targetFile = join9(contentRoot, locale, relativeFromContent);
1133
- if (existsSync8(targetFile) && !force) {
889
+ const targetFile = join8(contentRoot, locale, relativeFromContent);
890
+ if (existsSync7(targetFile) && !force) {
1134
891
  skipped.push(`${pageId} (already translated)`);
1135
892
  continue;
1136
893
  }
@@ -1146,14 +903,14 @@ async function handleTranslateDocs(input) {
1146
903
  toTranslate.map(
1147
904
  ({ pageId, sourceFile, targetFile }) => limit(async () => {
1148
905
  try {
1149
- const sourceContent = readFileSync8(sourceFile, "utf8");
906
+ const sourceContent = readFileSync7(sourceFile, "utf8");
1150
907
  const parsed = parseFrontmatter(sourceContent);
1151
908
  if (!parsed.data.title) {
1152
909
  console.warn(`[translate] ${pageId}: missing title in frontmatter`);
1153
910
  }
1154
911
  const translated = await translatePage(sourceContent, targetLocale.label, locale, model, client);
1155
- mkdirSync3(dirname2(targetFile), { recursive: true });
1156
- writeFileSync6(targetFile, translated + "\n", "utf8");
912
+ mkdirSync2(dirname2(targetFile), { recursive: true });
913
+ writeFileSync5(targetFile, translated + "\n", "utf8");
1157
914
  results.push({ pageId, success: true });
1158
915
  } catch (err) {
1159
916
  const msg = err instanceof Error ? err.message : String(err);