grimoire-wizard 0.5.2 → 0.6.1

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/cli.js CHANGED
@@ -56,6 +56,11 @@ var separatorOptionSchema = z.object({
56
56
  separator: z.string()
57
57
  });
58
58
  var selectChoiceSchema = z.union([selectOptionSchema, separatorOptionSchema]);
59
+ var stepReviewConfigSchema = z.object({
60
+ hide: z.boolean().optional(),
61
+ label: z.string().optional(),
62
+ format: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional()
63
+ }).optional();
59
64
  var baseStepFields = {
60
65
  id: z.string(),
61
66
  message: z.string(),
@@ -64,7 +69,8 @@ var baseStepFields = {
64
69
  when: conditionSchema.optional(),
65
70
  keepValuesOnPrevious: z.boolean().optional(),
66
71
  required: z.boolean().optional(),
67
- group: z.string().optional()
72
+ group: z.string().optional(),
73
+ review: stepReviewConfigSchema
68
74
  };
69
75
  var textStepSchema = z.object({
70
76
  ...baseStepFields,
@@ -81,7 +87,8 @@ var selectStepSchema = z.object({
81
87
  default: z.string().optional(),
82
88
  routes: z.record(z.string(), z.string()).optional(),
83
89
  pageSize: z.number().int().positive().optional(),
84
- loop: z.boolean().optional()
90
+ loop: z.boolean().optional(),
91
+ columns: z.number().int().min(1).max(8).optional()
85
92
  });
86
93
  var multiSelectStepSchema = z.object({
87
94
  ...baseStepFields,
@@ -92,7 +99,8 @@ var multiSelectStepSchema = z.object({
92
99
  min: z.number().int().nonnegative().optional(),
93
100
  max: z.number().int().positive().optional(),
94
101
  pageSize: z.number().int().positive().optional(),
95
- loop: z.boolean().optional()
102
+ loop: z.boolean().optional(),
103
+ columns: z.number().int().min(1).max(8).optional()
96
104
  });
97
105
  var confirmStepSchema = z.object({
98
106
  ...baseStepFields,
@@ -120,7 +128,8 @@ var searchStepSchema = z.object({
120
128
  default: z.string().optional(),
121
129
  placeholder: z.string().optional(),
122
130
  pageSize: z.number().int().positive().optional(),
123
- loop: z.boolean().optional()
131
+ loop: z.boolean().optional(),
132
+ columns: z.number().int().min(1).max(8).optional()
124
133
  });
125
134
  var editorStepSchema = z.object({
126
135
  ...baseStepFields,
@@ -148,7 +157,14 @@ var messageStepSchema = z.object({
148
157
  });
149
158
  var noteStepSchema = z.object({
150
159
  ...baseStepFields,
151
- type: z.literal("note")
160
+ type: z.literal("note"),
161
+ style: z.enum(["info", "warning", "error", "success", "code", "banner"]).optional()
162
+ });
163
+ var browserStepSchema = z.object({
164
+ ...baseStepFields,
165
+ type: z.literal("browser"),
166
+ url: z.string(),
167
+ fallback: z.string().optional()
152
168
  });
153
169
  var stepConfigSchema = z.discriminatedUnion("type", [
154
170
  textStepSchema,
@@ -162,12 +178,19 @@ var stepConfigSchema = z.discriminatedUnion("type", [
162
178
  pathStepSchema,
163
179
  toggleStepSchema,
164
180
  messageStepSchema,
165
- noteStepSchema
181
+ noteStepSchema,
182
+ browserStepSchema
166
183
  ]);
167
184
  var hexColorSchema = z.string().regex(
168
185
  /^#[0-9a-fA-F]{6}$/,
169
186
  "Must be a 6-digit hex color (e.g., #FF0000)"
170
187
  );
188
+ var progressBarConfigSchema = z.object({
189
+ width: z.number().int().positive().max(200).optional(),
190
+ filledColor: hexColorSchema.optional(),
191
+ emptyColor: hexColorSchema.optional(),
192
+ style: z.enum(["blocks", "line", "dots", "arrow"]).optional()
193
+ }).optional();
171
194
  var themeConfigSchema = z.object({
172
195
  preset: z.enum(["default", "catppuccin", "dracula", "nord", "tokyonight", "monokai"]).optional(),
173
196
  tokens: z.object({
@@ -177,7 +200,12 @@ var themeConfigSchema = z.object({
177
200
  warning: hexColorSchema.optional(),
178
201
  info: hexColorSchema.optional(),
179
202
  muted: hexColorSchema.optional(),
180
- accent: hexColorSchema.optional()
203
+ accent: hexColorSchema.optional(),
204
+ highlight: hexColorSchema.optional(),
205
+ highlightBg: hexColorSchema.optional(),
206
+ pointer: hexColorSchema.optional(),
207
+ checked: hexColorSchema.optional(),
208
+ dimmed: hexColorSchema.optional()
181
209
  }).optional(),
182
210
  icons: z.object({
183
211
  step: z.string().optional(),
@@ -191,12 +219,15 @@ var themeConfigSchema = z.object({
191
219
  frames: z.array(z.string()).min(1),
192
220
  interval: z.number().positive().optional()
193
221
  })
194
- ]).optional()
222
+ ]).optional(),
223
+ spinnerElapsed: z.boolean().optional(),
224
+ progressBar: progressBarConfigSchema
195
225
  });
196
226
  var preFlightCheckSchema = z.object({
197
227
  name: z.string(),
198
228
  run: z.string(),
199
- message: z.string()
229
+ message: z.string(),
230
+ showOutput: z.boolean().optional()
200
231
  });
201
232
  var actionConfigSchema = z.object({
202
233
  name: z.string().optional(),
@@ -209,7 +240,13 @@ var wizardConfigSchema = z.object({
209
240
  version: z.string().optional(),
210
241
  description: z.string().optional(),
211
242
  review: z.boolean().optional(),
212
- icon: z.string().optional()
243
+ icon: z.string().optional(),
244
+ iconSize: z.union([z.enum(["small", "medium", "large"]), z.number().int().positive()]).optional(),
245
+ font: z.string().min(1).optional(),
246
+ banner: z.union([z.string(), z.function()]).optional(),
247
+ subtitle: z.string().optional(),
248
+ clearBetweenSteps: z.boolean().optional(),
249
+ checksStyle: z.enum(["spinner", "tasklist"]).optional()
213
250
  }),
214
251
  theme: themeConfigSchema.optional(),
215
252
  steps: z.array(stepConfigSchema).min(1),
@@ -490,11 +527,12 @@ async function loadWithInheritance(filePath, seen) {
490
527
  async function loadWizardConfig(filePath) {
491
528
  const config = await loadWithInheritance(filePath, /* @__PURE__ */ new Set());
492
529
  detectCycles(config);
530
+ config._configFilePath = resolve(filePath);
493
531
  return config;
494
532
  }
495
533
 
496
534
  // src/runner.ts
497
- import { execSync } from "child_process";
535
+ import { execSync, execFileSync } from "child_process";
498
536
  import { resolve as resolve2, dirname as dirname2 } from "path";
499
537
  import { pathToFileURL } from "url";
500
538
 
@@ -671,7 +709,8 @@ function wizardReducer(state, transition, config) {
671
709
  errors: { ...state.errors, [state.currentStepId]: validationError }
672
710
  };
673
711
  }
674
- const updatedAnswers = {
712
+ const isDisplayOnly = currentStep.type === "note" || currentStep.type === "message" || currentStep.type === "browser";
713
+ const updatedAnswers = isDisplayOnly ? { ...state.answers } : {
675
714
  ...state.answers,
676
715
  [state.currentStepId]: transition.value
677
716
  };
@@ -879,7 +918,12 @@ var DEFAULT_TOKENS = {
879
918
  warning: "#FFD93D",
880
919
  info: "#4D96FF",
881
920
  muted: "#888888",
882
- accent: "#C084FC"
921
+ accent: "#C084FC",
922
+ highlight: "#5B9BD5",
923
+ highlightBg: "#1E1E2E",
924
+ pointer: "#C084FC",
925
+ checked: "#6BCB77",
926
+ dimmed: "#555555"
883
927
  };
884
928
  var DEFAULT_ICONS = {
885
929
  step: "\u25CF",
@@ -887,6 +931,25 @@ var DEFAULT_ICONS = {
887
931
  stepPending: "\u25CB",
888
932
  pointer: "\u203A"
889
933
  };
934
+ var PROGRESS_BAR_CHARS = {
935
+ blocks: { filled: "\u2588", empty: "\u2591" },
936
+ line: { filled: "\u2500", empty: "\u2500" },
937
+ dots: { filled: "\u2022", empty: "\xB7" },
938
+ arrow: { filled: "\u2550", empty: "\u2500" }
939
+ };
940
+ function resolveProgressBar(config, tokens) {
941
+ const style = config?.style ?? "blocks";
942
+ const chars = PROGRESS_BAR_CHARS[style];
943
+ const filledHex = config?.filledColor ?? tokens?.success ?? DEFAULT_TOKENS.success;
944
+ const emptyHex = config?.emptyColor ?? tokens?.muted ?? DEFAULT_TOKENS.muted;
945
+ return {
946
+ width: config?.width ?? 20,
947
+ filledColor: chalk.hex(filledHex),
948
+ emptyColor: chalk.hex(emptyHex),
949
+ style,
950
+ chars
951
+ };
952
+ }
890
953
  function resolveTheme(themeConfig) {
891
954
  const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
892
955
  const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
@@ -899,9 +962,16 @@ function resolveTheme(themeConfig) {
899
962
  info: chalk.hex(tokens.info),
900
963
  muted: chalk.hex(tokens.muted),
901
964
  accent: chalk.hex(tokens.accent),
965
+ highlight: chalk.hex(tokens.highlight),
966
+ highlightBg: chalk.bgHex(tokens.highlightBg),
967
+ pointer: chalk.hex(tokens.pointer),
968
+ checked: chalk.hex(tokens.checked),
969
+ dimmed: chalk.hex(tokens.dimmed),
902
970
  bold: chalk.bold,
903
971
  icons,
904
- spinner: resolveSpinner(themeConfig?.spinner)
972
+ spinner: resolveSpinner(themeConfig?.spinner),
973
+ spinnerElapsed: themeConfig?.spinnerElapsed ?? false,
974
+ progressBar: resolveProgressBar(themeConfig?.progressBar, tokens)
905
975
  };
906
976
  }
907
977
 
@@ -919,11 +989,11 @@ import {
919
989
  } from "@inquirer/prompts";
920
990
  var InquirerRenderer = class {
921
991
  renderStepHeader(stepIndex, totalVisible, message, theme, description) {
922
- const barWidth = 20;
923
- const filledCount = totalVisible > 0 ? Math.round(stepIndex / totalVisible * barWidth) : 0;
924
- const remainingCount = barWidth - filledCount;
925
- const filledBar = theme.success("\u2588".repeat(filledCount));
926
- const remainingBar = theme.muted("\u2591".repeat(remainingCount));
992
+ const pb = theme.progressBar;
993
+ const filledCount = totalVisible > 0 ? Math.round(stepIndex / totalVisible * pb.width) : 0;
994
+ const remainingCount = pb.width - filledCount;
995
+ const filledBar = pb.filledColor(pb.chars.filled.repeat(filledCount));
996
+ const remainingBar = pb.emptyColor(pb.chars.empty.repeat(remainingCount));
927
997
  const counter = theme.muted(`Step ${String(stepIndex + 1)}/${String(totalVisible)}`);
928
998
  const stepMessage = theme.muted(`\u2014 ${message}`);
929
999
  console.log(`
@@ -932,6 +1002,39 @@ var InquirerRenderer = class {
932
1002
  console.log(` ${theme.muted(description)}`);
933
1003
  }
934
1004
  }
1005
+ renderNote(step, theme) {
1006
+ const title = step.message;
1007
+ const body = step.description ?? "";
1008
+ const style = step.style ?? "info";
1009
+ const { colorFn, icon } = getNoteStyleConfig(style, theme);
1010
+ if (style === "banner") {
1011
+ const width = Math.max(4, (process.stdout.columns ?? 60) - 4);
1012
+ const rule = colorFn("\u2500".repeat(width));
1013
+ console.log();
1014
+ console.log(` ${rule}`);
1015
+ console.log(` ${colorFn(icon)} ${theme.bold(title)}`);
1016
+ if (body) console.log(` ${theme.muted(body)}`);
1017
+ console.log(` ${rule}`);
1018
+ console.log();
1019
+ return;
1020
+ }
1021
+ const border = colorFn("\u2502");
1022
+ const topRule = colorFn(`\u250C${"\u2500".repeat(2)}`);
1023
+ const bottomRule = colorFn(`\u2514${"\u2500".repeat(2)}`);
1024
+ console.log();
1025
+ console.log(` ${topRule} ${colorFn(icon)} ${theme.bold(title)}`);
1026
+ if (body) {
1027
+ for (const line of body.split("\n")) {
1028
+ if (style === "code") {
1029
+ console.log(` ${border} ${theme.muted(line)}`);
1030
+ } else {
1031
+ console.log(` ${border} ${line}`);
1032
+ }
1033
+ }
1034
+ }
1035
+ console.log(` ${bottomRule}`);
1036
+ console.log();
1037
+ }
935
1038
  async renderText(step, state, theme) {
936
1039
  const existingAnswer = state.answers[step.id];
937
1040
  const defaultValue = typeof existingAnswer === "string" ? existingAnswer : step.default;
@@ -944,7 +1047,7 @@ var InquirerRenderer = class {
944
1047
  async renderSelect(step, state, theme) {
945
1048
  const existingAnswer = state.answers[step.id];
946
1049
  const defaultValue = typeof existingAnswer === "string" ? existingAnswer : step.default;
947
- const choices = step.options.map((opt) => {
1050
+ const rawChoices = step.options.map((opt) => {
948
1051
  if ("separator" in opt) {
949
1052
  return new Separator(opt.separator);
950
1053
  }
@@ -955,19 +1058,27 @@ var InquirerRenderer = class {
955
1058
  disabled: opt.disabled
956
1059
  };
957
1060
  });
1061
+ const itemChoices = rawChoices.filter((c) => !(c instanceof Separator));
1062
+ const columnedItems = step.columns ? applyColumns(itemChoices, step.columns) : itemChoices;
1063
+ let itemIdx = 0;
1064
+ const choices = rawChoices.map((c) => c instanceof Separator ? c : columnedItems[itemIdx++] ?? c);
958
1065
  return select({
959
1066
  message: step.message,
960
1067
  choices,
961
1068
  default: defaultValue,
962
1069
  pageSize: step.pageSize,
963
1070
  loop: step.loop,
964
- theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
1071
+ theme: {
1072
+ prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone },
1073
+ icon: { cursor: theme.icons.pointer },
1074
+ style: { disabled: theme.dimmed }
1075
+ }
965
1076
  });
966
1077
  }
967
1078
  async renderMultiSelect(step, state, theme) {
968
1079
  const existingAnswer = state.answers[step.id];
969
1080
  const previousSelections = Array.isArray(existingAnswer) ? existingAnswer.filter((v) => typeof v === "string") : step.default;
970
- const choices = step.options.map((opt) => {
1081
+ const rawChoices = step.options.map((opt) => {
971
1082
  if ("separator" in opt) {
972
1083
  return new Separator(opt.separator);
973
1084
  }
@@ -978,12 +1089,20 @@ var InquirerRenderer = class {
978
1089
  disabled: opt.disabled
979
1090
  };
980
1091
  });
1092
+ const itemChoices = rawChoices.filter((c) => !(c instanceof Separator));
1093
+ const columnedItems = step.columns ? applyColumns(itemChoices, step.columns) : itemChoices;
1094
+ let itemIdx = 0;
1095
+ const choices = rawChoices.map((c) => c instanceof Separator ? c : columnedItems[itemIdx++] ?? c);
981
1096
  return checkbox({
982
1097
  message: step.message,
983
1098
  choices,
984
1099
  pageSize: step.pageSize,
985
1100
  loop: step.loop,
986
- theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
1101
+ theme: {
1102
+ prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone },
1103
+ icon: { cursor: theme.icons.pointer, checked: theme.checked("\u2714"), unchecked: theme.muted("\u25CB") },
1104
+ style: { disabledChoice: theme.dimmed }
1105
+ }
987
1106
  });
988
1107
  }
989
1108
  async renderConfirm(step, state, theme) {
@@ -1015,15 +1134,17 @@ var InquirerRenderer = class {
1015
1134
  return result ?? defaultValue ?? 0;
1016
1135
  }
1017
1136
  async renderSearch(step, _state, theme) {
1137
+ const message = step.placeholder ? `${step.message} ${theme.muted(`(${step.placeholder})`)}` : step.message;
1018
1138
  return search({
1019
- message: step.message,
1139
+ message,
1020
1140
  source: (input4) => {
1021
1141
  const term = (input4 ?? "").toLowerCase();
1022
- return step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(term)).map((opt) => ({
1142
+ const filtered = step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(term)).map((opt) => ({
1023
1143
  name: opt.label,
1024
1144
  value: opt.value,
1025
1145
  description: opt.hint
1026
1146
  }));
1147
+ return step.columns ? applyColumns(filtered, step.columns) : filtered;
1027
1148
  },
1028
1149
  pageSize: step.pageSize,
1029
1150
  theme: { prefix: { idle: theme.icons.pointer, done: theme.icons.stepDone } }
@@ -1091,6 +1212,42 @@ ${theme.muted("\u2500".repeat(40))}`);
1091
1212
  process.stdout.write("\x1B[2J\x1B[0f");
1092
1213
  }
1093
1214
  };
1215
+ var NOTE_STYLE_MAP = {
1216
+ info: { tokenKey: "info", icon: "\u2139" },
1217
+ warning: { tokenKey: "warning", icon: "\u26A0" },
1218
+ error: { tokenKey: "error", icon: "\u2716" },
1219
+ success: { tokenKey: "success", icon: "\u2714" },
1220
+ code: { tokenKey: "muted", icon: "\u276F" },
1221
+ banner: { tokenKey: "primary", icon: "\u2605" }
1222
+ };
1223
+ function getNoteStyleConfig(style, theme) {
1224
+ const entry = NOTE_STYLE_MAP[style];
1225
+ return {
1226
+ colorFn: theme[entry.tokenKey],
1227
+ icon: entry.icon
1228
+ };
1229
+ }
1230
+ function applyColumns(items, columns) {
1231
+ const cols = Math.max(1, Math.floor(columns));
1232
+ if (cols <= 1) return items;
1233
+ if (items.length === 0) return items;
1234
+ const maxLabel = Math.max(...items.map((i) => i.name.length));
1235
+ const colWidth = maxLabel + 4;
1236
+ const grouped = [];
1237
+ for (let i = 0; i < items.length; i += cols) {
1238
+ grouped.push(items.slice(i, i + cols));
1239
+ }
1240
+ const result = [];
1241
+ for (const row of grouped) {
1242
+ for (let c = 0; c < row.length; c++) {
1243
+ const item = row[c];
1244
+ const isLast = c === row.length - 1;
1245
+ const paddedName = isLast ? item.name : item.name.padEnd(colWidth);
1246
+ result.push({ ...item, name: paddedName });
1247
+ }
1248
+ }
1249
+ return result;
1250
+ }
1094
1251
 
1095
1252
  // src/resolve.ts
1096
1253
  function resolveEnvDefault(value) {
@@ -1145,23 +1302,71 @@ function renderBanner(name, theme, options) {
1145
1302
  const icon = options?.icon;
1146
1303
  const prefix = icon ? `${icon} ` : "";
1147
1304
  if (options?.plain) {
1148
- return ` ${prefix}${theme.bold(name)}`;
1305
+ const line = ` ${prefix}${theme.bold(name)}`;
1306
+ return options?.subtitle ? `${line}
1307
+ ${theme.muted(options.subtitle)}` : line;
1308
+ }
1309
+ if (options?.banner) {
1310
+ const bannerStr = (typeof options.banner === "function" ? options.banner(theme) : options.banner).replace(/\n$/, "");
1311
+ const raw = bannerStr.split("\n");
1312
+ const mid = Math.floor(raw.length / 2);
1313
+ const bannerLines = raw.map((l, i) => {
1314
+ if (icon && i === mid) return ` ${icon} ${l}`;
1315
+ return icon ? ` ${l}` : ` ${l}`;
1316
+ }).join("\n");
1317
+ const colored = typeof options.banner === "function" ? bannerLines : GRIMOIRE_GRADIENT(bannerLines);
1318
+ return options?.subtitle ? `${colored}
1319
+ ${theme.muted(options.subtitle)}` : colored;
1149
1320
  }
1150
1321
  try {
1151
- const art = figlet.textSync(name, {
1152
- font: "Small",
1153
- horizontalLayout: "default"
1154
- });
1322
+ const requestedFont = options?.font ?? "Small";
1323
+ let art;
1324
+ try {
1325
+ art = figlet.textSync(name, { font: requestedFont, horizontalLayout: "default" });
1326
+ } catch {
1327
+ art = figlet.textSync(name, { font: "Small", horizontalLayout: "default" });
1328
+ }
1155
1329
  const artLines = art.split("\n");
1156
1330
  const lines = artLines.map((line, i) => {
1157
- if (icon && i === Math.floor(artLines.length / 2)) {
1158
- return ` ${icon} ${line}`;
1331
+ const iconStr = getIconForLine(icon, options?.iconSize, i, artLines.length);
1332
+ if (iconStr !== void 0) {
1333
+ return ` ${iconStr}${line}`;
1159
1334
  }
1160
1335
  return icon ? ` ${line}` : ` ${line}`;
1161
1336
  }).join("\n");
1162
- return GRIMOIRE_GRADIENT(lines);
1337
+ const colored = GRIMOIRE_GRADIENT(lines);
1338
+ return options?.subtitle ? `${colored}
1339
+ ${theme.muted(options.subtitle)}` : colored;
1163
1340
  } catch {
1164
- return ` ${prefix}${theme.bold(name)}`;
1341
+ const line = ` ${prefix}${theme.bold(name)}`;
1342
+ return options?.subtitle ? `${line}
1343
+ ${theme.muted(options.subtitle)}` : line;
1344
+ }
1345
+ }
1346
+ function getIconForLine(icon, iconSize, lineIndex, totalLines) {
1347
+ if (!icon) return void 0;
1348
+ const mid = Math.floor(totalLines / 2);
1349
+ const span = resolveIconSpan(iconSize, totalLines);
1350
+ const halfSpan = Math.floor(span / 2);
1351
+ const start = Math.max(0, mid - halfSpan);
1352
+ const end = Math.min(totalLines - 1, start + span - 1);
1353
+ if (lineIndex >= start && lineIndex <= end) {
1354
+ return lineIndex === mid ? `${icon} ` : ` `;
1355
+ }
1356
+ return void 0;
1357
+ }
1358
+ function resolveIconSpan(iconSize, totalLines) {
1359
+ if (typeof iconSize === "number") {
1360
+ const clamped = Math.max(1, Math.floor(isFinite(iconSize) ? iconSize : 1));
1361
+ return Math.min(clamped, totalLines);
1362
+ }
1363
+ switch (iconSize ?? "small") {
1364
+ case "small":
1365
+ return 1;
1366
+ case "medium":
1367
+ return 3;
1368
+ case "large":
1369
+ return totalLines;
1165
1370
  }
1166
1371
  }
1167
1372
 
@@ -1252,7 +1457,7 @@ function clearCache(wizardName, customDir) {
1252
1457
  }
1253
1458
 
1254
1459
  // src/progress.ts
1255
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
1460
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
1256
1461
  import { join as join2 } from "path";
1257
1462
  import { homedir as homedir2 } from "os";
1258
1463
  var DEFAULT_PROGRESS_DIR = join2(homedir2(), ".config", "grimoire", "progress");
@@ -1302,6 +1507,20 @@ function clearProgress(wizardName, customDir) {
1302
1507
  } catch {
1303
1508
  }
1304
1509
  }
1510
+ function clearAllProgress(customDir) {
1511
+ try {
1512
+ const dir = customDir ?? DEFAULT_PROGRESS_DIR;
1513
+ for (const file of readdirSync2(dir)) {
1514
+ if (file.endsWith(".json")) {
1515
+ try {
1516
+ unlinkSync2(join2(dir, file));
1517
+ } catch {
1518
+ }
1519
+ }
1520
+ }
1521
+ } catch {
1522
+ }
1523
+ }
1305
1524
 
1306
1525
  // src/mru.ts
1307
1526
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
@@ -1391,7 +1610,14 @@ function emitEvent(renderer, event, theme) {
1391
1610
  renderer.onEvent(event, theme);
1392
1611
  }
1393
1612
  }
1394
- function runPreFlightChecks(checks, theme, renderer) {
1613
+ function runPreFlightChecks(checks, theme, renderer, checksStyle) {
1614
+ if (checksStyle === "tasklist") {
1615
+ runPreFlightChecksTasklist(checks, theme, renderer);
1616
+ } else {
1617
+ runPreFlightChecksSpinner(checks, theme, renderer);
1618
+ }
1619
+ }
1620
+ function runPreFlightChecksSpinner(checks, theme, renderer) {
1395
1621
  if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
1396
1622
  for (const check of checks) {
1397
1623
  if (renderer) emitEvent(renderer, { type: "spinner:start", message: check.name }, theme);
@@ -1409,20 +1635,69 @@ function runPreFlightChecks(checks, theme, renderer) {
1409
1635
  }
1410
1636
  console.log();
1411
1637
  }
1638
+ function runPreFlightChecksTasklist(checks, theme, renderer) {
1639
+ if (!process.stdout.isTTY) {
1640
+ runPreFlightChecksSpinner(checks, theme, renderer);
1641
+ return;
1642
+ }
1643
+ if (renderer) emitEvent(renderer, { type: "checks:start", checks, mode: "tasklist" }, theme);
1644
+ const statuses = checks.map(() => "pending");
1645
+ const icons = theme.icons;
1646
+ function renderTasklist() {
1647
+ process.stdout.write(`\x1B[${checks.length}A\x1B[0G`);
1648
+ for (let i = 0; i < checks.length; i++) {
1649
+ const check = checks[i];
1650
+ const s = statuses[i];
1651
+ let icon;
1652
+ if (s === "pass") icon = theme.success(icons.stepDone);
1653
+ else if (s === "fail") icon = theme.error("\u2717");
1654
+ else if (s === "running") icon = theme.info("\u280B");
1655
+ else icon = theme.muted(icons.stepPending);
1656
+ process.stdout.write(` ${icon} ${check.name}\x1B[K
1657
+ `);
1658
+ }
1659
+ }
1660
+ for (let i = 0; i < checks.length; i++) {
1661
+ process.stdout.write(` ${theme.muted(icons.stepPending)} ${checks[i].name}
1662
+ `);
1663
+ }
1664
+ let failedCheck;
1665
+ for (let i = 0; i < checks.length; i++) {
1666
+ const check = checks[i];
1667
+ statuses[i] = "running";
1668
+ renderTasklist();
1669
+ try {
1670
+ execSync(check.run, { stdio: "pipe" });
1671
+ statuses[i] = "pass";
1672
+ renderTasklist();
1673
+ if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
1674
+ } catch {
1675
+ statuses[i] = "fail";
1676
+ renderTasklist();
1677
+ failedCheck = check;
1678
+ if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
1679
+ break;
1680
+ }
1681
+ }
1682
+ renderTasklist();
1683
+ console.log();
1684
+ if (failedCheck) {
1685
+ throw new Error(`Pre-flight check failed: ${failedCheck.name} \u2014 ${failedCheck.message}`);
1686
+ }
1687
+ }
1688
+ var MOCK_MISS = /* @__PURE__ */ Symbol("mock-miss");
1412
1689
  function getMockValue(step, mockAnswers) {
1413
1690
  if (step.id in mockAnswers) {
1414
1691
  return mockAnswers[step.id];
1415
1692
  }
1416
- if (step.type === "message" || step.type === "note") {
1693
+ if (step.type === "message" || step.type === "note" || step.type === "browser") {
1417
1694
  return true;
1418
1695
  }
1419
1696
  const defaultValue = getStepDefault(step);
1420
1697
  if (defaultValue !== void 0) {
1421
1698
  return defaultValue;
1422
1699
  }
1423
- throw new Error(
1424
- `Mock mode: no answer provided for step "${step.id}" and no default available`
1425
- );
1700
+ return MOCK_MISS;
1426
1701
  }
1427
1702
  function getStepDefault(step) {
1428
1703
  switch (step.type) {
@@ -1442,6 +1717,7 @@ function getStepDefault(step) {
1442
1717
  case "password":
1443
1718
  case "message":
1444
1719
  case "note":
1720
+ case "browser":
1445
1721
  return void 0;
1446
1722
  }
1447
1723
  }
@@ -1455,7 +1731,7 @@ async function runWizard(config, options) {
1455
1731
  const cacheDir = typeof options?.cache === "object" ? options.cache.dir : void 0;
1456
1732
  const mruEnabled = !isMock && options?.mru !== false;
1457
1733
  let state = createWizardState(config);
1458
- const resumeEnabled = !isMock && options?.resume !== false;
1734
+ const resumeEnabled = !isMock && options?.resume !== false && options?.cache !== false;
1459
1735
  if (resumeEnabled) {
1460
1736
  const saved = loadProgress(config.meta.name);
1461
1737
  if (saved) {
@@ -1477,9 +1753,36 @@ async function runWizard(config, options) {
1477
1753
  registerPlugin(plugin);
1478
1754
  }
1479
1755
  }
1756
+ let cancelFired = false;
1757
+ const performCancel = async () => {
1758
+ if (cancelFired) return;
1759
+ cancelFired = true;
1760
+ state = wizardReducer(state, { type: "CANCEL" }, config);
1761
+ if (resumeEnabled) {
1762
+ const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1763
+ saveProgress(config.meta.name, {
1764
+ currentStepId: state.currentStepId,
1765
+ answers: state.answers,
1766
+ history: state.history
1767
+ }, void 0, passwordStepIds);
1768
+ }
1769
+ try {
1770
+ await options?.onCancel?.(state);
1771
+ } catch {
1772
+ }
1773
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1774
+ if (!quiet) console.log(theme.warning("\n Wizard cancelled.\n"));
1775
+ };
1776
+ const signalHandler = !isMock ? () => {
1777
+ performCancel().finally(() => process.exit(130));
1778
+ } : void 0;
1779
+ if (signalHandler) {
1780
+ process.once("SIGINT", signalHandler);
1781
+ process.once("SIGTERM", signalHandler);
1782
+ }
1480
1783
  try {
1481
1784
  if (!isMock && config.checks && config.checks.length > 0) {
1482
- runPreFlightChecks(config.checks, theme, renderer);
1785
+ runPreFlightChecks(config.checks, theme, renderer, config.meta.checksStyle);
1483
1786
  }
1484
1787
  if (!quiet) {
1485
1788
  printWizardHeader(config, theme, options?.plain);
@@ -1489,7 +1792,62 @@ async function runWizard(config, options) {
1489
1792
  let needsReview = true;
1490
1793
  while (needsReview) {
1491
1794
  let previousGroup;
1795
+ let stepsCompleted = 0;
1492
1796
  while (state.status === "running") {
1797
+ if (!isMock && config.meta.clearBetweenSteps && stepsCompleted > 0) {
1798
+ renderer.clear();
1799
+ if (!quiet) {
1800
+ printWizardHeader(config, theme, options?.plain);
1801
+ }
1802
+ previousGroup = void 0;
1803
+ }
1804
+ let nextStepOverride;
1805
+ const createHookContext = (stateOverride) => ({
1806
+ answers: { ...(stateOverride ?? state).answers },
1807
+ state: stateOverride ?? state,
1808
+ showNote: (title, body) => {
1809
+ emitEvent(renderer, { type: "note", title, body }, theme);
1810
+ if (!("onEvent" in renderer) && process.stdout.isTTY) {
1811
+ console.log(`
1812
+ ${theme.bold(title)}`);
1813
+ if (body) console.log(` ${theme.muted(body)}`);
1814
+ console.log();
1815
+ }
1816
+ },
1817
+ setNextStep: (stepId) => {
1818
+ nextStepOverride = stepId;
1819
+ },
1820
+ openBrowser: async (url) => {
1821
+ if (isMock) return;
1822
+ try {
1823
+ new URL(url);
1824
+ } catch {
1825
+ return;
1826
+ }
1827
+ if (process.platform === "darwin") {
1828
+ execFileSync("open", [url], { stdio: "ignore" });
1829
+ } else if (process.platform === "win32") {
1830
+ execFileSync("powershell", ["-NoProfile", "-Command", `Start-Process '${url.replace(/'/g, "''")}'`], { stdio: "ignore" });
1831
+ } else {
1832
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
1833
+ }
1834
+ },
1835
+ prompt: async (promptConfig) => {
1836
+ if (isMock) {
1837
+ if (promptConfig.default !== void 0) return promptConfig.default;
1838
+ throw new Error("Mock mode: context.prompt() requires a default value");
1839
+ }
1840
+ const contextState = stateOverride ?? state;
1841
+ const tempStep = {
1842
+ id: "__hook_prompt__",
1843
+ message: promptConfig.message,
1844
+ type: promptConfig.type,
1845
+ ...promptConfig.type === "select" ? { options: promptConfig.options } : {},
1846
+ default: promptConfig.default
1847
+ };
1848
+ return renderStep(renderer, tempStep, contextState, theme);
1849
+ }
1850
+ });
1493
1851
  const visibleSteps = getVisibleSteps(config, state.answers);
1494
1852
  const currentStep = config.steps.find((s) => s.id === state.currentStepId);
1495
1853
  if (!currentStep) {
@@ -1512,9 +1870,13 @@ async function runWizard(config, options) {
1512
1870
  emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
1513
1871
  if (currentStep.type === "note") {
1514
1872
  emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
1873
+ if (!isMock && "style" in currentStep && currentStep.style && "renderNote" in renderer && !("onEvent" in renderer)) {
1874
+ const resolvedStep2 = { ...currentStep, message: resolvedMessage, description: resolvedDescription };
1875
+ renderer.renderNote(resolvedStep2, theme);
1876
+ }
1515
1877
  }
1516
1878
  if (options?.onBeforeStep) {
1517
- await options.onBeforeStep(currentStep.id, currentStep, state);
1879
+ await options.onBeforeStep(currentStep.id, currentStep, createHookContext());
1518
1880
  }
1519
1881
  const pluginStep = getPluginStep(currentStep.type);
1520
1882
  const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
@@ -1531,7 +1893,19 @@ async function runWizard(config, options) {
1531
1893
  }
1532
1894
  }
1533
1895
  try {
1534
- const value = isMock ? getMockValue(finalStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(finalStep), state, theme) : await renderStep(renderer, finalStep, state, theme);
1896
+ let value;
1897
+ if (isMock) {
1898
+ const mockResult = getMockValue(finalStep, mockAnswers);
1899
+ if (mockResult === MOCK_MISS) {
1900
+ throw new Error(
1901
+ `Mock mode: no answer provided for step "${finalStep.id}" and no default available`
1902
+ );
1903
+ } else {
1904
+ value = mockResult;
1905
+ }
1906
+ } else {
1907
+ value = pluginStep ? await pluginStep.render(toStepRecord(finalStep), state, theme) : await renderStep(renderer, finalStep, state, theme);
1908
+ }
1535
1909
  if (pluginStep?.validate) {
1536
1910
  const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
1537
1911
  if (pluginError) {
@@ -1572,28 +1946,31 @@ async function runWizard(config, options) {
1572
1946
  }
1573
1947
  }
1574
1948
  if (options?.onAfterStep) {
1575
- await options.onAfterStep(currentStep.id, value, nextState);
1949
+ await options.onAfterStep(currentStep.id, value, createHookContext(nextState));
1950
+ }
1951
+ if (nextStepOverride) {
1952
+ if (nextStepOverride === "__done__") {
1953
+ state = { ...nextState, status: "done" };
1954
+ } else {
1955
+ const targetExists = config.steps.some((s) => s.id === nextStepOverride);
1956
+ if (!targetExists) {
1957
+ throw new Error(`setNextStep: step "${nextStepOverride}" does not exist`);
1958
+ }
1959
+ state = { ...nextState, status: "running", currentStepId: nextStepOverride };
1960
+ }
1961
+ nextStepOverride = void 0;
1962
+ } else {
1963
+ state = nextState;
1576
1964
  }
1577
- state = nextState;
1578
1965
  emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
1579
1966
  if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1580
1967
  recordSelection(config.meta.name, currentStep.id, value);
1581
1968
  }
1582
1969
  options?.onStepComplete?.(currentStep.id, value, state);
1970
+ stepsCompleted++;
1583
1971
  } catch (error) {
1584
1972
  if (!isMock && isUserCancel(error)) {
1585
- state = wizardReducer(state, { type: "CANCEL" }, config);
1586
- options?.onCancel?.(state);
1587
- const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1588
- saveProgress(config.meta.name, {
1589
- currentStepId: state.currentStepId,
1590
- answers: state.answers,
1591
- history: state.history
1592
- }, void 0, passwordStepIds);
1593
- emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1594
- if (!quiet) {
1595
- console.log(theme.warning("\n Wizard cancelled.\n"));
1596
- }
1973
+ await performCancel();
1597
1974
  return state.answers;
1598
1975
  }
1599
1976
  throw error;
@@ -1602,10 +1979,14 @@ async function runWizard(config, options) {
1602
1979
  if (config.meta.review && !isMock && state.status === "done") {
1603
1980
  const reviewLines = [];
1604
1981
  for (const step of config.steps) {
1982
+ if (step.review?.hide) continue;
1983
+ if (step.type === "note" || step.type === "message") continue;
1605
1984
  const answer = state.answers[step.id];
1606
1985
  if (answer === void 0) continue;
1607
- const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
1608
- reviewLines.push(`${step.id}: ${display}`);
1986
+ const label = step.review?.label ?? step.id;
1987
+ const raw = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
1988
+ const display = formatReviewValue(raw, step.review?.format);
1989
+ reviewLines.push(`${label}: ${display}`);
1609
1990
  }
1610
1991
  emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
1611
1992
  console.log(`
@@ -1625,12 +2006,16 @@ async function runWizard(config, options) {
1625
2006
  } else {
1626
2007
  const { select: selectPrompt } = await import("@inquirer/prompts");
1627
2008
  const stepsWithAnswers = config.steps.filter(
1628
- (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
2009
+ (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message" && !s.review?.hide
1629
2010
  );
2011
+ if (stepsWithAnswers.length === 0) {
2012
+ needsReview = false;
2013
+ break;
2014
+ }
1630
2015
  const stepToRevisit = await selectPrompt({
1631
2016
  message: "Which step would you like to change?",
1632
2017
  choices: stepsWithAnswers.map((s) => ({
1633
- name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
2018
+ name: `${s.review?.label ?? s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
1634
2019
  value: s.id
1635
2020
  }))
1636
2021
  });
@@ -1673,6 +2058,10 @@ async function runWizard(config, options) {
1673
2058
  }
1674
2059
  return state.answers;
1675
2060
  } finally {
2061
+ if (signalHandler) {
2062
+ process.removeListener("SIGINT", signalHandler);
2063
+ process.removeListener("SIGTERM", signalHandler);
2064
+ }
1676
2065
  if (userPlugins) {
1677
2066
  clearPlugins();
1678
2067
  }
@@ -1712,6 +2101,18 @@ function renderStep(renderer, step, state, theme) {
1712
2101
  return Promise.resolve(true);
1713
2102
  case "note":
1714
2103
  return Promise.resolve(true);
2104
+ case "browser": {
2105
+ const resolvedUrl = resolveTemplate(step.url, state.answers);
2106
+ console.log(`
2107
+ ${theme.info("\u2192")} ${step.message}`);
2108
+ console.log(` ${theme.muted(resolvedUrl)}`);
2109
+ openUrl(resolvedUrl);
2110
+ if (step.fallback) {
2111
+ console.log(` ${theme.muted(step.fallback)}`);
2112
+ }
2113
+ console.log();
2114
+ return Promise.resolve(true);
2115
+ }
1715
2116
  }
1716
2117
  }
1717
2118
  function resolveStepDefaults(step, cachedAnswers) {
@@ -1763,6 +2164,7 @@ function resolveStepDefaults(step, cachedAnswers) {
1763
2164
  case "password":
1764
2165
  case "message":
1765
2166
  case "note":
2167
+ case "browser":
1766
2168
  return step;
1767
2169
  }
1768
2170
  }
@@ -1772,7 +2174,7 @@ function getCachedDefault(stepId, cachedAnswers) {
1772
2174
  }
1773
2175
  function applyTemplateDefaults(step, templateAnswers) {
1774
2176
  if (!(step.id in templateAnswers)) return step;
1775
- if (step.type === "password" || step.type === "message" || step.type === "note") return step;
2177
+ if (step.type === "password" || step.type === "message" || step.type === "note" || step.type === "browser") return step;
1776
2178
  const value = templateAnswers[step.id];
1777
2179
  switch (step.type) {
1778
2180
  case "text":
@@ -1862,6 +2264,7 @@ function resolveStepTemplates(step, answers) {
1862
2264
  case "toggle":
1863
2265
  case "message":
1864
2266
  case "note":
2267
+ case "browser":
1865
2268
  return {
1866
2269
  ...step,
1867
2270
  description: step.description ? resolveTemplate(step.description, answers) : void 0
@@ -1919,18 +2322,50 @@ async function executeActions(actions, answers, theme, renderer) {
1919
2322
  }
1920
2323
  function printWizardHeader(config, theme, plain) {
1921
2324
  console.log();
1922
- console.log(renderBanner(config.meta.name, theme, { plain, icon: config.meta.icon }));
2325
+ console.log(renderBanner(config.meta.name, theme, {
2326
+ plain,
2327
+ icon: config.meta.icon,
2328
+ iconSize: config.meta.iconSize,
2329
+ font: config.meta.font,
2330
+ banner: config.meta.banner,
2331
+ subtitle: config.meta.subtitle
2332
+ }));
1923
2333
  if (config.meta.description) {
1924
2334
  console.log(` ${theme.muted(config.meta.description)}`);
1925
2335
  }
1926
2336
  console.log();
1927
2337
  }
2338
+ function openUrl(url) {
2339
+ try {
2340
+ if (process.platform === "darwin") {
2341
+ execFileSync("open", [url], { stdio: "ignore" });
2342
+ } else if (process.platform === "win32") {
2343
+ const safeUrl = url.replace(/'/g, "''");
2344
+ execFileSync("powershell", ["-NoProfile", "-Command", `Start-Process '${safeUrl}'`], { stdio: "ignore" });
2345
+ } else {
2346
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
2347
+ }
2348
+ } catch {
2349
+ }
2350
+ }
1928
2351
  function isUserCancel(error) {
1929
2352
  if (error instanceof Error) {
1930
2353
  return error.message.includes("User force closed") || error.name === "ExitPromptError";
1931
2354
  }
1932
2355
  return false;
1933
2356
  }
2357
+ function formatReviewValue(value, format) {
2358
+ switch (format) {
2359
+ case "uppercase":
2360
+ return value.toUpperCase();
2361
+ case "lowercase":
2362
+ return value.toLowerCase();
2363
+ case "capitalize":
2364
+ return value.charAt(0).toUpperCase() + value.slice(1);
2365
+ default:
2366
+ return value;
2367
+ }
2368
+ }
1934
2369
 
1935
2370
  // src/scaffolder.ts
1936
2371
  import { input as input2, select as select2, confirm as confirm2, number as number2 } from "@inquirer/prompts";
@@ -2243,7 +2678,7 @@ complete -c grimoire -n "__fish_seen_subcommand_from completion" -f -a "fish" -d
2243
2678
  }
2244
2679
 
2245
2680
  // src/templates.ts
2246
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
2681
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, readdirSync as readdirSync3, existsSync as existsSync2 } from "fs";
2247
2682
  import { join as join4 } from "path";
2248
2683
  import { homedir as homedir4 } from "os";
2249
2684
  var TEMPLATES_DIR = join4(homedir4(), ".config", "grimoire", "templates");
@@ -2270,7 +2705,7 @@ function listTemplates(wizardName) {
2270
2705
  try {
2271
2706
  const dir = getWizardTemplateDir(wizardName);
2272
2707
  if (!existsSync2(dir)) return [];
2273
- return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
2708
+ return readdirSync3(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
2274
2709
  } catch {
2275
2710
  return [];
2276
2711
  }
@@ -2400,8 +2835,9 @@ var InkRenderer = class {
2400
2835
  return result ?? defaultValue ?? 0;
2401
2836
  }
2402
2837
  async renderSearch(step, _state, theme) {
2838
+ const message = step.placeholder ? `${step.message} ${theme.muted(`(${step.placeholder})`)}` : step.message;
2403
2839
  return search2({
2404
- message: step.message,
2840
+ message,
2405
2841
  source: (term) => {
2406
2842
  const query = (term ?? "").toLowerCase();
2407
2843
  return step.options.filter((opt) => "value" in opt).filter((opt) => !opt.disabled && opt.label.toLowerCase().includes(query)).map((opt) => ({
@@ -2505,6 +2941,8 @@ var S_BAR_H = u("\u2500", "-");
2505
2941
  var ClackRenderer = class extends InquirerRenderer {
2506
2942
  spinnerInterval;
2507
2943
  spinnerFrameIndex = 0;
2944
+ spinnerStartTime = 0;
2945
+ checksMode = void 0;
2508
2946
  renderStepHeader() {
2509
2947
  }
2510
2948
  renderGroupHeader() {
@@ -2561,18 +2999,25 @@ var ClackRenderer = class extends InquirerRenderer {
2561
2999
  this.stopSpinner(event.message, theme);
2562
3000
  break;
2563
3001
  case "checks:start":
2564
- process.stdout.write(`${chalk2.gray(S_BAR)}
3002
+ this.checksMode = event.mode === "tasklist" ? "tasklist" : "spinner";
3003
+ if (this.checksMode !== "tasklist") {
3004
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2565
3005
  `);
2566
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
3006
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
2567
3007
  `);
3008
+ }
2568
3009
  break;
2569
3010
  case "check:pass":
2570
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
3011
+ if (this.checksMode !== "tasklist") {
3012
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2571
3013
  `);
3014
+ }
2572
3015
  break;
2573
3016
  case "check:fail":
2574
- process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
3017
+ if (this.checksMode !== "tasklist") {
3018
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
2575
3019
  `);
3020
+ }
2576
3021
  break;
2577
3022
  case "actions:start":
2578
3023
  process.stdout.write(`${chalk2.gray(S_BAR)}
@@ -2639,11 +3084,17 @@ var ClackRenderer = class extends InquirerRenderer {
2639
3084
  `);
2640
3085
  }
2641
3086
  startSpinner(message, theme) {
3087
+ if (this.spinnerInterval) {
3088
+ clearInterval(this.spinnerInterval);
3089
+ this.spinnerInterval = void 0;
3090
+ }
2642
3091
  this.spinnerFrameIndex = 0;
3092
+ this.spinnerStartTime = Date.now();
2643
3093
  const { frames, interval } = theme.spinner;
2644
3094
  this.spinnerInterval = setInterval(() => {
2645
3095
  const frame = frames[this.spinnerFrameIndex % frames.length];
2646
- process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
3096
+ const elapsed = theme.spinnerElapsed ? ` ${chalk2.gray(`(${((Date.now() - this.spinnerStartTime) / 1e3).toFixed(1)}s)`)}` : "";
3097
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}${elapsed}`);
2647
3098
  this.spinnerFrameIndex++;
2648
3099
  }, interval);
2649
3100
  }
@@ -2653,7 +3104,7 @@ var ClackRenderer = class extends InquirerRenderer {
2653
3104
  this.spinnerInterval = void 0;
2654
3105
  }
2655
3106
  const finalMessage = message ?? "Done";
2656
- process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
3107
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}\x1B[K
2657
3108
  `);
2658
3109
  }
2659
3110
  };
@@ -2821,11 +3272,13 @@ var cacheCommand = program.command("cache").description("Manage cached wizard an
2821
3272
  cacheCommand.command("clear").description("Delete cached wizard answers").argument("[name]", "Wizard name to clear (clears all if omitted)").action((name) => {
2822
3273
  clearCache(name);
2823
3274
  if (name) {
3275
+ clearProgress(name);
2824
3276
  console.log(`
2825
- Cache cleared for "${name}".
3277
+ Cache and progress cleared for "${name}".
2826
3278
  `);
2827
3279
  } else {
2828
- console.log("\n All cached answers cleared.\n");
3280
+ clearAllProgress();
3281
+ console.log("\n All cached answers and progress cleared.\n");
2829
3282
  }
2830
3283
  });
2831
3284
  var templateCommand = program.command("template").description("Manage saved wizard answer templates");