create-thally-docs 0.10.78 → 0.10.79

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.
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/terminal.ts
4
+ import { intro, outro, spinner } from "@clack/prompts";
5
+ import { stripVTControlCharacters } from "util";
6
+ var PRODUCT_TAGLINE = "Product knowledge, kept in step with your code.";
7
+ var OLIVE = { rgb: "38;2;174;187;106", palette: "38;5;143" };
8
+ var OLIVE_EDGE = { rgb: "38;2;115;121;56", palette: "38;5;101" };
9
+ function oliveCode(env, edge = false) {
10
+ const color = edge ? OLIVE_EDGE : OLIVE;
11
+ return env.COLORTERM === "truecolor" || env.COLORTERM === "24bit" || ["iTerm.app", "vscode", "WezTerm"].includes(env.TERM_PROGRAM ?? "") ? color.rgb : color.palette;
12
+ }
13
+ var accentUsers = 0;
14
+ var restoreAccent;
15
+ function acquireTerminalAccent() {
16
+ if (!terminal.isRich) return () => {
17
+ };
18
+ if (accentUsers === 0) {
19
+ const output = process.stdout;
20
+ const previous = output.write;
21
+ const writeOriginal = previous.bind(output);
22
+ const opening = `\x1B[${oliveCode(process.env)}m`;
23
+ const themed = function(chunk, encodingOrCallback, callback) {
24
+ const text = typeof chunk === "string" ? chunk.replace(/\x1b\[(?:32|35|36)m/g, opening) : chunk;
25
+ return typeof encodingOrCallback === "string" ? writeOriginal(text, encodingOrCallback, callback) : writeOriginal(text, encodingOrCallback);
26
+ };
27
+ output.write = themed;
28
+ restoreAccent = () => {
29
+ if (output.write === themed) output.write = previous;
30
+ };
31
+ }
32
+ accentUsers += 1;
33
+ let released = false;
34
+ return () => {
35
+ if (released) return;
36
+ released = true;
37
+ accentUsers -= 1;
38
+ if (accentUsers === 0) {
39
+ restoreAccent?.();
40
+ restoreAccent = void 0;
41
+ }
42
+ };
43
+ }
44
+ var WORDMARK = [
45
+ "\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",
46
+ "\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",
47
+ " \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 ",
48
+ " \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 ",
49
+ " \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 ",
50
+ " \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 "
51
+ ];
52
+ var SMALL_WORDMARK = [
53
+ "\u2580\u2588\u2580 \u2588 \u2588 \u2584\u2580\u2588 \u2588 \u2588 \u2588 \u2588",
54
+ " \u2588 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588 \u2588 \u2588 ",
55
+ " \u2580 \u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580\u2580\u2580 \u2580 "
56
+ ];
57
+ function terminalText(value) {
58
+ return stripVTControlCharacters(value).replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, "");
59
+ }
60
+ function createTerminal(options = {}) {
61
+ const output = options.stdout ?? process.stdout;
62
+ const errors = options.stderr ?? process.stderr;
63
+ const environment = () => options.env ?? process.env;
64
+ const arguments_ = () => options.argv ?? process.argv.slice(2);
65
+ const isRich = () => Boolean(output.isTTY) && !environment().CI && environment().TERM !== "dumb" && environment().NO_COLOR === void 0;
66
+ const canAnimate = () => isRich() && Boolean(process.stdin.isTTY) && output === process.stdout && errors === process.stderr && !arguments_().includes("--verbose");
67
+ const paint = (text, code, rich = isRich()) => rich ? `\x1B[${code}m${text}\x1B[0m` : text;
68
+ const accent = (text, edge = false) => paint(text, oliveCode(environment(), edge));
69
+ const write = (text) => output.write(`${text}
70
+ `);
71
+ const wordmark = () => {
72
+ const columns = output.columns ?? 80;
73
+ const lines = columns >= Math.max(...WORDMARK.map((line) => line.length)) + 2 ? WORDMARK : SMALL_WORDMARK;
74
+ if (!isRich() || columns < Math.max(...lines.map((line) => line.length)) + 2) {
75
+ write(` ${accent("THALLY")}`);
76
+ return;
77
+ }
78
+ const lettering = lines.map((line) => line.split(/(█+)/).map((part) => accent(part, !part.includes("\u2588"))).join(""));
79
+ for (const line of lettering) write(` ${line}`);
80
+ };
81
+ const diagnostic = (message, isError) => {
82
+ const rich = isRich() && Boolean(errors.isTTY);
83
+ const symbol = isError ? rich ? "\u25A0" : "[error]" : rich ? "\u25B2" : "[warn]";
84
+ const line = ` ${paint(symbol, isError ? 31 : 33, rich)} ${terminalText(message)}`;
85
+ if (errors === process.stderr) console.warn(line);
86
+ else errors.write(`${line}
87
+ `);
88
+ };
89
+ const render = {
90
+ /** Whether interactive decoration is supported by the current output. */
91
+ get isRich() {
92
+ return isRich();
93
+ },
94
+ /** Whether the caller requested detailed subprocess output. */
95
+ get isVerbose() {
96
+ return arguments_().includes("--verbose");
97
+ },
98
+ /** Introduce a command with the responsive wordmark and its immediate task. */
99
+ intro(command, version, subtitle = PRODUCT_TAGLINE) {
100
+ const heading = `${terminalText(command)}${version ? ` ${isRich() ? "\xB7" : "-"} v${terminalText(version)}` : ""}`;
101
+ write("");
102
+ wordmark();
103
+ write("");
104
+ write(` ${PRODUCT_TAGLINE}`);
105
+ if (subtitle !== PRODUCT_TAGLINE) write(` ${paint(terminalText(subtitle), 2)}`);
106
+ write("");
107
+ if (isRich() && output === process.stdout) intro(accent(heading));
108
+ else write(` ${accent(heading)}`);
109
+ write("");
110
+ },
111
+ /** Display a source, destination, or result with a consistent label. */
112
+ detail(label, value) {
113
+ write(` ${paint(terminalText(label).padEnd(10), 2)} ${terminalText(value)}`);
114
+ },
115
+ /** Display a neutral fact without implying success. */
116
+ info(message) {
117
+ write(` ${paint(isRich() ? "\u2502" : "-", 2)} ${terminalText(message)}`);
118
+ },
119
+ /** Record a completed operation in the retained transcript. */
120
+ success(message) {
121
+ write(` ${accent(isRich() ? "\u2713" : "[ok]")} ${terminalText(message)}`);
122
+ },
123
+ /** Keep warnings visible on stderr even when successful task logs are quiet. */
124
+ warn(message) {
125
+ diagnostic(message, false);
126
+ },
127
+ /** Keep errors visible on stderr and separate from machine-readable stdout. */
128
+ error(message) {
129
+ diagnostic(message, true);
130
+ },
131
+ /** Group next steps or results without a fixed-width box. */
132
+ section(title, lines) {
133
+ write("");
134
+ write(` ${paint(terminalText(title), 1)}`);
135
+ for (const line of lines) write(` ${terminalText(line)}`);
136
+ write("");
137
+ },
138
+ /** Make the next action prominent while retaining one intact shell command. */
139
+ nextAction(title, command) {
140
+ const cleanTitle = terminalText(title);
141
+ const cleanCommand = terminalText(command);
142
+ write("");
143
+ if (!isRich()) {
144
+ write(` Next action: ${cleanTitle}`);
145
+ write(" Copy and run this command:");
146
+ write(` ${cleanCommand}`);
147
+ write("");
148
+ return;
149
+ }
150
+ const label = `NEXT \u2192 ${cleanTitle}`;
151
+ write(` ${paint(paint(paint(` ${label} `, 1), 30), oliveCode(environment()).replace(/^38;/, "48;"))}`);
152
+ write("");
153
+ write(" Copy and run to open your local preview:");
154
+ const canFrame = !cleanCommand.includes("\n") && cleanCommand.length + 8 <= (output.columns ?? 80);
155
+ if (canFrame) {
156
+ const width = cleanCommand.length + 4;
157
+ write(` ${accent(`\u256D${"\u2500".repeat(width)}\u256E`)}`);
158
+ write(` ${accent("\u2502")}${" ".repeat(width)}${accent("\u2502")}`);
159
+ write(` ${accent("\u2502")} ${accent(paint(cleanCommand, 1))} ${accent("\u2502")}`);
160
+ write(` ${accent("\u2502")}${" ".repeat(width)}${accent("\u2502")}`);
161
+ write(` ${accent(`\u2570${"\u2500".repeat(width)}\u256F`)}`);
162
+ } else write(` ${accent(paint(cleanCommand, 1))}`);
163
+ write("");
164
+ },
165
+ /** Finish a command without masking warnings or incomplete validation. */
166
+ outro(message) {
167
+ if (isRich() && output === process.stdout) outro(terminalText(message));
168
+ else {
169
+ write("");
170
+ write(` ${terminalText(message)}`);
171
+ write("");
172
+ }
173
+ },
174
+ /** Render progress, retain the outcome, and always release Clack's cursor hooks. */
175
+ async step(label, action, completedLabel = label) {
176
+ const progress = canAnimate() ? spinner({ indicator: "timer" }) : void 0;
177
+ const releaseAccent = progress ? acquireTerminalAccent() : void 0;
178
+ const cleanLabel = terminalText(label).replace(/\n/g, " ");
179
+ const limit = Math.max(8, (output.columns ?? 80) - 16);
180
+ const fit = (message) => Array.from(terminalText(message).replace(/\n/g, " ")).slice(0, limit).join("");
181
+ let activity = cleanLabel;
182
+ const began = Date.now();
183
+ const update = (message) => {
184
+ const next = terminalText(message).replace(/\n/g, " ");
185
+ if (next === activity) return;
186
+ activity = next;
187
+ if (progress) progress.message(fit(next));
188
+ else render.info(next);
189
+ };
190
+ const heartbeat = !progress ? setInterval(() => {
191
+ render.info(`${activity} (${Math.floor((Date.now() - began) / 1e3)}s elapsed)`);
192
+ }, 15e3) : void 0;
193
+ const cancel = () => {
194
+ progress?.stop("Cancelled", 1);
195
+ releaseAccent?.();
196
+ process.exit(130);
197
+ };
198
+ const terminate = () => {
199
+ progress?.stop("Cancelled", 1);
200
+ releaseAccent?.();
201
+ process.exit(143);
202
+ };
203
+ if (progress) {
204
+ process.once("SIGINT", cancel);
205
+ process.once("SIGTERM", terminate);
206
+ }
207
+ try {
208
+ if (progress) progress.start(fit(cleanLabel));
209
+ else render.info(cleanLabel);
210
+ const result = await action(update);
211
+ if (progress) progress.stop(terminalText(completedLabel));
212
+ else render.success(completedLabel);
213
+ return result;
214
+ } catch (error) {
215
+ if (progress) progress.stop(`${cleanLabel}: failed`, 2);
216
+ else render.error(`${cleanLabel}: failed`);
217
+ throw error;
218
+ } finally {
219
+ releaseAccent?.();
220
+ if (heartbeat) clearInterval(heartbeat);
221
+ if (progress) {
222
+ process.removeListener("SIGINT", cancel);
223
+ process.removeListener("SIGTERM", terminate);
224
+ }
225
+ }
226
+ }
227
+ };
228
+ return render;
229
+ }
230
+ var terminal = createTerminal();
231
+ function formatHelp(text) {
232
+ if (!terminal.isRich) return text;
233
+ const accent = `\x1B[${oliveCode(process.env)}m`;
234
+ return text.split("\n").map((line) => {
235
+ const heading = line.trimStart();
236
+ if (heading.length < 2 || !heading.endsWith(":")) return line;
237
+ const indentation = line.slice(0, line.length - heading.length);
238
+ return `${indentation}${accent}${heading}\x1B[0m`;
239
+ }).join("\n");
240
+ }
241
+
242
+ export {
243
+ PRODUCT_TAGLINE,
244
+ acquireTerminalAccent,
245
+ terminalText,
246
+ createTerminal,
247
+ terminal,
248
+ formatHelp
249
+ };
@@ -5,12 +5,14 @@ import {
5
5
  import {
6
6
  STABLE_SCAFFOLD_RELEASE
7
7
  } from "./chunk-BP2REWTO.js";
8
+ import {
9
+ terminal
10
+ } from "./chunk-L2YCVQBR.js";
8
11
 
9
12
  // src/download.ts
10
13
  import { Readable, Transform, pipeline } from "stream";
11
14
  import { promisify } from "util";
12
15
  import {
13
- cpSync,
14
16
  lstatSync,
15
17
  mkdtempSync,
16
18
  readdirSync,
@@ -20,6 +22,7 @@ import {
20
22
  } from "fs";
21
23
  import { tmpdir } from "os";
22
24
  import { dirname, join } from "path";
25
+ import { cp } from "fs/promises";
23
26
  import { extract } from "tar";
24
27
  var pipelineAsync = promisify(pipeline);
25
28
  var STARTER_FETCH_TIMEOUT_MS = 15e3;
@@ -86,9 +89,16 @@ function compressedArchiveLimit() {
86
89
  }
87
90
  async function downloadStarter(targetDir, siteName, release = STABLE_SCAFFOLD_RELEASE, options = {}) {
88
91
  if (options.announce !== false) {
89
- console.log("");
90
- console.log(` \u23F3 Creating ${siteName?.trim() || "your docs site"}...`);
92
+ await terminal.step(
93
+ `Downloading starter for ${siteName?.trim() || "your docs site"}`,
94
+ () => extractStarter(targetDir, release),
95
+ "Starter downloaded"
96
+ );
97
+ return;
91
98
  }
99
+ await extractStarter(targetDir, release);
100
+ }
101
+ async function extractStarter(targetDir, release) {
92
102
  const targetEntry = lstatSync(targetDir);
93
103
  if (!targetEntry.isDirectory() || targetEntry.isSymbolicLink()) {
94
104
  throw new Error("The Thally starter target must be a regular directory.");
@@ -137,7 +147,7 @@ async function downloadStarter(targetDir, siteName, release = STABLE_SCAFFOLD_RE
137
147
  );
138
148
  try {
139
149
  for (const entry of readdirSync(stagingDir)) {
140
- cpSync(join(stagingDir, entry), join(deliveryDir, entry), {
150
+ await cp(join(stagingDir, entry), join(deliveryDir, entry), {
141
151
  recursive: true,
142
152
  errorOnExist: true
143
153
  });
package/dist/index.js CHANGED
@@ -1,27 +1,88 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- migrateDocs,
3
+ migrateDocs
4
+ } from "./chunk-IZQHRVPV.js";
5
+ import {
4
6
  parseFrontmatter,
5
7
  runCheck
6
- } from "./chunk-VGGE6JIL.js";
8
+ } from "./chunk-DIX4EUI4.js";
9
+ import "./chunk-KDLP27DS.js";
7
10
  import {
8
11
  logo,
12
+ previewCommand,
9
13
  scaffold,
10
14
  slugify,
11
15
  success
12
- } from "./chunk-QLMRRNLL.js";
16
+ } from "./chunk-KGKSB3PU.js";
13
17
  import "./chunk-4VKRBJV2.js";
14
- import "./chunk-W5WQ2V7P.js";
18
+ import "./chunk-J7255UQA.js";
19
+ import "./chunk-WWIATVAD.js";
15
20
  import "./chunk-RI56XMBI.js";
16
21
  import "./chunk-BP2REWTO.js";
22
+ import {
23
+ acquireTerminalAccent,
24
+ formatHelp,
25
+ terminal
26
+ } from "./chunk-L2YCVQBR.js";
17
27
 
18
28
  // src/index.ts
19
29
  import { existsSync as existsSync2, readdirSync } from "fs";
20
30
  import { createRequire } from "module";
21
31
  import { resolve as resolve2 } from "path";
22
32
 
33
+ // src/interactive-prompts.ts
34
+ import * as clack from "@clack/prompts";
35
+ import * as inquirer from "@inquirer/prompts";
36
+ async function withAccent(prompt) {
37
+ const release = acquireTerminalAccent();
38
+ try {
39
+ return await prompt();
40
+ } finally {
41
+ release();
42
+ }
43
+ }
44
+ function answerOrCancel(answer) {
45
+ if (clack.isCancel(answer)) {
46
+ throw Object.assign(new Error("Cancelled."), { exitCode: 130 });
47
+ }
48
+ return answer;
49
+ }
50
+ async function input2(options) {
51
+ if (!terminal.isRich) return inquirer.input(options);
52
+ return answerOrCancel(await withAccent(() => clack.text({
53
+ message: options.message.trim(),
54
+ initialValue: options.default,
55
+ // Clack otherwise finalizes an empty answer as undefined. Optional fields
56
+ // such as the repository URL must keep Inquirer's empty-string contract.
57
+ defaultValue: "",
58
+ validate: options.validate ? (value) => {
59
+ const result = options.validate(value);
60
+ return result === true ? void 0 : result;
61
+ } : void 0
62
+ })));
63
+ }
64
+ async function select3(options) {
65
+ if (!terminal.isRich) return inquirer.select(options);
66
+ return answerOrCancel(await withAccent(() => clack.select({
67
+ message: options.message.trim(),
68
+ initialValue: options.default,
69
+ // Every existing choice has a label, including nonprimitive values such as
70
+ // undefined; Clack's conditional Option type cannot infer this for a generic.
71
+ options: options.choices.map((choice) => ({
72
+ label: choice.name,
73
+ value: choice.value
74
+ }))
75
+ })));
76
+ }
77
+ async function confirm3(options) {
78
+ if (!terminal.isRich) return inquirer.confirm(options);
79
+ return answerOrCancel(await withAccent(() => clack.confirm({
80
+ message: options.message.trim(),
81
+ initialValue: options.default ?? true
82
+ })));
83
+ }
84
+
23
85
  // src/prompts.ts
24
- import { confirm, input, select } from "@inquirer/prompts";
25
86
  import { basename, resolve } from "path";
26
87
  import {
27
88
  parseGitHubRepositoryUrl
@@ -43,7 +104,7 @@ async function gatherMigrationPlatform(value, useDefaults) {
43
104
  );
44
105
  return void 0;
45
106
  }
46
- return select({
107
+ return select3({
47
108
  message: " Which platform currently hosts these docs?",
48
109
  choices: [
49
110
  { name: "Mintlify", value: "mintlify" },
@@ -69,7 +130,7 @@ async function resolveAutoDetectedMigrationSource(sourceUrl, shouldPrompt) {
69
130
  console.warn(`
70
131
  \u26A0 ${LIVE_URL_MIGRATION_WARNING}`);
71
132
  if (!shouldPrompt) return sourceUrl;
72
- const sourcePreference = await select({
133
+ const sourcePreference = await select3({
73
134
  message: " Which source should Thally migrate?",
74
135
  choices: [
75
136
  {
@@ -84,14 +145,14 @@ async function resolveAutoDetectedMigrationSource(sourceUrl, shouldPrompt) {
84
145
  default: "github"
85
146
  });
86
147
  if (sourcePreference === "github") {
87
- const repositoryUrl = await input({
148
+ const repositoryUrl = await input2({
88
149
  message: " GitHub repository URL:",
89
150
  validate: validateGitHubRepositorySource
90
151
  });
91
152
  parseGitHubRepositoryUrl(repositoryUrl);
92
153
  return repositoryUrl;
93
154
  }
94
- const hasAcceptedAlignment = await confirm({
155
+ const hasAcceptedAlignment = await confirm3({
95
156
  message: " Continue knowing the migration may need manual alignment, using the Thally MCP afterward if needed?",
96
157
  default: false
97
158
  });
@@ -119,23 +180,23 @@ async function gatherAnswers(dirArg, useDefaults, installPreference) {
119
180
  } else if (useDefaults) {
120
181
  projectDir = resolve("my-docs");
121
182
  } else {
122
- const dirName = await input({
183
+ const dirName = await input2({
123
184
  message: " Project directory:",
124
185
  default: "my-docs"
125
186
  });
126
187
  projectDir = resolve(dirName);
127
188
  }
128
189
  const defaultName = basename(projectDir).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
129
- const projectName = useDefaults ? defaultName : await input({
190
+ const projectName = useDefaults ? defaultName : await input2({
130
191
  message: " Project name:",
131
192
  default: defaultName
132
193
  });
133
194
  const defaultDesc = `Documentation for ${projectName}.`;
134
- const description = useDefaults ? defaultDesc : await input({
195
+ const description = useDefaults ? defaultDesc : await input2({
135
196
  message: " Description:",
136
197
  default: defaultDesc
137
198
  });
138
- const brandPreset = useDefaults ? "primary" : await select({
199
+ const brandPreset = useDefaults ? "primary" : await select3({
139
200
  message: " Brand preset:",
140
201
  choices: [
141
202
  { name: "primary", value: "primary" },
@@ -143,18 +204,18 @@ async function gatherAnswers(dirArg, useDefaults, installPreference) {
143
204
  ],
144
205
  default: "primary"
145
206
  });
146
- const repoUrl = useDefaults ? "" : await input({
207
+ const repoUrl = useDefaults ? "" : await input2({
147
208
  message: " GitHub repo URL (optional):",
148
209
  default: ""
149
210
  });
150
211
  let trackRepos;
151
212
  if (!useDefaults) {
152
- const enableTrack = await input({
213
+ const enableTrack = await input2({
153
214
  message: " Keep your docs in sync automatically with Thally Track? When a PR merges in a repo you list,\n the docs agent drafts the doc updates as a PR for you to review. (y/N):",
154
215
  default: "N"
155
216
  });
156
217
  if (enableTrack.trim().toLowerCase() === "y") {
157
- const reposInput = await input({
218
+ const reposInput = await input2({
158
219
  message: " Which repo(s) should Thally watch? (comma-separated owner/repo or GitHub URLs, e.g. acme/api,acme/web):",
159
220
  default: ""
160
221
  });
@@ -165,7 +226,7 @@ async function gatherAnswers(dirArg, useDefaults, installPreference) {
165
226
  }
166
227
  let doInstall = installPreference ?? false;
167
228
  if (!useDefaults && installPreference === void 0) {
168
- const shouldInstall = await input({
229
+ const shouldInstall = await input2({
169
230
  message: " Install dependencies now? (y/N):",
170
231
  default: "N"
171
232
  });
@@ -173,12 +234,12 @@ async function gatherAnswers(dirArg, useDefaults, installPreference) {
173
234
  }
174
235
  let i18nLocales;
175
236
  if (!useDefaults) {
176
- const enableI18n = await input({
237
+ const enableI18n = await input2({
177
238
  message: " Add another documentation language? (y/N):",
178
239
  default: "N"
179
240
  });
180
241
  if (enableI18n.toLowerCase() === "y") {
181
- const localesInput = await input({
242
+ const localesInput = await input2({
182
243
  message: " Which additional locales? (comma-separated codes, e.g. fr,de,ja):",
183
244
  default: "fr"
184
245
  });
@@ -212,7 +273,7 @@ import { parseGitHubRepositoryUrl as parseGitHubRepositoryUrl2 } from "@thallyla
212
273
  // src/translate.ts
213
274
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
214
275
  import { join, dirname } from "path";
215
- import { input as input2 } from "@inquirer/prompts";
276
+ import { input as input3 } from "@inquirer/prompts";
216
277
  import Anthropic from "@anthropic-ai/sdk";
217
278
  import pLimit from "p-limit";
218
279
 
@@ -480,7 +541,7 @@ ${examples.maskedSource}`
480
541
  }
481
542
  ]
482
543
  });
483
- const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
544
+ const text2 = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
484
545
  const values = [];
485
546
  for (let start = 0; start < examples.segments.length; start += 32) {
486
547
  const sourceSegments = examples.segments.slice(start, start + 32);
@@ -498,7 +559,7 @@ ${examples.maskedSource}`
498
559
  }
499
560
  values.push(...translated);
500
561
  }
501
- return examples.restore(text.trim(), values);
562
+ return examples.restore(text2.trim(), values);
502
563
  }
503
564
  async function runTranslateCommand(locale, pages, force, apiKey, model, yes, projectDir) {
504
565
  const config = readDocsJson(projectDir);
@@ -569,11 +630,11 @@ async function runTranslateCommand(locale, pages, force, apiKey, model, yes, pro
569
630
  }
570
631
  console.log("");
571
632
  if (!yes) {
572
- const confirm2 = await input2({
633
+ const confirm4 = await input3({
573
634
  message: " Proceed? (Y/n):",
574
635
  default: "Y"
575
636
  });
576
- if (confirm2.toLowerCase() === "n") {
637
+ if (confirm4.toLowerCase() === "n") {
577
638
  console.log("\n Aborted.");
578
639
  return;
579
640
  }
@@ -632,7 +693,7 @@ var valueFlags = /* @__PURE__ */ new Set([
632
693
  "--platform"
633
694
  ]);
634
695
  var commandFlags = {
635
- scaffold: /* @__PURE__ */ new Set(["--help", "-h", "--yes", "-y", "--install", "--no-install"]),
696
+ scaffold: /* @__PURE__ */ new Set(["--help", "-h", "--yes", "-y", "--install", "--no-install", "--verbose"]),
636
697
  migrate: /* @__PURE__ */ new Set([
637
698
  "--help",
638
699
  "-h",
@@ -643,6 +704,7 @@ var commandFlags = {
643
704
  "--max-pages",
644
705
  "--platform",
645
706
  "--skip-validation",
707
+ "--verbose",
646
708
  "--yes",
647
709
  "-y"
648
710
  ]),
@@ -673,6 +735,7 @@ Scaffold options:
673
735
  -y, --yes Accept defaults and skip interactive prompts
674
736
  --install Install project dependencies after scaffolding
675
737
  --no-install Skip dependency installation without prompting
738
+ --verbose Show detailed installation and Git output
676
739
  -h, --help Show this help
677
740
  -v, -V, --version Show the installed version
678
741
 
@@ -690,6 +753,7 @@ Options:
690
753
  --max-pages <count> Limit a public URL crawl to 1-1000 pages
691
754
  --platform <name> Use mintlify, docusaurus, fern, or auto
692
755
  --skip-validation Import only; explicitly skip content and build verification
756
+ --verbose Show detailed installation, build, and Git output
693
757
  --api-key <key> Anthropic API key for non-Markdown conversion
694
758
  -y, --yes Skip interactive prompts
695
759
  -h, --help Show this help
@@ -743,7 +807,7 @@ function resolveCommand(subcommand) {
743
807
  return "scaffold";
744
808
  }
745
809
  function printHelp(command) {
746
- console.log(command === "scaffold" ? mainHelp : commandHelp[command]);
810
+ console.log(formatHelp(command === "scaffold" ? mainHelp : commandHelp[command]));
747
811
  }
748
812
  function validateFlags(command) {
749
813
  const unknownFlag = flags.find((flag) => !commandFlags[command].has(flag));
@@ -772,10 +836,11 @@ async function runMigrateCommand() {
772
836
  const branch = getFlagValue("--branch");
773
837
  const docsDir = getFlagValue("--docs-dir");
774
838
  const yes = flags.includes("--yes") || flags.includes("-y");
839
+ terminal.intro("migrate", packageMetadata.version, "Bring your docs to Thally. Built for people and AI tools.");
775
840
  const platformFlag = getFlagValue("--platform");
776
841
  const platform = await gatherMigrationPlatform(platformFlag, yes);
777
842
  if (platform === void 0) {
778
- const shouldPromptForSource = platformFlag === void 0 && !yes;
843
+ const shouldPromptForSource = platformFlag === void 0 && !yes && Boolean(process.stdin.isTTY);
779
844
  sourceUrl = await resolveAutoDetectedMigrationSource(sourceUrl, shouldPromptForSource);
780
845
  source = new URL(sourceUrl);
781
846
  }
@@ -797,15 +862,11 @@ async function runMigrateCommand() {
797
862
  console.error("\n \u274C --max-pages must be an integer between 1 and 1000.");
798
863
  process.exit(1);
799
864
  }
800
- logo();
801
- console.log(" \u{1F680} Thally Migrate");
802
- console.log("");
803
- console.log(` Source: ${sourceUrl}`);
804
- console.log(` Target: ${projectDir}`);
805
- if (branch) console.log(` Branch: ${branch}`);
806
- if (docsDir) console.log(` Docs dir: ${docsDir}`);
807
- console.log(` Platform: ${platform ?? "auto-detect"}`);
808
- console.log("");
865
+ terminal.detail("Source", `${source.origin}${source.pathname}`);
866
+ terminal.detail("Output", projectDir);
867
+ if (branch) terminal.detail("Branch", branch);
868
+ if (docsDir) terminal.detail("Docs dir", docsDir);
869
+ terminal.detail("Platform", platform ?? "auto-detect");
809
870
  const result = await migrateDocs({
810
871
  sourceUrl,
811
872
  projectDir,
@@ -818,14 +879,20 @@ async function runMigrateCommand() {
818
879
  yes,
819
880
  skipValidation: flags.includes("--skip-validation")
820
881
  });
821
- console.log("");
822
- console.log(` Migrated docs: ${result.projectDir}`);
823
- const quotedDir = process.platform === "win32" ? `"${result.projectDir.replace(/"/g, '""')}"` : `'${result.projectDir.replace(/'/g, "'\\''")}'`;
824
- console.log(` To preview: cd ${quotedDir} && npm run dev`);
882
+ terminal.section("Migration summary", [
883
+ `${result.pagesWritten} pages \xB7 ${result.assetsWritten} assets \xB7 ${result.platform}`,
884
+ `Content: ${result.validation.content} \xB7 Build: ${result.validation.build}`,
885
+ `${result.warnings.length} migration warning(s)`
886
+ ]);
887
+ terminal.detail("Output", result.projectDir);
888
+ terminal.detail("Report", result.reportPath);
889
+ const isVerified = result.validation.content === "passed" && result.validation.build === "passed";
890
+ terminal.outro(isVerified ? "Migration verified. Review any warnings before publishing." : "Import saved. Validation is incomplete; review the migration report.");
891
+ terminal.nextAction("Preview locally", previewCommand(result.projectDir, !existsSync2(resolve2(result.projectDir, "node_modules"))));
825
892
  if (result.validation.content === "failed" || result.validation.build === "failed") process.exitCode = 1;
826
893
  }
827
894
  async function runScaffoldCommand() {
828
- const useDefaults = flags.includes("--yes") || flags.includes("-y");
895
+ const useDefaults = flags.includes("--yes") || flags.includes("-y") || !process.stdin.isTTY;
829
896
  const installPreference = flags.includes("--install") ? true : flags.includes("--no-install") ? false : void 0;
830
897
  const dirArg = positional[0];
831
898
  if (dirArg) {
@@ -873,9 +940,7 @@ async function runTranslateSubcommand() {
873
940
  const model = getFlagValue("--model") ?? "claude-sonnet-4-6";
874
941
  const yes = flags.includes("--yes") || flags.includes("-y");
875
942
  const projectDir = resolve2(positional[1] ?? ".");
876
- logo();
877
- console.log(" \u{1F310} Thally Translate");
878
- console.log("");
943
+ terminal.intro("translate", packageMetadata.version, "Make your docs available in another language.");
879
944
  await runTranslateCommand(locale, pages, force, apiKey, model, yes, projectDir);
880
945
  }
881
946
  async function main() {
@@ -897,11 +962,11 @@ async function main() {
897
962
  } else if (subcommand === "translate") {
898
963
  await runTranslateSubcommand();
899
964
  } else {
900
- logo();
965
+ logo(packageMetadata.version);
901
966
  await runScaffoldCommand();
902
967
  }
903
968
  }
904
969
  main().catch((err) => {
905
- console.error("\n \u274C Error:", err.message);
906
- process.exit(1);
970
+ terminal.error(err.message);
971
+ process.exit(err.exitCode ?? 1);
907
972
  });