grimoire-wizard 0.3.1 → 0.4.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 (37) hide show
  1. package/README.md +173 -18
  2. package/dist/cli.js +503 -97
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +107 -3
  5. package/dist/index.js +832 -367
  6. package/dist/index.js.map +1 -1
  7. package/examples/json/all-features.json +66 -0
  8. package/examples/json/appstore-screenshot-wizard.json +362 -0
  9. package/examples/json/appstore-upload.json +104 -0
  10. package/examples/json/basic.json +72 -0
  11. package/examples/json/batch-generate.json +186 -0
  12. package/examples/json/brief-builder.json +519 -0
  13. package/examples/json/conditional.json +155 -0
  14. package/examples/json/cost-analyzer.json +83 -0
  15. package/examples/json/demo.json +130 -0
  16. package/examples/json/scraper-selector.json +63 -0
  17. package/examples/json/themed-catppuccin.json +39 -0
  18. package/examples/json/themed.json +103 -0
  19. package/examples/json/with-checks.json +47 -0
  20. package/examples/yaml/appstore-screenshot-wizard.yaml +321 -0
  21. package/examples/yaml/appstore-upload.yaml +84 -0
  22. package/examples/yaml/batch-generate.yaml +156 -0
  23. package/examples/yaml/brief-builder.yaml +429 -0
  24. package/examples/yaml/cost-analyzer.yaml +69 -0
  25. package/examples/yaml/pipeline.yaml +35 -0
  26. package/examples/yaml/scraper-selector.yaml +52 -0
  27. package/examples/yaml/themed-catppuccin.yaml +31 -0
  28. package/package.json +1 -1
  29. /package/examples/{all-features.yaml → yaml/all-features.yaml} +0 -0
  30. /package/examples/{base.yaml → yaml/base.yaml} +0 -0
  31. /package/examples/{basic.yaml → yaml/basic.yaml} +0 -0
  32. /package/examples/{conditional.yaml → yaml/conditional.yaml} +0 -0
  33. /package/examples/{demo.yaml → yaml/demo.yaml} +0 -0
  34. /package/examples/{ebay-mcp-setup.yaml → yaml/ebay-mcp-setup.yaml} +0 -0
  35. /package/examples/{extended.yaml → yaml/extended.yaml} +0 -0
  36. /package/examples/{themed.yaml → yaml/themed.yaml} +0 -0
  37. /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,308 @@ 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
+ });
223
+ preFlightCheckSchema = z.object({
224
+ name: z.string(),
225
+ run: z.string(),
226
+ message: z.string()
227
+ });
228
+ actionConfigSchema = z.object({
229
+ name: z.string().optional(),
230
+ run: z.string(),
231
+ when: conditionSchema.optional()
232
+ });
233
+ wizardConfigSchema = z.object({
234
+ meta: z.object({
235
+ name: z.string(),
236
+ version: z.string().optional(),
237
+ description: z.string().optional(),
238
+ review: z.boolean().optional()
239
+ }),
240
+ theme: themeConfigSchema.optional(),
241
+ steps: z.array(stepConfigSchema).min(1),
242
+ output: z.object({
243
+ format: z.enum(["json", "env", "yaml"]),
244
+ path: z.string().optional()
245
+ }).optional(),
246
+ extends: z.string().optional(),
247
+ checks: z.array(preFlightCheckSchema).optional(),
248
+ actions: z.array(actionConfigSchema).optional()
249
+ }).superRefine((config, ctx) => {
250
+ const stepIds = /* @__PURE__ */ new Set();
251
+ for (const step of config.steps) {
252
+ if (stepIds.has(step.id)) {
253
+ ctx.addIssue({
254
+ code: z.ZodIssueCode.custom,
255
+ message: `Duplicate step ID: "${step.id}"`,
256
+ path: ["steps"]
257
+ });
258
+ }
259
+ stepIds.add(step.id);
260
+ }
261
+ config.steps.forEach((step, i) => {
262
+ if (step.next && step.next !== "__done__" && !stepIds.has(step.next)) {
263
+ ctx.addIssue({
264
+ code: z.ZodIssueCode.custom,
265
+ message: `Step "${step.id}" references unknown next step: "${step.next}"`,
266
+ path: ["steps", i, "next"]
267
+ });
268
+ }
269
+ if (step.type === "select" && step.routes) {
270
+ for (const [key, target] of Object.entries(step.routes)) {
271
+ if (target !== "__done__" && !stepIds.has(target)) {
272
+ ctx.addIssue({
273
+ code: z.ZodIssueCode.custom,
274
+ message: `Step "${step.id}" route "${key}" references unknown step: "${target}"`,
275
+ path: ["steps", i, "routes", key]
276
+ });
277
+ }
278
+ }
279
+ }
280
+ if (step.when) {
281
+ collectConditionFieldIssues(step.when, stepIds, ctx, ["steps", i, "when"]);
282
+ }
283
+ if (step.type === "select" && step.routes && step.options) {
284
+ const optionValues = /* @__PURE__ */ new Set();
285
+ for (const o of step.options) {
286
+ if ("value" in o) {
287
+ optionValues.add(o.value);
288
+ }
289
+ }
290
+ for (const routeKey of Object.keys(step.routes)) {
291
+ if (!optionValues.has(routeKey)) {
292
+ ctx.addIssue({
293
+ code: z.ZodIssueCode.custom,
294
+ message: `Step "${step.id}" route key "${routeKey}" does not match any option value`,
295
+ path: ["steps", i, "routes", routeKey]
296
+ });
297
+ }
298
+ }
299
+ }
300
+ if (step.type === "select" || step.type === "multiselect" || step.type === "search") {
301
+ const hasOptions = step.options !== void 0;
302
+ const hasOptionsFrom = step.optionsFrom !== void 0;
303
+ if (hasOptions && hasOptionsFrom) {
304
+ ctx.addIssue({
305
+ code: z.ZodIssueCode.custom,
306
+ message: `Step "${step.id}" has both "options" and "optionsFrom" \u2014 only one is allowed`,
307
+ path: ["steps", i]
308
+ });
309
+ }
310
+ if (!hasOptions && !hasOptionsFrom) {
311
+ ctx.addIssue({
312
+ code: z.ZodIssueCode.custom,
313
+ message: `Step "${step.id}" must have either "options" or "optionsFrom"`,
314
+ path: ["steps", i]
315
+ });
316
+ }
317
+ }
318
+ if ((step.type === "number" || step.type === "multiselect") && step.min !== void 0 && step.max !== void 0 && step.min > step.max) {
319
+ ctx.addIssue({
320
+ code: z.ZodIssueCode.custom,
321
+ message: `Step "${step.id}" has min (${String(step.min)}) greater than max (${String(step.max)})`,
322
+ path: ["steps", i]
323
+ });
324
+ }
325
+ });
326
+ if (config.actions) {
327
+ config.actions.forEach((action, i) => {
328
+ if (action.when) {
329
+ collectConditionFieldIssues(action.when, stepIds, ctx, ["actions", i, "when"]);
330
+ }
331
+ });
332
+ }
333
+ });
334
+ }
335
+ });
313
336
 
