grimoire-wizard 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1399 @@
1
+ <p align="center">
2
+ <img src="grimoire-pro.png" alt="Grimoire" width="720" />
3
+ </p>
4
+
5
+ <h1 align="center">grimoire</h1>
6
+
7
+ <p align="center">
8
+ A wizard's spell book. Your config is the spell, the CLI is the magic.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/grimoire-wizard"><img src="https://img.shields.io/npm/v/grimoire-wizard" alt="npm version" /></a>
13
+ <a href="./LICENSE"><img src="https://img.shields.io/npm/l/grimoire-wizard" alt="license" /></a>
14
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/grimoire-wizard" alt="node" /></a>
15
+ <a href="https://github.com/YosefHayim/grimoire/actions"><img src="https://img.shields.io/github/actions/workflow/status/YosefHayim/grimoire/ci.yml?label=tests" alt="tests" /></a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ Grimoire is a config-driven CLI wizard framework for Node.js. You write a YAML or JSON file describing your prompts, branching logic, and output format, then grimoire handles the rest: rendering interactive prompts, tracking navigation history, evaluating conditions, and writing structured output. No code required for simple wizards. Full TypeScript support when you need to go deeper. It works equally well as a standalone CLI tool, a library embedded in your own tooling, or a non-interactive automation step in CI/CD pipelines.
21
+
22
+ ## Features
23
+
24
+ - **10 input types** — text, select, multiselect, confirm, password, number, search, editor, path, toggle
25
+ - **Conditional branching** — show or skip steps based on previous answers using `when` conditions
26
+ - **Route-based navigation** — select steps can branch to different step sequences
27
+ - **Back-navigation** — built-in history stack lets users go back through steps
28
+ - **Step groups** — organize steps into named sections with visual headers
29
+ - **Visual progress bar** — step counter shown at each prompt
30
+ - **Theming** — 7 semantic color tokens and 4 icon overrides for full visual control
31
+ - **Structured output** — export answers as JSON, YAML, or `.env` format
32
+ - **Config inheritance** — `extends` keyword merges a base config into your wizard
33
+ - **Pre-flight checks** — run shell commands before the wizard starts; abort on failure
34
+ - **Plugin system** — register custom step types with their own render and validate logic
35
+ - **`$ENV_VAR` resolution** — use environment variables as default values in any step
36
+ - **Async validation** — hook into step completion to run async checks (API calls, file system, etc.)
37
+ - **`--dry-run` preview** — print the full step plan without running any prompts
38
+ - **`--mock` non-interactive mode** — supply preset answers as JSON for CI/CD pipelines
39
+ - **`--json` structured output** — emit a machine-readable JSON result envelope for AI agents and scripts
40
+ - **JSON Schema** — `grimoire.schema.json` for IDE autocomplete in VS Code and any JSON Schema-aware editor
41
+ - **Shell completions** — bash, zsh, and fish completion scripts via `grimoire completion`
42
+ - **`grimoire create` scaffolder** — interactively generate a new wizard config file
43
+ - **`grimoire demo` showcase** — run a built-in demo that exercises all 10 step types
44
+ - **Answer caching** — previous answers become defaults on the next run; password steps are never cached
45
+ - **Templates** — save and load named answer presets per wizard
46
+ - **MRU ordering** — frequently selected options float to the top of select/multiselect/search lists
47
+ - **`optionsFrom`** — load select/multiselect/search options from an external JSON or YAML file
48
+ - **Lifecycle hooks** — `onBeforeStep` and `onAfterStep` callbacks in the programmatic API
49
+ - **Ink renderer** — alternative renderer with box-drawing characters and progress percentages
50
+ - **ASCII art banner** — figlet + gradient banner shown at startup; suppressed with `--plain`
51
+ - **`--plain` / `--no-color` flags** — disable colors and banner for plain-text environments
52
+
53
+ ## Quick Start
54
+
55
+ ```bash
56
+ npm install -g grimoire-wizard
57
+ ```
58
+
59
+ Create `setup.yaml`:
60
+
61
+ ```yaml
62
+ meta:
63
+ name: Project Setup
64
+
65
+ steps:
66
+ - id: project-name
67
+ type: text
68
+ message: What is your project name?
69
+ validate:
70
+ - rule: required
71
+ - rule: minLength
72
+ value: 2
73
+
74
+ - id: language
75
+ type: select
76
+ message: Pick a language
77
+ options:
78
+ - { value: typescript, label: TypeScript }
79
+ - { value: javascript, label: JavaScript }
80
+
81
+ output:
82
+ format: json
83
+ ```
84
+
85
+ Run it:
86
+
87
+ ```bash
88
+ grimoire run setup.yaml
89
+ ```
90
+
91
+ Or scaffold a new config interactively:
92
+
93
+ ```bash
94
+ grimoire create my-wizard.yaml
95
+ ```
96
+
97
+ ## Installation
98
+
99
+ ```bash
100
+ npm install grimoire-wizard
101
+ ```
102
+
103
+ Node.js >= 18 required. ESM only — your project must use `"type": "module"` or import from `.mjs` files.
104
+
105
+ ## CLI Commands
106
+
107
+ ### `grimoire run <config>`
108
+
109
+ Run a wizard from a config file. Accepts `.yaml`, `.json`, `.js`, or `.ts` files.
110
+
111
+ ```bash
112
+ grimoire run setup.yaml
113
+ grimoire run setup.yaml -o answers.json
114
+ grimoire run setup.yaml --dry-run
115
+ grimoire run setup.yaml --mock '{"project-name":"my-app","language":"typescript"}'
116
+ grimoire run setup.yaml --json
117
+ ```
118
+
119
+ | Flag | Description |
120
+ |------|-------------|
121
+ | `-o, --output <path>` | Write answers to a file |
122
+ | `-f, --format <format>` | Output format: `json`, `yaml`, or `env` (default: `json`) |
123
+ | `-q, --quiet` | Suppress header and summary output |
124
+ | `--dry-run` | Print the step plan without running any prompts |
125
+ | `--mock <json>` | Run non-interactively with preset answers (JSON object string) |
126
+ | `--json` | Emit a structured JSON result envelope to stdout |
127
+ | `--no-cache` | Disable answer caching for this run |
128
+ | `--template <name>` | Load a saved template as default answers |
129
+ | `--renderer <type>` | Renderer to use: `inquirer` (default) or `ink` |
130
+ | `--plain` | Plain output mode (no colors, no banner) |
131
+ | `--no-color` | Disable colored output |
132
+
133
+ #### `--dry-run`
134
+
135
+ Prints every step with its type, ID, prompt message, visibility status, and any conditions or routes. Useful for reviewing a config before running it.
136
+
137
+ ```
138
+ Dry Run: "Project Setup"
139
+
140
+ Step 1 text project-name "What is your project name?"
141
+ Step 2 select language "Pick a language"
142
+ ```
143
+
144
+ #### `--mock`
145
+
146
+ Runs the wizard without any interactive prompts. Pass a JSON object where keys are step IDs and values are the answers. Steps with defaults will use their defaults if not provided in the mock object.
147
+
148
+ ```bash
149
+ grimoire run setup.yaml --mock '{"project-name":"my-app","language":"typescript"}'
150
+ ```
151
+
152
+ #### `--json`
153
+
154
+ Suppresses all interactive output and emits a single JSON object to stdout:
155
+
156
+ ```json
157
+ {
158
+ "ok": true,
159
+ "wizard": "Project Setup",
160
+ "answers": {
161
+ "project-name": "my-app",
162
+ "language": "typescript"
163
+ },
164
+ "stepsCompleted": 2,
165
+ "format": "json"
166
+ }
167
+ ```
168
+
169
+ On error, emits `{ "ok": false, "error": "..." }` and exits with code 1.
170
+
171
+ ---
172
+
173
+ ### `grimoire validate <config>`
174
+
175
+ Parse and validate a config file without running any prompts. Exits with a non-zero code if the config is invalid.
176
+
177
+ ```bash
178
+ grimoire validate setup.yaml
179
+ # Valid wizard config: "Project Setup"
180
+ # 2 steps defined
181
+ ```
182
+
183
+ ---
184
+
185
+ ### `grimoire create [output]`
186
+
187
+ Interactively scaffold a new wizard config file. Asks for a wizard name, description, number of steps, step types, output format, and optional theme. Writes a ready-to-run YAML file.
188
+
189
+ ```bash
190
+ grimoire create # writes to wizard.yaml
191
+ grimoire create my-wizard.yaml # writes to my-wizard.yaml
192
+ ```
193
+
194
+ ---
195
+
196
+ ### `grimoire demo`
197
+
198
+ Run the built-in demo wizard that exercises all 10 step types, step groups, and theming.
199
+
200
+ ```bash
201
+ grimoire demo
202
+ ```
203
+
204
+ ---
205
+
206
+ ### `grimoire completion <shell>`
207
+
208
+ Print a shell completion script to stdout. Pipe it into your shell's completion directory.
209
+
210
+ ```bash
211
+ grimoire completion bash
212
+ grimoire completion zsh
213
+ grimoire completion fish
214
+ ```
215
+
216
+ See [Shell Completions](#shell-completions) for installation instructions.
217
+
218
+ ---
219
+
220
+ ### `grimoire cache clear [name]`
221
+
222
+ Delete cached answers for a specific wizard, or all wizards if no name is given.
223
+
224
+ ```bash
225
+ grimoire cache clear # clears all cached answers
226
+ grimoire cache clear "Project Setup" # clears cache for one wizard
227
+ ```
228
+
229
+ ---
230
+
231
+ ### `grimoire template list <wizard-name>`
232
+
233
+ List all saved templates for a wizard.
234
+
235
+ ```bash
236
+ grimoire template list "Project Setup"
237
+ # Templates for "Project Setup":
238
+ # - staging
239
+ # - production
240
+ ```
241
+
242
+ ---
243
+
244
+ ### `grimoire template delete <wizard-name> <template-name>`
245
+
246
+ Delete a saved template.
247
+
248
+ ```bash
249
+ grimoire template delete "Project Setup" staging
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Config Format
255
+
256
+ A grimoire config has these top-level sections:
257
+
258
+ | Section | Required | Description |
259
+ |---------|----------|-------------|
260
+ | `meta` | yes | Wizard name, version, description |
261
+ | `steps` | yes | Array of step definitions |
262
+ | `output` | no | Output format and file path |
263
+ | `theme` | no | Color tokens and icon overrides |
264
+ | `checks` | no | Pre-flight shell commands |
265
+ | `extends` | no | Path to a base config to inherit from |
266
+
267
+ ```yaml
268
+ meta:
269
+ name: My Wizard
270
+ version: 1.0.0
271
+ description: A short description shown at startup.
272
+
273
+ extends: ./base.yaml
274
+
275
+ theme:
276
+ tokens:
277
+ primary: "#cba6f7"
278
+ icons:
279
+ pointer: ">"
280
+
281
+ checks:
282
+ - name: Git available
283
+ run: git --version
284
+ message: Git is required.
285
+
286
+ steps:
287
+ - id: project-name
288
+ type: text
289
+ message: What is your project name?
290
+
291
+ output:
292
+ format: json
293
+ path: answers.json
294
+ ```
295
+
296
+ ### `meta`
297
+
298
+ | Field | Required | Description |
299
+ |-------|----------|-------------|
300
+ | `name` | yes | Wizard name, shown in the header |
301
+ | `version` | no | Config version string |
302
+ | `description` | no | Short description shown below the name |
303
+
304
+ ---
305
+
306
+ ## Step Types Reference
307
+
308
+ Every step requires `id`, `type`, and `message`. All other fields are optional unless noted.
309
+
310
+ ### `text`
311
+
312
+ Free-form text input with optional placeholder, default, and validation.
313
+
314
+ ```yaml
315
+ - id: project-name
316
+ type: text
317
+ message: What is your project name?
318
+ placeholder: my-awesome-project
319
+ default: my-app
320
+ validate:
321
+ - rule: required
322
+ - rule: minLength
323
+ value: 2
324
+ - rule: maxLength
325
+ value: 64
326
+ - rule: pattern
327
+ value: "^[a-z0-9-]+$"
328
+ message: Only lowercase letters, numbers, and hyphens
329
+ ```
330
+
331
+ ### `select`
332
+
333
+ Single-choice list. Returns the selected `value` string. Options can have a `hint` and can be `disabled`.
334
+
335
+ ```yaml
336
+ - id: language
337
+ type: select
338
+ message: Pick a language
339
+ default: typescript
340
+ options:
341
+ - { value: typescript, label: TypeScript }
342
+ - { value: javascript, label: JavaScript }
343
+ - { value: python, label: Python }
344
+ - { value: go, label: Go, hint: "Fast and simple" }
345
+ - { value: rust, label: Rust, disabled: true }
346
+ routes:
347
+ typescript: ts-config
348
+ python: python-version
349
+ ```
350
+
351
+ Instead of inline `options`, use `optionsFrom` to load options from an external file. See [Dynamic Options (`optionsFrom`)](#dynamic-options-optionsfrom).
352
+
353
+ ### `multiselect`
354
+
355
+ Multi-choice list. Returns an array of selected `value` strings. Use `min` and `max` to constrain selection count.
356
+
357
+ ```yaml
358
+ - id: features
359
+ type: multiselect
360
+ message: Select features to include
361
+ min: 1
362
+ max: 4
363
+ options:
364
+ - { value: eslint, label: ESLint, hint: Linting }
365
+ - { value: prettier, label: Prettier, hint: Formatting }
366
+ - { value: vitest, label: Vitest, hint: Testing }
367
+ - { value: husky, label: Husky, hint: "Git hooks" }
368
+ ```
369
+
370
+ Supports `optionsFrom` as an alternative to inline `options`. See [Dynamic Options (`optionsFrom`)](#dynamic-options-optionsfrom).
371
+
372
+ ### `confirm`
373
+
374
+ Yes/no prompt. Returns a boolean.
375
+
376
+ ```yaml
377
+ - id: use-typescript
378
+ type: confirm
379
+ message: Use TypeScript?
380
+ default: true
381
+ ```
382
+
383
+ ### `password`
384
+
385
+ Masked text input. The value is included in output but never echoed to the terminal.
386
+
387
+ ```yaml
388
+ - id: api-key
389
+ type: password
390
+ message: Enter your API key
391
+ validate:
392
+ - rule: required
393
+ - rule: minLength
394
+ value: 32
395
+ message: API keys must be at least 32 characters
396
+ ```
397
+
398
+ ### `number`
399
+
400
+ Numeric input. Supports `min`, `max`, and `step` constraints.
401
+
402
+ ```yaml
403
+ - id: port
404
+ type: number
405
+ message: Dev server port
406
+ default: 3000
407
+ min: 1024
408
+ max: 65535
409
+ step: 1
410
+ ```
411
+
412
+ ### `search`
413
+
414
+ Searchable single-choice list with fuzzy filtering. Useful for long option lists.
415
+
416
+ ```yaml
417
+ - id: framework
418
+ type: search
419
+ message: Search for a framework
420
+ placeholder: Type to filter...
421
+ options:
422
+ - { value: nextjs, label: "Next.js" }
423
+ - { value: remix, label: Remix }
424
+ - { value: astro, label: Astro }
425
+ - { value: sveltekit, label: SvelteKit }
426
+ - { value: nuxt, label: Nuxt }
427
+ - { value: gatsby, label: Gatsby }
428
+ ```
429
+
430
+ Supports `optionsFrom` as an alternative to inline `options`. See [Dynamic Options (`optionsFrom`)](#dynamic-options-optionsfrom).
431
+
432
+ ### `editor`
433
+
434
+ Opens a multi-line text editor in the terminal. Returns the full text content when the user saves and exits.
435
+
436
+ ```yaml
437
+ - id: readme-content
438
+ type: editor
439
+ message: Write your README content
440
+ default: "# My Project\n\nA short description."
441
+ required: false
442
+ ```
443
+
444
+ ### `path`
445
+
446
+ File system path input with tab-completion for existing paths.
447
+
448
+ ```yaml
449
+ - id: project-dir
450
+ type: path
451
+ message: Where should we create the project?
452
+ placeholder: ./my-project
453
+ default: .
454
+ validate:
455
+ - rule: required
456
+ ```
457
+
458
+ ### `toggle`
459
+
460
+ A binary toggle with custom labels for each state. Returns a boolean.
461
+
462
+ ```yaml
463
+ - id: dark-mode
464
+ type: toggle
465
+ message: Color scheme
466
+ active: Dark
467
+ inactive: Light
468
+ default: true
469
+ ```
470
+
471
+ ---
472
+
473
+ ## Dynamic Options (`optionsFrom`)
474
+
475
+ `select`, `multiselect`, and `search` steps can load their options from an external JSON or YAML file instead of defining them inline. This is useful when the option list is large, shared across multiple wizards, or generated by another tool.
476
+
477
+ ```yaml
478
+ - id: framework
479
+ type: select
480
+ message: Pick a framework
481
+ optionsFrom: ./frameworks.json
482
+ ```
483
+
484
+ `frameworks.json`:
485
+
486
+ ```json
487
+ [
488
+ { "value": "nextjs", "label": "Next.js" },
489
+ { "value": "remix", "label": "Remix" },
490
+ { "value": "astro", "label": "Astro" }
491
+ ]
492
+ ```
493
+
494
+ Or as YAML:
495
+
496
+ ```yaml
497
+ # frameworks.yaml
498
+ - value: nextjs
499
+ label: Next.js
500
+ - value: remix
501
+ label: Remix
502
+ - value: astro
503
+ label: Astro
504
+ ```
505
+
506
+ ```yaml
507
+ - id: framework
508
+ type: select
509
+ message: Pick a framework
510
+ optionsFrom: ./frameworks.yaml
511
+ ```
512
+
513
+ The path in `optionsFrom` is resolved relative to the config file. Absolute paths are also accepted. You cannot use both `options` and `optionsFrom` on the same step. `optionsFrom` is not supported in `parseWizardYAML`; use `loadWizardConfig` with a file path instead.
514
+
515
+ ---
516
+
517
+ ## Step Groups
518
+
519
+ Add a `group` field to any step to display a section header when that group starts. The header is shown once, the first time a step in that group is reached.
520
+
521
+ ```yaml
522
+ steps:
523
+ - id: name
524
+ type: text
525
+ group: "Project Info"
526
+ message: Project name?
527
+
528
+ - id: description
529
+ type: text
530
+ group: "Project Info"
531
+ message: Short description?
532
+
533
+ - id: framework
534
+ type: select
535
+ group: "Tech Stack"
536
+ message: Pick a framework
537
+ options:
538
+ - { value: react, label: React }
539
+ - { value: vue, label: Vue }
540
+ ```
541
+
542
+ ---
543
+
544
+ ## Conditions
545
+
546
+ Use the `when` field on any step to show it only when a condition is met. Steps that don't pass their condition are skipped and excluded from the output.
547
+
548
+ ```yaml
549
+ - id: ts-config
550
+ type: select
551
+ message: Which tsconfig preset?
552
+ when:
553
+ field: language
554
+ equals: typescript
555
+ options:
556
+ - { value: strict, label: Strict }
557
+ - { value: base, label: Base }
558
+ ```
559
+
560
+ ### Condition operators
561
+
562
+ | Operator | Shape | Description |
563
+ |----------|-------|-------------|
564
+ | `equals` | `{ field, equals }` | Field value equals the given value |
565
+ | `notEquals` | `{ field, notEquals }` | Field value does not equal the given value |
566
+ | `includes` | `{ field, includes }` | Array field includes the given value |
567
+ | `notIncludes` | `{ field, notIncludes }` | Array field does not include the given value |
568
+ | `greaterThan` | `{ field, greaterThan }` | Numeric field is greater than the given value |
569
+ | `lessThan` | `{ field, lessThan }` | Numeric field is less than the given value |
570
+ | `isEmpty` | `{ field, isEmpty: true }` | Field is empty, null, or an empty array |
571
+ | `isNotEmpty` | `{ field, isNotEmpty: true }` | Field is not empty |
572
+
573
+ ### Compound conditions
574
+
575
+ Combine conditions with `all`, `any`, or `not`:
576
+
577
+ ```yaml
578
+ # All conditions must be true
579
+ when:
580
+ all:
581
+ - field: language
582
+ equals: typescript
583
+ - field: features
584
+ includes: eslint
585
+
586
+ # Any condition must be true
587
+ when:
588
+ any:
589
+ - field: project-type
590
+ equals: web
591
+ - field: project-type
592
+ equals: api
593
+
594
+ # Negate a condition
595
+ when:
596
+ not:
597
+ field: skip-tests
598
+ equals: true
599
+ ```
600
+
601
+ ---
602
+
603
+ ## Routes
604
+
605
+ A `select` step can branch to different step sequences based on the chosen value. Set `routes` to a map of option values to step IDs. Grimoire jumps to the mapped step instead of the next step in the list.
606
+
607
+ ```yaml
608
+ - id: project-type
609
+ type: select
610
+ message: What kind of project?
611
+ options:
612
+ - { value: web, label: "Web App" }
613
+ - { value: api, label: "REST API" }
614
+ - { value: cli, label: "CLI Tool" }
615
+ routes:
616
+ web: web-framework
617
+ api: api-framework
618
+ cli: cli-features
619
+
620
+ - id: web-framework
621
+ type: select
622
+ message: Which web framework?
623
+ options:
624
+ - { value: nextjs, label: "Next.js" }
625
+ - { value: remix, label: Remix }
626
+ next: deploy
627
+
628
+ - id: api-framework
629
+ type: select
630
+ message: Which API framework?
631
+ options:
632
+ - { value: express, label: Express }
633
+ - { value: fastify, label: Fastify }
634
+ next: deploy
635
+
636
+ - id: cli-features
637
+ type: multiselect
638
+ message: CLI features to include
639
+ options:
640
+ - { value: args, label: "Argument parsing" }
641
+ - { value: colors, label: "Colored output" }
642
+ next: deploy
643
+
644
+ - id: deploy
645
+ type: select
646
+ message: Deployment target?
647
+ options:
648
+ - { value: vercel, label: Vercel }
649
+ - { value: docker, label: Docker }
650
+ ```
651
+
652
+ Steps not reached via routing are skipped and excluded from output. Use `next` on any step to override the default sequential flow and jump to a specific step ID. The special value `__done__` ends the wizard immediately.
653
+
654
+ ---
655
+
656
+ ## Validation
657
+
658
+ Add a `validate` array to any step that supports it (`text`, `password`, `editor`, `path`). Rules are checked in order and the first failure stops validation.
659
+
660
+ ```yaml
661
+ validate:
662
+ - rule: required
663
+ - rule: minLength
664
+ value: 2
665
+ - rule: maxLength
666
+ value: 128
667
+ - rule: pattern
668
+ value: "^[a-zA-Z0-9_-]+$"
669
+ message: Only letters, numbers, underscores, and hyphens allowed
670
+ - rule: min
671
+ value: 0
672
+ - rule: max
673
+ value: 100
674
+ ```
675
+
676
+ | Rule | Applies to | Description |
677
+ |------|-----------|-------------|
678
+ | `required` | all | Value must not be empty |
679
+ | `minLength` | text, password, editor, path | Minimum character count |
680
+ | `maxLength` | text, password, editor, path | Maximum character count |
681
+ | `pattern` | text, password, editor, path | Must match the given regex string |
682
+ | `min` | number | Minimum numeric value |
683
+ | `max` | number | Maximum numeric value |
684
+
685
+ All rules accept an optional `message` field to override the default error text.
686
+
687
+ ---
688
+
689
+ ## Theming
690
+
691
+ Define a `theme` block in your config to customize colors and icons. Colors accept any 6-digit hex string. All tokens are optional and fall back to grimoire's defaults.
692
+
693
+ ```yaml
694
+ theme:
695
+ tokens:
696
+ primary: "#89b4fa"
697
+ success: "#a6e3a1"
698
+ error: "#f38ba8"
699
+ warning: "#fab387"
700
+ info: "#74c7ec"
701
+ muted: "#6c7086"
702
+ accent: "#cba6f7"
703
+ icons:
704
+ step: "●"
705
+ stepDone: "✔"
706
+ stepPending: "○"
707
+ pointer: "❯"
708
+ ```
709
+
710
+ ### Color tokens
711
+
712
+ | Token | Used for |
713
+ |-------|---------|
714
+ | `primary` | Step headers, active selections |
715
+ | `success` | Completion messages, check marks |
716
+ | `error` | Validation errors, failed checks |
717
+ | `warning` | Cancellation messages |
718
+ | `info` | Informational text |
719
+ | `muted` | Descriptions, secondary text |
720
+ | `accent` | Highlights, group headers |
721
+
722
+ ### Icons
723
+
724
+ | Icon | Used for |
725
+ |------|---------|
726
+ | `step` | Current step indicator |
727
+ | `stepDone` | Completed step indicator |
728
+ | `stepPending` | Upcoming step indicator |
729
+ | `pointer` | Selection cursor in lists |
730
+
731
+ ---
732
+
733
+ ## Environment Variables
734
+
735
+ Any `default` field that starts with `$` is treated as an environment variable reference. Grimoire resolves it at runtime from `process.env`. If the variable is not set, the literal string (e.g., `$MY_VAR`) is used as the default.
736
+
737
+ ```yaml
738
+ - id: api-url
739
+ type: text
740
+ message: API base URL
741
+ default: $API_BASE_URL
742
+
743
+ - id: port
744
+ type: number
745
+ message: Port number
746
+ default: $PORT
747
+
748
+ - id: debug
749
+ type: confirm
750
+ message: Enable debug mode?
751
+ default: $DEBUG_MODE
752
+ ```
753
+
754
+ Supported on: `text`, `select`, `search`, `editor`, `path`, `number`, `confirm`, `toggle`.
755
+
756
+ ---
757
+
758
+ ## Config Inheritance
759
+
760
+ Use `extends` to inherit from a base config. The child config's `steps`, `theme`, `output`, and `checks` replace the base config's values entirely. The `meta` from the child takes precedence.
761
+
762
+ ```yaml
763
+ # base.yaml
764
+ meta:
765
+ name: Base Wizard
766
+ theme:
767
+ tokens:
768
+ primary: "#7C3AED"
769
+ steps:
770
+ - id: name
771
+ type: text
772
+ message: Project name?
773
+ validate:
774
+ - rule: required
775
+ output:
776
+ format: json
777
+ ```
778
+
779
+ ```yaml
780
+ # extended.yaml
781
+ extends: ./base.yaml
782
+
783
+ meta:
784
+ name: Extended Wizard
785
+ description: Extends base config with additional steps
786
+
787
+ steps:
788
+ - id: name
789
+ type: text
790
+ message: Project name?
791
+ validate:
792
+ - rule: required
793
+
794
+ - id: language
795
+ type: select
796
+ message: Language?
797
+ options:
798
+ - { value: typescript, label: TypeScript }
799
+ - { value: javascript, label: JavaScript }
800
+
801
+ - id: confirm
802
+ type: confirm
803
+ message: Create project?
804
+ next: __done__
805
+
806
+ output:
807
+ format: yaml
808
+ path: project-config.yaml
809
+ ```
810
+
811
+ The `extends` path is resolved relative to the child config file.
812
+
813
+ ---
814
+
815
+ ## Pre-flight Checks
816
+
817
+ The `checks` block runs shell commands before the wizard starts. If any command exits with a non-zero code, grimoire prints the associated `message` and aborts. Pre-flight checks are skipped in `--mock` mode.
818
+
819
+ ```yaml
820
+ checks:
821
+ - name: Node.js installed
822
+ run: node --version
823
+ message: "Node.js is required. Install from https://nodejs.org"
824
+
825
+ - name: Git available
826
+ run: git --version
827
+ message: "Git is required. Install from https://git-scm.com"
828
+
829
+ - name: Docker running
830
+ run: docker info
831
+ message: "Docker must be running before deployment."
832
+ ```
833
+
834
+ | Field | Required | Description |
835
+ |-------|----------|-------------|
836
+ | `name` | yes | Display name shown in the pre-flight output |
837
+ | `run` | yes | Shell command to execute |
838
+ | `message` | yes | Error message shown if the command fails |
839
+
840
+ ---
841
+
842
+ ## Plugin System
843
+
844
+ Plugins let you register custom step types with their own render and validate logic. A plugin is a plain object with a `name` and a `steps` map.
845
+
846
+ ```typescript
847
+ import { runWizard, defineWizard } from 'grimoire-wizard'
848
+ import type { GrimoirePlugin } from 'grimoire-wizard'
849
+
850
+ const myPlugin: GrimoirePlugin = {
851
+ name: 'my-plugin',
852
+ steps: {
853
+ 'date-picker': {
854
+ async render(config, state, theme) {
855
+ // config is the raw step config object
856
+ // state is the current WizardState
857
+ // theme is the ResolvedTheme
858
+ const answer = await myDatePickerPrompt(config.message as string)
859
+ return answer
860
+ },
861
+ validate(value, config) {
862
+ if (!value) return 'A date is required'
863
+ return null // null means valid
864
+ },
865
+ },
866
+ },
867
+ }
868
+
869
+ const config = defineWizard({
870
+ meta: { name: 'My Wizard' },
871
+ steps: [
872
+ {
873
+ id: 'launch-date',
874
+ type: 'date-picker',
875
+ message: 'Pick a launch date',
876
+ } as any,
877
+ ],
878
+ output: { format: 'json' },
879
+ })
880
+
881
+ const answers = await runWizard(config, {
882
+ plugins: [myPlugin],
883
+ })
884
+ ```
885
+
886
+ Built-in step types (`text`, `select`, `multiselect`, `confirm`, `password`, `number`, `search`, `editor`, `path`, `toggle`) cannot be overridden by plugins.
887
+
888
+ ### Plugin interfaces
889
+
890
+ ```typescript
891
+ interface GrimoirePlugin {
892
+ name: string;
893
+ steps: Record<string, StepPlugin>;
894
+ }
895
+
896
+ interface StepPlugin {
897
+ render(
898
+ config: Record<string, unknown>,
899
+ state: WizardState,
900
+ theme: ResolvedTheme
901
+ ): Promise<unknown>;
902
+ validate?(value: unknown, config: Record<string, unknown>): string | null;
903
+ }
904
+ ```
905
+
906
+ ---
907
+
908
+ ## Programmatic API
909
+
910
+ ### Core functions
911
+
912
+ ```typescript
913
+ import { loadWizardConfig, runWizard, defineWizard } from 'grimoire-wizard'
914
+
915
+ // Load from a file (YAML, JSON, JS, or TS)
916
+ const config = await loadWizardConfig('./setup.yaml')
917
+ const answers = await runWizard(config)
918
+ console.log(answers)
919
+ ```
920
+
921
+ ### `defineWizard(config)`
922
+
923
+ Identity function that returns the config unchanged. Its value is type inference: TypeScript will check your config object against `WizardConfig` at compile time.
924
+
925
+ ```typescript
926
+ import { defineWizard, runWizard } from 'grimoire-wizard'
927
+
928
+ const config = defineWizard({
929
+ meta: {
930
+ name: 'Deploy Config',
931
+ description: 'Configure your deployment target.',
932
+ },
933
+ steps: [
934
+ {
935
+ id: 'environment',
936
+ type: 'select',
937
+ message: 'Target environment?',
938
+ options: [
939
+ { label: 'Production', value: 'prod' },
940
+ { label: 'Staging', value: 'staging' },
941
+ ],
942
+ },
943
+ {
944
+ id: 'confirm',
945
+ type: 'confirm',
946
+ message: 'Ready to deploy?',
947
+ default: false,
948
+ },
949
+ ],
950
+ output: { format: 'json' },
951
+ })
952
+
953
+ const answers = await runWizard(config, {
954
+ onStepComplete: (stepId, value, state) => {
955
+ console.log(`${stepId} answered: ${String(value)}`)
956
+ },
957
+ onCancel: (state) => {
958
+ console.log('Wizard cancelled')
959
+ process.exit(1)
960
+ },
961
+ })
962
+ ```
963
+
964
+ ### `RunWizardOptions`
965
+
966
+ | Option | Type | Description |
967
+ |--------|------|-------------|
968
+ | `renderer` | `WizardRenderer` | Custom renderer (defaults to `InquirerRenderer`) |
969
+ | `quiet` | `boolean` | Suppress title, description, and summary output |
970
+ | `plain` | `boolean` | Disable colors and banner (plain-text mode) |
971
+ | `mockAnswers` | `Record<string, unknown>` | Preset answers for non-interactive mode |
972
+ | `templateAnswers` | `Record<string, unknown>` | Pre-loaded template answers used as defaults |
973
+ | `onBeforeStep` | `(stepId, step, state) => void \| Promise<void>` | Called before each step is rendered |
974
+ | `onAfterStep` | `(stepId, value, state) => void \| Promise<void>` | Called after each step completes, before moving on |
975
+ | `onStepComplete` | `(stepId, value, state) => void` | Called after each step completes |
976
+ | `onCancel` | `(state) => void` | Called when the user cancels with Ctrl+C |
977
+ | `plugins` | `GrimoirePlugin[]` | Custom step type plugins |
978
+ | `asyncValidate` | `(stepId, value, answers) => Promise<string \| null>` | Async validation hook called after each step |
979
+ | `cache` | `boolean \| { dir?: string }` | Enable/disable answer caching, or set a custom cache directory (default: `true`) |
980
+ | `mru` | `boolean` | Enable/disable MRU ordering for select/multiselect/search steps (default: `true`) |
981
+
982
+ ### Async validation
983
+
984
+ The `asyncValidate` hook runs after each step's synchronous validation passes. Return a string to show as an error and re-prompt the step, or return `null` to accept the value.
985
+
986
+ ```typescript
987
+ const answers = await runWizard(config, {
988
+ asyncValidate: async (stepId, value, answers) => {
989
+ if (stepId === 'username') {
990
+ const taken = await checkUsernameAvailability(value as string)
991
+ if (taken) return 'That username is already taken'
992
+ }
993
+ return null
994
+ },
995
+ })
996
+ ```
997
+
998
+ ### Lifecycle hooks
999
+
1000
+ `onBeforeStep` fires just before a step is rendered. `onAfterStep` fires after the user answers but before the wizard advances. Both can be async.
1001
+
1002
+ ```typescript
1003
+ const answers = await runWizard(config, {
1004
+ onBeforeStep: async (stepId, step, state) => {
1005
+ console.log(`About to render: ${stepId}`)
1006
+ },
1007
+ onAfterStep: async (stepId, value, state) => {
1008
+ await logAnswer(stepId, value)
1009
+ },
1010
+ })
1011
+ ```
1012
+
1013
+ ### Ink renderer
1014
+
1015
+ The `InkRenderer` is an alternative to the default `InquirerRenderer`. It uses box-drawing characters, a filled progress bar with percentage, and formatted step headers.
1016
+
1017
+ ```typescript
1018
+ import { runWizard } from 'grimoire-wizard'
1019
+ import { InkRenderer } from 'grimoire-wizard/renderers/ink'
1020
+
1021
+ const answers = await runWizard(config, {
1022
+ renderer: new InkRenderer(),
1023
+ })
1024
+ ```
1025
+
1026
+ Or from the CLI:
1027
+
1028
+ ```bash
1029
+ grimoire run setup.yaml --renderer ink
1030
+ ```
1031
+
1032
+ ### Exported types
1033
+
1034
+ ```typescript
1035
+ import type {
1036
+ WizardConfig,
1037
+ StepConfig,
1038
+ TextStepConfig,
1039
+ SelectStepConfig,
1040
+ MultiSelectStepConfig,
1041
+ ConfirmStepConfig,
1042
+ PasswordStepConfig,
1043
+ NumberStepConfig,
1044
+ SearchStepConfig,
1045
+ EditorStepConfig,
1046
+ PathStepConfig,
1047
+ ToggleStepConfig,
1048
+ SelectOption,
1049
+ ValidationRule,
1050
+ Condition,
1051
+ ThemeConfig,
1052
+ ResolvedTheme,
1053
+ WizardState,
1054
+ WizardTransition,
1055
+ WizardRenderer,
1056
+ PreFlightCheck,
1057
+ RunWizardOptions,
1058
+ GrimoirePlugin,
1059
+ StepPlugin,
1060
+ } from 'grimoire-wizard'
1061
+ ```
1062
+
1063
+ ### Utility functions
1064
+
1065
+ ```typescript
1066
+ import {
1067
+ parseWizardConfig, // Parse and validate a raw config object against the schema
1068
+ parseWizardYAML, // Parse a YAML string into a WizardConfig
1069
+ evaluateCondition, // Evaluate a Condition against an answers map
1070
+ isStepVisible, // Check if a step should be shown given current answers
1071
+ createWizardState, // Create an initial WizardState from a config
1072
+ wizardReducer, // Pure reducer for wizard state transitions
1073
+ getVisibleSteps, // Get all currently visible steps given current answers
1074
+ resolveNextStep, // Resolve the next step ID (respects routes and next fields)
1075
+ validateStepAnswer, // Run validation rules against a value
1076
+ resolveTheme, // Merge a ThemeConfig with defaults into a ResolvedTheme
1077
+ resolveEnvDefault, // Resolve a $ENV_VAR string from process.env
1078
+ runPreFlightChecks, // Run a checks array and throw on first failure
1079
+ registerPlugin, // Register a GrimoirePlugin into the global registry
1080
+ getPluginStep, // Look up a registered StepPlugin by type name
1081
+ clearPlugins, // Clear all registered plugins
1082
+ // Cache
1083
+ loadCachedAnswers, // Load cached answers for a wizard by name
1084
+ saveCachedAnswers, // Save answers to the cache for a wizard
1085
+ clearCache, // Delete cached answers (one wizard or all)
1086
+ // MRU
1087
+ recordSelection, // Record a user's selection for MRU tracking
1088
+ getOrderedOptions, // Return options sorted by selection frequency
1089
+ // Templates
1090
+ saveTemplate, // Save a named answer preset for a wizard
1091
+ loadTemplate, // Load a named answer preset
1092
+ listTemplates, // List all saved template names for a wizard
1093
+ deleteTemplate, // Delete a named template
1094
+ // Banner
1095
+ renderBanner, // Render the ASCII art banner for a wizard name
1096
+ } from 'grimoire-wizard'
1097
+ ```
1098
+
1099
+ ---
1100
+
1101
+ ## Answer Caching
1102
+
1103
+ Grimoire remembers your answers between runs. The next time you run the same wizard, cached answers become the default values for each step, so you only need to change what's different.
1104
+
1105
+ Password steps are never cached.
1106
+
1107
+ Cache files are stored in `~/.config/grimoire/cache/` as JSON, one file per wizard (named by slugifying the wizard's `meta.name`).
1108
+
1109
+ Caching is enabled by default. Disable it for a single run with `--no-cache`:
1110
+
1111
+ ```bash
1112
+ grimoire run setup.yaml --no-cache
1113
+ ```
1114
+
1115
+ Clear the cache from the CLI:
1116
+
1117
+ ```bash
1118
+ grimoire cache clear # clears all wizards
1119
+ grimoire cache clear "Project Setup" # clears one wizard
1120
+ ```
1121
+
1122
+ Control caching in the programmatic API via the `cache` option:
1123
+
1124
+ ```typescript
1125
+ // Disable caching entirely
1126
+ const answers = await runWizard(config, { cache: false })
1127
+
1128
+ // Use a custom cache directory
1129
+ const answers = await runWizard(config, { cache: { dir: '/tmp/my-cache' } })
1130
+ ```
1131
+
1132
+ ---
1133
+
1134
+ ## Templates
1135
+
1136
+ Templates are named answer presets. Save a set of answers under a name, then load them as defaults on future runs. Useful for environments like staging vs. production that share the same wizard but need different values.
1137
+
1138
+ Templates are stored in `~/.config/grimoire/templates/<wizard-slug>/`.
1139
+
1140
+ ### Saving a template
1141
+
1142
+ Templates are saved programmatically after a run:
1143
+
1144
+ ```typescript
1145
+ import { runWizard, saveTemplate } from 'grimoire-wizard'
1146
+
1147
+ const answers = await runWizard(config)
1148
+ saveTemplate('Project Setup', 'production', answers)
1149
+ ```
1150
+
1151
+ ### Loading a template via CLI
1152
+
1153
+ ```bash
1154
+ grimoire run setup.yaml --template production
1155
+ ```
1156
+
1157
+ Template answers become the defaults for each step. The user can still change any value.
1158
+
1159
+ ### Managing templates
1160
+
1161
+ ```bash
1162
+ grimoire template list "Project Setup"
1163
+ grimoire template delete "Project Setup" production
1164
+ ```
1165
+
1166
+ ### Programmatic API
1167
+
1168
+ ```typescript
1169
+ import { saveTemplate, loadTemplate, listTemplates, deleteTemplate } from 'grimoire-wizard'
1170
+
1171
+ saveTemplate('Project Setup', 'staging', answers)
1172
+
1173
+ const staging = loadTemplate('Project Setup', 'staging')
1174
+
1175
+ const names = listTemplates('Project Setup')
1176
+ // ['production', 'staging']
1177
+
1178
+ deleteTemplate('Project Setup', 'staging')
1179
+ ```
1180
+
1181
+ ---
1182
+
1183
+ ## MRU (Most Recently Used) Ordering
1184
+
1185
+ For `select`, `multiselect`, and `search` steps, grimoire tracks which options you pick and floats the most frequently chosen ones to the top of the list on subsequent runs. Options you've never picked stay in their original order below.
1186
+
1187
+ MRU data is stored in `~/.config/grimoire/mru/` and is per-wizard, per-step.
1188
+
1189
+ MRU ordering is enabled by default. Disable it programmatically:
1190
+
1191
+ ```typescript
1192
+ const answers = await runWizard(config, { mru: false })
1193
+ ```
1194
+
1195
+ There's no CLI flag for this. It's a programmatic-only option.
1196
+
1197
+ ---
1198
+
1199
+ ## Banner
1200
+
1201
+ When grimoire starts a wizard in interactive mode, it renders the wizard name as ASCII art using figlet with a purple-to-blue-to-green gradient. If figlet rendering fails for any reason, it falls back to plain bold text.
1202
+
1203
+ The banner is suppressed in `--quiet` mode and in `--plain` mode. Use `--plain` to get clean, color-free output suitable for terminals that don't support ANSI codes or for piping:
1204
+
1205
+ ```bash
1206
+ grimoire run setup.yaml --plain
1207
+ ```
1208
+
1209
+ The `NO_COLOR` environment variable also disables colors and the banner:
1210
+
1211
+ ```bash
1212
+ NO_COLOR=1 grimoire run setup.yaml
1213
+ ```
1214
+
1215
+ ---
1216
+
1217
+ ## CI/CD Integration
1218
+
1219
+ Combine `--mock` and `--json` to run wizards non-interactively in pipelines and capture structured output.
1220
+
1221
+ ```bash
1222
+ # Run with preset answers, capture JSON output
1223
+ grimoire run deploy.yaml \
1224
+ --mock '{"environment":"staging","version":"1.2.3","confirm":true}' \
1225
+ --json > deploy-answers.json
1226
+
1227
+ # Use the output in subsequent steps
1228
+ cat deploy-answers.json | jq '.answers.environment'
1229
+ ```
1230
+
1231
+ The `--json` flag always emits to stdout. On success:
1232
+
1233
+ ```json
1234
+ {
1235
+ "ok": true,
1236
+ "wizard": "Deployment Wizard",
1237
+ "answers": { "environment": "staging", "version": "1.2.3" },
1238
+ "stepsCompleted": 3,
1239
+ "format": "json"
1240
+ }
1241
+ ```
1242
+
1243
+ On failure:
1244
+
1245
+ ```json
1246
+ {
1247
+ "ok": false,
1248
+ "error": "Mock mode: no answer provided for step \"confirm\" and no default available"
1249
+ }
1250
+ ```
1251
+
1252
+ Exit code is `0` on success and `1` on failure, so pipelines can branch on the result.
1253
+
1254
+ ---
1255
+
1256
+ ## JSON Schema
1257
+
1258
+ Grimoire ships a JSON Schema at `schema/grimoire.schema.json`. Add it to your editor for autocomplete and inline validation of config files.
1259
+
1260
+ ### VS Code
1261
+
1262
+ Add this to your workspace `.vscode/settings.json`:
1263
+
1264
+ ```json
1265
+ {
1266
+ "yaml.schemas": {
1267
+ "./node_modules/grimoire-wizard/schema/grimoire.schema.json": "*.wizard.yaml"
1268
+ }
1269
+ }
1270
+ ```
1271
+
1272
+ Or add a `$schema` comment at the top of any config file:
1273
+
1274
+ ```yaml
1275
+ # yaml-language-server: $schema=./node_modules/grimoire-wizard/schema/grimoire.schema.json
1276
+ meta:
1277
+ name: My Wizard
1278
+ ```
1279
+
1280
+ ### Other editors
1281
+
1282
+ Point your editor's JSON Schema support at:
1283
+
1284
+ ```
1285
+ ./node_modules/grimoire-wizard/schema/grimoire.schema.json
1286
+ ```
1287
+
1288
+ ---
1289
+
1290
+ ## Shell Completions
1291
+
1292
+ ### Bash
1293
+
1294
+ ```bash
1295
+ grimoire completion bash > ~/.bash_completion.d/grimoire
1296
+ source ~/.bash_completion.d/grimoire
1297
+ ```
1298
+
1299
+ Or add to your `.bashrc`:
1300
+
1301
+ ```bash
1302
+ eval "$(grimoire completion bash)"
1303
+ ```
1304
+
1305
+ ### Zsh
1306
+
1307
+ ```bash
1308
+ grimoire completion zsh > ~/.zsh/completions/_grimoire
1309
+ ```
1310
+
1311
+ Make sure `~/.zsh/completions` is in your `$fpath`. Then reload:
1312
+
1313
+ ```bash
1314
+ autoload -Uz compinit && compinit
1315
+ ```
1316
+
1317
+ ### Fish
1318
+
1319
+ ```bash
1320
+ grimoire completion fish > ~/.config/fish/completions/grimoire.fish
1321
+ ```
1322
+
1323
+ ---
1324
+
1325
+ ## Examples
1326
+
1327
+ The `examples/` directory contains 8 ready-to-run configs.
1328
+
1329
+ ### `examples/basic.yaml`
1330
+
1331
+ A 6-step project setup wizard covering the core input types: project name (text with validation and pattern), description (optional text), language (select with default), features (multiselect with min), license (select), and confirm. Good starting point for your own configs.
1332
+
1333
+ ```bash
1334
+ grimoire run examples/basic.yaml
1335
+ ```
1336
+
1337
+ ### `examples/conditional.yaml`
1338
+
1339
+ An 11-step wizard that branches based on project type. A `select` step with `routes` sends users down one of four paths (web, api, cli, lib), each with its own framework or feature selection step. Additional steps use `when` conditions to show or hide options based on earlier answers.
1340
+
1341
+ ```bash
1342
+ grimoire run examples/conditional.yaml
1343
+ ```
1344
+
1345
+ ### `examples/themed.yaml`
1346
+
1347
+ An 8-step account setup wizard styled with the Catppuccin Mocha color palette. Demonstrates all 7 color tokens and custom icon overrides. Steps cover username, email, role, experience, interests, newsletter, password, and confirm.
1348
+
1349
+ ```bash
1350
+ grimoire run examples/themed.yaml
1351
+ ```
1352
+
1353
+ ### `examples/demo.yaml`
1354
+
1355
+ The built-in demo config used by `grimoire demo`. Exercises all 10 step types across 4 named groups: Text Inputs (text, editor, path, password), Selections (select, multiselect, search), Toggles and Numbers (toggle, number), and Confirmation (confirm).
1356
+
1357
+ ```bash
1358
+ grimoire run examples/demo.yaml
1359
+ # or
1360
+ grimoire demo
1361
+ ```
1362
+
1363
+ ### `examples/all-features.yaml`
1364
+
1365
+ A focused showcase of the newer step types: search, path, toggle, editor, and number, organized into two groups (Project Setup and Configuration).
1366
+
1367
+ ```bash
1368
+ grimoire run examples/all-features.yaml
1369
+ ```
1370
+
1371
+ ### `examples/base.yaml`
1372
+
1373
+ A minimal base config with a name step, optional description, and confirm. Intended to be inherited via `extends`.
1374
+
1375
+ ```bash
1376
+ grimoire run examples/base.yaml
1377
+ ```
1378
+
1379
+ ### `examples/extended.yaml`
1380
+
1381
+ Extends `base.yaml` and adds a language select step. Demonstrates how `extends` merges configs and how the child's `output` and `meta` override the base.
1382
+
1383
+ ```bash
1384
+ grimoire run examples/extended.yaml
1385
+ ```
1386
+
1387
+ ### `examples/with-checks.yaml`
1388
+
1389
+ A deployment wizard with two pre-flight checks (Node.js and Git). Shows how `checks` abort the wizard before any prompts if the environment isn't ready.
1390
+
1391
+ ```bash
1392
+ grimoire run examples/with-checks.yaml
1393
+ ```
1394
+
1395
+ ---
1396
+
1397
+ ## License
1398
+
1399
+ MIT. PRs welcome.