grimoire-wizard 0.3.1 → 0.5.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.
Files changed (42) hide show
  1. package/README.md +185 -18
  2. package/dist/cli.js +602 -109
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +207 -3
  5. package/dist/index.js +921 -371
  6. package/dist/index.js.map +1 -1
  7. package/examples/handlers/setup-project.ts +9 -0
  8. package/examples/json/all-features.json +66 -0
  9. package/examples/json/appstore-screenshot-wizard.json +362 -0
  10. package/examples/json/appstore-upload.json +104 -0
  11. package/examples/json/basic.json +72 -0
  12. package/examples/json/batch-generate.json +186 -0
  13. package/examples/json/brief-builder.json +519 -0
  14. package/examples/json/conditional.json +155 -0
  15. package/examples/json/cost-analyzer.json +83 -0
  16. package/examples/json/demo.json +130 -0
  17. package/examples/json/scraper-selector.json +63 -0
  18. package/examples/json/themed-catppuccin.json +39 -0
  19. package/examples/json/themed.json +103 -0
  20. package/examples/json/with-actions.json +61 -0
  21. package/examples/json/with-checks.json +47 -0
  22. package/examples/json/with-oncomplete.json +45 -0
  23. package/examples/yaml/appstore-screenshot-wizard.yaml +321 -0
  24. package/examples/yaml/appstore-upload.yaml +84 -0
  25. package/examples/yaml/batch-generate.yaml +156 -0
  26. package/examples/yaml/brief-builder.yaml +429 -0
  27. package/examples/yaml/cost-analyzer.yaml +69 -0
  28. package/examples/yaml/pipeline.yaml +35 -0
  29. package/examples/yaml/scraper-selector.yaml +52 -0
  30. package/examples/yaml/themed-catppuccin.yaml +31 -0
  31. package/examples/yaml/with-actions.yaml +45 -0
  32. package/examples/yaml/with-oncomplete.yaml +35 -0
  33. package/package.json +1 -1
  34. /package/examples/{all-features.yaml → yaml/all-features.yaml} +0 -0
  35. /package/examples/{base.yaml → yaml/base.yaml} +0 -0
  36. /package/examples/{basic.yaml → yaml/basic.yaml} +0 -0
  37. /package/examples/{conditional.yaml → yaml/conditional.yaml} +0 -0
  38. /package/examples/{demo.yaml → yaml/demo.yaml} +0 -0
  39. /package/examples/{ebay-mcp-setup.yaml → yaml/ebay-mcp-setup.yaml} +0 -0
  40. /package/examples/{extended.yaml → yaml/extended.yaml} +0 -0
  41. /package/examples/{themed.yaml → yaml/themed.yaml} +0 -0
  42. /package/examples/{with-checks.yaml → yaml/with-checks.yaml} +0 -0
package/dist/index.js CHANGED
@@ -1,283 +1,15 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
1
11
  // src/schema.ts
2
12
  import { z } from "zod";
