create-thally-docs 0.8.0 → 0.9.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.
@@ -1,93 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/scaffold.ts
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
5
- import { resolve } from "path";
6
-
7
- // src/download.ts
8
- import { Readable, pipeline } from "stream";
9
- import { promisify } from "util";
10
- import tar from "tar";
11
- var pipelineAsync = promisify(pipeline);
12
- var TEMPLATE_REPOSITORY = "thallylabs/docs";
13
- var TARBALL_URL = `https://codeload.github.com/${TEMPLATE_REPOSITORY}/tar.gz/main`;
14
- var EXCLUDE_PATHS = [
15
- // Match both the directory entry itself and every nested file. Tar invokes
16
- // the filter for `.../node_modules` before descendants, without a trailing `/`.
17
- "/node_modules",
18
- // The canonical docs repository may temporarily retain package sources while
19
- // runtime work is being upstreamed. A scaffold consumes the published
20
- // packages declared in package.json; it must never inherit those sources.
21
- "/packages",
22
- "/.git/",
23
- "/.next/",
24
- "/.data/",
25
- "/.thally/",
26
- "/thally-track.yml",
27
- "/CODEOWNERS",
28
- "/CLAUDE.md",
29
- "/notes/",
30
- "/public/images/",
31
- "/src/public/",
32
- "/snippets/",
33
- "/.github/ISSUE_TEMPLATE/",
34
- "/.github/PULL_REQUEST_TEMPLATE.md",
35
- "/README.md"
36
- ];
37
- function shouldInclude(path) {
38
- for (const excluded of EXCLUDE_PATHS) {
39
- if (path.includes(excluded)) {
40
- return false;
41
- }
42
- }
43
- return true;
44
- }
45
- async function downloadTemplate(targetDir, siteName) {
46
- console.log("");
47
- console.log(` \u23F3 Creating ${siteName?.trim() || "your docs site"}...`);
48
- const response = await fetch(TARBALL_URL);
49
- if (!response.ok) {
50
- throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
51
- }
52
- if (!response.body) {
53
- throw new Error("Response body is empty");
54
- }
55
- const nodeStream = Readable.fromWeb(response.body);
56
- await pipelineAsync(
57
- nodeStream,
58
- tar.extract({ cwd: targetDir, strip: 1, filter: shouldInclude })
59
- );
60
- }
61
-
62
- // src/docs-json.ts
63
- import { readFileSync, writeFileSync } from "fs";
64
- import { join } from "path";
65
- function readDocsJson(projectDir) {
66
- const docsPath = join(projectDir, "docs.json");
67
- const raw = readFileSync(docsPath, "utf8");
68
- return JSON.parse(raw);
69
- }
70
- function writeDocsJson(projectDir, config) {
71
- const docsPath = join(projectDir, "docs.json");
72
- writeFileSync(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
73
- }
74
- function resetTrackingConfig(projectDir) {
75
- const config = readDocsJson(projectDir);
76
- if (config.tracking) {
77
- delete config.tracking;
78
- writeDocsJson(projectDir, config);
3
+ // src/customize.ts
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync, rmSync } from "fs";
5
+ import { dirname, join } from "path";
6
+ var MIN_CLI_VERSION = "0.7.0";
7
+ var MIN_MCP_VERSION = "0.9.0";
8
+ function compareExactVersions(a, b) {
9
+ const parse = (value) => /^\d+\.\d+\.\d+$/.test(value) ? value.split(".").map(Number) : null;
10
+ const left = parse(a);
11
+ const right = parse(b);
12
+ if (!left || !right) return null;
13
+ for (let i = 0; i < 3; i += 1) {
14
+ if (left[i] !== right[i]) return left[i] - right[i];
79
15
  }
16
+ return 0;
80
17
  }
81
- function writeTrackingConfig(projectDir, repos) {
82
- if (repos.length === 0) return;
83
- const config = readDocsJson(projectDir);
84
- config.tracking = { repos: repos.map((r) => ({ owner: r.owner, repo: r.repo, branch: "main" })) };
85
- writeDocsJson(projectDir, config);
18
+ function raisePinToFloor(current, floor) {
19
+ if (!current || current === "*") return floor;
20
+ const ordering = compareExactVersions(current, floor);
21
+ if (ordering === null) return current;
22
+ return ordering < 0 ? floor : current;
86
23
  }
87
-
88
- // src/customize.ts
89
- import { existsSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2, readdirSync, cpSync, rmSync } from "fs";
90
- import { join as join2 } from "path";
91
24
  var STARTER_PAGES = {
92
25
  "introduction.mdx": `---
93
26
  title: Introduction
@@ -101,32 +34,43 @@ keywords:
101
34
  ---
102
35
 
103
36
  <Hero
104
- title="Welcome to {NAME}"
105
- subtitle="Use this starter to introduce your product, guide readers to their first successful outcome, and publish a clear API reference."
37
+ title="Start building with {NAME}"
38
+ subtitle="Complete the quickstart, learn the core workflow, then choose the next task that matches your goal."
106
39
  primaryLabel="Start the quickstart"
107
40
  primaryHref="/quickstart"
108
- secondaryLabel="See components"
41
+ secondaryLabel="Explore components"
109
42
  secondaryHref="/components"
110
43
  />
111
44
 
112
- <CardGroup cols={3}>
113
- <Card title="Quickstart" icon="party-horn" href="/quickstart">
114
- Show readers the fastest path to a useful first result.
45
+ ## Build your documentation
46
+
47
+ Start with the first useful result, then shape the content around your readers.
48
+
49
+ <CardGroup cols="2">
50
+ <Card title="Complete the quickstart" icon="party-horn" href="/quickstart">
51
+ Install {NAME}, complete the smallest useful workflow, and verify the result.
115
52
  </Card>
116
- <Card title="Components" icon="grid-round" href="/components">
117
- Structure guides with steps, tabs, cards, callouts, accordions, and more.
53
+ <Card title="Create a guide" icon="grid-round" href="/components">
54
+ Turn a product task into clear steps using tabs, cards, callouts, and more.
118
55
  </Card>
119
- <Card title="API reference" icon="code-simple" href="/api">
120
- Replace \`openapi.yaml\` with your specification to publish interactive endpoints.
56
+ <Card title="Integrate the API" icon="code-simple" href="/api">
57
+ Find an endpoint, understand its inputs, and make your first request.
121
58
  </Card>
122
- <Card title="Customize" icon="wrench" href="/customization">
59
+ <Card title="Customize your site" icon="wrench" href="/customization">
123
60
  Make the navigation, brand, typography, and links your own.
124
61
  </Card>
125
- <Card title="Multi-language" icon="message" href="/es">
126
- Switch to the included Spanish example from the language menu.
62
+ </CardGroup>
63
+
64
+ ## Publish and extend
65
+
66
+ Add another language or make the same documentation available to an AI agent.
67
+
68
+ <CardGroup cols="2">
69
+ <Card title="Publish another language" icon="message" href="/es">
70
+ Use the included Spanish pages as a starting point for localized documentation.
127
71
  </Card>
128
- <Card title="Agent-ready docs" icon="link-simple" href="/llms.txt">
129
- Give coding agents a clean, structured version of your documentation.
72
+ <Card title="Connect an AI agent" icon="link-simple" href="/llms.txt">
73
+ Give an agent clean, structured context through the generated discovery endpoints.
130
74
  </Card>
131
75
  </CardGroup>
132
76
 
@@ -136,8 +80,8 @@ keywords:
136
80
  </Note>
137
81
  `,
138
82
  "quickstart.mdx": `---
139
- title: Quickstart
140
- description: Give readers the fastest path to a successful first result with {NAME}.
83
+ title: Get started with {NAME}
84
+ description: Install {NAME}, complete the core workflow, and verify your first successful result.
141
85
  keywords:
142
86
  - {NAME}
143
87
  - quickstart
@@ -145,41 +89,63 @@ keywords:
145
89
  - getting started
146
90
  ---
147
91
 
148
- Describe the prerequisites and the shortest useful workflow. A good quickstart
149
- takes someone from zero to a visible result without explaining every option.
92
+ By the end of this guide, you will install {NAME}, complete its smallest useful
93
+ workflow, and verify the result. Keep this path focused on one outcome; move
94
+ optional configuration and alternative workflows into separate guides.
95
+
96
+ ## Before you begin
150
97
 
151
- ## Prerequisites
98
+ Replace this starter list with the exact requirements a reader needs:
152
99
 
153
- - Requirement one, such as an account, API key, or supported runtime
154
- - Requirement two, such as a compatible device, browser, or operating system
100
+ - An account or credential, if the workflow requires one
101
+ - A supported runtime, device, browser, or operating system
102
+ - Permission to create or modify the resource used in the example
155
103
 
156
104
  <Steps>
157
- <Step title="Install">
158
- Explain how to install {NAME} or create an account.
105
+ <Step title="Install {NAME}">
106
+ Give readers one recommended installation path.
159
107
 
160
108
  \`\`\`bash
161
109
  npm install your-package
162
110
  \`\`\`
163
111
  </Step>
164
- <Step title="Configure">
165
- Show only the configuration required for the first successful run.
112
+ <Step title="Add the required configuration">
113
+ Include only the settings required for this first successful run.
166
114
 
167
115
  \`\`\`bash
168
116
  your-cli init
169
117
  \`\`\`
170
118
  </Step>
171
- <Step title="Run it">
172
- Give readers a command or action with an observable result.
119
+ <Step title="Complete your first task">
120
+ Use one realistic example that produces an observable result.
173
121
 
174
122
  \`\`\`bash
175
123
  your-cli start
176
124
  \`\`\`
177
125
  </Step>
126
+ <Step title="Verify the result">
127
+ Tell readers exactly what they should see, where they should see it, and
128
+ how to recover if the expected result does not appear.
129
+ </Step>
178
130
  </Steps>
179
131
 
180
- <Tip>
181
- Tell readers where to get help, then link to the next guide they should read.
182
- </Tip>
132
+ ## What you accomplished
133
+
134
+ Summarize the working state the reader now has in one or two sentences.
135
+
136
+ ## Choose your next task
137
+
138
+ <CardGroup cols={3}>
139
+ <Card title="Create a guide" icon="grid-round" href="/components">
140
+ Learn which content components make a task easier to follow.
141
+ </Card>
142
+ <Card title="Integrate the API" icon="code-simple" href="/api">
143
+ Explore endpoints and make a request against your API.
144
+ </Card>
145
+ <Card title="Customize your site" icon="wrench" href="/customization">
146
+ Update navigation, branding, typography, and project links.
147
+ </Card>
148
+ </CardGroup>
183
149
  `,
184
150
  "components.mdx": `---
185
151
  title: Components
@@ -241,7 +207,7 @@ keywords:
241
207
 
242
208
  Your documentation should feel like part of the product\u2014not a separate website.
243
209
 
244
- <CardGroup cols={2}>
210
+ <CardGroup cols="2">
245
211
  <Card title="Brand and theme" icon="party-horn" href="https://docs.thally.io/guides/branding-and-theming">
246
212
  Configure colors, logos, favicons, typography, and light or dark presentation.
247
213
  </Card>
@@ -277,7 +243,7 @@ The first release of your **{NAME}** documentation.
277
243
 
278
244
  - Initial docs site scaffolded with [Thally](https://github.com/thallylabs/thally)
279
245
  - Agent-ready endpoints live: \`/llms.txt\`, \`/ai.txt\`, \`/api/docs-index\`, and \`/api/agent-readiness\`
280
- - Starter guides in the Overview tab and an interactive API reference
246
+ - Starter guides in the Get started tab and an interactive API reference
281
247
 
282
248
  Edit this page at \`src/content/changelog.mdx\` to announce your own releases as you ship.
283
249
  `
@@ -290,60 +256,86 @@ mode: home
290
256
  ---
291
257
 
292
258
  <Hero
293
- title="Te damos la bienvenida a {NAME}"
294
- subtitle="Usa este sitio inicial para presentar tu producto, guiar a tus lectores hasta su primer resultado y publicar una referencia de API clara."
295
- primaryLabel="Abrir inicio r\xE1pido"
259
+ title="Empieza a crear con {NAME}"
260
+ subtitle="Completa el inicio r\xE1pido, aprende el flujo principal y elige la siguiente tarea seg\xFAn tu objetivo."
261
+ primaryLabel="Empezar el inicio r\xE1pido"
296
262
  primaryHref="/es/quickstart"
297
- secondaryLabel="Ver componentes"
263
+ secondaryLabel="Explorar componentes"
298
264
  secondaryHref="/es/components"
299
265
  />
300
266
 
301
- <CardGroup cols={3}>
302
- <Card title="Inicio r\xE1pido" icon="party-horn" href="/es/quickstart">
303
- Ayuda a tus lectores a lograr su primer resultado en minutos.
267
+ ## Crea tu documentaci\xF3n
268
+
269
+ Empieza con el primer resultado \xFAtil y organiza el contenido para tus lectores.
270
+
271
+ <CardGroup cols="2">
272
+ <Card title="Completa el inicio r\xE1pido" icon="party-horn" href="/es/quickstart">
273
+ Instala {NAME}, completa el flujo principal y comprueba el resultado.
304
274
  </Card>
305
- <Card title="Componentes" icon="grid-round" href="/es/components">
306
- Usa pesta\xF1as, pasos, tarjetas, avisos y acordeones.
275
+ <Card title="Crea una gu\xEDa" icon="grid-round" href="/es/components">
276
+ Convierte una tarea en pasos claros con pesta\xF1as, tarjetas y avisos.
307
277
  </Card>
308
- <Card title="Referencia de API" icon="code-simple" href="/es/api">
309
- Convierte \`openapi.yaml\` en documentaci\xF3n interactiva.
278
+ <Card title="Integra la API" icon="code-simple" href="/es/api">
279
+ Encuentra un endpoint, comprende sus entradas y realiza una solicitud.
310
280
  </Card>
311
- <Card title="Personalizaci\xF3n" icon="wrench" href="/es/customization">
281
+ <Card title="Personaliza el sitio" icon="wrench" href="/es/customization">
312
282
  Adapta la navegaci\xF3n, marca, tipograf\xEDa y enlaces.
313
283
  </Card>
314
- <Card title="Varios idiomas" icon="message" href="/">
284
+ </CardGroup>
285
+
286
+ ## Publica y ampl\xEDa
287
+
288
+ A\xF1ade otro idioma o comparte la misma documentaci\xF3n con un agente de IA.
289
+
290
+ <CardGroup cols="2">
291
+ <Card title="Publica otro idioma" icon="message" href="/">
315
292
  Cambia entre ingl\xE9s y espa\xF1ol desde el selector de idioma.
316
293
  </Card>
317
- <Card title="Preparado para IA" icon="link-simple" href="/llms.txt">
318
- Publica contenido legible por agentes desde el primer d\xEDa.
294
+ <Card title="Conecta un agente de IA" icon="link-simple" href="/llms.txt">
295
+ Ofrece contexto estructurado mediante los endpoints de descubrimiento.
319
296
  </Card>
320
297
  </CardGroup>
321
298
  `,
322
299
  "quickstart.mdx": `---
323
- title: Inicio r\xE1pido
324
- description: Gu\xEDa a tus lectores hasta su primer resultado con {NAME}.
300
+ title: Empieza con {NAME}
301
+ description: Instala {NAME}, completa el flujo principal y comprueba tu primer resultado.
325
302
  ---
326
303
 
327
- Un buen inicio r\xE1pido lleva al lector de cero a un resultado visible sin explicar
328
- todas las opciones.
304
+ Al terminar esta gu\xEDa, habr\xE1s instalado {NAME}, completado su flujo m\xE1s \xFAtil y
305
+ comprobado el resultado.
306
+
307
+ ## Antes de empezar
308
+
309
+ - Una cuenta o credencial, si el flujo la necesita
310
+ - Un entorno, dispositivo o navegador compatible
311
+ - Permiso para crear o modificar el recurso del ejemplo
329
312
 
330
313
  <Steps>
331
- <Step title="Instala">
332
- Explica c\xF3mo instalar {NAME} o crear una cuenta.
314
+ <Step title="Instala {NAME}">
315
+ Muestra una \xFAnica ruta de instalaci\xF3n recomendada.
333
316
 
334
317
  \`\`\`bash
335
318
  npm install your-package
336
319
  \`\`\`
337
320
  </Step>
338
- <Step title="Configura">
339
- Muestra \xFAnicamente la configuraci\xF3n necesaria para comenzar.
321
+ <Step title="A\xF1ade la configuraci\xF3n necesaria">
322
+ Incluye solo los ajustes necesarios para completar este flujo.
323
+ </Step>
324
+ <Step title="Completa tu primera tarea">
325
+ Usa un ejemplo realista que produzca un resultado observable.
340
326
  </Step>
341
- <Step title="Ejecuta">
342
- Termina con una acci\xF3n y un resultado que el lector pueda comprobar.
327
+ <Step title="Comprueba el resultado">
328
+ Explica qu\xE9 debe ver el lector y d\xF3nde debe encontrarlo.
343
329
  </Step>
344
330
  </Steps>
345
331
 
346
- <Tip>Enlaza la siguiente gu\xEDa que deber\xEDa leer una vez completado este flujo.</Tip>
332
+ ## Elige tu siguiente tarea
333
+
334
+ <CardGroup cols={3}>
335
+ <Card title="Crea una gu\xEDa" icon="grid-round" href="/es/components" />
336
+ <Card title="Integra la API" icon="code-simple" href="/es/api" />
337
+ <Card title="Personaliza el sitio" icon="wrench" href="/es/customization" />
338
+ </CardGroup>
347
339
  `,
348
340
  "components.mdx": `---
349
341
  title: Componentes
@@ -369,7 +361,7 @@ title: Personalizaci\xF3n
369
361
  description: Haz que {NAME} se sienta como una parte natural de tu producto.
370
362
  ---
371
363
 
372
- <CardGroup cols={2}>
364
+ <CardGroup cols="2">
373
365
  <Card title="Marca y tema" icon="party-horn">Configura colores, logotipos, tipograf\xEDa y apariencia.</Card>
374
366
  <Card title="Navegaci\xF3n" icon="book-open">Organiza pesta\xF1as, grupos con iconos y p\xE1ginas en \`docs.json\`.</Card>
375
367
  <Card title="Dominios" icon="link-simple">Conecta un dominio personalizado desde Thally Cloud.</Card>
@@ -414,11 +406,11 @@ function buildStarterDocsJson({
414
406
  config.i18n = { defaultLocale: "en", locales };
415
407
  config.tabs = [
416
408
  {
417
- tab: "Overview",
409
+ tab: "Get started",
418
410
  groups: [
419
- { group: "Getting Started", icon: "book-open", pages: ["introduction", "quickstart"] },
420
- { group: "Explore", icon: "grid-round", pages: ["components"] },
421
- { group: "Project", icon: "wrench", pages: ["customization"] }
411
+ { group: "Start here", icon: "book-open", pages: ["introduction", "quickstart"] },
412
+ { group: "Create content", icon: "grid-round", pages: ["components"] },
413
+ { group: "Customize your site", icon: "wrench", pages: ["customization"] }
422
414
  ]
423
415
  },
424
416
  { tab: "API Reference", api: { source: "openapi.yaml" } },
@@ -426,35 +418,8 @@ function buildStarterDocsJson({
426
418
  ];
427
419
  return JSON.stringify(config, null, 2) + "\n";
428
420
  }
429
- function writeStarterContent(targetDir, projectName, enableAiChat = true, repoUrl = "", i18nLocales) {
430
- const contentDir = join2(targetDir, "src", "content");
431
- if (existsSync(contentDir)) {
432
- const entries = readdirSync(contentDir);
433
- for (const entry of entries) {
434
- const fullPath = join2(contentDir, entry);
435
- rmSync(fullPath, { recursive: true, force: true });
436
- }
437
- } else {
438
- mkdirSync(contentDir, { recursive: true });
439
- }
440
- for (const [filename, template] of Object.entries(STARTER_PAGES)) {
441
- const content = template.replace(/\{NAME\}/g, projectName);
442
- writeFileSync2(join2(contentDir, filename), content, "utf8");
443
- }
444
- const spanishDir = join2(contentDir, "es");
445
- mkdirSync(spanishDir, { recursive: true });
446
- for (const [filename, template] of Object.entries(STARTER_SPANISH_PAGES)) {
447
- const content = template.replace(/\{NAME\}/g, projectName);
448
- writeFileSync2(join2(spanishDir, filename), content, "utf8");
449
- }
450
- writeFileSync2(
451
- join2(targetDir, "docs.json"),
452
- buildStarterDocsJson({ enableAiChat, repoUrl: repoUrl || void 0, i18nLocales }),
453
- "utf8"
454
- );
455
- }
456
- function writeStarterAgentGuide(targetDir, projectName) {
457
- const guide = `# ${projectName} documentation instructions
421
+ function renderStarterAgentGuide(projectName) {
422
+ return `# ${projectName} documentation instructions
458
423
 
459
424
  ## About this project
460
425
 
@@ -479,10 +444,9 @@ function writeStarterAgentGuide(targetDir, projectName) {
479
444
 
480
445
  <!-- Define what belongs in public docs and what must remain internal. -->
481
446
  `;
482
- writeFileSync2(join2(targetDir, "AGENTS.md"), guide, "utf8");
483
447
  }
484
- function writeStarterReadme(targetDir, projectName) {
485
- const readme = `# ${projectName}
448
+ function renderStarterReadme(projectName) {
449
+ return `# ${projectName}
486
450
 
487
451
  Documentation powered by [Thally](https://github.com/thallylabs/thally).
488
452
 
@@ -517,11 +481,71 @@ Run \`npx create-thally-docs check --ci .\` before publishing. Deploy the site
517
481
  anywhere Next.js is supported, or connect the repository to
518
482
  [Thally Cloud](https://app.thally.io) for managed hosting and services.
519
483
  `;
520
- writeFileSync2(join2(targetDir, "README.md"), readme, "utf8");
484
+ }
485
+ function buildStarterFiles({
486
+ projectName,
487
+ enableAiChat = true,
488
+ repoUrl = "",
489
+ i18nLocales
490
+ }) {
491
+ const files = [
492
+ {
493
+ path: "docs.json",
494
+ content: buildStarterDocsJson({
495
+ enableAiChat,
496
+ repoUrl: repoUrl || void 0,
497
+ i18nLocales
498
+ })
499
+ },
500
+ { path: "AGENTS.md", content: renderStarterAgentGuide(projectName) },
501
+ { path: "README.md", content: renderStarterReadme(projectName) }
502
+ ];
503
+ for (const [filename, template] of Object.entries(STARTER_PAGES)) {
504
+ files.push({
505
+ path: `src/content/${filename}`,
506
+ content: template.replace(/\{NAME\}/g, projectName)
507
+ });
508
+ }
509
+ for (const [filename, template] of Object.entries(STARTER_SPANISH_PAGES)) {
510
+ files.push({
511
+ path: `src/content/es/${filename}`,
512
+ content: template.replace(/\{NAME\}/g, projectName)
513
+ });
514
+ }
515
+ return files;
516
+ }
517
+ function writeStarterContent(targetDir, projectName, enableAiChat = true, repoUrl = "", i18nLocales) {
518
+ const contentDir = join(targetDir, "src", "content");
519
+ if (existsSync(contentDir)) {
520
+ const entries = readdirSync(contentDir);
521
+ for (const entry of entries) {
522
+ const fullPath = join(contentDir, entry);
523
+ rmSync(fullPath, { recursive: true, force: true });
524
+ }
525
+ } else {
526
+ mkdirSync(contentDir, { recursive: true });
527
+ }
528
+ for (const file of buildStarterFiles({
529
+ projectName,
530
+ enableAiChat,
531
+ repoUrl,
532
+ i18nLocales
533
+ })) {
534
+ if (file.path === "AGENTS.md" || file.path === "README.md") continue;
535
+ const targetPath = join(targetDir, file.path);
536
+ mkdirSync(dirname(targetPath), { recursive: true });
537
+ writeFileSync(targetPath, file.content, "utf8");
538
+ }
539
+ }
540
+ function writeStarterAgentGuide(targetDir, projectName) {
541
+ writeFileSync(join(targetDir, "AGENTS.md"), renderStarterAgentGuide(projectName), "utf8");
542
+ }
543
+ function writeStarterReadme(targetDir, projectName) {
544
+ writeFileSync(join(targetDir, "README.md"), renderStarterReadme(projectName), "utf8");
521
545
  }
522
546
  function writeCloudflareRuntimeConfig(targetDir, slug) {
523
- writeFileSync2(
524
- join2(targetDir, "open-next.config.ts"),
547
+ writeFileSync(
548
+ join(targetDir, "open-next.config.ts"),
525
549
  `/** OpenNext adapter configuration for the Cloudflare Workers runtime. */
526
550
  import { defineCloudflareConfig } from '@opennextjs/cloudflare/config'
527
551
 
@@ -529,8 +553,8 @@ export default defineCloudflareConfig()
529
553
  `,
530
554
  "utf8"
531
555
  );
532
- writeFileSync2(
533
- join2(targetDir, "wrangler.jsonc"),
556
+ writeFileSync(
557
+ join(targetDir, "wrangler.jsonc"),
534
558
  `${JSON.stringify(
535
559
  {
536
560
  $schema: "node_modules/wrangler/config-schema.json",
@@ -556,12 +580,12 @@ export default defineCloudflareConfig()
556
580
  );
557
581
  }
558
582
  function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
559
- const siteFile = join2(targetDir, "src", "data", "site.ts");
583
+ const siteFile = join(targetDir, "src", "data", "site.ts");
560
584
  if (!existsSync(siteFile)) {
561
585
  console.log(" \u26A0\uFE0F Could not find src/data/site.ts \u2014 skipping config update.");
562
586
  return;
563
587
  }
564
- let source = readFileSync2(siteFile, "utf8");
588
+ let source = readFileSync(siteFile, "utf8");
565
589
  source = source.replace(
566
590
  /name:\s*'[^']*'/,
567
591
  `name: '${projectName.replace(/'/g, "\\'")}'`
@@ -590,23 +614,23 @@ function updateSiteConfig(targetDir, projectName, description, brandPreset, repo
590
614
  ""
591
615
  );
592
616
  }
593
- writeFileSync2(siteFile, source, "utf8");
617
+ writeFileSync(siteFile, source, "utf8");
594
618
  }
595
619
  function patchApiReferenceGuard(targetDir) {
596
- const filePath = join2(targetDir, "src", "data", "api-reference.ts");
620
+ const filePath = join(targetDir, "src", "data", "api-reference.ts");
597
621
  if (!existsSync(filePath)) return;
598
- let source = readFileSync2(filePath, "utf8");
622
+ let source = readFileSync(filePath, "utf8");
599
623
  source = source.replace(
600
624
  /export async function buildApiNavigation\([^)]*\)[^{]*\{\n/,
601
625
  (match) => `${match} if (apiReferenceConfig.specs.length === 0) return []
602
626
  `
603
627
  );
604
- writeFileSync2(filePath, source, "utf8");
628
+ writeFileSync(filePath, source, "utf8");
605
629
  }
606
630
  function patchTopBarNavigation(targetDir) {
607
- const filePath = join2(targetDir, "src", "components", "layout", "top-bar.tsx");
631
+ const filePath = join(targetDir, "src", "components", "layout", "top-bar.tsx");
608
632
  if (!existsSync(filePath)) return;
609
- const source = readFileSync2(filePath, "utf8");
633
+ const source = readFileSync(filePath, "utf8");
610
634
  if (!source.includes("target={isExternal ? '_blank' : undefined}")) return;
611
635
  const patched = source.replace(
612
636
  /if \(collection\.href\) \{\n const isExternal[^\n]+\n return \(\n <a[\s\S]*?<\/a>\n \)\n \}/,
@@ -636,36 +660,36 @@ function patchTopBarNavigation(targetDir) {
636
660
  )
637
661
  }`
638
662
  );
639
- writeFileSync2(filePath, patched, "utf8");
663
+ writeFileSync(filePath, patched, "utf8");
640
664
  }
641
665
  function patchOpenApiFetch(targetDir) {
642
- const filePath = join2(targetDir, "src", "lib", "openapi", "fetch.ts");
666
+ const filePath = join(targetDir, "src", "lib", "openapi", "fetch.ts");
643
667
  if (!existsSync(filePath)) return;
644
- let source = readFileSync2(filePath, "utf8");
668
+ let source = readFileSync(filePath, "utf8");
645
669
  source = source.replace(
646
670
  /const absolutePath = path\.isAbsolute\(filePath\) \? filePath : path\.resolve\(process\.cwd\(\), filePath\)/,
647
671
  `const absolutePath = filePath.startsWith('/')
648
672
  ? path.resolve(process.cwd(), 'public', filePath.slice(1))
649
673
  : path.resolve(process.cwd(), filePath)`
650
674
  );
651
- writeFileSync2(filePath, source, "utf8");
675
+ writeFileSync(filePath, source, "utf8");
652
676
  }
653
677
  function updateEnvExample(targetDir) {
654
- const envFile = join2(targetDir, ".env.example");
678
+ const envFile = join(targetDir, ".env.example");
655
679
  if (existsSync(envFile)) {
656
- const envLocal = join2(targetDir, ".env.local");
680
+ const envLocal = join(targetDir, ".env.local");
657
681
  if (!existsSync(envLocal)) {
658
682
  cpSync(envFile, envLocal);
659
683
  }
660
684
  }
661
685
  }
662
686
  function patchPackageJson(targetDir, slug) {
663
- const pkgPath = join2(targetDir, "package.json");
687
+ const pkgPath = join(targetDir, "package.json");
664
688
  if (!existsSync(pkgPath)) return;
665
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
689
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
666
690
  const hadWorkspaces = Array.isArray(pkg.workspaces) && pkg.workspaces.length > 0;
667
691
  const hasRuntimeSourceCompiler = existsSync(
668
- join2(targetDir, "scripts", "build-runtime-sources.mts")
692
+ join(targetDir, "scripts", "build-runtime-sources.mts")
669
693
  );
670
694
  pkg.name = slug;
671
695
  delete pkg.workspaces;
@@ -686,159 +710,60 @@ function patchPackageJson(targetDir, slug) {
686
710
  pkg.scripts["upload:cloudflare"] = "npm run build:cloudflare && opennextjs-cloudflare upload";
687
711
  }
688
712
  pkg.devDependencies ??= {};
689
- pkg.devDependencies["@thallylabs/cli"] = "0.5.2";
713
+ const cliPin = raisePinToFloor(pkg.devDependencies["@thallylabs/cli"], MIN_CLI_VERSION);
714
+ const raisedCliPin = cliPin !== pkg.devDependencies["@thallylabs/cli"];
715
+ pkg.devDependencies["@thallylabs/cli"] = cliPin;
690
716
  pkg.devDependencies["@opennextjs/cloudflare"] ??= "1.15.0";
691
717
  pkg.devDependencies.vite ??= "7.2.6";
692
718
  pkg.devDependencies.wrangler ??= "4.111.0";
693
- if (pkg.dependencies?.["@thallylabs/mcp"] === "*") {
694
- pkg.dependencies["@thallylabs/mcp"] = "0.7.1";
719
+ let raisedMcpPin = false;
720
+ if (pkg.dependencies?.["@thallylabs/mcp"]) {
721
+ const mcpPin = raisePinToFloor(pkg.dependencies["@thallylabs/mcp"], MIN_MCP_VERSION);
722
+ raisedMcpPin = mcpPin !== pkg.dependencies["@thallylabs/mcp"];
723
+ pkg.dependencies["@thallylabs/mcp"] = mcpPin;
695
724
  }
696
- writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
725
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
697
726
  `, "utf8");
698
- const lockPath = join2(targetDir, "package-lock.json");
727
+ const lockPath = join(targetDir, "package-lock.json");
699
728
  if (!existsSync(lockPath)) return;
700
- const lock = JSON.parse(readFileSync2(lockPath, "utf8"));
729
+ const lock = JSON.parse(readFileSync(lockPath, "utf8"));
701
730
  const hasWorkspaceEntries = Object.keys(lock.packages ?? {}).some(
702
731
  (key) => key === "packages" || key.startsWith("packages/")
703
732
  );
704
- if (hadWorkspaces || hasWorkspaceEntries) {
733
+ if (hadWorkspaces || hasWorkspaceEntries || raisedCliPin || raisedMcpPin) {
705
734
  rmSync(lockPath);
706
735
  return;
707
736
  }
708
737
  lock.name = slug;
709
738
  if (lock.packages?.[""]) lock.packages[""].name = slug;
710
- writeFileSync2(lockPath, `${JSON.stringify(lock, null, 2)}
739
+ writeFileSync(lockPath, `${JSON.stringify(lock, null, 2)}
711
740
  `, "utf8");
712
741
  }
713
742
  function patchGitignore(targetDir) {
714
- const gitignorePath = join2(targetDir, ".gitignore");
715
- const existing = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf8") : "";
743
+ const gitignorePath = join(targetDir, ".gitignore");
744
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf8") : "";
716
745
  const lines = existing.split(/\r?\n/);
717
746
  if (lines.includes("node_modules/")) return;
718
747
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
719
- writeFileSync2(gitignorePath, `${existing}${separator}node_modules/
748
+ writeFileSync(gitignorePath, `${existing}${separator}node_modules/
720
749
  `, "utf8");
721
750
  }
722
751
 
723
- // src/utils.ts
724
- import { execSync } from "child_process";
725
- import { basename } from "path";
726
- function slugify(name) {
727
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
728
- }
729
- function run(cmd, cwd) {
730
- execSync(cmd, { cwd, stdio: "inherit" });
731
- }
732
- function initGit(targetDir) {
733
- try {
734
- run("git init", targetDir);
735
- run("git add -A", targetDir);
736
- run('git commit -m "Initial commit from create-thally-docs"', targetDir);
737
- } catch {
738
- console.log(" \u26A0\uFE0F Could not initialize git (you can do this manually).");
739
- }
740
- }
741
- function installDeps(targetDir) {
742
- console.log("");
743
- console.log(" \u{1F4E6} Installing dependencies...");
744
- console.log("");
745
- run("npm install --prefer-offline --no-audit --no-fund --progress=false", targetDir);
746
- }
747
- function logo() {
748
- console.log("");
749
- console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
750
- console.log(" \u2551 \u2551");
751
- console.log(" \u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2551");
752
- console.log(" \u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D \u2551");
753
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2551");
754
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2551");
755
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551");
756
- console.log(" \u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2551");
757
- console.log(" \u2551 \u2551");
758
- console.log(" \u2551Give your product and docs first-class agent visibility.\u2551");
759
- console.log(" \u2551 \u2551");
760
- console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
761
- console.log("");
762
- }
763
- function success(projectDir, projectName, dependenciesInstalled) {
764
- console.log("");
765
- console.log(" \u2705 Your Thally project is ready!");
766
- console.log("");
767
- console.log(` \u{1F4C2} ${projectDir}`);
768
- console.log("");
769
- console.log(" Next steps:");
770
- console.log("");
771
- console.log(` cd ${basename(projectDir)}`);
772
- if (!dependenciesInstalled) {
773
- console.log(" npm install");
774
- }
775
- console.log(" npm run dev");
776
- console.log("");
777
- console.log(` Your terminal will print the local URL for your ${projectName} docs.`);
778
- console.log("");
779
- console.log(" \u{1F4DD} Key files to edit:");
780
- console.log(" \u2022 src/data/site.ts \u2014 name, links, branding");
781
- console.log(" \u2022 docs.json \u2014 navigation structure");
782
- console.log(" \u2022 src/content/*.mdx \u2014 your documentation");
783
- console.log(" \u2022 openapi.yaml \u2014 API spec (optional)");
784
- console.log("");
785
- console.log(" Happy documenting! \u{1F680}");
786
- console.log("");
787
- }
788
-
789
- // src/scaffold.ts
790
- async function scaffold(options) {
791
- const {
792
- projectDir,
793
- projectName,
794
- description,
795
- brandPreset,
796
- repoUrl,
797
- doInstall,
798
- enableAiChat = true,
799
- i18nLocales,
800
- trackRepos
801
- } = options;
802
- const targetDir = resolve(projectDir);
803
- if (existsSync2(targetDir) && readdirSync2(targetDir).length > 0) {
804
- throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
805
- }
806
- mkdirSync2(targetDir, { recursive: true });
807
- const slug = slugify(projectName);
808
- await downloadTemplate(targetDir, projectName);
809
- resetTrackingConfig(targetDir);
810
- if (trackRepos?.length) {
811
- writeTrackingConfig(targetDir, trackRepos);
812
- const list = trackRepos.map((r) => `${r.owner}/${r.repo}`).join(", ");
813
- console.log(` \u2713 Thally Track enabled \u2014 watching ${list} (branch main, all files; refine in docs.json).`);
814
- console.log(" To finish wiring it: `thally track setup` (pick a trigger) + `thally agent init`,");
815
- console.log(" then add your ANTHROPIC_API_KEY. See /guides/thally-track.");
816
- }
817
- writeStarterContent(targetDir, projectName, enableAiChat, repoUrl, i18nLocales);
818
- writeStarterReadme(targetDir, projectName);
819
- writeStarterAgentGuide(targetDir, projectName);
820
- updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl);
821
- patchApiReferenceGuard(targetDir);
822
- patchTopBarNavigation(targetDir);
823
- patchOpenApiFetch(targetDir);
824
- patchPackageJson(targetDir, slug);
825
- writeCloudflareRuntimeConfig(targetDir, slug);
826
- patchGitignore(targetDir);
827
- updateEnvExample(targetDir);
828
- if (doInstall) {
829
- installDeps(targetDir);
830
- }
831
- initGit(targetDir);
832
- return { projectDir: targetDir };
833
- }
834
-
835
752
  export {
836
- slugify,
837
- initGit,
838
- installDeps,
839
- logo,
840
- success,
841
- readDocsJson,
842
- writeDocsJson,
843
- scaffold
753
+ MIN_CLI_VERSION,
754
+ MIN_MCP_VERSION,
755
+ raisePinToFloor,
756
+ buildStarterDocsJson,
757
+ buildStarterFiles,
758
+ writeStarterContent,
759
+ writeStarterAgentGuide,
760
+ writeStarterReadme,
761
+ writeCloudflareRuntimeConfig,
762
+ updateSiteConfig,
763
+ patchApiReferenceGuard,
764
+ patchTopBarNavigation,
765
+ patchOpenApiFetch,
766
+ updateEnvExample,
767
+ patchPackageJson,
768
+ patchGitignore
844
769
  };