314
337
  // src/parser.ts
338
+ var parser_exports = {};
339
+ __export(parser_exports, {
340
+ loadWizardConfig: () => loadWizardConfig,
341
+ parseWizardYAML: () => parseWizardYAML
342
+ });
315
343
  import { cosmiconfig } from "cosmiconfig";
316
344
  import { readFileSync } from "fs";
317
345
  import { dirname, resolve, isAbsolute } from "path";
318
346
  import { parse as parseYAML } from "yaml";
319
- var DONE_SENTINEL = "__done__";
320
347
  function buildStepGraph(steps) {
321
348
  const graph = /* @__PURE__ */ new Map();
322
349
  for (let i = 0; i < steps.length; i++) {
@@ -395,7 +422,6 @@ function mergeConfigs(parent, child) {
395
422
  actions: child.actions ?? parent.actions
396
423
  };
397
424
  }
398
- var OPTION_STEP_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "search"]);
399
425
  function resolveOptionsFromSteps(raw, configDir) {
400
426
  const steps = raw["steps"];
401
427
  if (!Array.isArray(steps)) return;
@@ -491,6 +517,19 @@ function parseWizardYAML(yamlString) {
491
517
  detectCycles(config);
492
518
  return config;
493
519
  }
520
+ var DONE_SENTINEL, OPTION_STEP_TYPES;
521
+ var init_parser = __esm({
522
+ "src/parser.ts"() {
523
+ "use strict";
524
+ init_schema();
525
+ DONE_SENTINEL = "__done__";
526
+ OPTION_STEP_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "search"]);
527
+ }
528
+ });
529
+
530
+ // src/index.ts
531
+ init_schema();
532
+ init_parser();
494
533
 
495
534
  // src/conditions.ts