3
- var conditionSchema = z.lazy(() => {
4
- const fieldEquals = z.object({ field: z.string(), equals: z.unknown() }).strict();
5
- const fieldNotEquals = z.object({ field: z.string(), notEquals: z.unknown() }).strict();
6
- const fieldIncludes = z.object({ field: z.string(), includes: z.unknown() }).strict();
7
- const fieldNotIncludes = z.object({ field: z.string(), notIncludes: z.unknown() }).strict();
8
- const fieldGreaterThan = z.object({ field: z.string(), greaterThan: z.number() }).strict();
9
- const fieldLessThan = z.object({ field: z.string(), lessThan: z.number() }).strict();
10
- const fieldIsEmpty = z.object({ field: z.string(), isEmpty: z.literal(true) }).strict();
11
- const fieldIsNotEmpty = z.object({ field: z.string(), isNotEmpty: z.literal(true) }).strict();
12
- const allCondition = z.object({ all: z.array(conditionSchema) }).strict();
13
- const anyCondition = z.object({ any: z.array(conditionSchema) }).strict();
14
- const notCondition = z.object({ not: conditionSchema }).strict();
15
- return z.union([
16
- fieldEquals,
17
- fieldNotEquals,
18
- fieldIncludes,
19
- fieldNotIncludes,
20
- fieldGreaterThan,
21
- fieldLessThan,
22
- fieldIsEmpty,
23
- fieldIsNotEmpty,
24
- allCondition,
25
- anyCondition,
26
- notCondition
27
- ]);
28
- });
29
- var validationRuleSchema = z.discriminatedUnion("rule", [
30
- z.object({ rule: z.literal("required"), message: z.string().optional() }),
31
- z.object({ rule: z.literal("minLength"), value: z.number(), message: z.string().optional() }),
32
- z.object({ rule: z.literal("maxLength"), value: z.number(), message: z.string().optional() }),
33
- z.object({ rule: z.literal("pattern"), value: z.string(), message: z.string().optional() }),
34
- z.object({ rule: z.literal("min"), value: z.number(), message: z.string().optional() }),
35
- z.object({ rule: z.literal("max"), value: z.number(), message: z.string().optional() })
36
- ]);
37
- var selectOptionSchema = z.object({
38
- value: z.string(),
39
- label: z.string(),
40
- hint: z.string().optional(),
41
- disabled: z.union([z.boolean(), z.string()]).optional()
42
- });
43
- var separatorOptionSchema = z.object({
44
- separator: z.string()
45
- });
46
- var selectChoiceSchema = z.union([selectOptionSchema, separatorOptionSchema]);
47
- var baseStepFields = {
48
- id: z.string(),
49
- message: z.string(),
50
- description: z.string().optional(),
51
- next: z.string().optional(),
52
- when: conditionSchema.optional(),
53
- keepValuesOnPrevious: z.boolean().optional(),
54
- required: z.boolean().optional(),
55
- group: z.string().optional()
56
- };
57
- var textStepSchema = z.object({
58
- ...baseStepFields,
59
- type: z.literal("text"),
60
- placeholder: z.string().optional(),
61
- default: z.string().optional(),
62
- validate: z.array(validationRuleSchema).optional()
63
- });
64
- var selectStepSchema = z.object({
65
- ...baseStepFields,
66
- type: z.literal("select"),
67
- options: z.array(selectChoiceSchema).min(1).optional(),
68
- optionsFrom: z.string().optional(),
69
- default: z.string().optional(),
70
- routes: z.record(z.string(), z.string()).optional(),
71
- pageSize: z.number().int().positive().optional(),
72
- loop: z.boolean().optional()
73
- });
74
- var multiSelectStepSchema = z.object({
75
- ...baseStepFields,
76
- type: z.literal("multiselect"),
77
- options: z.array(selectChoiceSchema).min(1).optional(),
78
- optionsFrom: z.string().optional(),
79
- default: z.array(z.string()).optional(),
80
- min: z.number().int().nonnegative().optional(),
81
- max: z.number().int().positive().optional(),
82
- pageSize: z.number().int().positive().optional(),
83
- loop: z.boolean().optional()
84
- });
85
- var confirmStepSchema = z.object({
86
- ...baseStepFields,
87
- type: z.literal("confirm"),
88
- default: z.boolean().optional()
89
- });
90
- var passwordStepSchema = z.object({
91
- ...baseStepFields,
92
- type: z.literal("password"),
93
- validate: z.array(validationRuleSchema).optional()
94
- });
95
- var numberStepSchema = z.object({
96
- ...baseStepFields,
97
- type: z.literal("number"),
98
- default: z.number().optional(),
99
- min: z.number().optional(),
100
- max: z.number().optional(),
101
- step: z.number().positive().optional()
102
- });
103
- var searchStepSchema = z.object({
104
- ...baseStepFields,
105
- type: z.literal("search"),
106
- options: z.array(selectChoiceSchema).min(1).optional(),
107
- optionsFrom: z.string().optional(),
108
- default: z.string().optional(),
109
- placeholder: z.string().optional(),
110
- pageSize: z.number().int().positive().optional(),
111
- loop: z.boolean().optional()
112
- });
113
- var editorStepSchema = z.object({
114
- ...baseStepFields,
115
- type: z.literal("editor"),
116
- default: z.string().optional(),
117
- validate: z.array(validationRuleSchema).optional()
118
- });
119
- var pathStepSchema = z.object({
120
- ...baseStepFields,
121
- type: z.literal("path"),
122
- default: z.string().optional(),
123
- placeholder: z.string().optional(),
124
- validate: z.array(validationRuleSchema).optional()
125
- });
126
- var toggleStepSchema = z.object({
127
- ...baseStepFields,
128
- type: z.literal("toggle"),
129
- default: z.boolean().optional(),
130
- active: z.string().optional(),
131
- inactive: z.string().optional()
132
- });
133
- var messageStepSchema = z.object({
134
- ...baseStepFields,
135
- type: z.literal("message")
136
- });
137
- var stepConfigSchema = z.discriminatedUnion("type", [
138
- textStepSchema,
139
- selectStepSchema,
140
- multiSelectStepSchema,
141
- confirmStepSchema,
142
- passwordStepSchema,
143
- numberStepSchema,
144
- searchStepSchema,
145
- editorStepSchema,
146
- pathStepSchema,
147
- toggleStepSchema,
148
- messageStepSchema
149
- ]);
150
- var hexColorSchema = z.string().regex(
151
- /^#[0-9a-fA-F]{6}$/,
152
- "Must be a 6-digit hex color (e.g., #FF0000)"
153
- );
154
- var themeConfigSchema = z.object({
155
- tokens: z.object({
156
- primary: hexColorSchema.optional(),
157
- success: hexColorSchema.optional(),
158
- error: hexColorSchema.optional(),
159
- warning: hexColorSchema.optional(),
160
- info: hexColorSchema.optional(),
161
- muted: hexColorSchema.optional(),
162
- accent: hexColorSchema.optional()
163
- }).optional(),
164
- icons: z.object({
165
- step: z.string().optional(),
166
- stepDone: z.string().optional(),
167
- stepPending: z.string().optional(),
168
- pointer: z.string().optional()
169
- }).optional()
170
- });
171
- var preFlightCheckSchema = z.object({
172
- name: z.string(),
173
- run: z.string(),
174
- message: z.string()
175
- });
176
- var actionConfigSchema = z.object({
177
- name: z.string().optional(),
178
- run: z.string(),
179
- when: conditionSchema.optional()
180
- });
181
- var wizardConfigSchema = z.object({
182
- meta: z.object({
183
- name: z.string(),
184
- version: z.string().optional(),
185
- description: z.string().optional()
186
- }),
187
- theme: themeConfigSchema.optional(),
188
- steps: z.array(stepConfigSchema).min(1),
189
- output: z.object({
190
- format: z.enum(["json", "env", "yaml"]),
191
- path: z.string().optional()
192
- }).optional(),
193
- extends: z.string().optional(),
194
- checks: z.array(preFlightCheckSchema).optional(),
195
- actions: z.array(actionConfigSchema).optional()
196
- }).superRefine((config, ctx) => {
197
- const stepIds = /* @__PURE__ */ new Set();
198
- for (const step of config.steps) {
199
- if (stepIds.has(step.id)) {
200
- ctx.addIssue({
201
- code: z.ZodIssueCode.custom,
202
- message: `Duplicate step ID: "${step.id}"`,
203
- path: ["steps"]
204
- });
205
- }
206
- stepIds.add(step.id);
207
- }
208
- config.steps.forEach((step, i) => {
209
- if (step.next && step.next !== "__done__" && !stepIds.has(step.next)) {
210
- ctx.addIssue({
211
- code: z.ZodIssueCode.custom,
212
- message: `Step "${step.id}" references unknown next step: "${step.next}"`,
213
- path: ["steps", i, "next"]
214
- });
215
- }
216
- if (step.type === "select" && step.routes) {
217
- for (const [key, target] of Object.entries(step.routes)) {
218
- if (target !== "__done__" && !stepIds.has(target)) {
219
- ctx.addIssue({
220
- code: z.ZodIssueCode.custom,
221
- message: `Step "${step.id}" route "${key}" references unknown step: "${target}"`,
222
- path: ["steps", i, "routes", key]
223
- });
224
- }
225
- }
226
- }
227
- if (step.when) {
228
- collectConditionFieldIssues(step.when, stepIds, ctx, ["steps", i, "when"]);
229
- }
230
- if (step.type === "select" && step.routes && step.options) {
231
- const optionValues = /* @__PURE__ */ new Set();
232
- for (const o of step.options) {
233
- if ("value" in o) {
234
- optionValues.add(o.value);
235
- }
236
- }
237
- for (const routeKey of Object.keys(step.routes)) {
238
- if (!optionValues.has(routeKey)) {
239
- ctx.addIssue({
240
- code: z.ZodIssueCode.custom,
241
- message: `Step "${step.id}" route key "${routeKey}" does not match any option value`,
242
- path: ["steps", i, "routes", routeKey]
243
- });
244
- }
245
- }
246
- }
247
- if (step.type === "select" || step.type === "multiselect" || step.type === "search") {
248
- const hasOptions = step.options !== void 0;
249
- const hasOptionsFrom = step.optionsFrom !== void 0;
250
- if (hasOptions && hasOptionsFrom) {
251
- ctx.addIssue({
252
- code: z.ZodIssueCode.custom,
253
- message: `Step "${step.id}" has both "options" and "optionsFrom" \u2014 only one is allowed`,
254
- path: ["steps", i]
255
- });
256
- }
257
- if (!hasOptions && !hasOptionsFrom) {
258
- ctx.addIssue({
259
- code: z.ZodIssueCode.custom,
260
- message: `Step "${step.id}" must have either "options" or "optionsFrom"`,
261
- path: ["steps", i]
262
- });
263
- }
264
- }
265
- if ((step.type === "number" || step.type === "multiselect") && step.min !== void 0 && step.max !== void 0 && step.min > step.max) {
266
- ctx.addIssue({
267
- code: z.ZodIssueCode.custom,
268
- message: `Step "${step.id}" has min (${String(step.min)}) greater than max (${String(step.max)})`,
269
- path: ["steps", i]
270
- });
271
- }
272
- });
273
- if (config.actions) {
274
- config.actions.forEach((action, i) => {
275
- if (action.when) {
276
- collectConditionFieldIssues(action.when, stepIds, ctx, ["actions", i, "when"]);
277
- }
278
- });
279
- }
280
- });
281
13
  function collectConditionFieldIssues(condition, validIds, ctx, path) {
282
14
  if ("field" in condition) {
283
15
  const fieldRoot = condition.field.split(".")[0];
@@ -310,13 +42,316 @@ function parseWizardConfig(raw) {
310
42
  const result = wizardConfigSchema.parse(raw);
311
43
  return result;
312
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;
46
+ var init_schema = __esm({
47
+ "src/schema.ts"() {
48
+ "use strict";
49
+ conditionSchema = z.lazy(() => {
50
+ const fieldEquals = z.object({ field: z.string(), equals: z.unknown() }).strict();
51
+ const fieldNotEquals = z.object({ field: z.string(), notEquals: z.unknown() }).strict();
52
+ const fieldIncludes = z.object({ field: z.string(), includes: z.unknown() }).strict();
53
+ const fieldNotIncludes = z.object({ field: z.string(), notIncludes: z.unknown() }).strict();
54
+ const fieldGreaterThan = z.object({ field: z.string(), greaterThan: z.number() }).strict();
55
+ const fieldLessThan = z.object({ field: z.string(), lessThan: z.number() }).strict();
56
+ const fieldIsEmpty = z.object({ field: z.string(), isEmpty: z.literal(true) }).strict();
57
+ const fieldIsNotEmpty = z.object({ field: z.string(), isNotEmpty: z.literal(true) }).strict();
58
+ const allCondition = z.object({ all: z.array(conditionSchema) }).strict();
59
+ const anyCondition = z.object({ any: z.array(conditionSchema) }).strict();
60
+ const notCondition = z.object({ not: conditionSchema }).strict();
61
+ return z.union([
62
+ fieldEquals,
63
+ fieldNotEquals,
64
+ fieldIncludes,
65
+ fieldNotIncludes,
66
+ fieldGreaterThan,
67
+ fieldLessThan,
68
+ fieldIsEmpty,
69
+ fieldIsNotEmpty,
70
+ allCondition,
71
+ anyCondition,
72
+ notCondition
73
+ ]);
74
+ });
75
+ validationRuleSchema = z.discriminatedUnion("rule", [
76
+ z.object({ rule: z.literal("required"), message: z.string().optional() }),
77
+ z.object({ rule: z.literal("minLength"), value: z.number(), message: z.string().optional() }),
78
+ z.object({ rule: z.literal("maxLength"), value: z.number(), message: z.string().optional() }),
79
+ z.object({ rule: z.literal("pattern"), value: z.string(), message: z.string().optional() }),
80
+ z.object({ rule: z.literal("min"), value: z.number(), message: z.string().optional() }),
81
+ z.object({ rule: z.literal("max"), value: z.number(), message: z.string().optional() })
82
+ ]);
83
+ selectOptionSchema = z.object({
84
+ value: z.string(),
85
+ label: z.string(),
86
+ hint: z.string().optional(),
87
+ disabled: z.union([z.boolean(), z.string()]).optional()
88
+ });
89
+ separatorOptionSchema = z.object({
90
+ separator: z.string()
91
+ });
92
+ selectChoiceSchema = z.union([selectOptionSchema, separatorOptionSchema]);
93
+ baseStepFields = {
94
+ id: z.string(),
95
+ message: z.string(),
96
+ description: z.string().optional(),
97
+ next: z.string().optional(),
98
+ when: conditionSchema.optional(),
99
+ keepValuesOnPrevious: z.boolean().optional(),
100
+ required: z.boolean().optional(),
101
+ group: z.string().optional()
102
+ };
103
+ textStepSchema = z.object({
104
+ ...baseStepFields,
105
+ type: z.literal("text"),
106
+ placeholder: z.string().optional(),
107
+ default: z.string().optional(),
108
+ validate: z.array(validationRuleSchema).optional()
109
+ });
110
+ selectStepSchema = z.object({
111
+ ...baseStepFields,
112
+ type: z.literal("select"),
113
+ options: z.array(selectChoiceSchema).min(1).optional(),
114
+ optionsFrom: z.string().optional(),
115
+ default: z.string().optional(),
116
+ routes: z.record(z.string(), z.string()).optional(),
117
+ pageSize: z.number().int().positive().optional(),
118
+ loop: z.boolean().optional()
119
+ });
120
+ multiSelectStepSchema = z.object({
121
+ ...baseStepFields,
122
+ type: z.literal("multiselect"),
123
+ options: z.array(selectChoiceSchema).min(1).optional(),
124
+ optionsFrom: z.string().optional(),
125
+ default: z.array(z.string()).optional(),
126
+ min: z.number().int().nonnegative().optional(),
127
+ max: z.number().int().positive().optional(),
128
+ pageSize: z.number().int().positive().optional(),
129
+ loop: z.boolean().optional()
130
+ });
131
+ confirmStepSchema = z.object({
132
+ ...baseStepFields,
133
+ type: z.literal("confirm"),
134
+ default: z.boolean().optional()
135
+ });
136
+ passwordStepSchema = z.object({
137
+ ...baseStepFields,
138
+ type: z.literal("password"),
139
+ validate: z.array(validationRuleSchema).optional()
140
+ });
141
+ numberStepSchema = z.object({
142
+ ...baseStepFields,
143
+ type: z.literal("number"),
144
+ default: z.number().optional(),
145
+ min: z.number().optional(),
146
+ max: z.number().optional(),
147
+ step: z.number().positive().optional()
148
+ });
149
+ searchStepSchema = z.object({
150
+ ...baseStepFields,
151
+ type: z.literal("search"),
152
+ options: z.array(selectChoiceSchema).min(1).optional(),
153
+ optionsFrom: z.string().optional(),
154
+ default: z.string().optional(),
155
+ placeholder: z.string().optional(),
156
+ pageSize: z.number().int().positive().optional(),
157
+ loop: z.boolean().optional()
158
+ });
159
+ editorStepSchema = z.object({
160
+ ...baseStepFields,
161
+ type: z.literal("editor"),
162
+ default: z.string().optional(),
163
+ validate: z.array(validationRuleSchema).optional()
164
+ });
165
+ pathStepSchema = z.object({
166
+ ...baseStepFields,
167
+ type: z.literal("path"),
168
+ default: z.string().optional(),
169
+ placeholder: z.string().optional(),
170
+ validate: z.array(validationRuleSchema).optional()
171
+ });
172
+ toggleStepSchema = z.object({
173
+ ...baseStepFields,
174
+ type: z.literal("toggle"),
175
+ default: z.boolean().optional(),
176
+ active: z.string().optional(),
177
+ inactive: z.string().optional()
178
+ });
179
+ messageStepSchema = z.object({
180
+ ...baseStepFields,
181
+ type: z.literal("message")
182
+ });
183
+ noteStepSchema = z.object({
184
+ ...baseStepFields,
185
+ type: z.literal("note")
186
+ });
187
+ stepConfigSchema = z.discriminatedUnion("type", [
188
+ textStepSchema,
189
+ selectStepSchema,
190
+ multiSelectStepSchema,
191
+ confirmStepSchema,
192
+ passwordStepSchema,
193
+ numberStepSchema,
194
+ searchStepSchema,
195
+ editorStepSchema,
196
+ pathStepSchema,
197
+ toggleStepSchema,
198
+ messageStepSchema,
199
+ noteStepSchema
200
+ ]);
201
+ hexColorSchema = z.string().regex(
202
+ /^#[0-9a-fA-F]{6}$/,
203
+ "Must be a 6-digit hex color (e.g., #FF0000)"
204
+ );
205
+ themeConfigSchema = z.object({
206
+ preset: z.enum(["default", "catppuccin", "dracula", "nord", "tokyonight", "monokai"]).optional(),
207
+ tokens: z.object({
208
+ primary: hexColorSchema.optional(),
209
+ success: hexColorSchema.optional(),
210
+ error: hexColorSchema.optional(),
211
+ warning: hexColorSchema.optional(),
212
+ info: hexColorSchema.optional(),
213
+ muted: hexColorSchema.optional(),
214
+ accent: hexColorSchema.optional()
215
+ }).optional(),
216
+ icons: z.object({
217
+ step: z.string().optional(),
218
+ stepDone: z.string().optional(),
219
+ stepPending: z.string().optional(),
220
+ pointer: z.string().optional()
221
+ }).optional(),
222
+ spinner: z.union([
223
+ z.string(),
224
+ z.object({
225
+ frames: z.array(z.string()).min(1),
226
+ interval: z.number().positive().optional()
227
+ })
228
+ ]).optional()
229
+ });
230
+ preFlightCheckSchema = z.object({
231
+ name: z.string(),
232
+ run: z.string(),
233
+ message: z.string()
234
+ });
235
+ actionConfigSchema = z.object({
236
+ name: z.string().optional(),
237
+ run: z.string(),
238
+ when: conditionSchema.optional()
239
+ });
240
+ wizardConfigSchema = z.object({
241
+ meta: z.object({
242
+ name: z.string(),
243
+ version: z.string().optional(),
244
+ description: z.string().optional(),
245
+ review: z.boolean().optional()
246
+ }),
247
+ theme: themeConfigSchema.optional(),
248
+ steps: z.array(stepConfigSchema).min(1),
249
+ output: z.object({
250
+ format: z.enum(["json", "env", "yaml"]),
251
+ path: z.string().optional()
252
+ }).optional(),
253
+ extends: z.string().optional(),
254
+ checks: z.array(preFlightCheckSchema).optional(),
255
+ actions: z.array(actionConfigSchema).optional(),
256
+ onComplete: z.string().optional()
257
+ }).superRefine((config, ctx) => {
258
+ const stepIds = /* @__PURE__ */ new Set();
259
+ for (const step of config.steps) {
260
+ if (stepIds.has(step.id)) {
261
+ ctx.addIssue({
262
+ code: z.ZodIssueCode.custom,
263
+ message: `Duplicate step ID: "${step.id}"`,
264
+ path: ["steps"]
265
+ });
266
+ }
267
+ stepIds.add(step.id);
268
+ }
269
+ config.steps.forEach((step, i) => {
270
+ if (step.next && step.next !== "__done__" && !stepIds.has(step.next)) {
271
+ ctx.addIssue({
272
+ code: z.ZodIssueCode.custom,
273
+ message: `Step "${step.id}" references unknown next step: "${step.next}"`,
274
+ path: ["steps", i, "next"]
275
+ });
276
+ }
277
+ if (step.type === "select" && step.routes) {
278
+ for (const [key, target] of Object.entries(step.routes)) {
279
+ if (target !== "__done__" && !stepIds.has(target)) {
280
+ ctx.addIssue({
281
+ code: z.ZodIssueCode.custom,
282
+ message: `Step "${step.id}" route "${key}" references unknown step: "${target}"`,
283
+ path: ["steps", i, "routes", key]
284
+ });
285
+ }
286
+ }
287
+ }
288
+ if (step.when) {
289
+ collectConditionFieldIssues(step.when, stepIds, ctx, ["steps", i, "when"]);
290
+ }
291
+ if (step.type === "select" && step.routes && step.options) {
292
+ const optionValues = /* @__PURE__ */ new Set();
293
+ for (const o of step.options) {
294
+ if ("value" in o) {
295
+ optionValues.add(o.value);
296
+ }
297
+ }
298
+ for (const routeKey of Object.keys(step.routes)) {
299
+ if (!optionValues.has(routeKey)) {
300
+ ctx.addIssue({
301
+ code: z.ZodIssueCode.custom,
302
+ message: `Step "${step.id}" route key "${routeKey}" does not match any option value`,
303
+ path: ["steps", i, "routes", routeKey]
304
+ });
305
+ }
306
+ }
307
+ }
308
+ if (step.type === "select" || step.type === "multiselect" || step.type === "search") {
309
+ const hasOptions = step.options !== void 0;
310
+ const hasOptionsFrom = step.optionsFrom !== void 0;
311
+ if (hasOptions && hasOptionsFrom) {
312
+ ctx.addIssue({
313
+ code: z.ZodIssueCode.custom,
314
+ message: `Step "${step.id}" has both "options" and "optionsFrom" \u2014 only one is allowed`,
315
+ path: ["steps", i]
316
+ });
317
+ }
318
+ if (!hasOptions && !hasOptionsFrom) {
319
+ ctx.addIssue({
320
+ code: z.ZodIssueCode.custom,
321
+ message: `Step "${step.id}" must have either "options" or "optionsFrom"`,
322
+ path: ["steps", i]
323
+ });
324
+ }
325
+ }
326
+ if ((step.type === "number" || step.type === "multiselect") && step.min !== void 0 && step.max !== void 0 && step.min > step.max) {
327
+ ctx.addIssue({
328
+ code: z.ZodIssueCode.custom,
329
+ message: `Step "${step.id}" has min (${String(step.min)}) greater than max (${String(step.max)})`,
330
+ path: ["steps", i]
331
+ });
332
+ }
333
+ });
334
+ if (config.actions) {
335
+ config.actions.forEach((action, i) => {
336
+ if (action.when) {
337
+ collectConditionFieldIssues(action.when, stepIds, ctx, ["actions", i, "when"]);
338
+ }
339
+ });
340
+ }
341
+ });
342
+ }
343
+ });
313
344
 
314
345
  // src/parser.ts
346
+ var parser_exports = {};
347
+ __export(parser_exports, {
348
+ loadWizardConfig: () => loadWizardConfig,
349
+ parseWizardYAML: () => parseWizardYAML
350
+ });
315
351
  import { cosmiconfig } from "cosmiconfig";
316
352
  import { readFileSync } from "fs";
317
353
  import { dirname, resolve, isAbsolute } from "path";
318
354
  import { parse as parseYAML } from "yaml";
319
- var DONE_SENTINEL = "__done__";
320
355
  function buildStepGraph(steps) {
321
356
  const graph = /* @__PURE__ */ new Map();
322
357
  for (let i = 0; i < steps.length; i++) {
@@ -395,7 +430,6 @@ function mergeConfigs(parent, child) {
395
430
  actions: child.actions ?? parent.actions
396
431
  };
397
432
  }
398
- var OPTION_STEP_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "search"]);
399
433
  function resolveOptionsFromSteps(raw, configDir) {
400
434
  const steps = raw["steps"];
401
435
  if (!Array.isArray(steps)) return;
@@ -491,6 +525,19 @@ function parseWizardYAML(yamlString) {
491
525
  detectCycles(config);
492
526
  return config;
493
527
  }
528
+ var DONE_SENTINEL, OPTION_STEP_TYPES;
529
+ var init_parser = __esm({
530
+ "src/parser.ts"() {
531
+ "use strict";
532
+ init_schema();
533
+ DONE_SENTINEL = "__done__";
534
+ OPTION_STEP_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "search"]);
535
+ }
536
+ });
537
+
538
+ // src/index.ts
539
+ init_schema();
540
+ init_parser();
494
541
 
495
542
  // src/conditions.ts
496
543
  function isRecord(value) {
@@ -770,6 +817,102 @@ function applyValidationRule(rule, value) {
770
817
 
771
818
  // src/theme.ts
772
819
  import chalk from "chalk";
820
+
821
+ // src/themes/presets.ts
822
+ var THEME_PRESETS = {
823
+ default: {
824
+ primary: "#7C3AED",
825
+ success: "#10B981",
826
+ error: "#EF4444",
827
+ warning: "#F59E0B",
828
+ info: "#3B82F6",
829
+ muted: "#6B7280",
830
+ accent: "#8B5CF6"
831
+ },
832
+ catppuccin: {
833
+ primary: "#cba6f7",
834
+ success: "#a6e3a1",
835
+ error: "#f38ba8",
836
+ warning: "#fab387",
837
+ info: "#74c7ec",
838
+ muted: "#6c7086",
839
+ accent: "#f5c2e7"
840
+ },
841
+ dracula: {
842
+ primary: "#bd93f9",
843
+ success: "#50fa7b",
844
+ error: "#ff5555",
845
+ warning: "#ffb86c",
846
+ info: "#8be9fd",
847
+ muted: "#6272a4",
848
+ accent: "#ff79c6"
849
+ },
850
+ nord: {
851
+ primary: "#88c0d0",
852
+ success: "#a3be8c",
853
+ error: "#bf616a",
854
+ warning: "#ebcb8b",
855
+ info: "#81a1c1",
856
+ muted: "#4c566a",
857
+ accent: "#b48ead"
858
+ },
859
+ tokyonight: {
860
+ primary: "#7aa2f7",
861
+ success: "#9ece6a",
862
+ error: "#f7768e",
863
+ warning: "#e0af68",
864
+ info: "#7dcfff",
865
+ muted: "#565f89",
866
+ accent: "#bb9af7"
867
+ },
868
+ monokai: {
869
+ primary: "#ab9df2",
870
+ success: "#a9dc76",
871
+ error: "#ff6188",
872
+ warning: "#ffd866",
873
+ info: "#78dce8",
874
+ muted: "#727072",
875
+ accent: "#fc9867"
876
+ }
877
+ };
878
+ var PRESET_NAMES = Object.keys(THEME_PRESETS);
879
+
880
+ // src/spinners.ts
881
+ var spinners = {
882
+ dots: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] },
883
+ dots2: { interval: 80, frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"] },
884
+ line: { interval: 130, frames: ["-", "\\", "|", "/"] },
885
+ arc: { interval: 100, frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"] },
886
+ circle: { interval: 80, frames: ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] },
887
+ circleHalves: { interval: 50, frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"] },
888
+ triangle: { interval: 50, frames: ["\u25E2", "\u25E3", "\u25E4", "\u25E5"] },
889
+ pipe: { interval: 100, frames: ["\u2524", "\u2518", "\u2534", "\u2514", "\u251C", "\u250C", "\u252C", "\u2510"] },
890
+ arrow: { interval: 100, frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"] },
891
+ arrow3: { interval: 120, frames: ["\u25B9\u25B9\u25B9\u25B9\u25B9", "\u25B8\u25B9\u25B9\u25B9\u25B9", "\u25B9\u25B8\u25B9\u25B9\u25B9", "\u25B9\u25B9\u25B8\u25B9\u25B9", "\u25B9\u25B9\u25B9\u25B8\u25B9", "\u25B9\u25B9\u25B9\u25B9\u25B8"] },
892
+ bouncingBar: { interval: 80, frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"] },
893
+ bouncingBall: { interval: 80, frames: ["( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF)", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "(\u25CF )"] },
894
+ simpleDots: { interval: 400, frames: [". ", ".. ", "...", " "] },
895
+ aesthetic: { interval: 80, frames: ["\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0", "\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1"] },
896
+ star: { interval: 70, frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"] }
897
+ };
898
+ var DEFAULT_SPINNER = "circle";
899
+ function resolveSpinner(config) {
900
+ if (!config) {
901
+ return spinners[DEFAULT_SPINNER];
902
+ }
903
+ if (typeof config === "string") {
904
+ if (config in spinners) {
905
+ return spinners[config];
906
+ }
907
+ throw new Error(`Unknown spinner preset: "${config}". Available: ${Object.keys(spinners).join(", ")}`);
908
+ }
909
+ return {
910
+ frames: config.frames,
911
+ interval: config.interval ?? 80
912
+ };
913
+ }
914
+
915
+ // src/theme.ts
773
916
  var DEFAULT_TOKENS = {
774
917
  primary: "#5B9BD5",
775
918
  success: "#6BCB77",
@@ -786,7 +929,8 @@ var DEFAULT_ICONS = {
786
929
  pointer: "\u203A"
787
930
  };
788
931
  function resolveTheme(themeConfig) {
789
- const tokens = { ...DEFAULT_TOKENS, ...themeConfig?.tokens };
932
+ const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
933
+ const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
790
934
  const icons = { ...DEFAULT_ICONS, ...themeConfig?.icons };
791
935
  return {
792
936
  primary: chalk.hex(tokens.primary),
@@ -797,7 +941,8 @@ function resolveTheme(themeConfig) {
797
941
  muted: chalk.hex(tokens.muted),
798
942
  accent: chalk.hex(tokens.accent),
799
943
  bold: chalk.bold,
800
- icons
944
+ icons,
945
+ spinner: resolveSpinner(themeConfig?.spinner)
801
946
  };
802
947
  }
803
948
 
@@ -824,6 +969,8 @@ function resolveEnvDefaultBoolean(value) {
824
969
 
825
970
  // src/runner.ts
826
971
  import { execSync } from "child_process";
972
+ import { resolve as resolve2, dirname as dirname2 } from "path";
973
+ import { pathToFileURL } from "url";
827
974
 
828
975
  // src/renderers/inquirer.ts
829
976
  import {
@@ -1024,6 +1171,17 @@ function resolveTemplate(template, answers) {
1024
1171
  return _match;
1025
1172
  });
1026
1173
  }
1174
+ function resolveTemplateStrict(template, answers) {
1175
+ return template.replace(/\{\{([^}]+)\}\}/g, (_match, key) => {
1176
+ const trimmedKey = key.trim();
1177
+ if (!(trimmedKey in answers)) {
1178
+ throw new Error(`Action references unknown step "${trimmedKey}"`);
1179
+ }
1180
+ const value = answers[trimmedKey];
1181
+ if (Array.isArray(value)) return value.join(", ");
1182
+ return String(value);
1183
+ });
1184
+ }
1027
1185
 
1028
1186
  // src/banner.ts
1029
1187
  import figlet from "figlet";
@@ -1131,14 +1289,66 @@ function clearCache(wizardName, customDir) {
1131
1289
  }
1132
1290
  }
1133
1291
 
1134
- // src/mru.ts
1135
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
1292
+ // src/progress.ts
1293
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
1136
1294
  import { join as join2 } from "path";
1137
1295
  import { homedir as homedir2 } from "os";
1138
- var MRU_DIR = join2(homedir2(), ".config", "grimoire", "mru");
1296
+ var DEFAULT_PROGRESS_DIR = join2(homedir2(), ".config", "grimoire", "progress");
1297
+ function getProgressFilePath(wizardName, customDir) {
1298
+ const dir = customDir ?? DEFAULT_PROGRESS_DIR;
1299
+ return join2(dir, `${slugify(wizardName)}.json`);
1300
+ }
1301
+ function saveProgress(wizardName, state, customDir, excludeStepIds) {
1302
+ try {
1303
+ const dir = customDir ?? DEFAULT_PROGRESS_DIR;
1304
+ mkdirSync2(dir, { recursive: true });
1305
+ const excludeSet = new Set(excludeStepIds ?? []);
1306
+ const filteredAnswers = {};
1307
+ for (const [key, value] of Object.entries(state.answers)) {
1308
+ if (!excludeSet.has(key)) {
1309
+ filteredAnswers[key] = value;
1310
+ }
1311
+ }
1312
+ const progress = {
1313
+ currentStepId: state.currentStepId,
1314
+ answers: filteredAnswers,
1315
+ history: state.history,
1316
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1317
+ };
1318
+ const filePath = getProgressFilePath(wizardName, customDir);
1319
+ writeFileSync2(filePath, JSON.stringify(progress, null, 2) + "\n", "utf-8");
1320
+ } catch {
1321
+ }
1322
+ }
1323
+ function loadProgress(wizardName, customDir) {
1324
+ try {
1325
+ const filePath = getProgressFilePath(wizardName, customDir);
1326
+ const raw = readFileSync3(filePath, "utf-8");
1327
+ const parsed = JSON.parse(raw);
1328
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "currentStepId" in parsed && "answers" in parsed && "history" in parsed && "savedAt" in parsed) {
1329
+ return parsed;
1330
+ }
1331
+ return null;
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ }
1336
+ function clearProgress(wizardName, customDir) {
1337
+ try {
1338
+ const filePath = getProgressFilePath(wizardName, customDir);
1339
+ unlinkSync2(filePath);
1340
+ } catch {
1341
+ }
1342
+ }
1343
+
1344
+ // src/mru.ts
1345
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
1346
+ import { join as join3 } from "path";
1347
+ import { homedir as homedir3 } from "os";
1348
+ var MRU_DIR = join3(homedir3(), ".config", "grimoire", "mru");
1139
1349
  function getMruFilePath(wizardName) {
1140
1350
  const safeName = wizardName.replace(/[^a-zA-Z0-9_-]/g, "_");
1141
- return join2(MRU_DIR, `${safeName}.json`);
1351
+ return join3(MRU_DIR, `${safeName}.json`);
1142
1352
  }
1143
1353
  function loadMruData(wizardName) {
1144
1354
  const filePath = getMruFilePath(wizardName);
@@ -1146,7 +1356,7 @@ function loadMruData(wizardName) {
1146
1356
  if (!existsSync(filePath)) {
1147
1357
  return {};
1148
1358
  }
1149
- const raw = readFileSync3(filePath, "utf-8");
1359
+ const raw = readFileSync4(filePath, "utf-8");
1150
1360
  const parsed = JSON.parse(raw);
1151
1361
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1152
1362
  return {};
@@ -1159,8 +1369,8 @@ function loadMruData(wizardName) {
1159
1369
  function saveMruData(wizardName, data) {
1160
1370
  const filePath = getMruFilePath(wizardName);
1161
1371
  try {
1162
- mkdirSync2(MRU_DIR, { recursive: true });
1163
- writeFileSync2(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1372
+ mkdirSync3(MRU_DIR, { recursive: true });
1373
+ writeFileSync3(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1164
1374
  } catch {
1165
1375
  }
1166
1376
  }
@@ -1216,20 +1426,28 @@ function clearMruData(wizardName) {
1216
1426
  const filePath = getMruFilePath(wizardName);
1217
1427
  try {
1218
1428
  if (existsSync(filePath)) {
1219
- writeFileSync2(filePath, "{}", "utf-8");
1429
+ writeFileSync3(filePath, "{}", "utf-8");
1220
1430
  }
1221
1431
  } catch {
1222
1432
  }
1223
1433
  }
1224
1434
 
1225
1435
  // src/runner.ts
1226
- function runPreFlightChecks(checks, theme) {
1436
+ function emitEvent(renderer, event, theme) {
1437
+ if (renderer.onEvent) {
1438
+ renderer.onEvent(event, theme);
1439
+ }
1440
+ }
1441
+ function runPreFlightChecks(checks, theme, renderer) {
1442
+ if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
1227
1443
  for (const check of checks) {
1228
1444
  try {
1229
1445
  execSync(check.run, { stdio: "pipe" });
1230
1446
  console.log(` ${theme.success("\u2713")} ${check.name}`);
1447
+ if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
1231
1448
  } catch {
1232
1449
  console.log(` ${theme.error("\u2717")} ${check.name}: ${check.message}`);
1450
+ if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
1233
1451
  throw new Error(`Pre-flight check failed: ${check.name} \u2014 ${check.message}`);
1234
1452
  }
1235
1453
  }
@@ -1239,7 +1457,7 @@ function getMockValue(step, mockAnswers) {
1239
1457
  if (step.id in mockAnswers) {
1240
1458
  return mockAnswers[step.id];
1241
1459
  }
1242
- if (step.type === "message") {
1460
+ if (step.type === "message" || step.type === "note") {
1243
1461
  return true;
1244
1462
  }
1245
1463
  const defaultValue = getStepDefault(step);
@@ -1267,6 +1485,7 @@ function getStepDefault(step) {
1267
1485
  return step.default;
1268
1486
  case "password":
1269
1487
  case "message":
1488
+ case "note":
1270
1489
  return void 0;
1271
1490
  }
1272
1491
  }
@@ -1280,6 +1499,21 @@ async function runWizard(config, options) {
1280
1499
  const cacheDir = typeof options?.cache === "object" ? options.cache.dir : void 0;
1281
1500
  const mruEnabled = !isMock && options?.mru !== false;
1282
1501
  let state = createWizardState(config);
1502
+ const resumeEnabled = !isMock && options?.resume !== false;
1503
+ if (resumeEnabled) {
1504
+ const saved = loadProgress(config.meta.name);
1505
+ if (saved) {
1506
+ const stepExists = config.steps.some((s) => s.id === saved.currentStepId);
1507
+ if (stepExists) {
1508
+ state = {
1509
+ ...state,
1510
+ currentStepId: saved.currentStepId,
1511
+ answers: { ...state.answers, ...saved.answers },
1512
+ history: saved.history
1513
+ };
1514
+ }
1515
+ }
1516
+ }
1283
1517
  const cachedAnswers = cacheEnabled ? loadCachedAnswers(config.meta.name, cacheDir) : void 0;
1284
1518
  const userPlugins = options?.plugins;
1285
1519
  if (userPlugins) {
@@ -1289,103 +1523,174 @@ async function runWizard(config, options) {
1289
1523
  }
1290
1524
  try {
1291
1525
  if (!isMock && config.checks && config.checks.length > 0) {
1292
- runPreFlightChecks(config.checks, theme);
1526
+ runPreFlightChecks(config.checks, theme, renderer);
1293
1527
  }
1294
1528
  if (!quiet) {
1295
1529
  printWizardHeader(config, theme, options?.plain);
1296
1530
  }
1297
- let previousGroup;
1298
- while (state.status === "running") {
1299
- const visibleSteps = getVisibleSteps(config, state.answers);
1300
- const currentStep = config.steps.find((s) => s.id === state.currentStepId);
1301
- if (!currentStep) {
1302
- throw new Error(`Current step not found: "${state.currentStepId}"`);
1303
- }
1304
- if (!isMock) {
1531
+ const visibleStepsForCount = getVisibleSteps(config, state.answers);
1532
+ emitEvent(renderer, { type: "session:start", wizard: config.meta.name, description: config.meta.description, totalSteps: visibleStepsForCount.length }, theme);
1533
+ let needsReview = true;
1534
+ while (needsReview) {
1535
+ let previousGroup;
1536
+ while (state.status === "running") {
1537
+ const visibleSteps = getVisibleSteps(config, state.answers);
1538
+ const currentStep = config.steps.find((s) => s.id === state.currentStepId);
1539
+ if (!currentStep) {
1540
+ throw new Error(`Current step not found: "${state.currentStepId}"`);
1541
+ }
1305
1542
  if (currentStep.group !== void 0 && currentStep.group !== previousGroup) {
1306
1543
  const resolvedGroup = resolveTemplate(currentStep.group, state.answers);
1307
- renderer.renderGroupHeader(resolvedGroup, theme);
1544
+ if (!isMock) {
1545
+ renderer.renderGroupHeader(resolvedGroup, theme);
1546
+ }
1547
+ emitEvent(renderer, { type: "group:start", group: resolvedGroup }, theme);
1308
1548
  }
1309
1549
  previousGroup = currentStep.group;
1310
1550
  const stepIndex = visibleSteps.findIndex((s) => s.id === state.currentStepId);
1311
1551
  const resolvedMessage = resolveTemplate(currentStep.message, state.answers);
1312
1552
  const resolvedDescription = currentStep.description ? resolveTemplate(currentStep.description, state.answers) : void 0;
1313
- renderer.renderStepHeader(stepIndex, visibleSteps.length, resolvedMessage, theme, resolvedDescription);
1314
- }
1315
- if (options?.onBeforeStep) {
1316
- await options.onBeforeStep(currentStep.id, currentStep, state);
1317
- }
1318
- const pluginStep = getPluginStep(currentStep.type);
1319
- const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
1320
- const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
1321
- const templatedStep = resolveStepTemplates(withTemplate, state.answers);
1322
- const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
1323
- try {
1324
- const value = isMock ? getMockValue(mruStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(mruStep), state, theme) : await renderStep(renderer, mruStep, state, theme);
1325
- if (pluginStep?.validate) {
1326
- const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
1327
- if (pluginError) {
1553
+ if (!isMock) {
1554
+ renderer.renderStepHeader(stepIndex, visibleSteps.length, resolvedMessage, theme, resolvedDescription);
1555
+ }
1556
+ emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
1557
+ if (currentStep.type === "note") {
1558
+ emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
1559
+ }
1560
+ if (options?.onBeforeStep) {
1561
+ await options.onBeforeStep(currentStep.id, currentStep, state);
1562
+ }
1563
+ const pluginStep = getPluginStep(currentStep.type);
1564
+ const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
1565
+ const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
1566
+ const templatedStep = resolveStepTemplates(withTemplate, state.answers);
1567
+ const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
1568
+ try {
1569
+ const value = isMock ? getMockValue(mruStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(mruStep), state, theme) : await renderStep(renderer, mruStep, state, theme);
1570
+ if (pluginStep?.validate) {
1571
+ const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
1572
+ if (pluginError) {
1573
+ if (isMock) {
1574
+ throw new Error(
1575
+ `Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
1576
+ );
1577
+ }
1578
+ console.log(theme.error(`
1579
+ ${pluginError}
1580
+ `));
1581
+ continue;
1582
+ }
1583
+ }
1584
+ const nextState = wizardReducer(state, { type: "NEXT", value }, config);
1585
+ if (nextState.errors[currentStep.id]) {
1586
+ const errorMsg = resolveTemplate(nextState.errors[currentStep.id] ?? "", state.answers);
1587
+ emitEvent(renderer, { type: "step:error", stepId: currentStep.id, error: errorMsg }, theme);
1328
1588
  if (isMock) {
1329
1589
  throw new Error(
1330
- `Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
1590
+ `Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
1331
1591
  );
1332
1592
  }
1333
1593
  console.log(theme.error(`
1334
- ${pluginError}
1594
+ ${errorMsg}
1335
1595
  `));
1596
+ state = { ...nextState, errors: {} };
1336
1597
  continue;
1337
1598
  }
1338
- }
1339
- const nextState = wizardReducer(state, { type: "NEXT", value }, config);
1340
- if (nextState.errors[currentStep.id]) {
1341
- const errorMsg = resolveTemplate(nextState.errors[currentStep.id] ?? "", state.answers);
1342
- if (isMock) {
1343
- throw new Error(
1344
- `Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
1345
- );
1346
- }
1347
- console.log(theme.error(`
1348
- ${errorMsg}
1349
- `));
1350
- state = { ...nextState, errors: {} };
1351
- continue;
1352
- }
1353
- if (!isMock && options?.asyncValidate) {
1354
- const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
1355
- if (asyncError !== null) {
1356
- console.log(theme.error(`
1599
+ if (!isMock && options?.asyncValidate) {
1600
+ const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
1601
+ if (asyncError !== null) {
1602
+ console.log(theme.error(`
1357
1603
  ${asyncError}
1358
1604
  `));
1359
- state = { ...nextState, errors: {} };
1360
- continue;
1605
+ state = { ...nextState, errors: {} };
1606
+ continue;
1607
+ }
1361
1608
  }
1609
+ if (options?.onAfterStep) {
1610
+ await options.onAfterStep(currentStep.id, value, nextState);
1611
+ }
1612
+ state = nextState;
1613
+ emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
1614
+ if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1615
+ recordSelection(config.meta.name, currentStep.id, value);
1616
+ }
1617
+ options?.onStepComplete?.(currentStep.id, value, state);
1618
+ } catch (error) {
1619
+ if (!isMock && isUserCancel(error)) {
1620
+ state = wizardReducer(state, { type: "CANCEL" }, config);
1621
+ options?.onCancel?.(state);
1622
+ const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1623
+ saveProgress(config.meta.name, {
1624
+ currentStepId: state.currentStepId,
1625
+ answers: state.answers,
1626
+ history: state.history
1627
+ }, void 0, passwordStepIds);
1628
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1629
+ if (!quiet) {
1630
+ console.log(theme.warning("\n Wizard cancelled.\n"));
1631
+ }
1632
+ return state.answers;
1633
+ }
1634
+ throw error;
1362
1635
  }
1363
- if (options?.onAfterStep) {
1364
- await options.onAfterStep(currentStep.id, value, nextState);
1636
+ }
1637
+ if (config.meta.review && !isMock && state.status === "done") {
1638
+ const reviewLines = [];
1639
+ for (const step of config.steps) {
1640
+ const answer = state.answers[step.id];
1641
+ if (answer === void 0) continue;
1642
+ const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
1643
+ reviewLines.push(`${step.id}: ${display}`);
1365
1644
  }
1366
- state = nextState;
1367
- if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1368
- recordSelection(config.meta.name, currentStep.id, value);
1645
+ emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
1646
+ console.log(`
1647
+ ${theme.bold("Review your answers:")}
1648
+ `);
1649
+ for (const line of reviewLines) {
1650
+ console.log(` ${line}`);
1369
1651
  }
1370
- options?.onStepComplete?.(currentStep.id, value, state);
1371
- } catch (error) {
1372
- if (!isMock && isUserCancel(error)) {
1373
- state = wizardReducer(state, { type: "CANCEL" }, config);
1374
- options?.onCancel?.(state);
1375
- if (!quiet) {
1376
- console.log(theme.warning("\n Wizard cancelled.\n"));
1377
- }
1378
- return state.answers;
1652
+ console.log();
1653
+ const { confirm: confirmPrompt } = await import("@inquirer/prompts");
1654
+ const ok = await confirmPrompt({
1655
+ message: "Everything look right?",
1656
+ default: true
1657
+ });
1658
+ if (ok) {
1659
+ needsReview = false;
1660
+ } else {
1661
+ const { select: selectPrompt } = await import("@inquirer/prompts");
1662
+ const stepsWithAnswers = config.steps.filter(
1663
+ (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
1664
+ );
1665
+ const stepToRevisit = await selectPrompt({
1666
+ message: "Which step would you like to change?",
1667
+ choices: stepsWithAnswers.map((s) => ({
1668
+ name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
1669
+ value: s.id
1670
+ }))
1671
+ });
1672
+ state = {
1673
+ ...state,
1674
+ currentStepId: stepToRevisit,
1675
+ status: "running"
1676
+ };
1379
1677
  }
1380
- throw error;
1678
+ } else {
1679
+ needsReview = false;
1381
1680
  }
1382
1681
  }
1383
1682
  if (state.status === "done" && !quiet) {
1384
1683
  renderer.renderSummary(state.answers, config.steps, theme);
1385
1684
  }
1386
- if (state.status === "done" && config.actions && config.actions.length > 0 && !isMock) {
1387
- await executeActions(config.actions, state.answers, theme);
1685
+ if (state.status === "done" && !isMock) {
1686
+ if (config.onComplete) {
1687
+ await executeOnComplete(config.onComplete, options?.configFilePath, state.answers, config, theme, renderer);
1688
+ }
1689
+ if (config.actions && config.actions.length > 0) {
1690
+ await executeActions(config.actions, state.answers, theme, renderer);
1691
+ }
1388
1692
  }
1693
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
1389
1694
  if (state.status === "done" && cacheEnabled) {
1390
1695
  const passwordStepIds = new Set(
1391
1696
  config.steps.filter((s) => s.type === "password").map((s) => s.id)
@@ -1398,6 +1703,9 @@ async function runWizard(config, options) {
1398
1703
  }
1399
1704
  saveCachedAnswers(config.meta.name, answersToCache, cacheDir);
1400
1705
  }
1706
+ if (state.status === "done") {
1707
+ clearProgress(config.meta.name);
1708
+ }
1401
1709
  return state.answers;
1402
1710
  } finally {
1403
1711
  if (userPlugins) {
@@ -1437,6 +1745,8 @@ function renderStep(renderer, step, state, theme) {
1437
1745
  case "message":
1438
1746
  renderer.renderMessage(step, state, theme);
1439
1747
  return Promise.resolve(true);
1748
+ case "note":
1749
+ return Promise.resolve(true);
1440
1750
  }
1441
1751
  }
1442
1752
  function resolveStepDefaults(step, cachedAnswers) {
@@ -1487,6 +1797,7 @@ function resolveStepDefaults(step, cachedAnswers) {
1487
1797
  }
1488
1798
  case "password":
1489
1799
  case "message":
1800
+ case "note":
1490
1801
  return step;
1491
1802
  }
1492
1803
  }
@@ -1496,7 +1807,7 @@ function getCachedDefault(stepId, cachedAnswers) {
1496
1807
  }
1497
1808
  function applyTemplateDefaults(step, templateAnswers) {
1498
1809
  if (!(step.id in templateAnswers)) return step;
1499
- if (step.type === "password" || step.type === "message") return step;
1810
+ if (step.type === "password" || step.type === "message" || step.type === "note") return step;
1500
1811
  const value = templateAnswers[step.id];
1501
1812
  switch (step.type) {
1502
1813
  case "text":
@@ -1585,13 +1896,34 @@ function resolveStepTemplates(step, answers) {
1585
1896
  case "confirm":
1586
1897
  case "toggle":
1587
1898
  case "message":
1899
+ case "note":
1588
1900
  return {
1589
1901
  ...step,
1590
1902
  description: step.description ? resolveTemplate(step.description, answers) : void 0
1591
1903
  };
1592
1904
  }
1593
1905
  }
1594
- async function executeActions(actions, answers, theme) {
1906
+ async function executeOnComplete(handlerPath, configFilePath, answers, config, theme, renderer) {
1907
+ if (renderer) emitEvent(renderer, { type: "oncomplete:start" }, theme);
1908
+ const resolvedPath = configFilePath ? resolve2(dirname2(configFilePath), handlerPath) : resolve2(handlerPath);
1909
+ try {
1910
+ const mod = await import(pathToFileURL(resolvedPath).href);
1911
+ if (typeof mod.default !== "function") {
1912
+ throw new Error(`onComplete handler "${handlerPath}" must export a default function`);
1913
+ }
1914
+ await mod.default({ answers, config });
1915
+ if (renderer) emitEvent(renderer, { type: "oncomplete:pass" }, theme);
1916
+ } catch (error) {
1917
+ const message = error instanceof Error ? error.message : String(error);
1918
+ if (renderer) emitEvent(renderer, { type: "oncomplete:fail", error: message }, theme);
1919
+ console.log(`
1920
+ ${theme.error("\u2717")} onComplete handler failed: ${message}
1921
+ `);
1922
+ throw error;
1923
+ }
1924
+ }
1925
+ async function executeActions(actions, answers, theme, renderer) {
1926
+ if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
1595
1927
  console.log(`
1596
1928
  ${theme.bold("Running actions...")}
1597
1929
  `);
@@ -1599,14 +1931,16 @@ async function executeActions(actions, answers, theme) {
1599
1931
  if (action.when && !evaluateCondition(action.when, answers)) {
1600
1932
  continue;
1601
1933
  }
1602
- const resolvedCommand = resolveTemplate(action.run, answers);
1603
- const resolvedName = action.name ? resolveTemplate(action.name, answers) : void 0;
1934
+ const resolvedCommand = resolveTemplateStrict(action.run, answers);
1935
+ const resolvedName = action.name ? resolveTemplateStrict(action.name, answers) : void 0;
1604
1936
  const label = resolvedName ?? resolvedCommand;
1605
1937
  try {
1606
1938
  execSync(resolvedCommand, { stdio: "pipe" });
1607
1939
  console.log(` ${theme.success("\u2713")} ${label}`);
1940
+ if (renderer) emitEvent(renderer, { type: "action:pass", name: label }, theme);
1608
1941
  } catch {
1609
1942
  console.log(` ${theme.error("\u2717")} ${label}`);
1943
+ if (renderer) emitEvent(renderer, { type: "action:fail", name: label }, theme);
1610
1944
  throw new Error(`Action failed: ${label}`);
1611
1945
  }
1612
1946
  }
@@ -1827,21 +2161,201 @@ var InkRenderer = class {
1827
2161
  }
1828
2162
  };
1829
2163
 
2164
+ // src/renderers/clack.ts
2165
+ import chalk2 from "chalk";
2166
+
2167
+ // src/renderers/symbols.ts
2168
+ function isUnicodeSupported() {
2169
+ if (process.platform === "win32") {
2170
+ return Boolean(process.env["WT_SESSION"]) || process.env["TERM_PROGRAM"] === "vscode";
2171
+ }
2172
+ return process.env["TERM"] !== "linux";
2173
+ }
2174
+ var unicode = isUnicodeSupported();
2175
+ var u = (unicodeChar, fallback) => unicode ? unicodeChar : fallback;
2176
+ var S_BAR_START = u("\u250C", "T");
2177
+ var S_BAR = u("\u2502", "|");
2178
+ var S_BAR_END = u("\u2514", "\u2014");
2179
+ var S_STEP_ACTIVE = u("\u25C6", "*");
2180
+ var S_STEP_SUBMIT = u("\u25C7", "o");
2181
+ var S_STEP_CANCEL = u("\u25A0", "x");
2182
+ var S_STEP_ERROR = u("\u25B2", "x");
2183
+ var S_CORNER_TR = u("\u256E", "+");
2184
+ var S_CORNER_BR = u("\u256F", "+");
2185
+ var S_BAR_H = u("\u2500", "-");
2186
+
2187
+ // src/renderers/clack.ts
2188
+ var ClackRenderer = class extends InquirerRenderer {
2189
+ spinnerInterval;
2190
+ spinnerFrameIndex = 0;
2191
+ renderStepHeader() {
2192
+ }
2193
+ renderGroupHeader() {
2194
+ }
2195
+ renderSummary(answers, steps, theme) {
2196
+ const entries = [];
2197
+ for (const step of steps) {
2198
+ const answer = answers[step.id];
2199
+ if (answer === void 0) continue;
2200
+ const display = Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
2201
+ entries.push({ id: step.id, display });
2202
+ }
2203
+ if (entries.length === 0) return;
2204
+ const title = "Summary";
2205
+ const lines = entries.map((e) => `${e.id}: ${e.display}`);
2206
+ this.writeNoteBox(title, lines, theme);
2207
+ }
2208
+ onEvent(event, theme) {
2209
+ switch (event.type) {
2210
+ case "session:start":
2211
+ this.handleSessionStart(event, theme);
2212
+ break;
2213
+ case "session:end":
2214
+ this.handleSessionEnd(event, theme);
2215
+ break;
2216
+ case "step:start":
2217
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2218
+ `);
2219
+ break;
2220
+ case "step:complete":
2221
+ this.handleStepComplete(event, theme);
2222
+ break;
2223
+ case "step:error":
2224
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(`${S_STEP_ERROR} ${event.error}`)}
2225
+ `);
2226
+ break;
2227
+ case "step:back":
2228
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted("\u21A9 Back")}
2229
+ `);
2230
+ break;
2231
+ case "group:start":
2232
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2233
+ `);
2234
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.accent(event.group)}
2235
+ `);
2236
+ break;
2237
+ case "note":
2238
+ this.writeNoteBox(event.title, event.body.split("\n"), theme);
2239
+ break;
2240
+ case "spinner:start":
2241
+ this.startSpinner(event.message, theme);
2242
+ break;
2243
+ case "spinner:stop":
2244
+ this.stopSpinner(event.message, theme);
2245
+ break;
2246
+ case "checks:start":
2247
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2248
+ `);
2249
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
2250
+ `);
2251
+ break;
2252
+ case "check:pass":
2253
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2254
+ `);
2255
+ break;
2256
+ case "check:fail":
2257
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
2258
+ `);
2259
+ break;
2260
+ case "actions:start":
2261
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2262
+ `);
2263
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running actions...")}
2264
+ `);
2265
+ break;
2266
+ case "action:pass":
2267
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2268
+ `);
2269
+ break;
2270
+ case "action:fail":
2271
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}
2272
+ `);
2273
+ break;
2274
+ }
2275
+ }
2276
+ handleSessionStart(event, theme) {
2277
+ process.stdout.write(`${chalk2.gray(S_BAR_START)} ${theme.bold(event.wizard)}
2278
+ `);
2279
+ if (event.description) {
2280
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted(event.description)}
2281
+ `);
2282
+ }
2283
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2284
+ `);
2285
+ }
2286
+ handleSessionEnd(event, theme) {
2287
+ if (event.cancelled) {
2288
+ process.stdout.write(`${theme.warning(S_STEP_CANCEL)} Cancelled
2289
+ `);
2290
+ } else {
2291
+ process.stdout.write(`${chalk2.gray(S_BAR_END)} ${theme.success("You're all set!")}
2292
+ `);
2293
+ }
2294
+ }
2295
+ handleStepComplete(event, theme) {
2296
+ const displayValue = event.step.type === "password" ? "****" : this.formatValue(event.value);
2297
+ process.stdout.write(
2298
+ `${chalk2.gray(S_STEP_SUBMIT)} ${event.step.message} ${chalk2.gray("\xB7")} ${theme.muted(displayValue)}
2299
+ `
2300
+ );
2301
+ }
2302
+ formatValue(value) {
2303
+ if (Array.isArray(value)) return value.map(String).join(", ");
2304
+ if (typeof value === "boolean") return value ? "Yes" : "No";
2305
+ return String(value);
2306
+ }
2307
+ writeNoteBox(title, lines, _theme) {
2308
+ const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
2309
+ const padded = maxLen + 2;
2310
+ const topLine = `${S_BAR_H.repeat(padded - title.length - 1)}${S_CORNER_TR}`;
2311
+ const bottomLine = `${S_BAR_H.repeat(padded)}${S_CORNER_BR}`;
2312
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2313
+ `);
2314
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256D${S_BAR_H} ${title} ${topLine}`)}
2315
+ `);
2316
+ for (const line of lines) {
2317
+ const pad = " ".repeat(maxLen - line.length);
2318
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(S_BAR)} ${line}${pad} ${chalk2.gray(S_BAR)}
2319
+ `);
2320
+ }
2321
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
2322
+ `);
2323
+ }
2324
+ startSpinner(message, theme) {
2325
+ this.spinnerFrameIndex = 0;
2326
+ const { frames, interval } = theme.spinner;
2327
+ this.spinnerInterval = setInterval(() => {
2328
+ const frame = frames[this.spinnerFrameIndex % frames.length];
2329
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
2330
+ this.spinnerFrameIndex++;
2331
+ }, interval);
2332
+ }
2333
+ stopSpinner(message, theme) {
2334
+ if (this.spinnerInterval) {
2335
+ clearInterval(this.spinnerInterval);
2336
+ this.spinnerInterval = void 0;
2337
+ }
2338
+ const finalMessage = message ?? "Done";
2339
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
2340
+ `);
2341
+ }
2342
+ };
2343
+
1830
2344
  // src/templates.ts
1831
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
1832
- import { join as join3 } from "path";
1833
- import { homedir as homedir3 } from "os";
1834
- var TEMPLATES_DIR = join3(homedir3(), ".config", "grimoire", "templates");
2345
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
2346
+ import { join as join4 } from "path";
2347
+ import { homedir as homedir4 } from "os";
2348
+ var TEMPLATES_DIR = join4(homedir4(), ".config", "grimoire", "templates");
1835
2349
  function getWizardTemplateDir(wizardName) {
1836
- return join3(TEMPLATES_DIR, slugify(wizardName));
2350
+ return join4(TEMPLATES_DIR, slugify(wizardName));
1837
2351
  }
1838
2352
  function getTemplateFilePath(wizardName, templateName) {
1839
- return join3(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
2353
+ return join4(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
1840
2354
  }
1841
2355
  function saveTemplate(wizardName, templateName, answers, excludeKeys) {
1842
2356
  try {
1843
2357
  const dir = getWizardTemplateDir(wizardName);
1844
- mkdirSync3(dir, { recursive: true });
2358
+ mkdirSync4(dir, { recursive: true });
1845
2359
  const filePath = getTemplateFilePath(wizardName, templateName);
1846
2360
  let answersToSave = answers;
1847
2361
  if (excludeKeys && excludeKeys.length > 0) {
@@ -1850,14 +2364,14 @@ function saveTemplate(wizardName, templateName, answers, excludeKeys) {
1850
2364
  Object.entries(answers).filter(([k]) => !excluded.has(k))
1851
2365
  );
1852
2366
  }
1853
- writeFileSync3(filePath, JSON.stringify(answersToSave, null, 2) + "\n", "utf-8");
2367
+ writeFileSync4(filePath, JSON.stringify(answersToSave, null, 2) + "\n", "utf-8");
1854
2368
  } catch {
1855
2369
  }
1856
2370
  }
1857
2371
  function loadTemplate(wizardName, templateName) {
1858
2372
  try {
1859
2373
  const filePath = getTemplateFilePath(wizardName, templateName);
1860
- const raw = readFileSync4(filePath, "utf-8");
2374
+ const raw = readFileSync5(filePath, "utf-8");
1861
2375
  const parsed = JSON.parse(raw);
1862
2376
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1863
2377
  return parsed;
@@ -1879,16 +2393,46 @@ function listTemplates(wizardName) {
1879
2393
  function deleteTemplate(wizardName, templateName) {
1880
2394
  try {
1881
2395
  const filePath = getTemplateFilePath(wizardName, templateName);
1882
- unlinkSync2(filePath);
2396
+ unlinkSync3(filePath);
1883
2397
  } catch {
1884
2398
  }
1885
2399
  }
2400
+
2401
+ // src/pipeline.ts
2402
+ async function runPipeline(steps, globalOptions) {
2403
+ const results = {};
2404
+ let accumulated = {};
2405
+ for (const step of steps) {
2406
+ let config;
2407
+ if (typeof step.config === "string") {
2408
+ const { loadWizardConfig: loadWizardConfig2 } = await Promise.resolve().then(() => (init_parser(), parser_exports));
2409
+ config = await loadWizardConfig2(step.config);
2410
+ } else {
2411
+ config = step.config;
2412
+ }
2413
+ if (step.when && !evaluateCondition(step.when, accumulated)) {
2414
+ continue;
2415
+ }
2416
+ const answers = await runWizard(config, {
2417
+ ...globalOptions,
2418
+ ...step.options,
2419
+ mockAnswers: step.mockAnswers,
2420
+ templateAnswers: accumulated
2421
+ });
2422
+ results[config.meta.name] = answers;
2423
+ accumulated = { ...accumulated, ...answers };
2424
+ }
2425
+ return results;
2426
+ }
1886
2427
  export {
2428
+ ClackRenderer,
2429
+ DEFAULT_SPINNER,
1887
2430
  InkRenderer,
1888
2431
  InquirerRenderer,
1889
2432
  clearCache,
1890
2433
  clearMruData,
1891
2434
  clearPlugins,
2435
+ clearProgress,
1892
2436
  createWizardState,
1893
2437
  defineWizard,
1894
2438
  deleteTemplate,
@@ -1900,6 +2444,7 @@ export {
1900
2444
  isStepVisible,
1901
2445
  listTemplates,
1902
2446
  loadCachedAnswers,
2447
+ loadProgress,
1903
2448
  loadTemplate,
1904
2449
  loadWizardConfig,
1905
2450
  parseWizardConfig,
@@ -1909,13 +2454,18 @@ export {
1909
2454
  renderBanner,
1910
2455
  resolveEnvDefault,
1911
2456
  resolveNextStep,
2457
+ resolveSpinner,
1912
2458
  resolveTemplate,
2459
+ resolveTemplateStrict,
1913
2460
  resolveTheme,
2461
+ runPipeline,
1914
2462
  runPreFlightChecks,
1915
2463
  runWizard,
1916
2464
  saveCachedAnswers,
2465
+ saveProgress,
1917
2466
  saveTemplate,
1918
2467
  slugify,
2468
+ spinners,
1919
2469
  validateStepAnswer,
1920
2470
  wizardReducer
1921
2471
  };