grimoire-wizard 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli.js +105 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +101 -1
- package/dist/index.js +96 -11
- package/dist/index.js.map +1 -1
- package/examples/handlers/setup-project.ts +9 -0
- package/examples/json/with-actions.json +61 -0
- package/examples/json/with-oncomplete.json +45 -0
- package/examples/yaml/with-actions.yaml +45 -0
- package/examples/yaml/with-oncomplete.yaml +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1549,6 +1549,18 @@ grimoire run examples/yaml/pipeline.yaml
|
|
|
1549
1549
|
|
|
1550
1550
|
---
|
|
1551
1551
|
|
|
1552
|
+
## AI Agent Integration
|
|
1553
|
+
|
|
1554
|
+
Building wizard configs with an AI assistant? Give it this reference:
|
|
1555
|
+
|
|
1556
|
+
```
|
|
1557
|
+
https://raw.githubusercontent.com/YosefHayim/grimoire/main/docs/GRIMOIRE_REFERENCE.md
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
This single file contains the complete grimoire-wizard schema, all step types, conditions, actions, handler contracts, and annotated examples — everything an AI agent needs to generate correct configs in one shot.
|
|
1561
|
+
|
|
1562
|
+
---
|
|
1563
|
+
|
|
1552
1564
|
## License
|
|
1553
1565
|
|
|
1554
1566
|
MIT. PRs welcome.
|
package/dist/cli.js
CHANGED
|
@@ -184,7 +184,14 @@ var themeConfigSchema = z.object({
|
|
|
184
184
|
stepDone: z.string().optional(),
|
|
185
185
|
stepPending: z.string().optional(),
|
|
186
186
|
pointer: z.string().optional()
|
|
187
|
-
}).optional()
|
|
187
|
+
}).optional(),
|
|
188
|
+
spinner: z.union([
|
|
189
|
+
z.string(),
|
|
190
|
+
z.object({
|
|
191
|
+
frames: z.array(z.string()).min(1),
|
|
192
|
+
interval: z.number().positive().optional()
|
|
193
|
+
})
|
|
194
|
+
]).optional()
|
|
188
195
|
});
|
|
189
196
|
var preFlightCheckSchema = z.object({
|
|
190
197
|
name: z.string(),
|
|
@@ -211,7 +218,8 @@ var wizardConfigSchema = z.object({
|
|
|
211
218
|
}).optional(),
|
|
212
219
|
extends: z.string().optional(),
|
|
213
220
|
checks: z.array(preFlightCheckSchema).optional(),
|
|
214
|
-
actions: z.array(actionConfigSchema).optional()
|
|
221
|
+
actions: z.array(actionConfigSchema).optional(),
|
|
222
|
+
onComplete: z.string().optional()
|
|
215
223
|
}).superRefine((config, ctx) => {
|
|
216
224
|
const stepIds = /* @__PURE__ */ new Set();
|
|
217
225
|
for (const step of config.steps) {
|
|
@@ -486,6 +494,8 @@ async function loadWizardConfig(filePath) {
|
|
|
486
494
|
|
|
487
495
|
// src/runner.ts
|
|
488
496
|
import { execSync } from "child_process";
|
|
497
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
498
|
+
import { pathToFileURL } from "url";
|
|
489
499
|
|
|
490
500
|
// src/conditions.ts
|
|
491
501
|
function isRecord(value) {
|
|
@@ -825,6 +835,41 @@ var THEME_PRESETS = {
|
|
|
825
835
|
};
|
|
826
836
|
var PRESET_NAMES = Object.keys(THEME_PRESETS);
|
|
827
837
|
|
|
838
|
+
// src/spinners.ts
|
|
839
|
+
var spinners = {
|
|
840
|
+
dots: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] },
|
|
841
|
+
dots2: { interval: 80, frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"] },
|
|
842
|
+
line: { interval: 130, frames: ["-", "\\", "|", "/"] },
|
|
843
|
+
arc: { interval: 100, frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"] },
|
|
844
|
+
circle: { interval: 80, frames: ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] },
|
|
845
|
+
circleHalves: { interval: 50, frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"] },
|
|
846
|
+
triangle: { interval: 50, frames: ["\u25E2", "\u25E3", "\u25E4", "\u25E5"] },
|
|
847
|
+
pipe: { interval: 100, frames: ["\u2524", "\u2518", "\u2534", "\u2514", "\u251C", "\u250C", "\u252C", "\u2510"] },
|
|
848
|
+
arrow: { interval: 100, frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"] },
|
|
849
|
+
arrow3: { interval: 120, frames: ["\u25B9\u25B9\u25B9\u25B9\u25B9", "\u25B8\u25B9\u25B9\u25B9\u25B9", "\u25B9\u25B8\u25B9\u25B9\u25B9", "\u25B9\u25B9\u25B8\u25B9\u25B9", "\u25B9\u25B9\u25B9\u25B8\u25B9", "\u25B9\u25B9\u25B9\u25B9\u25B8"] },
|
|
850
|
+
bouncingBar: { interval: 80, frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"] },
|
|
851
|
+
bouncingBall: { interval: 80, frames: ["( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF)", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "(\u25CF )"] },
|
|
852
|
+
simpleDots: { interval: 400, frames: [". ", ".. ", "...", " "] },
|
|
853
|
+
aesthetic: { interval: 80, frames: ["\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0", "\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1"] },
|
|
854
|
+
star: { interval: 70, frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"] }
|
|
855
|
+
};
|
|
856
|
+
var DEFAULT_SPINNER = "circle";
|
|
857
|
+
function resolveSpinner(config) {
|
|
858
|
+
if (!config) {
|
|
859
|
+
return spinners[DEFAULT_SPINNER];
|
|
860
|
+
}
|
|
861
|
+
if (typeof config === "string") {
|
|
862
|
+
if (config in spinners) {
|
|
863
|
+
return spinners[config];
|
|
864
|
+
}
|
|
865
|
+
throw new Error(`Unknown spinner preset: "${config}". Available: ${Object.keys(spinners).join(", ")}`);
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
frames: config.frames,
|
|
869
|
+
interval: config.interval ?? 80
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
828
873
|
// src/theme.ts
|
|
829
874
|
var DEFAULT_TOKENS = {
|
|
830
875
|
primary: "#5B9BD5",
|
|
@@ -854,7 +899,8 @@ function resolveTheme(themeConfig) {
|
|
|
854
899
|
muted: chalk.hex(tokens.muted),
|
|
855
900
|
accent: chalk.hex(tokens.accent),
|
|
856
901
|
bold: chalk.bold,
|
|
857
|
-
icons
|
|
902
|
+
icons,
|
|
903
|
+
spinner: resolveSpinner(themeConfig?.spinner)
|
|
858
904
|
};
|
|
859
905
|
}
|
|
860
906
|
|
|
@@ -1078,6 +1124,17 @@ function resolveTemplate(template, answers) {
|
|
|
1078
1124
|
return _match;
|
|
1079
1125
|
});
|
|
1080
1126
|
}
|
|
1127
|
+
function resolveTemplateStrict(template, answers) {
|
|
1128
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_match, key) => {
|
|
1129
|
+
const trimmedKey = key.trim();
|
|
1130
|
+
if (!(trimmedKey in answers)) {
|
|
1131
|
+
throw new Error(`Action references unknown step "${trimmedKey}"`);
|
|
1132
|
+
}
|
|
1133
|
+
const value = answers[trimmedKey];
|
|
1134
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
1135
|
+
return String(value);
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1081
1138
|
|
|
1082
1139
|
// src/banner.ts
|
|
1083
1140
|
import figlet from "figlet";
|
|
@@ -1569,8 +1626,13 @@ async function runWizard(config, options) {
|
|
|
1569
1626
|
if (state.status === "done" && !quiet) {
|
|
1570
1627
|
renderer.renderSummary(state.answers, config.steps, theme);
|
|
1571
1628
|
}
|
|
1572
|
-
if (state.status === "done" &&
|
|
1573
|
-
|
|
1629
|
+
if (state.status === "done" && !isMock) {
|
|
1630
|
+
if (config.onComplete) {
|
|
1631
|
+
await executeOnComplete(config.onComplete, options?.configFilePath, state.answers, config, theme, renderer);
|
|
1632
|
+
}
|
|
1633
|
+
if (config.actions && config.actions.length > 0) {
|
|
1634
|
+
await executeActions(config.actions, state.answers, theme, renderer);
|
|
1635
|
+
}
|
|
1574
1636
|
}
|
|
1575
1637
|
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
|
|
1576
1638
|
if (state.status === "done" && cacheEnabled) {
|
|
@@ -1785,6 +1847,25 @@ function resolveStepTemplates(step, answers) {
|
|
|
1785
1847
|
};
|
|
1786
1848
|
}
|
|
1787
1849
|
}
|
|
1850
|
+
async function executeOnComplete(handlerPath, configFilePath, answers, config, theme, renderer) {
|
|
1851
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:start" }, theme);
|
|
1852
|
+
const resolvedPath = configFilePath ? resolve2(dirname2(configFilePath), handlerPath) : resolve2(handlerPath);
|
|
1853
|
+
try {
|
|
1854
|
+
const mod = await import(pathToFileURL(resolvedPath).href);
|
|
1855
|
+
if (typeof mod.default !== "function") {
|
|
1856
|
+
throw new Error(`onComplete handler "${handlerPath}" must export a default function`);
|
|
1857
|
+
}
|
|
1858
|
+
await mod.default({ answers, config });
|
|
1859
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:pass" }, theme);
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1862
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:fail", error: message }, theme);
|
|
1863
|
+
console.log(`
|
|
1864
|
+
${theme.error("\u2717")} onComplete handler failed: ${message}
|
|
1865
|
+
`);
|
|
1866
|
+
throw error;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1788
1869
|
async function executeActions(actions, answers, theme, renderer) {
|
|
1789
1870
|
if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
|
|
1790
1871
|
console.log(`
|
|
@@ -1794,8 +1875,8 @@ async function executeActions(actions, answers, theme, renderer) {
|
|
|
1794
1875
|
if (action.when && !evaluateCondition(action.when, answers)) {
|
|
1795
1876
|
continue;
|
|
1796
1877
|
}
|
|
1797
|
-
const resolvedCommand =
|
|
1798
|
-
const resolvedName = action.name ?
|
|
1878
|
+
const resolvedCommand = resolveTemplateStrict(action.run, answers);
|
|
1879
|
+
const resolvedName = action.name ? resolveTemplateStrict(action.name, answers) : void 0;
|
|
1799
1880
|
const label = resolvedName ?? resolvedCommand;
|
|
1800
1881
|
try {
|
|
1801
1882
|
execSync(resolvedCommand, { stdio: "pipe" });
|
|
@@ -2392,7 +2473,6 @@ var S_STEP_ERROR = u("\u25B2", "x");
|
|
|
2392
2473
|
var S_CORNER_TR = u("\u256E", "+");
|
|
2393
2474
|
var S_CORNER_BR = u("\u256F", "+");
|
|
2394
2475
|
var S_BAR_H = u("\u2500", "-");
|
|
2395
|
-
var S_SPINNER_FRAMES = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
|
|
2396
2476
|
|
|
2397
2477
|
// src/renderers/clack.ts
|
|
2398
2478
|
var ClackRenderer = class extends InquirerRenderer {
|
|
@@ -2531,13 +2611,14 @@ var ClackRenderer = class extends InquirerRenderer {
|
|
|
2531
2611
|
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
|
|
2532
2612
|
`);
|
|
2533
2613
|
}
|
|
2534
|
-
startSpinner(message,
|
|
2614
|
+
startSpinner(message, theme) {
|
|
2535
2615
|
this.spinnerFrameIndex = 0;
|
|
2616
|
+
const { frames, interval } = theme.spinner;
|
|
2536
2617
|
this.spinnerInterval = setInterval(() => {
|
|
2537
|
-
const frame =
|
|
2618
|
+
const frame = frames[this.spinnerFrameIndex % frames.length];
|
|
2538
2619
|
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
|
|
2539
2620
|
this.spinnerFrameIndex++;
|
|
2540
|
-
},
|
|
2621
|
+
}, interval);
|
|
2541
2622
|
}
|
|
2542
2623
|
stopSpinner(message, theme) {
|
|
2543
2624
|
if (this.spinnerInterval) {
|
|
@@ -2552,7 +2633,7 @@ var ClackRenderer = class extends InquirerRenderer {
|
|
|
2552
2633
|
|
|
2553
2634
|
// src/cli.ts
|
|
2554
2635
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
2555
|
-
import { resolve as
|
|
2636
|
+
import { resolve as resolve3 } from "path";
|
|
2556
2637
|
import { fileURLToPath } from "url";
|
|
2557
2638
|
import { stringify as yamlStringify } from "yaml";
|
|
2558
2639
|
var plainMode = false;
|
|
@@ -2568,7 +2649,7 @@ program.name("grimoire").description("Config-driven CLI wizard framework").versi
|
|
|
2568
2649
|
});
|
|
2569
2650
|
program.command("run").description("Run a wizard from a config file").argument("<config>", "Path to wizard config file (.yaml, .json, .js, .ts)").option("-o, --output <path>", "Write answers to file").option("-f, --format <format>", "Output format: json, env, yaml", "json").option("-q, --quiet", "Suppress header and summary output").option("--dry-run", "Show step plan without running the wizard").option("--mock <json>", "Run wizard with preset answers (JSON string)").option("--json", "Output structured JSON result to stdout").option("--no-cache", "Disable answer caching for this run").option("--no-resume", "Disable progress resume for this run").option("--renderer <type>", "Renderer to use: inquirer (default), ink, or clack", "inquirer").option("--template <name>", "Load a saved template as defaults").action(async (configPath, opts) => {
|
|
2570
2651
|
try {
|
|
2571
|
-
const fullPath =
|
|
2652
|
+
const fullPath = resolve3(configPath);
|
|
2572
2653
|
const config = await loadWizardConfig(fullPath);
|
|
2573
2654
|
if (opts.dryRun) {
|
|
2574
2655
|
printDryRun(config);
|
|
@@ -2594,10 +2675,11 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2594
2675
|
mockAnswers,
|
|
2595
2676
|
templateAnswers,
|
|
2596
2677
|
cache: opts.cache,
|
|
2597
|
-
resume: opts.resume
|
|
2678
|
+
resume: opts.resume,
|
|
2679
|
+
configFilePath: fullPath
|
|
2598
2680
|
});
|
|
2599
2681
|
const rawOutputPath = opts.output ?? config.output?.path;
|
|
2600
|
-
const outputPath = rawOutputPath ?
|
|
2682
|
+
const outputPath = rawOutputPath ? resolve3(resolveTemplate(rawOutputPath, answers)) : void 0;
|
|
2601
2683
|
if (isJsonOutput) {
|
|
2602
2684
|
const stepsCompleted = Object.keys(answers).length;
|
|
2603
2685
|
const result = {
|
|
@@ -2643,7 +2725,7 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2643
2725
|
});
|
|
2644
2726
|
program.command("validate").description("Validate a wizard config file without running it").argument("<config>", "Path to wizard config file").action(async (configPath) => {
|
|
2645
2727
|
try {
|
|
2646
|
-
const fullPath =
|
|
2728
|
+
const fullPath = resolve3(configPath);
|
|
2647
2729
|
const config = await loadWizardConfig(fullPath);
|
|
2648
2730
|
console.log(`
|
|
2649
2731
|
\u2713 Valid wizard config: "${config.meta.name}"`);
|
|
@@ -2660,7 +2742,7 @@ program.command("validate").description("Validate a wizard config file without r
|
|
|
2660
2742
|
});
|
|
2661
2743
|
program.command("create").description("Interactively scaffold a new wizard config file").argument("[output]", "Output file path", "wizard.yaml").action(async (output) => {
|
|
2662
2744
|
try {
|
|
2663
|
-
const resolvedPath =
|
|
2745
|
+
const resolvedPath = resolve3(output);
|
|
2664
2746
|
await scaffoldWizard(resolvedPath);
|
|
2665
2747
|
} catch (error) {
|
|
2666
2748
|
if (error instanceof Error) {
|
|
@@ -2673,7 +2755,7 @@ program.command("create").description("Interactively scaffold a new wizard confi
|
|
|
2673
2755
|
});
|
|
2674
2756
|
program.command("demo").description("Run a demo wizard showcasing all step types").action(async () => {
|
|
2675
2757
|
try {
|
|
2676
|
-
const demoPath =
|
|
2758
|
+
const demoPath = resolve3(
|
|
2677
2759
|
fileURLToPath(import.meta.url),
|
|
2678
2760
|
"..",
|
|
2679
2761
|
"..",
|
|
@@ -2812,6 +2894,11 @@ function printDryRun(config) {
|
|
|
2812
2894
|
console.log(` Step ${num} ${typeStr} ${idStr} ${msg}${suffix}`);
|
|
2813
2895
|
}
|
|
2814
2896
|
console.log();
|
|
2897
|
+
if (config.onComplete) {
|
|
2898
|
+
console.log(` ${theme.bold("onComplete handler:")}`);
|
|
2899
|
+
console.log(` ${theme.muted(config.onComplete)}`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
}
|
|
2815
2902
|
if (config.actions && config.actions.length > 0) {
|
|
2816
2903
|
console.log(` ${theme.bold("Post-wizard actions:")}`);
|
|
2817
2904
|
for (const action of config.actions) {
|