grimoire-wizard 0.5.1 → 0.6.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
@@ -42,7 +42,7 @@ function parseWizardConfig(raw) {
42
42
  const result = wizardConfigSchema.parse(raw);
43
43
  return result;
44
44
  }
45
- var conditionSchema, validationRuleSchema, selectOptionSchema, separatorOptionSchema, selectChoiceSchema, baseStepFields, textStepSchema, selectStepSchema, multiSelectStepSchema, confirmStepSchema, passwordStepSchema, numberStepSchema, searchStepSchema, editorStepSchema, pathStepSchema, toggleStepSchema, messageStepSchema, noteStepSchema, stepConfigSchema, hexColorSchema, themeConfigSchema, preFlightCheckSchema, actionConfigSchema, wizardConfigSchema;
45
+ var conditionSchema, validationRuleSchema, selectOptionSchema, separatorOptionSchema, selectChoiceSchema, stepReviewConfigSchema, baseStepFields, textStepSchema, selectStepSchema, multiSelectStepSchema, confirmStepSchema, passwordStepSchema, numberStepSchema, searchStepSchema, editorStepSchema, pathStepSchema, toggleStepSchema, messageStepSchema, noteStepSchema, browserStepSchema, stepConfigSchema, hexColorSchema, progressBarConfigSchema, themeConfigSchema, preFlightCheckSchema, actionConfigSchema, wizardConfigSchema;
46
46
  var init_schema = __esm({
47
47
  "src/schema.ts"() {
48
48
  "use strict";
@@ -90,6 +90,11 @@ var init_schema = __esm({
90
90
  separator: z.string()
91
91
  });
92
92
  selectChoiceSchema = z.union([selectOptionSchema, separatorOptionSchema]);
93
+ stepReviewConfigSchema = z.object({
94
+ hide: z.boolean().optional(),
95
+ label: z.string().optional(),
96
+ format: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional()
97
+ }).optional();
93
98
  baseStepFields = {
94
99
  id: z.string(),
95
100
  message: z.string(),
@@ -98,7 +103,8 @@ var init_schema = __esm({
98
103
  when: conditionSchema.optional(),
99
104
  keepValuesOnPrevious: z.boolean().optional(),
100
105
  required: z.boolean().optional(),
101
- group: z.string().optional()
106
+ group: z.string().optional(),
107
+ review: stepReviewConfigSchema
102
108
  };
103
109
  textStepSchema = z.object({
104
110
  ...baseStepFields,
@@ -115,7 +121,8 @@ var init_schema = __esm({
115
121
  default: z.string().optional(),
116
122
  routes: z.record(z.string(), z.string()).optional(),
117
123
  pageSize: z.number().int().positive().optional(),
118
- loop: z.boolean().optional()
124
+ loop: z.boolean().optional(),
125
+ columns: z.number().int().min(1).max(8).optional()
119
126
  });
120
127
  multiSelectStepSchema = z.object({
121
128
  ...baseStepFields,
@@ -126,7 +133,8 @@ var init_schema = __esm({
126
133
  min: z.number().int().nonnegative().optional(),
127
134
  max: z.number().int().positive().optional(),
128
135
  pageSize: z.number().int().positive().optional(),
129
- loop: z.boolean().optional()
136
+ loop: z.boolean().optional(),
137
+ columns: z.number().int().min(1).max(8).optional()
130
138
  });
131
139
  confirmStepSchema = z.object({
132
140
  ...baseStepFields,
@@ -154,7 +162,8 @@ var init_schema = __esm({
154
162
  default: z.string().optional(),
155
163
  placeholder: z.string().optional(),
156
164
  pageSize: z.number().int().positive().optional(),
157
- loop: z.boolean().optional()
165
+ loop: z.boolean().optional(),
166
+ columns: z.number().int().min(1).max(8).optional()
158
167
  });
159
168
  editorStepSchema = z.object({
160
169
  ...baseStepFields,
@@ -182,7 +191,14 @@ var init_schema = __esm({
182
191
  });
183
192
  noteStepSchema = z.object({
184
193
  ...baseStepFields,
185
- type: z.literal("note")
194
+ type: z.literal("note"),
195
+ style: z.enum(["info", "warning", "error", "success", "code", "banner"]).optional()
196
+ });
197
+ browserStepSchema = z.object({
198
+ ...baseStepFields,
199
+ type: z.literal("browser"),
200
+ url: z.string(),
201
+ fallback: z.string().optional()
186
202
  });
187
203
  stepConfigSchema = z.discriminatedUnion("type", [
188
204
  textStepSchema,
@@ -196,12 +212,19 @@ var init_schema = __esm({
196
212
  pathStepSchema,
197
213
  toggleStepSchema,
198
214
  messageStepSchema,
199
- noteStepSchema
215
+ noteStepSchema,
216
+ browserStepSchema
200
217
  ]);
201
218
  hexColorSchema = z.string().regex(
202
219
  /^#[0-9a-fA-F]{6}$/,
203
220
  "Must be a 6-digit hex color (e.g., #FF0000)"
204
221
  );
222
+ progressBarConfigSchema = z.object({
223
+ width: z.number().int().positive().max(200).optional(),
224
+ filledColor: hexColorSchema.optional(),
225
+ emptyColor: hexColorSchema.optional(),
226
+ style: z.enum(["blocks", "line", "dots", "arrow"]).optional()
227
+ }).optional();
205
228
  themeConfigSchema = z.object({
206
229
  preset: z.enum(["default", "catppuccin", "dracula", "nord", "tokyonight", "monokai"]).optional(),
207
230
  tokens: z.object({
@@ -211,7 +234,12 @@ var init_schema = __esm({
211
234
  warning: hexColorSchema.optional(),
212
235
  info: hexColorSchema.optional(),
213
236
  muted: hexColorSchema.optional(),
214
- accent: hexColorSchema.optional()
237
+ accent: hexColorSchema.optional(),
238
+ highlight: hexColorSchema.optional(),
239
+ highlightBg: hexColorSchema.optional(),
240
+ pointer: hexColorSchema.optional(),
241
+ checked: hexColorSchema.optional(),
242
+ dimmed: hexColorSchema.optional()
215
243
  }).optional(),
216
244
  icons: z.object({
217
245
  step: z.string().optional(),
@@ -225,12 +253,15 @@ var init_schema = __esm({
225
253
  frames: z.array(z.string()).min(1),
226
254
  interval: z.number().positive().optional()
227
255
  })
228
- ]).optional()
256
+ ]).optional(),
257
+ spinnerElapsed: z.boolean().optional(),
258
+ progressBar: progressBarConfigSchema
229
259
  });
230
260
  preFlightCheckSchema = z.object({
231
261
  name: z.string(),
232
262
  run: z.string(),
233
- message: z.string()
263
+ message: z.string(),
264
+ showOutput: z.boolean().optional()
234
265
  });
235
266
  actionConfigSchema = z.object({
236
267
  name: z.string().optional(),
@@ -243,7 +274,13 @@ var init_schema = __esm({
243
274
  version: z.string().optional(),
244
275
  description: z.string().optional(),
245
276
  review: z.boolean().optional(),
246
- icon: z.string().optional()
277
+ icon: z.string().optional(),
278
+ iconSize: z.union([z.enum(["small", "medium", "large"]), z.number().int().positive()]).optional(),
279
+ font: z.string().min(1).optional(),
280
+ banner: z.union([z.string(), z.function()]).optional(),
281
+ subtitle: z.string().optional(),
282
+ clearBetweenSteps: z.boolean().optional(),
283
+ checksStyle: z.enum(["spinner", "tasklist"]).optional()
247
284
  }),
248
285
  theme: themeConfigSchema.optional(),
249
286
  steps: z.array(stepConfigSchema).min(1),
@@ -501,6 +538,7 @@ async function loadWithInheritance(filePath, seen) {
501
538
  async function loadWizardConfig(filePath) {
502
539
  const config = await loadWithInheritance(filePath, /* @__PURE__ */ new Set());
503
540
  detectCycles(config);
541
+ config._configFilePath = resolve(filePath);
504
542
  return config;
505
543
  }
506
544
  function parseWizardYAML(yamlString) {
@@ -713,7 +751,8 @@ function wizardReducer(state, transition, config) {
713
751
  errors: { ...state.errors, [state.currentStepId]: validationError }
714
752
  };
715
753
  }
716
- const updatedAnswers = {
754
+ const isDisplayOnly = currentStep.type === "note" || currentStep.type === "message" || currentStep.type === "browser";
755
+ const updatedAnswers = isDisplayOnly ? { ...state.answers } : {
717
756
  ...state.answers,
718
757
  [state.currentStepId]: transition.value
719
758
  };
@@ -921,7 +960,12 @@ var DEFAULT_TOKENS = {
921
960
  warning: "#FFD93D",
922
961
  info: "#4D96FF",
923
962
  muted: "#888888",
924
- accent: "#C084FC"
963
+ accent: "#C084FC",
964
+ highlight: "#5B9BD5",
965
+ highlightBg: "#1E1E2E",
966
+ pointer: "#C084FC",
967
+ checked: "#6BCB77",
968
+ dimmed: "#555555"
925
969
  };
926
970
  var DEFAULT_ICONS = {
927
971
  step: "\u25CF",
@@ -929,6 +973,25 @@ var DEFAULT_ICONS = {
929
973
  stepPending: "\u25CB",
930
974
  pointer: "\u203A"
931
975
  };
976
+ var PROGRESS_BAR_CHARS = {
977
+ blocks: { filled: "\u2588", empty: "\u2591" },
978
+ line: { filled: "\u2500", empty: "\u2500" },
979
+ dots: { filled: "\u2022", empty: "\xB7" },
980
+ arrow: { filled: "\u2550", empty: "\u2500" }
981
+ };
982
+ function resolveProgressBar(config, tokens) {
983
+ const style = config?.style ?? "blocks";
984
+ const chars = PROGRESS_BAR_CHARS[style];
985
+ const filledHex = config?.filledColor ?? tokens?.success ?? DEFAULT_TOKENS.success;
986
+ const emptyHex = config?.emptyColor ?? tokens?.muted ?? DEFAULT_TOKENS.muted;
987
+ return {
988
+ width: config?.width ?? 20,
989
+ filledColor: chalk.hex(filledHex),
990
+ emptyColor: chalk.hex(emptyHex),
991
+ style,
992
+ chars
993
+ };
994
+ }
932
995
  function resolveTheme(themeConfig) {
933
996
  const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
934
997
  const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
@@ -941,9 +1004,16 @@ function resolveTheme(themeConfig) {
941
1004
  info: chalk.hex(tokens.info),
942
1005
  muted: chalk.hex(tokens.muted),
943
1006
  accent: chalk.hex(tokens.accent),
1007
+ highlight: chalk.hex(tokens.highlight),
1008
+ highlightBg: chalk.bgHex(tokens.highlightBg),
1009
+ pointer: chalk.hex(tokens.pointer),
1010
+ checked: chalk.hex(tokens.checked),
1011
+ dimmed: chalk.hex(tokens.dimmed),
944
1012
  bold: chalk.bold,
945
1013
  icons,
946
- spinner: resolveSpinner(themeConfig?.spinner)
1014
+ spinner: resolveSpinner(themeConfig?.spinner),
1015
+ spinnerElapsed: themeConfig?.spinnerElapsed ?? false,
1016
+ progressBar: resolveProgressBar(themeConfig?.progressBar, tokens)
947
1017
  };
948
1018
  }
949
1019
 
@@ -969,7 +1039,7 @@ function resolveEnvDefaultBoolean(value) {
969
1039
  }
970
1040
 
971
1041
  // src/runner.ts
972
- import { execSync } from "child_process";
1042
+ import { execSync, execFileSync } from "child_process";
973
1043
  import { resolve as resolve2, dirname as dirname2 } from "path";
974
1044
  import { pathToFileURL } from "url";
975
1045
 
@@ -987,11 +1057,11 @@ import {
987
1057
  } from "@inquirer/prompts";
988
1058
  var InquirerRenderer = class {
989
1059
  renderStepHeader(stepIndex, totalVisible, message, theme, description) {
990
- const barWidth = 20;
991
- const filledCount = totalVisible > 0 ? Math.round(stepIndex / totalVisible * barWidth) : 0;
992
- const remainingCount = barWidth - filledCount;
993
- const filledBar = theme.success("\u2588".repeat(filledCount));
994
- const remainingBar = theme.muted("\u2591".repeat(remainingCount));
1060
+ const pb = theme.progressBar;
1061
+ const filledCount = totalVisible > 0 ? Math.round(stepIndex / totalVisible * pb.width) : 0;
1062
+ const remainingCount = pb.width - filledCount;
1063
+ const filledBar = pb.filledColor(pb.chars.filled.repeat(filledCount));
1064
+ const remainingBar = pb.emptyColor(pb.chars.empty.repeat(remainingCount));
995
1065
  const counter = theme.muted(`Step ${String(stepIndex + 1)}/${String(totalVisible)}`);
996
1066
  const stepMessage = theme.muted(`\u2014 ${message}`);
997
1067
  console.log(`
@@ -1000,6 +1070,39 @@ var InquirerRenderer = class {
1000
1070
  console.log(` ${theme.muted(description)}`);
1001
1071
  }
1002
1072
  }
1073
+ renderNote(step, theme) {
1074
+ const title = step.message;
1075
+ const body = step.description ?? "";
1076
+ const style = step.style ?? "info";
1077
+ const { colorFn, icon } = getNoteStyleConfig(style, theme);
1078
+ if (style === "banner") {
1079
+ const width = Math.max(4, (process.stdout.columns ?? 60) - 4);
1080
+ const rule = colorFn("\u2500".repeat(width));
1081
+ console.log();
1082
+ console.log(` ${rule}`);
1083
+ console.log(` ${colorFn(icon)} ${theme.bold(title)}`);
1084
+ if (body) console.log(` ${theme.muted(body)}`);
1085
+ console.log(` ${rule}`);
1086
+ console.log();
1087
+ return;
1088
+ }
1089
+ const border = colorFn("\u2502");
1090
+ const topRule = colorFn(`\u250C${"\u2500".repeat(2)}`);
1091
+ const bottomRule = colorFn(`\u2514${"\u2500".repeat(2)}`);
1092
+ console.log();
1093
+ console.log(` ${topRule} ${colorFn(icon)} ${theme.bold(title)}`);
1094
+ if (body) {
1095
+ for (const line of body.split("\n")) {
1096
+ if (style === "code") {
1097
+ console.log(` ${border} ${theme.muted(line)}`);
1098
+ } else {
1099
+ console.log(` ${border} ${line}`);
1100
+ }
1101
+ }
1102
+ }
1103
+ console.log(` ${bottomRule}`);
1104
+ console.log();
1105
+ }
1003
1106
  async renderText(step, state, theme) {
1004
1107
  const existingAnswer = state.answers[step.id];
1005
1108
  const defaultValue = typeof existingAnswer === "string" ? existingAnswer : step.default;
@@ -1012,7 +1115,7 @@ var InquirerRenderer = class {
1012
1115
  async renderSelect(step, state, theme) {
1013
1116
  const existingAnswer = state.answers[step.id];
1014
1117
  const defaultValue = typeof existingAnswer === "string" ? existingAnswer : step.default;
1015
- const choices = step.options.map((opt) => {
1118
+ const rawChoices = step.options.map((opt) => {
1016
1119
  if ("separator" in opt) {
1017
1120
  return new Separator(opt.separator);
1018
1121
  }
@@ -1023,19 +1126,27 @@ var InquirerRenderer = class {
1023
1126
  disabled: opt.disabled
1024
1127
  };
1025
1128
  });
1129
+ const itemChoices = rawChoices.filter((c) => !(c instanceof Separator));
1130
+ const columnedItems = step.columns ? applyColumns(itemChoices, step.columns) : itemChoices;
1131
+ let itemIdx = 0;
1132
+ const choices = rawChoices.map((c) => c instanceof Separator ? c : columnedItems[itemIdx++] ?? c);
1026
1133
  return select({
1027
1134
  message: step.message,
1028
1135
  choices,
1029
1136
  default: defaultValue,
1030
1137
  pageSize: step.pageSize,
1031
1138
  loop: step.loop,
1032
- theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
1139
+ theme: {
1140
+ prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone },
1141
+ icon: { cursor: theme.icons.pointer },
1142
+ style: { disabled: theme.dimmed }
1143
+ }
1033
1144
  });
1034
1145
  }
1035
1146
  async renderMultiSelect(step, state, theme) {
1036
1147
  const existingAnswer = state.answers[step.id];
1037
1148
  const previousSelections = Array.isArray(existingAnswer) ? existingAnswer.filter((v) => typeof v === "string") : step.default;
1038
- const choices = step.options.map((opt) => {
1149
+ const rawChoices = step.options.map((opt) => {
1039
1150
  if ("separator" in opt) {
1040
1151
  return new Separator(opt.separator);
1041
1152
  }
@@ -1046,12 +1157,20 @@ var InquirerRenderer = class {
1046
1157
  disabled: opt.disabled
1047
1158
  };
1048
1159
  });
1160
+ const itemChoices = rawChoices.filter((c) => !(c instanceof Separator));
1161
+ const columnedItems = step.columns ? applyColumns(itemChoices, step.columns) : itemChoices;
1162
+ let itemIdx = 0;
1163
+ const choices = rawChoices.map((c) => c instanceof Separator ? c : columnedItems[itemIdx++] ?? c);
1049
1164
  return checkbox({
1050
1165
  message: step.message,
1051
1166
  choices,
1052
1167
  pageSize: step.pageSize,
1053
1168
  loop: step.loop,
1054
- theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
1169
+ theme: {
1170
+ prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone },
1171
+ icon: { cursor: theme.icons.pointer, checked: theme.checked("\u2714"), unchecked: theme.muted("\u25CB") },
1172
+ style: { disabledChoice: theme.dimmed }
1173
+ }
1055
1174
  });
1056
1175
  }
1057
1176
  async renderConfirm(step, state, theme) {
@@ -1083,15 +1202,17 @@ var InquirerRenderer = class {
1083
1202
  return result ?? defaultValue ?? 0;
1084
1203
  }
1085
1204
  async renderSearch(step, _state, theme) {
1205
+ const message = step.placeholder ? `${step.message} ${theme.muted(`(${step.placeholder})`)}` : step.message;
1086
1206
  return search({
1087
- message: step.message,
1207
+ message,
1088
1208
  source: (input3) => {
1089
1209
  const term = (input3 ?? "").toLowerCase();
1090
- return step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(term)).map((opt) => ({
1210
+ const filtered = step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(term)).map((opt) => ({
1091
1211
  name: opt.label,
1092
1212
  value: opt.value,
1093
1213
  description: opt.hint
1094
1214
  }));
1215
+ return step.columns ? applyColumns(filtered, step.columns) : filtered;
1095
1216
  },
1096
1217
  pageSize: step.pageSize,
1097
1218
  theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
@@ -1159,6 +1280,42 @@ ${theme.muted("\u2500".repeat(40))}`);
1159
1280
  process.stdout.write("\x1B[2J\x1B[0f");
1160
1281
  }
1161
1282
  };
1283
+ var NOTE_STYLE_MAP = {
1284
+ info: { tokenKey: "info", icon: "\u2139" },
1285
+ warning: { tokenKey: "warning", icon: "\u26A0" },
1286
+ error: { tokenKey: "error", icon: "\u2716" },
1287
+ success: { tokenKey: "success", icon: "\u2714" },
1288
+ code: { tokenKey: "muted", icon: "\u276F" },
1289
+ banner: { tokenKey: "primary", icon: "\u2605" }
1290
+ };
1291
+ function getNoteStyleConfig(style, theme) {
1292
+ const entry = NOTE_STYLE_MAP[style];
1293
+ return {
1294
+ colorFn: theme[entry.tokenKey],
1295
+ icon: entry.icon
1296
+ };
1297
+ }
1298
+ function applyColumns(items, columns) {
1299
+ const cols = Math.max(1, Math.floor(columns));
1300
+ if (cols <= 1) return items;
1301
+ if (items.length === 0) return items;
1302
+ const maxLabel = Math.max(...items.map((i) => i.name.length));
1303
+ const colWidth = maxLabel + 4;
1304
+ const grouped = [];
1305
+ for (let i = 0; i < items.length; i += cols) {
1306
+ grouped.push(items.slice(i, i + cols));
1307
+ }
1308
+ const result = [];
1309
+ for (const row of grouped) {
1310
+ for (let c = 0; c < row.length; c++) {
1311
+ const item = row[c];
1312
+ const isLast = c === row.length - 1;
1313
+ const paddedName = isLast ? item.name : item.name.padEnd(colWidth);
1314
+ result.push({ ...item, name: paddedName });
1315
+ }
1316
+ }
1317
+ return result;
1318
+ }
1162
1319
 
1163
1320
  // src/template.ts
1164
1321
  function resolveTemplate(template, answers) {
@@ -1192,19 +1349,71 @@ function renderBanner(name, theme, options) {
1192
1349
  const icon = options?.icon;
1193
1350
  const prefix = icon ? `${icon} ` : "";
1194
1351
  if (options?.plain) {
1195
- return ` ${prefix}${theme.bold(name)}`;
1352
+ const line = ` ${prefix}${theme.bold(name)}`;
1353
+ return options?.subtitle ? `${line}
1354
+ ${theme.muted(options.subtitle)}` : line;
1355
+ }
1356
+ if (options?.banner) {
1357
+ const bannerStr = (typeof options.banner === "function" ? options.banner(theme) : options.banner).replace(/\n$/, "");
1358
+ const raw = bannerStr.split("\n");
1359
+ const mid = Math.floor(raw.length / 2);
1360
+ const bannerLines = raw.map((l, i) => {
1361
+ if (icon && i === mid) return ` ${icon} ${l}`;
1362
+ return icon ? ` ${l}` : ` ${l}`;
1363
+ }).join("\n");
1364
+ const colored = typeof options.banner === "function" ? bannerLines : GRIMOIRE_GRADIENT(bannerLines);
1365
+ return options?.subtitle ? `${colored}
1366
+ ${theme.muted(options.subtitle)}` : colored;
1196
1367
  }
1197
1368
  try {
1198
- const art = figlet.textSync(name, {
1199
- font: "Small",
1200
- horizontalLayout: "default"
1201
- });
1202
- const lines = art.split("\n").map((line) => ` ${line}`).join("\n");
1203
- const banner = GRIMOIRE_GRADIENT(lines);
1204
- return icon ? ` ${icon}
1205
- ${banner}` : banner;
1369
+ const requestedFont = options?.font ?? "Small";
1370
+ let art;
1371
+ try {
1372
+ art = figlet.textSync(name, { font: requestedFont, horizontalLayout: "default" });
1373
+ } catch {
1374
+ art = figlet.textSync(name, { font: "Small", horizontalLayout: "default" });
1375
+ }
1376
+ const artLines = art.split("\n");
1377
+ const lines = artLines.map((line, i) => {
1378
+ const iconStr = getIconForLine(icon, options?.iconSize, i, artLines.length);
1379
+ if (iconStr !== void 0) {
1380
+ return ` ${iconStr}${line}`;
1381
+ }
1382
+ return icon ? ` ${line}` : ` ${line}`;
1383
+ }).join("\n");
1384
+ const colored = GRIMOIRE_GRADIENT(lines);
1385
+ return options?.subtitle ? `${colored}
1386
+ ${theme.muted(options.subtitle)}` : colored;
1206
1387
  } catch {
1207
- return ` ${prefix}${theme.bold(name)}`;
1388
+ const line = ` ${prefix}${theme.bold(name)}`;
1389
+ return options?.subtitle ? `${line}
1390
+ ${theme.muted(options.subtitle)}` : line;
1391
+ }
1392
+ }
1393
+ function getIconForLine(icon, iconSize, lineIndex, totalLines) {
1394
+ if (!icon) return void 0;
1395
+ const mid = Math.floor(totalLines / 2);
1396
+ const span = resolveIconSpan(iconSize, totalLines);
1397
+ const halfSpan = Math.floor(span / 2);
1398
+ const start = Math.max(0, mid - halfSpan);
1399
+ const end = Math.min(totalLines - 1, start + span - 1);
1400
+ if (lineIndex >= start && lineIndex <= end) {
1401
+ return lineIndex === mid ? `${icon} ` : ` `;
1402
+ }
1403
+ return void 0;
1404
+ }
1405
+ function resolveIconSpan(iconSize, totalLines) {
1406
+ if (typeof iconSize === "number") {
1407
+ const clamped = Math.max(1, Math.floor(isFinite(iconSize) ? iconSize : 1));
1408
+ return Math.min(clamped, totalLines);
1409
+ }
1410
+ switch (iconSize ?? "small") {
1411
+ case "small":
1412
+ return 1;
1413
+ case "medium":
1414
+ return 3;
1415
+ case "large":
1416
+ return totalLines;
1208
1417
  }
1209
1418
  }
1210
1419
 
@@ -1443,7 +1652,14 @@ function emitEvent(renderer, event, theme) {
1443
1652
  renderer.onEvent(event, theme);
1444
1653
  }
1445
1654
  }
1446
- function runPreFlightChecks(checks, theme, renderer) {
1655
+ function runPreFlightChecks(checks, theme, renderer, checksStyle) {
1656
+ if (checksStyle === "tasklist") {
1657
+ runPreFlightChecksTasklist(checks, theme, renderer);
1658
+ } else {
1659
+ runPreFlightChecksSpinner(checks, theme, renderer);
1660
+ }
1661
+ }
1662
+ function runPreFlightChecksSpinner(checks, theme, renderer) {
1447
1663
  if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
1448
1664
  for (const check of checks) {
1449
1665
  if (renderer) emitEvent(renderer, { type: "spinner:start", message: check.name }, theme);
@@ -1461,20 +1677,69 @@ function runPreFlightChecks(checks, theme, renderer) {
1461
1677
  }
1462
1678
  console.log();
1463
1679
  }
1680
+ function runPreFlightChecksTasklist(checks, theme, renderer) {
1681
+ if (!process.stdout.isTTY) {
1682
+ runPreFlightChecksSpinner(checks, theme, renderer);
1683
+ return;
1684
+ }
1685
+ if (renderer) emitEvent(renderer, { type: "checks:start", checks, mode: "tasklist" }, theme);
1686
+ const statuses = checks.map(() => "pending");
1687
+ const icons = theme.icons;
1688
+ function renderTasklist() {
1689
+ process.stdout.write(`\x1B[${checks.length}A\x1B[0G`);
1690
+ for (let i = 0; i < checks.length; i++) {
1691
+ const check = checks[i];
1692
+ const s = statuses[i];
1693
+ let icon;
1694
+ if (s === "pass") icon = theme.success(icons.stepDone);
1695
+ else if (s === "fail") icon = theme.error("\u2717");
1696
+ else if (s === "running") icon = theme.info("\u280B");
1697
+ else icon = theme.muted(icons.stepPending);
1698
+ process.stdout.write(` ${icon} ${check.name}\x1B[K
1699
+ `);
1700
+ }
1701
+ }
1702
+ for (let i = 0; i < checks.length; i++) {
1703
+ process.stdout.write(` ${theme.muted(icons.stepPending)} ${checks[i].name}
1704
+ `);
1705
+ }
1706
+ let failedCheck;
1707
+ for (let i = 0; i < checks.length; i++) {
1708
+ const check = checks[i];
1709
+ statuses[i] = "running";
1710
+ renderTasklist();
1711
+ try {
1712
+ execSync(check.run, { stdio: "pipe" });
1713
+ statuses[i] = "pass";
1714
+ renderTasklist();
1715
+ if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
1716
+ } catch {
1717
+ statuses[i] = "fail";
1718
+ renderTasklist();
1719
+ failedCheck = check;
1720
+ if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
1721
+ break;
1722
+ }
1723
+ }
1724
+ renderTasklist();
1725
+ console.log();
1726
+ if (failedCheck) {
1727
+ throw new Error(`Pre-flight check failed: ${failedCheck.name} \u2014 ${failedCheck.message}`);
1728
+ }
1729
+ }
1730
+ var MOCK_MISS = /* @__PURE__ */ Symbol("mock-miss");
1464
1731
  function getMockValue(step, mockAnswers) {
1465
1732
  if (step.id in mockAnswers) {
1466
1733
  return mockAnswers[step.id];
1467
1734
  }
1468
- if (step.type === "message" || step.type === "note") {
1735
+ if (step.type === "message" || step.type === "note" || step.type === "browser") {
1469
1736
  return true;
1470
1737
  }
1471
1738
  const defaultValue = getStepDefault(step);
1472
1739
  if (defaultValue !== void 0) {
1473
1740
  return defaultValue;
1474
1741
  }
1475
- throw new Error(
1476
- `Mock mode: no answer provided for step "${step.id}" and no default available`
1477
- );
1742
+ return MOCK_MISS;
1478
1743
  }
1479
1744
  function getStepDefault(step) {
1480
1745
  switch (step.type) {
@@ -1494,6 +1759,7 @@ function getStepDefault(step) {
1494
1759
  case "password":
1495
1760
  case "message":
1496
1761
  case "note":
1762
+ case "browser":
1497
1763
  return void 0;
1498
1764
  }
1499
1765
  }
@@ -1529,9 +1795,34 @@ async function runWizard(config, options) {
1529
1795
  registerPlugin(plugin);
1530
1796
  }
1531
1797
  }
1798
+ let cancelFired = false;
1799
+ const performCancel = async () => {
1800
+ if (cancelFired) return;
1801
+ cancelFired = true;
1802
+ state = wizardReducer(state, { type: "CANCEL" }, config);
1803
+ const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1804
+ saveProgress(config.meta.name, {
1805
+ currentStepId: state.currentStepId,
1806
+ answers: state.answers,
1807
+ history: state.history
1808
+ }, void 0, passwordStepIds);
1809
+ try {
1810
+ await options?.onCancel?.(state);
1811
+ } catch {
1812
+ }
1813
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1814
+ if (!quiet) console.log(theme.warning("\n Wizard cancelled.\n"));
1815
+ };
1816
+ const signalHandler = !isMock ? () => {
1817
+ performCancel().finally(() => process.exit(130));
1818
+ } : void 0;
1819
+ if (signalHandler) {
1820
+ process.once("SIGINT", signalHandler);
1821
+ process.once("SIGTERM", signalHandler);
1822
+ }
1532
1823
  try {
1533
1824
  if (!isMock && config.checks && config.checks.length > 0) {
1534
- runPreFlightChecks(config.checks, theme, renderer);
1825
+ runPreFlightChecks(config.checks, theme, renderer, config.meta.checksStyle);
1535
1826
  }
1536
1827
  if (!quiet) {
1537
1828
  printWizardHeader(config, theme, options?.plain);
@@ -1541,7 +1832,62 @@ async function runWizard(config, options) {
1541
1832
  let needsReview = true;
1542
1833
  while (needsReview) {
1543
1834
  let previousGroup;
1835
+ let stepsCompleted = 0;
1544
1836
  while (state.status === "running") {
1837
+ if (!isMock && config.meta.clearBetweenSteps && stepsCompleted > 0) {
1838
+ renderer.clear();
1839
+ if (!quiet) {
1840
+ printWizardHeader(config, theme, options?.plain);
1841
+ }
1842
+ previousGroup = void 0;
1843
+ }
1844
+ let nextStepOverride;
1845
+ const createHookContext = (stateOverride) => ({
1846
+ answers: { ...(stateOverride ?? state).answers },
1847
+ state: stateOverride ?? state,
1848
+ showNote: (title, body) => {
1849
+ emitEvent(renderer, { type: "note", title, body }, theme);
1850
+ if (!("onEvent" in renderer) && process.stdout.isTTY) {
1851
+ console.log(`
1852
+ ${theme.bold(title)}`);
1853
+ if (body) console.log(` ${theme.muted(body)}`);
1854
+ console.log();
1855
+ }
1856
+ },
1857
+ setNextStep: (stepId) => {
1858
+ nextStepOverride = stepId;
1859
+ },
1860
+ openBrowser: async (url) => {
1861
+ if (isMock) return;
1862
+ try {
1863
+ new URL(url);
1864
+ } catch {
1865
+ return;
1866
+ }
1867
+ if (process.platform === "darwin") {
1868
+ execFileSync("open", [url], { stdio: "ignore" });
1869
+ } else if (process.platform === "win32") {
1870
+ execFileSync("powershell", ["-NoProfile", "-Command", `Start-Process '${url.replace(/'/g, "''")}'`], { stdio: "ignore" });
1871
+ } else {
1872
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
1873
+ }
1874
+ },
1875
+ prompt: async (promptConfig) => {
1876
+ if (isMock) {
1877
+ if (promptConfig.default !== void 0) return promptConfig.default;
1878
+ throw new Error("Mock mode: context.prompt() requires a default value");
1879
+ }
1880
+ const contextState = stateOverride ?? state;
1881
+ const tempStep = {
1882
+ id: "__hook_prompt__",
1883
+ message: promptConfig.message,
1884
+ type: promptConfig.type,
1885
+ ...promptConfig.type === "select" ? { options: promptConfig.options } : {},
1886
+ default: promptConfig.default
1887
+ };
1888
+ return renderStep(renderer, tempStep, contextState, theme);
1889
+ }
1890
+ });
1545
1891
  const visibleSteps = getVisibleSteps(config, state.answers);
1546
1892
  const currentStep = config.steps.find((s) => s.id === state.currentStepId);
1547
1893
  if (!currentStep) {
@@ -1564,9 +1910,13 @@ async function runWizard(config, options) {
1564
1910
  emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
1565
1911
  if (currentStep.type === "note") {
1566
1912
  emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
1913
+ if (!isMock && "style" in currentStep && currentStep.style && "renderNote" in renderer && !("onEvent" in renderer)) {
1914
+ const resolvedStep2 = { ...currentStep, message: resolvedMessage, description: resolvedDescription };
1915
+ renderer.renderNote(resolvedStep2, theme);
1916
+ }
1567
1917
  }
1568
1918
  if (options?.onBeforeStep) {
1569
- await options.onBeforeStep(currentStep.id, currentStep, state);
1919
+ await options.onBeforeStep(currentStep.id, currentStep, createHookContext());
1570
1920
  }
1571
1921
  const pluginStep = getPluginStep(currentStep.type);
1572
1922
  const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
@@ -1583,7 +1933,19 @@ async function runWizard(config, options) {
1583
1933
  }
1584
1934
  }
1585
1935
  try {
1586
- const value = isMock ? getMockValue(finalStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(finalStep), state, theme) : await renderStep(renderer, finalStep, state, theme);
1936
+ let value;
1937
+ if (isMock) {
1938
+ const mockResult = getMockValue(finalStep, mockAnswers);
1939
+ if (mockResult === MOCK_MISS) {
1940
+ throw new Error(
1941
+ `Mock mode: no answer provided for step "${finalStep.id}" and no default available`
1942
+ );
1943
+ } else {
1944
+ value = mockResult;
1945
+ }
1946
+ } else {
1947
+ value = pluginStep ? await pluginStep.render(toStepRecord(finalStep), state, theme) : await renderStep(renderer, finalStep, state, theme);
1948
+ }
1587
1949
  if (pluginStep?.validate) {
1588
1950
  const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
1589
1951
  if (pluginError) {
@@ -1624,28 +1986,31 @@ async function runWizard(config, options) {
1624
1986
  }
1625
1987
  }
1626
1988
  if (options?.onAfterStep) {
1627
- await options.onAfterStep(currentStep.id, value, nextState);
1989
+ await options.onAfterStep(currentStep.id, value, createHookContext(nextState));
1990
+ }
1991
+ if (nextStepOverride) {
1992
+ if (nextStepOverride === "__done__") {
1993
+ state = { ...nextState, status: "done" };
1994
+ } else {
1995
+ const targetExists = config.steps.some((s) => s.id === nextStepOverride);
1996
+ if (!targetExists) {
1997
+ throw new Error(`setNextStep: step "${nextStepOverride}" does not exist`);
1998
+ }
1999
+ state = { ...nextState, status: "running", currentStepId: nextStepOverride };
2000
+ }
2001
+ nextStepOverride = void 0;
2002
+ } else {
2003
+ state = nextState;
1628
2004
  }
1629
- state = nextState;
1630
2005
  emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
1631
2006
  if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1632
2007
  recordSelection(config.meta.name, currentStep.id, value);
1633
2008
  }
1634
2009
  options?.onStepComplete?.(currentStep.id, value, state);
2010
+ stepsCompleted++;
1635
2011
  } catch (error) {
1636
2012
  if (!isMock && isUserCancel(error)) {
1637
- state = wizardReducer(state, { type: "CANCEL" }, config);
1638
- options?.onCancel?.(state);
1639
- const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1640
- saveProgress(config.meta.name, {
1641
- currentStepId: state.currentStepId,
1642
- answers: state.answers,
1643
- history: state.history
1644
- }, void 0, passwordStepIds);
1645
- emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1646
- if (!quiet) {
1647
- console.log(theme.warning("\n Wizard cancelled.\n"));
1648
- }
2013
+ await performCancel();
1649
2014
  return state.answers;
1650
2015
  }
1651
2016
  throw error;
@@ -1654,10 +2019,14 @@ async function runWizard(config, options) {
1654
2019
  if (config.meta.review && !isMock && state.status === "done") {
1655
2020
  const reviewLines = [];
1656
2021
  for (const step of config.steps) {
2022
+ if (step.review?.hide) continue;
2023
+ if (step.type === "note" || step.type === "message") continue;
1657
2024
  const answer = state.answers[step.id];
1658
2025
  if (answer === void 0) continue;
1659
- const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
1660
- reviewLines.push(`${step.id}: ${display}`);
2026
+ const label = step.review?.label ?? step.id;
2027
+ const raw = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
2028
+ const display = formatReviewValue(raw, step.review?.format);
2029
+ reviewLines.push(`${label}: ${display}`);
1661
2030
  }
1662
2031
  emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
1663
2032
  console.log(`
@@ -1677,12 +2046,16 @@ async function runWizard(config, options) {
1677
2046
  } else {
1678
2047
  const { select: selectPrompt } = await import("@inquirer/prompts");
1679
2048
  const stepsWithAnswers = config.steps.filter(
1680
- (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
2049
+ (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message" && !s.review?.hide
1681
2050
  );
2051
+ if (stepsWithAnswers.length === 0) {
2052
+ needsReview = false;
2053
+ break;
2054
+ }
1682
2055
  const stepToRevisit = await selectPrompt({
1683
2056
  message: "Which step would you like to change?",
1684
2057
  choices: stepsWithAnswers.map((s) => ({
1685
- name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
2058
+ name: `${s.review?.label ?? s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
1686
2059
  value: s.id
1687
2060
  }))
1688
2061
  });
@@ -1725,6 +2098,10 @@ async function runWizard(config, options) {
1725
2098
  }
1726
2099
  return state.answers;
1727
2100
  } finally {
2101
+ if (signalHandler) {
2102
+ process.removeListener("SIGINT", signalHandler);
2103
+ process.removeListener("SIGTERM", signalHandler);
2104
+ }
1728
2105
  if (userPlugins) {
1729
2106
  clearPlugins();
1730
2107
  }
@@ -1764,6 +2141,18 @@ function renderStep(renderer, step, state, theme) {
1764
2141
  return Promise.resolve(true);
1765
2142
  case "note":
1766
2143
  return Promise.resolve(true);
2144
+ case "browser": {
2145
+ const resolvedUrl = resolveTemplate(step.url, state.answers);
2146
+ console.log(`
2147
+ ${theme.info("\u2192")} ${step.message}`);
2148
+ console.log(` ${theme.muted(resolvedUrl)}`);
2149
+ openUrl(resolvedUrl);
2150
+ if (step.fallback) {
2151
+ console.log(` ${theme.muted(step.fallback)}`);
2152
+ }
2153
+ console.log();
2154
+ return Promise.resolve(true);
2155
+ }
1767
2156
  }
1768
2157
  }
1769
2158
  function resolveStepDefaults(step, cachedAnswers) {
@@ -1815,6 +2204,7 @@ function resolveStepDefaults(step, cachedAnswers) {
1815
2204
  case "password":
1816
2205
  case "message":
1817
2206
  case "note":
2207
+ case "browser":
1818
2208
  return step;
1819
2209
  }
1820
2210
  }
@@ -1824,7 +2214,7 @@ function getCachedDefault(stepId, cachedAnswers) {
1824
2214
  }
1825
2215
  function applyTemplateDefaults(step, templateAnswers) {
1826
2216
  if (!(step.id in templateAnswers)) return step;
1827
- if (step.type === "password" || step.type === "message" || step.type === "note") return step;
2217
+ if (step.type === "password" || step.type === "message" || step.type === "note" || step.type === "browser") return step;
1828
2218
  const value = templateAnswers[step.id];
1829
2219
  switch (step.type) {
1830
2220
  case "text":
@@ -1914,6 +2304,7 @@ function resolveStepTemplates(step, answers) {
1914
2304
  case "toggle":
1915
2305
  case "message":
1916
2306
  case "note":
2307
+ case "browser":
1917
2308
  return {
1918
2309
  ...step,
1919
2310
  description: step.description ? resolveTemplate(step.description, answers) : void 0
@@ -1971,18 +2362,50 @@ async function executeActions(actions, answers, theme, renderer) {
1971
2362
  }
1972
2363
  function printWizardHeader(config, theme, plain) {
1973
2364
  console.log();
1974
- console.log(renderBanner(config.meta.name, theme, { plain, icon: config.meta.icon }));
2365
+ console.log(renderBanner(config.meta.name, theme, {
2366
+ plain,
2367
+ icon: config.meta.icon,
2368
+ iconSize: config.meta.iconSize,
2369
+ font: config.meta.font,
2370
+ banner: config.meta.banner,
2371
+ subtitle: config.meta.subtitle
2372
+ }));
1975
2373
  if (config.meta.description) {
1976
2374
  console.log(` ${theme.muted(config.meta.description)}`);
1977
2375
  }
1978
2376
  console.log();
1979
2377
  }
2378
+ function openUrl(url) {
2379
+ try {
2380
+ if (process.platform === "darwin") {
2381
+ execFileSync("open", [url], { stdio: "ignore" });
2382
+ } else if (process.platform === "win32") {
2383
+ const safeUrl = url.replace(/'/g, "''");
2384
+ execFileSync("powershell", ["-NoProfile", "-Command", `Start-Process '${safeUrl}'`], { stdio: "ignore" });
2385
+ } else {
2386
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
2387
+ }
2388
+ } catch {
2389
+ }
2390
+ }
1980
2391
  function isUserCancel(error) {
1981
2392
  if (error instanceof Error) {
1982
2393
  return error.message.includes("User force closed") || error.name === "ExitPromptError";
1983
2394
  }
1984
2395
  return false;
1985
2396
  }
2397
+ function formatReviewValue(value, format) {
2398
+ switch (format) {
2399
+ case "uppercase":
2400
+ return value.toUpperCase();
2401
+ case "lowercase":
2402
+ return value.toLowerCase();
2403
+ case "capitalize":
2404
+ return value.charAt(0).toUpperCase() + value.slice(1);
2405
+ default:
2406
+ return value;
2407
+ }
2408
+ }
1986
2409
 
1987
2410
  // src/define.ts
1988
2411
  function defineWizard(config) {
@@ -2106,8 +2529,9 @@ var InkRenderer = class {
2106
2529
  return result ?? defaultValue ?? 0;
2107
2530
  }
2108
2531
  async renderSearch(step, _state, theme) {
2532
+ const message = step.placeholder ? `${step.message} ${theme.muted(`(${step.placeholder})`)}` : step.message;
2109
2533
  return search2({
2110
- message: step.message,
2534
+ message,
2111
2535
  source: (term) => {
2112
2536
  const query = (term ?? "").toLowerCase();
2113
2537
  return step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(query)).map((opt) => ({
@@ -2211,6 +2635,8 @@ var S_BAR_H = u("\u2500", "-");
2211
2635
  var ClackRenderer = class extends InquirerRenderer {
2212
2636
  spinnerInterval;
2213
2637
  spinnerFrameIndex = 0;
2638
+ spinnerStartTime = 0;
2639
+ checksMode = void 0;
2214
2640
  renderStepHeader() {
2215
2641
  }
2216
2642
  renderGroupHeader() {
@@ -2267,18 +2693,25 @@ var ClackRenderer = class extends InquirerRenderer {
2267
2693
  this.stopSpinner(event.message, theme);
2268
2694
  break;
2269
2695
  case "checks:start":
2270
- process.stdout.write(`${chalk2.gray(S_BAR)}
2696
+ this.checksMode = event.mode === "tasklist" ? "tasklist" : "spinner";
2697
+ if (this.checksMode !== "tasklist") {
2698
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2271
2699
  `);
2272
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
2700
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
2273
2701
  `);
2702
+ }
2274
2703
  break;
2275
2704
  case "check:pass":
2276
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2705
+ if (this.checksMode !== "tasklist") {
2706
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2277
2707
  `);
2708
+ }
2278
2709
  break;
2279
2710
  case "check:fail":
2280
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
2711
+ if (this.checksMode !== "tasklist") {
2712
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
2281
2713
  `);
2714
+ }
2282
2715
  break;
2283
2716
  case "actions:start":
2284
2717
  process.stdout.write(`${chalk2.gray(S_BAR)}
@@ -2345,11 +2778,17 @@ var ClackRenderer = class extends InquirerRenderer {
2345
2778
  `);
2346
2779
  }
2347
2780
  startSpinner(message, theme) {
2781
+ if (this.spinnerInterval) {
2782
+ clearInterval(this.spinnerInterval);
2783
+ this.spinnerInterval = void 0;
2784
+ }
2348
2785
  this.spinnerFrameIndex = 0;
2786
+ this.spinnerStartTime = Date.now();
2349
2787
  const { frames, interval } = theme.spinner;
2350
2788
  this.spinnerInterval = setInterval(() => {
2351
2789
  const frame = frames[this.spinnerFrameIndex % frames.length];
2352
- process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
2790
+ const elapsed = theme.spinnerElapsed ? ` ${chalk2.gray(`(${((Date.now() - this.spinnerStartTime) / 1e3).toFixed(1)}s)`)}` : "";
2791
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}${elapsed}`);
2353
2792
  this.spinnerFrameIndex++;
2354
2793
  }, interval);
2355
2794
  }
@@ -2359,7 +2798,7 @@ var ClackRenderer = class extends InquirerRenderer {
2359
2798
  this.spinnerInterval = void 0;
2360
2799
  }
2361
2800
  const finalMessage = message ?? "Done";
2362
- process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
2801
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}\x1B[K
2363
2802
  `);
2364
2803
  }
2365
2804
  };
@@ -2452,6 +2891,7 @@ export {
2452
2891
  DEFAULT_SPINNER,
2453
2892
  InkRenderer,
2454
2893
  InquirerRenderer,
2894
+ applyColumns,
2455
2895
  clearCache,
2456
2896
  clearMruData,
2457
2897
  clearPlugins,