496
535
  function isRecord(value) {
@@ -770,6 +809,67 @@ function applyValidationRule(rule, value) {
770
809
 
771
810
  // src/theme.ts
772
811
  import chalk from "chalk";
812
+
813
+ // src/themes/presets.ts
814
+ var THEME_PRESETS = {
815
+ default: {
816
+ primary: "#7C3AED",
817
+ success: "#10B981",
818
+ error: "#EF4444",
819
+ warning: "#F59E0B",
820
+ info: "#3B82F6",
821
+ muted: "#6B7280",
822
+ accent: "#8B5CF6"
823
+ },
824
+ catppuccin: {
825
+ primary: "#cba6f7",
826
+ success: "#a6e3a1",
827
+ error: "#f38ba8",
828
+ warning: "#fab387",
829
+ info: "#74c7ec",
830
+ muted: "#6c7086",
831
+ accent: "#f5c2e7"
832
+ },
833
+ dracula: {
834
+ primary: "#bd93f9",
835
+ success: "#50fa7b",
836
+ error: "#ff5555",
837
+ warning: "#ffb86c",
838
+ info: "#8be9fd",
839
+ muted: "#6272a4",
840
+ accent: "#ff79c6"
841
+ },
842
+ nord: {
843
+ primary: "#88c0d0",
844
+ success: "#a3be8c",
845
+ error: "#bf616a",
846
+ warning: "#ebcb8b",
847
+ info: "#81a1c1",
848
+ muted: "#4c566a",
849
+ accent: "#b48ead"
850
+ },
851
+ tokyonight: {
852
+ primary: "#7aa2f7",
853
+ success: "#9ece6a",
854
+ error: "#f7768e",
855
+ warning: "#e0af68",
856
+ info: "#7dcfff",
857
+ muted: "#565f89",
858
+ accent: "#bb9af7"
859
+ },
860
+ monokai: {
861
+ primary: "#ab9df2",
862
+ success: "#a9dc76",
863
+ error: "#ff6188",
864
+ warning: "#ffd866",
865
+ info: "#78dce8",
866
+ muted: "#727072",
867
+ accent: "#fc9867"
868
+ }
869
+ };
870
+ var PRESET_NAMES = Object.keys(THEME_PRESETS);
871
+
872
+ // src/theme.ts
773
873
  var DEFAULT_TOKENS = {
774
874
  primary: "#5B9BD5",
775
875
  success: "#6BCB77",
@@ -786,7 +886,8 @@ var DEFAULT_ICONS = {
786
886
  pointer: "\u203A"
787
887
  };
788
888
  function resolveTheme(themeConfig) {
789
- const tokens = { ...DEFAULT_TOKENS, ...themeConfig?.tokens };
889
+ const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
890
+ const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
790
891
  const icons = { ...DEFAULT_ICONS, ...themeConfig?.icons };
791
892
  return {
792
893
  primary: chalk.hex(tokens.primary),
@@ -1131,14 +1232,66 @@ function clearCache(wizardName, customDir) {
1131
1232
  }
1132
1233
  }
1133
1234
 
1134
- // src/mru.ts
1135
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
1235
+ // src/progress.ts
1236
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
1136
1237
  import { join as join2 } from "path";
1137
1238
  import { homedir as homedir2 } from "os";
1138
- var MRU_DIR = join2(homedir2(), ".config", "grimoire", "mru");
1239
+ var DEFAULT_PROGRESS_DIR = join2(homedir2(), ".config", "grimoire", "progress");
1240
+ function getProgressFilePath(wizardName, customDir) {
1241
+ const dir = customDir ?? DEFAULT_PROGRESS_DIR;
1242
+ return join2(dir, `${slugify(wizardName)}.json`);
1243
+ }
1244
+ function saveProgress(wizardName, state, customDir, excludeStepIds) {
1245
+ try {
1246
+ const dir = customDir ?? DEFAULT_PROGRESS_DIR;
1247
+ mkdirSync2(dir, { recursive: true });
1248
+ const excludeSet = new Set(excludeStepIds ?? []);
1249
+ const filteredAnswers = {};
1250
+ for (const [key, value] of Object.entries(state.answers)) {
1251
+ if (!excludeSet.has(key)) {
1252
+ filteredAnswers[key] = value;
1253
+ }
1254
+ }
1255
+ const progress = {
1256
+ currentStepId: state.currentStepId,
1257
+ answers: filteredAnswers,
1258
+ history: state.history,
1259
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1260
+ };
1261
+ const filePath = getProgressFilePath(wizardName, customDir);
1262
+ writeFileSync2(filePath, JSON.stringify(progress, null, 2) + "\n", "utf-8");
1263
+ } catch {
1264
+ }
1265
+ }
1266
+ function loadProgress(wizardName, customDir) {
1267
+ try {
1268
+ const filePath = getProgressFilePath(wizardName, customDir);
1269
+ const raw = readFileSync3(filePath, "utf-8");
1270
+ const parsed = JSON.parse(raw);
1271
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "currentStepId" in parsed && "answers" in parsed && "history" in parsed && "savedAt" in parsed) {
1272
+ return parsed;
1273
+ }
1274
+ return null;
1275
+ } catch {
1276
+ return null;
1277
+ }
1278
+ }
1279
+ function clearProgress(wizardName, customDir) {
1280
+ try {
1281
+ const filePath = getProgressFilePath(wizardName, customDir);
1282
+ unlinkSync2(filePath);
1283
+ } catch {
1284
+ }
1285
+ }
1286
+
1287
+ // src/mru.ts
1288
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
1289
+ import { join as join3 } from "path";
1290
+ import { homedir as homedir3 } from "os";
1291
+ var MRU_DIR = join3(homedir3(), ".config", "grimoire", "mru");
1139
1292
  function getMruFilePath(wizardName) {
1140
1293
  const safeName = wizardName.replace(/[^a-zA-Z0-9_-]/g, "_");
1141
- return join2(MRU_DIR, `${safeName}.json`);
1294
+ return join3(MRU_DIR, `${safeName}.json`);
1142
1295
  }
1143
1296
  function loadMruData(wizardName) {
1144
1297
  const filePath = getMruFilePath(wizardName);
@@ -1146,7 +1299,7 @@ function loadMruData(wizardName) {
1146
1299
  if (!existsSync(filePath)) {
1147
1300
  return {};
1148
1301
  }
1149
- const raw = readFileSync3(filePath, "utf-8");
1302
+ const raw = readFileSync4(filePath, "utf-8");
1150
1303
  const parsed = JSON.parse(raw);
1151
1304
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1152
1305
  return {};
@@ -1159,8 +1312,8 @@ function loadMruData(wizardName) {
1159
1312
  function saveMruData(wizardName, data) {
1160
1313
  const filePath = getMruFilePath(wizardName);
1161
1314
  try {
1162
- mkdirSync2(MRU_DIR, { recursive: true });
1163
- writeFileSync2(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1315
+ mkdirSync3(MRU_DIR, { recursive: true });
1316
+ writeFileSync3(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1164
1317
  } catch {
1165
1318
  }
1166
1319
  }
@@ -1216,20 +1369,28 @@ function clearMruData(wizardName) {
1216
1369
  const filePath = getMruFilePath(wizardName);
1217
1370
  try {
1218
1371
  if (existsSync(filePath)) {
1219
- writeFileSync2(filePath, "{}", "utf-8");
1372
+ writeFileSync3(filePath, "{}", "utf-8");
1220
1373
  }
1221
1374
  } catch {
1222
1375
  }
1223
1376
  }
1224
1377
 
1225
1378
  // src/runner.ts
1226
- function runPreFlightChecks(checks, theme) {
1379
+ function emitEvent(renderer, event, theme) {
1380
+ if (renderer.onEvent) {
1381
+ renderer.onEvent(event, theme);
1382
+ }
1383
+ }
1384
+ function runPreFlightChecks(checks, theme, renderer) {
1385
+ if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
1227
1386
  for (const check of checks) {
1228
1387
  try {
1229
1388
  execSync(check.run, { stdio: "pipe" });
1230
1389
  console.log(` ${theme.success("\u2713")} ${check.name}`);
1390
+ if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
1231
1391
  } catch {
1232
1392
  console.log(` ${theme.error("\u2717")} ${check.name}: ${check.message}`);
1393
+ if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
1233
1394
  throw new Error(`Pre-flight check failed: ${check.name} \u2014 ${check.message}`);
1234
1395
  }
1235
1396
  }
@@ -1239,7 +1400,7 @@ function getMockValue(step, mockAnswers) {
1239
1400
  if (step.id in mockAnswers) {
1240
1401
  return mockAnswers[step.id];
1241
1402
  }
1242
- if (step.type === "message") {
1403
+ if (step.type === "message" || step.type === "note") {
1243
1404
  return true;
1244
1405
  }
1245
1406
  const defaultValue = getStepDefault(step);
@@ -1267,6 +1428,7 @@ function getStepDefault(step) {
1267
1428
  return step.default;
1268
1429
  case "password":
1269
1430
  case "message":
1431
+ case "note":
1270
1432
  return void 0;
1271
1433
  }
1272
1434
  }
@@ -1280,6 +1442,21 @@ async function runWizard(config, options) {
1280
1442
  const cacheDir = typeof options?.cache === "object" ? options.cache.dir : void 0;
1281
1443
  const mruEnabled = !isMock && options?.mru !== false;
1282
1444
  let state = createWizardState(config);
1445
+ const resumeEnabled = !isMock && options?.resume !== false;
1446
+ if (resumeEnabled) {
1447
+ const saved = loadProgress(config.meta.name);
1448
+ if (saved) {
1449
+ const stepExists = config.steps.some((s) => s.id === saved.currentStepId);
1450
+ if (stepExists) {
1451
+ state = {
1452
+ ...state,
1453
+ currentStepId: saved.currentStepId,
1454
+ answers: { ...state.answers, ...saved.answers },
1455
+ history: saved.history
1456
+ };
1457
+ }
1458
+ }
1459
+ }
1283
1460
  const cachedAnswers = cacheEnabled ? loadCachedAnswers(config.meta.name, cacheDir) : void 0;
1284
1461
  const userPlugins = options?.plugins;
1285
1462
  if (userPlugins) {
@@ -1289,103 +1466,169 @@ async function runWizard(config, options) {
1289
1466
  }
1290
1467
  try {
1291
1468
  if (!isMock && config.checks && config.checks.length > 0) {
1292
- runPreFlightChecks(config.checks, theme);
1469
+ runPreFlightChecks(config.checks, theme, renderer);
1293
1470
  }
1294
1471
  if (!quiet) {
1295
1472
  printWizardHeader(config, theme, options?.plain);
1296
1473
  }
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) {
1474
+ const visibleStepsForCount = getVisibleSteps(config, state.answers);
1475
+ emitEvent(renderer, { type: "session:start", wizard: config.meta.name, description: config.meta.description, totalSteps: visibleStepsForCount.length }, theme);
1476
+ let needsReview = true;
1477
+ while (needsReview) {
1478
+ let previousGroup;
1479
+ while (state.status === "running") {
1480
+ const visibleSteps = getVisibleSteps(config, state.answers);
1481
+ const currentStep = config.steps.find((s) => s.id === state.currentStepId);
1482
+ if (!currentStep) {
1483
+ throw new Error(`Current step not found: "${state.currentStepId}"`);
1484
+ }
1305
1485
  if (currentStep.group !== void 0 && currentStep.group !== previousGroup) {
1306
1486
  const resolvedGroup = resolveTemplate(currentStep.group, state.answers);
1307
- renderer.renderGroupHeader(resolvedGroup, theme);
1487
+ if (!isMock) {
1488
+ renderer.renderGroupHeader(resolvedGroup, theme);
1489
+ }
1490
+ emitEvent(renderer, { type: "group:start", group: resolvedGroup }, theme);
1308
1491
  }
1309
1492
  previousGroup = currentStep.group;
1310
1493
  const stepIndex = visibleSteps.findIndex((s) => s.id === state.currentStepId);
1311
1494
  const resolvedMessage = resolveTemplate(currentStep.message, state.answers);
1312
1495
  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) {
1496
+ if (!isMock) {
1497
+ renderer.renderStepHeader(stepIndex, visibleSteps.length, resolvedMessage, theme, resolvedDescription);
1498
+ }
1499
+ emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
1500
+ if (currentStep.type === "note") {
1501
+ emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
1502
+ }
1503
+ if (options?.onBeforeStep) {
1504
+ await options.onBeforeStep(currentStep.id, currentStep, state);
1505
+ }
1506
+ const pluginStep = getPluginStep(currentStep.type);
1507
+ const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
1508
+ const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
1509
+ const templatedStep = resolveStepTemplates(withTemplate, state.answers);
1510
+ const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
1511
+ try {
1512
+ const value = isMock ? getMockValue(mruStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(mruStep), state, theme) : await renderStep(renderer, mruStep, state, theme);
1513
+ if (pluginStep?.validate) {
1514
+ const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
1515
+ if (pluginError) {
1516
+ if (isMock) {
1517
+ throw new Error(
1518
+ `Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
1519
+ );
1520
+ }
1521
+ console.log(theme.error(`
1522
+ ${pluginError}
1523
+ `));
1524
+ continue;
1525
+ }
1526
+ }
1527
+ const nextState = wizardReducer(state, { type: "NEXT", value }, config);
1528
+ if (nextState.errors[currentStep.id]) {
1529
+ const errorMsg = resolveTemplate(nextState.errors[currentStep.id] ?? "", state.answers);
1530
+ emitEvent(renderer, { type: "step:error", stepId: currentStep.id, error: errorMsg }, theme);
1328
1531
  if (isMock) {
1329
1532
  throw new Error(
1330
- `Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
1533
+ `Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
1331
1534
  );
1332
1535
  }
1333
1536
  console.log(theme.error(`
1334
- ${pluginError}
1537
+ ${errorMsg}
1335
1538
  `));
1539
+ state = { ...nextState, errors: {} };
1336
1540
  continue;
1337
1541
  }
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(`
1542
+ if (!isMock && options?.asyncValidate) {
1543
+ const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
1544
+ if (asyncError !== null) {
1545
+ console.log(theme.error(`
1357
1546
  ${asyncError}
1358
1547
  `));
1359
- state = { ...nextState, errors: {} };
1360
- continue;
1548
+ state = { ...nextState, errors: {} };
1549
+ continue;
1550
+ }
1551
+ }
1552
+ if (options?.onAfterStep) {
1553
+ await options.onAfterStep(currentStep.id, value, nextState);
1554
+ }
1555
+ state = nextState;
1556
+ emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
1557
+ if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1558
+ recordSelection(config.meta.name, currentStep.id, value);
1361
1559
  }
1560
+ options?.onStepComplete?.(currentStep.id, value, state);
1561
+ } catch (error) {
1562
+ if (!isMock && isUserCancel(error)) {
1563
+ state = wizardReducer(state, { type: "CANCEL" }, config);
1564
+ options?.onCancel?.(state);
1565
+ const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
1566
+ saveProgress(config.meta.name, {
1567
+ currentStepId: state.currentStepId,
1568
+ answers: state.answers,
1569
+ history: state.history
1570
+ }, void 0, passwordStepIds);
1571
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
1572
+ if (!quiet) {
1573
+ console.log(theme.warning("\n Wizard cancelled.\n"));
1574
+ }
1575
+ return state.answers;
1576
+ }
1577
+ throw error;
1362
1578
  }
1363
- if (options?.onAfterStep) {
1364
- await options.onAfterStep(currentStep.id, value, nextState);
1579
+ }
1580
+ if (config.meta.review && !isMock && state.status === "done") {
1581
+ const reviewLines = [];
1582
+ for (const step of config.steps) {
1583
+ const answer = state.answers[step.id];
1584
+ if (answer === void 0) continue;
1585
+ const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
1586
+ reviewLines.push(`${step.id}: ${display}`);
1365
1587
  }
1366
- state = nextState;
1367
- if (mruEnabled && isSelectLikeStep(currentStep.type)) {
1368
- recordSelection(config.meta.name, currentStep.id, value);
1588
+ emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
1589
+ console.log(`
1590
+ ${theme.bold("Review your answers:")}
1591
+ `);
1592
+ for (const line of reviewLines) {
1593
+ console.log(` ${line}`);
1369
1594
  }
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;
1595
+ console.log();
1596
+ const { confirm: confirmPrompt } = await import("@inquirer/prompts");
1597
+ const ok = await confirmPrompt({
1598
+ message: "Everything look right?",
1599
+ default: true
1600
+ });
1601
+ if (ok) {
1602
+ needsReview = false;
1603
+ } else {
1604
+ const { select: selectPrompt } = await import("@inquirer/prompts");
1605
+ const stepsWithAnswers = config.steps.filter(
1606
+ (s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
1607
+ );
1608
+ const stepToRevisit = await selectPrompt({
1609
+ message: "Which step would you like to change?",
1610
+ choices: stepsWithAnswers.map((s) => ({
1611
+ name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
1612
+ value: s.id
1613
+ }))
1614
+ });
1615
+ state = {
1616
+ ...state,
1617
+ currentStepId: stepToRevisit,
1618
+ status: "running"
1619
+ };
1379
1620
  }
1380
- throw error;
1621
+ } else {
1622
+ needsReview = false;
1381
1623
  }
1382
1624
  }
1383
1625
  if (state.status === "done" && !quiet) {
1384
1626
  renderer.renderSummary(state.answers, config.steps, theme);
1385
1627
  }
1386
1628
  if (state.status === "done" && config.actions && config.actions.length > 0 && !isMock) {
1387
- await executeActions(config.actions, state.answers, theme);
1629
+ await executeActions(config.actions, state.answers, theme, renderer);
1388
1630
  }
1631
+ emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
1389
1632
  if (state.status === "done" && cacheEnabled) {
1390
1633
  const passwordStepIds = new Set(
1391
1634
  config.steps.filter((s) => s.type === "password").map((s) => s.id)
@@ -1398,6 +1641,9 @@ async function runWizard(config, options) {
1398
1641
  }
1399
1642
  saveCachedAnswers(config.meta.name, answersToCache, cacheDir);
1400
1643
  }
1644
+ if (state.status === "done") {
1645
+ clearProgress(config.meta.name);
1646
+ }
1401
1647
  return state.answers;
1402
1648
  } finally {
1403
1649
  if (userPlugins) {
@@ -1437,6 +1683,8 @@ function renderStep(renderer, step, state, theme) {
1437
1683
  case "message":
1438
1684
  renderer.renderMessage(step, state, theme);
1439
1685
  return Promise.resolve(true);
1686
+ case "note":
1687
+ return Promise.resolve(true);
1440
1688
  }
1441
1689
  }
1442
1690
  function resolveStepDefaults(step, cachedAnswers) {
@@ -1487,6 +1735,7 @@ function resolveStepDefaults(step, cachedAnswers) {
1487
1735
  }
1488
1736
  case "password":
1489
1737
  case "message":
1738
+ case "note":
1490
1739
  return step;
1491
1740
  }
1492
1741
  }
@@ -1496,7 +1745,7 @@ function getCachedDefault(stepId, cachedAnswers) {
1496
1745
  }
1497
1746
  function applyTemplateDefaults(step, templateAnswers) {
1498
1747
  if (!(step.id in templateAnswers)) return step;
1499
- if (step.type === "password" || step.type === "message") return step;
1748
+ if (step.type === "password" || step.type === "message" || step.type === "note") return step;
1500
1749
  const value = templateAnswers[step.id];
1501
1750
  switch (step.type) {
1502
1751
  case "text":
@@ -1585,13 +1834,15 @@ function resolveStepTemplates(step, answers) {
1585
1834
  case "confirm":
1586
1835
  case "toggle":
1587
1836
  case "message":
1837
+ case "note":
1588
1838
  return {
1589
1839
  ...step,
1590
1840
  description: step.description ? resolveTemplate(step.description, answers) : void 0
1591
1841
  };
1592
1842
  }
1593
1843
  }
1594
- async function executeActions(actions, answers, theme) {
1844
+ async function executeActions(actions, answers, theme, renderer) {
1845
+ if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
1595
1846
  console.log(`
1596
1847
  ${theme.bold("Running actions...")}
1597
1848
  `);
@@ -1605,8 +1856,10 @@ async function executeActions(actions, answers, theme) {
1605
1856
  try {
1606
1857
  execSync(resolvedCommand, { stdio: "pipe" });
1607
1858
  console.log(` ${theme.success("\u2713")} ${label}`);
1859
+ if (renderer) emitEvent(renderer, { type: "action:pass", name: label }, theme);
1608
1860
  } catch {
1609
1861
  console.log(` ${theme.error("\u2717")} ${label}`);
1862
+ if (renderer) emitEvent(renderer, { type: "action:fail", name: label }, theme);
1610
1863
  throw new Error(`Action failed: ${label}`);
1611
1864
  }
1612
1865
  }
@@ -1827,21 +2080,201 @@ var InkRenderer = class {
1827
2080
  }
1828
2081
  };
1829
2082
 
2083
+ // src/renderers/clack.ts
2084
+ import chalk2 from "chalk";
2085
+
2086
+ // src/renderers/symbols.ts
2087
+ function isUnicodeSupported() {
2088
+ if (process.platform === "win32") {
2089
+ return Boolean(process.env["WT_SESSION"]) || process.env["TERM_PROGRAM"] === "vscode";
2090
+ }
2091
+ return process.env["TERM"] !== "linux";
2092
+ }
2093
+ var unicode = isUnicodeSupported();
2094
+ var u = (unicodeChar, fallback) => unicode ? unicodeChar : fallback;
2095
+ var S_BAR_START = u("\u250C", "T");
2096
+ var S_BAR = u("\u2502", "|");
2097
+ var S_BAR_END = u("\u2514", "\u2014");
2098
+ var S_STEP_ACTIVE = u("\u25C6", "*");
2099
+ var S_STEP_SUBMIT = u("\u25C7", "o");
2100
+ var S_STEP_CANCEL = u("\u25A0", "x");
2101
+ var S_STEP_ERROR = u("\u25B2", "x");
2102
+ var S_CORNER_TR = u("\u256E", "+");
2103
+ var S_CORNER_BR = u("\u256F", "+");
2104
+ var S_BAR_H = u("\u2500", "-");
2105
+ var S_SPINNER_FRAMES = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
2106
+
2107
+ // src/renderers/clack.ts
2108
+ var ClackRenderer = class extends InquirerRenderer {
2109
+ spinnerInterval;
2110
+ spinnerFrameIndex = 0;
2111
+ renderStepHeader() {
2112
+ }
2113
+ renderGroupHeader() {
2114
+ }
2115
+ renderSummary(answers, steps, theme) {
2116
+ const entries = [];
2117
+ for (const step of steps) {
2118
+ const answer = answers[step.id];
2119
+ if (answer === void 0) continue;
2120
+ const display = Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
2121
+ entries.push({ id: step.id, display });
2122
+ }
2123
+ if (entries.length === 0) return;
2124
+ const title = "Summary";
2125
+ const lines = entries.map((e) => `${e.id}: ${e.display}`);
2126
+ this.writeNoteBox(title, lines, theme);
2127
+ }
2128
+ onEvent(event, theme) {
2129
+ switch (event.type) {
2130
+ case "session:start":
2131
+ this.handleSessionStart(event, theme);
2132
+ break;
2133
+ case "session:end":
2134
+ this.handleSessionEnd(event, theme);
2135
+ break;
2136
+ case "step:start":
2137
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2138
+ `);
2139
+ break;
2140
+ case "step:complete":
2141
+ this.handleStepComplete(event, theme);
2142
+ break;
2143
+ case "step:error":
2144
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(`${S_STEP_ERROR} ${event.error}`)}
2145
+ `);
2146
+ break;
2147
+ case "step:back":
2148
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted("\u21A9 Back")}
2149
+ `);
2150
+ break;
2151
+ case "group:start":
2152
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2153
+ `);
2154
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.accent(event.group)}
2155
+ `);
2156
+ break;
2157
+ case "note":
2158
+ this.writeNoteBox(event.title, event.body.split("\n"), theme);
2159
+ break;
2160
+ case "spinner:start":
2161
+ this.startSpinner(event.message, theme);
2162
+ break;
2163
+ case "spinner:stop":
2164
+ this.stopSpinner(event.message, theme);
2165
+ break;
2166
+ case "checks:start":
2167
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2168
+ `);
2169
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
2170
+ `);
2171
+ break;
2172
+ case "check:pass":
2173
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2174
+ `);
2175
+ break;
2176
+ case "check:fail":
2177
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
2178
+ `);
2179
+ break;
2180
+ case "actions:start":
2181
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2182
+ `);
2183
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running actions...")}
2184
+ `);
2185
+ break;
2186
+ case "action:pass":
2187
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
2188
+ `);
2189
+ break;
2190
+ case "action:fail":
2191
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}
2192
+ `);
2193
+ break;
2194
+ }
2195
+ }
2196
+ handleSessionStart(event, theme) {
2197
+ process.stdout.write(`${chalk2.gray(S_BAR_START)} ${theme.bold(event.wizard)}
2198
+ `);
2199
+ if (event.description) {
2200
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted(event.description)}
2201
+ `);
2202
+ }
2203
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2204
+ `);
2205
+ }
2206
+ handleSessionEnd(event, theme) {
2207
+ if (event.cancelled) {
2208
+ process.stdout.write(`${theme.warning(S_STEP_CANCEL)} Cancelled
2209
+ `);
2210
+ } else {
2211
+ process.stdout.write(`${chalk2.gray(S_BAR_END)} ${theme.success("You're all set!")}
2212
+ `);
2213
+ }
2214
+ }
2215
+ handleStepComplete(event, theme) {
2216
+ const displayValue = event.step.type === "password" ? "****" : this.formatValue(event.value);
2217
+ process.stdout.write(
2218
+ `${chalk2.gray(S_STEP_SUBMIT)} ${event.step.message} ${chalk2.gray("\xB7")} ${theme.muted(displayValue)}
2219
+ `
2220
+ );
2221
+ }
2222
+ formatValue(value) {
2223
+ if (Array.isArray(value)) return value.map(String).join(", ");
2224
+ if (typeof value === "boolean") return value ? "Yes" : "No";
2225
+ return String(value);
2226
+ }
2227
+ writeNoteBox(title, lines, _theme) {
2228
+ const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
2229
+ const padded = maxLen + 2;
2230
+ const topLine = `${S_BAR_H.repeat(padded - title.length - 1)}${S_CORNER_TR}`;
2231
+ const bottomLine = `${S_BAR_H.repeat(padded)}${S_CORNER_BR}`;
2232
+ process.stdout.write(`${chalk2.gray(S_BAR)}
2233
+ `);
2234
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256D${S_BAR_H} ${title} ${topLine}`)}
2235
+ `);
2236
+ for (const line of lines) {
2237
+ const pad = " ".repeat(maxLen - line.length);
2238
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(S_BAR)} ${line}${pad} ${chalk2.gray(S_BAR)}
2239
+ `);
2240
+ }
2241
+ process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
2242
+ `);
2243
+ }
2244
+ startSpinner(message, _theme) {
2245
+ this.spinnerFrameIndex = 0;
2246
+ this.spinnerInterval = setInterval(() => {
2247
+ const frame = S_SPINNER_FRAMES[this.spinnerFrameIndex % S_SPINNER_FRAMES.length];
2248
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
2249
+ this.spinnerFrameIndex++;
2250
+ }, 80);
2251
+ }
2252
+ stopSpinner(message, theme) {
2253
+ if (this.spinnerInterval) {
2254
+ clearInterval(this.spinnerInterval);
2255
+ this.spinnerInterval = void 0;
2256
+ }
2257
+ const finalMessage = message ?? "Done";
2258
+ process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
2259
+ `);
2260
+ }
2261
+ };
2262
+
1830
2263
  // 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");
2264
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
2265
+ import { join as join4 } from "path";
2266
+ import { homedir as homedir4 } from "os";
2267
+ var TEMPLATES_DIR = join4(homedir4(), ".config", "grimoire", "templates");
1835
2268
  function getWizardTemplateDir(wizardName) {
1836
- return join3(TEMPLATES_DIR, slugify(wizardName));
2269
+ return join4(TEMPLATES_DIR, slugify(wizardName));
1837
2270
  }
1838
2271
  function getTemplateFilePath(wizardName, templateName) {
1839
- return join3(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
2272
+ return join4(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
1840
2273
  }
1841
2274
  function saveTemplate(wizardName, templateName, answers, excludeKeys) {
1842
2275
  try {
1843
2276
  const dir = getWizardTemplateDir(wizardName);
1844
- mkdirSync3(dir, { recursive: true });
2277
+ mkdirSync4(dir, { recursive: true });
1845
2278
  const filePath = getTemplateFilePath(wizardName, templateName);
1846
2279
  let answersToSave = answers;
1847
2280
  if (excludeKeys && excludeKeys.length > 0) {
@@ -1850,14 +2283,14 @@ function saveTemplate(wizardName, templateName, answers, excludeKeys) {
1850
2283
  Object.entries(answers).filter(([k]) => !excluded.has(k))
1851
2284
  );
1852
2285
  }
1853
- writeFileSync3(filePath, JSON.stringify(answersToSave, null, 2) + "\n", "utf-8");
2286
+ writeFileSync4(filePath, JSON.stringify(answersToSave, null, 2) + "\n", "utf-8");
1854
2287
  } catch {
1855
2288
  }
1856
2289
  }
1857
2290
  function loadTemplate(wizardName, templateName) {
1858
2291
  try {
1859
2292
  const filePath = getTemplateFilePath(wizardName, templateName);
1860
- const raw = readFileSync4(filePath, "utf-8");
2293
+ const raw = readFileSync5(filePath, "utf-8");
1861
2294
  const parsed = JSON.parse(raw);
1862
2295
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1863
2296
  return parsed;
@@ -1879,16 +2312,45 @@ function listTemplates(wizardName) {
1879
2312
  function deleteTemplate(wizardName, templateName) {
1880
2313
  try {
1881
2314
  const filePath = getTemplateFilePath(wizardName, templateName);
1882
- unlinkSync2(filePath);
2315
+ unlinkSync3(filePath);
1883
2316
  } catch {
1884
2317
  }
1885
2318
  }
2319
+
2320
+ // src/pipeline.ts
2321
+ async function runPipeline(steps, globalOptions) {
2322
+ const results = {};
2323
+ let accumulated = {};
2324
+ for (const step of steps) {
2325
+ let config;
2326
+ if (typeof step.config === "string") {
2327
+ const { loadWizardConfig: loadWizardConfig2 } = await Promise.resolve().then(() => (init_parser(), parser_exports));
2328
+ config = await loadWizardConfig2(step.config);
2329
+ } else {
2330
+ config = step.config;
2331
+ }
2332
+ if (step.when && !evaluateCondition(step.when, accumulated)) {
2333
+ continue;
2334
+ }
2335
+ const answers = await runWizard(config, {
2336
+ ...globalOptions,
2337
+ ...step.options,
2338
+ mockAnswers: step.mockAnswers,
2339
+ templateAnswers: accumulated
2340
+ });
2341
+ results[config.meta.name] = answers;
2342
+ accumulated = { ...accumulated, ...answers };
2343
+ }
2344
+ return results;
2345
+ }
1886
2346
  export {
2347
+ ClackRenderer,
1887
2348
  InkRenderer,
1888
2349
  InquirerRenderer,
1889
2350
  clearCache,
1890
2351
  clearMruData,
1891
2352
  clearPlugins,
2353
+ clearProgress,
1892
2354
  createWizardState,
1893
2355
  defineWizard,
1894
2356
  deleteTemplate,
@@ -1900,6 +2362,7 @@ export {
1900
2362
  isStepVisible,
1901
2363
  listTemplates,
1902
2364
  loadCachedAnswers,
2365
+ loadProgress,
1903
2366
  loadTemplate,
1904
2367
  loadWizardConfig,
1905
2368
  parseWizardConfig,
@@ -1911,9 +2374,11 @@ export {
1911
2374
  resolveNextStep,
1912
2375
  resolveTemplate,
1913
2376
  resolveTheme,
2377
+ runPipeline,
1914
2378
  runPreFlightChecks,
1915
2379
  runWizard,
1916
2380
  saveCachedAnswers,
2381
+ saveProgress,
1917
2382
  saveTemplate,
1918
2383
  slugify,
1919
2384
  validateStepAnswer,