@markbrutx/promptbook-cli 0.1.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/bin/promptbook.ts +4 -0
  4. package/dist/bin/promptbook.d.ts +3 -0
  5. package/dist/bin/promptbook.d.ts.map +1 -0
  6. package/dist/bin/promptbook.js +4 -0
  7. package/dist/bin/promptbook.js.map +1 -0
  8. package/dist/src/args.d.ts +43 -0
  9. package/dist/src/args.d.ts.map +1 -0
  10. package/dist/src/args.js +96 -0
  11. package/dist/src/args.js.map +1 -0
  12. package/dist/src/commands/annotations.d.ts +10 -0
  13. package/dist/src/commands/annotations.d.ts.map +1 -0
  14. package/dist/src/commands/annotations.js +92 -0
  15. package/dist/src/commands/annotations.js.map +1 -0
  16. package/dist/src/commands/bundle.d.ts +13 -0
  17. package/dist/src/commands/bundle.d.ts.map +1 -0
  18. package/dist/src/commands/bundle.js +61 -0
  19. package/dist/src/commands/bundle.js.map +1 -0
  20. package/dist/src/commands/eval.d.ts +13 -0
  21. package/dist/src/commands/eval.d.ts.map +1 -0
  22. package/dist/src/commands/eval.js +113 -0
  23. package/dist/src/commands/eval.js.map +1 -0
  24. package/dist/src/commands/lint.d.ts +11 -0
  25. package/dist/src/commands/lint.d.ts.map +1 -0
  26. package/dist/src/commands/lint.js +66 -0
  27. package/dist/src/commands/lint.js.map +1 -0
  28. package/dist/src/commands/ls.d.ts +11 -0
  29. package/dist/src/commands/ls.d.ts.map +1 -0
  30. package/dist/src/commands/ls.js +84 -0
  31. package/dist/src/commands/ls.js.map +1 -0
  32. package/dist/src/commands/resolve.d.ts +9 -0
  33. package/dist/src/commands/resolve.d.ts.map +1 -0
  34. package/dist/src/commands/resolve.js +41 -0
  35. package/dist/src/commands/resolve.js.map +1 -0
  36. package/dist/src/commands/view.d.ts +30 -0
  37. package/dist/src/commands/view.d.ts.map +1 -0
  38. package/dist/src/commands/view.js +51 -0
  39. package/dist/src/commands/view.js.map +1 -0
  40. package/dist/src/config.d.ts +56 -0
  41. package/dist/src/config.d.ts.map +1 -0
  42. package/dist/src/config.js +175 -0
  43. package/dist/src/config.js.map +1 -0
  44. package/dist/src/index.d.ts +4 -0
  45. package/dist/src/index.d.ts.map +1 -0
  46. package/dist/src/index.js +2 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/io.d.ts +43 -0
  49. package/dist/src/io.d.ts.map +1 -0
  50. package/dist/src/io.js +37 -0
  51. package/dist/src/io.js.map +1 -0
  52. package/dist/src/render-eval.d.ts +8 -0
  53. package/dist/src/render-eval.d.ts.map +1 -0
  54. package/dist/src/render-eval.js +51 -0
  55. package/dist/src/render-eval.js.map +1 -0
  56. package/dist/src/render-explain.d.ts +8 -0
  57. package/dist/src/render-explain.d.ts.map +1 -0
  58. package/dist/src/render-explain.js +57 -0
  59. package/dist/src/render-explain.js.map +1 -0
  60. package/dist/src/render-lint.d.ts +8 -0
  61. package/dist/src/render-lint.d.ts.map +1 -0
  62. package/dist/src/render-lint.js +57 -0
  63. package/dist/src/render-lint.js.map +1 -0
  64. package/dist/src/run.d.ts +8 -0
  65. package/dist/src/run.d.ts.map +1 -0
  66. package/dist/src/run.js +105 -0
  67. package/dist/src/run.js.map +1 -0
  68. package/dist/src/style.d.ts +16 -0
  69. package/dist/src/style.d.ts.map +1 -0
  70. package/dist/src/style.js +22 -0
  71. package/dist/src/style.js.map +1 -0
  72. package/package.json +50 -0
  73. package/src/args.ts +145 -0
  74. package/src/commands/annotations.ts +107 -0
  75. package/src/commands/bundle.ts +71 -0
  76. package/src/commands/eval.ts +137 -0
  77. package/src/commands/lint.ts +71 -0
  78. package/src/commands/ls.ts +90 -0
  79. package/src/commands/resolve.ts +47 -0
  80. package/src/commands/view.ts +82 -0
  81. package/src/config.ts +209 -0
  82. package/src/index.ts +3 -0
  83. package/src/io.ts +77 -0
  84. package/src/render-eval.ts +55 -0
  85. package/src/render-explain.ts +64 -0
  86. package/src/render-lint.ts +63 -0
  87. package/src/run.ts +107 -0
  88. package/src/style.ts +37 -0
package/dist/src/io.js ADDED
@@ -0,0 +1,37 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { nodeFs } from "@markbrutx/promptbook-core";
4
+ /** Real-process IO: stdout/stderr streams, Node fs, live env and cwd. */
5
+ export function defaultIO() {
6
+ return {
7
+ stdout(text) {
8
+ process.stdout.write(text);
9
+ },
10
+ stderr(text) {
11
+ process.stderr.write(text);
12
+ },
13
+ async writeFile(path, contents) {
14
+ await mkdir(dirname(path), { recursive: true });
15
+ await writeFile(path, contents);
16
+ },
17
+ cwd: () => process.cwd(),
18
+ env: process.env,
19
+ fs: nodeFs(),
20
+ colorDefault: Boolean(process.stderr.isTTY),
21
+ };
22
+ }
23
+ /** Emit each warning to stderr with the standard `warning:` prefix. */
24
+ export function emitWarnings(io, warnings) {
25
+ for (const warning of warnings) {
26
+ io.stderr(`warning: ${warning}\n`);
27
+ }
28
+ }
29
+ /** Resolve whether colored output is allowed: off if `NO_COLOR` is set. */
30
+ export function colorEnabled(io) {
31
+ const flag = io.env.NO_COLOR;
32
+ if (flag !== undefined && flag !== "") {
33
+ return false;
34
+ }
35
+ return io.colorDefault;
36
+ }
37
+ //# sourceMappingURL=io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAuCpD,yEAAyE;AACzE,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,MAAM,CAAC,IAAI;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,CAAC,IAAI;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ;YAC5B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;QACD,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QACxB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,EAAE,EAAE,MAAM,EAAE;QACZ,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,QAAkB;IACrD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,EAAE,CAAC,MAAM,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,EAAM;IACjC,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,EAAE,CAAC,YAAY,CAAC;AACzB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { EvalReport } from "@markbrutx/promptbook-core";
2
+ /**
3
+ * Render an {@link EvalReport} as a human-readable block: a per-fixture line
4
+ * (pass/fail mark, passRate, samples) with indented failing assertions and
5
+ * output excerpts, then a summary line gated by `threshold`.
6
+ */
7
+ export declare function renderEvalReport(report: EvalReport, threshold: number, color: boolean): string;
8
+ //# sourceMappingURL=render-eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-eval.d.ts","sourceRoot":"","sources":["../../src/render-eval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,UAAU,EAAiB,MAAM,4BAA4B,CAAC;AAsC7F;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAW9F"}
@@ -0,0 +1,51 @@
1
+ import { makeStyle, plural } from "./style.js";
2
+ function rate(value) {
3
+ return value.toFixed(2);
4
+ }
5
+ /** One line per fixture plus indented failure details for the failed ones. */
6
+ function renderFixture(s, fixture, threshold) {
7
+ const ok = fixture.passRate >= threshold;
8
+ const mark = ok ? s.green("✓") : s.red("✗");
9
+ const head = ` ${mark} ${fixture.name} passRate ${rate(fixture.passRate)} (${fixture.passes}/${fixture.samples})`;
10
+ const lines = [head];
11
+ if (!ok) {
12
+ for (const failure of dedupeFailures(fixture.failures)) {
13
+ lines.push(` ${s.red(failure.type)}: ${failure.message}`);
14
+ if (failure.excerpt !== undefined && failure.excerpt !== "") {
15
+ lines.push(` ${s.dim(`output: ${failure.excerpt}`)}`);
16
+ }
17
+ }
18
+ }
19
+ return lines;
20
+ }
21
+ /** Collapse identical failures (same type + message) repeated across samples. */
22
+ function dedupeFailures(failures) {
23
+ const seen = new Set();
24
+ const unique = [];
25
+ for (const failure of failures) {
26
+ const key = `${failure.type}\u0000${failure.message}`;
27
+ if (!seen.has(key)) {
28
+ seen.add(key);
29
+ unique.push(failure);
30
+ }
31
+ }
32
+ return unique;
33
+ }
34
+ /**
35
+ * Render an {@link EvalReport} as a human-readable block: a per-fixture line
36
+ * (pass/fail mark, passRate, samples) with indented failing assertions and
37
+ * output excerpts, then a summary line gated by `threshold`.
38
+ */
39
+ export function renderEvalReport(report, threshold, color) {
40
+ const s = makeStyle(color);
41
+ const lines = [s.bold("eval")];
42
+ for (const fixture of report.results) {
43
+ lines.push(...renderFixture(s, fixture, threshold));
44
+ }
45
+ const total = report.results.length;
46
+ const summary = `${report.passed}/${total} fixtures passed (threshold ${rate(threshold)})`;
47
+ const colored = report.failed > 0 ? s.red(summary) : s.green(summary);
48
+ lines.push(`summary: ${colored}, ${plural(report.failed, "failure")}`);
49
+ return `${lines.join("\n")}\n`;
50
+ }
51
+ //# sourceMappingURL=render-eval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-eval.js","sourceRoot":"","sources":["../../src/render-eval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Trace } from "@markbrutx/promptbook-core";
2
+ /**
3
+ * Render an explain {@link Trace} as a human-readable block for stderr: which
4
+ * rules fired (and why not), the final order, the replace/add/forbid effects,
5
+ * and a highlighted "no rule matched" section for unmatched context axes.
6
+ */
7
+ export declare function renderExplain(trace: Trace, color: boolean): string;
8
+ //# sourceMappingURL=render-explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-explain.d.ts","sourceRoot":"","sources":["../../src/render-explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAQtE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAkDlE"}
@@ -0,0 +1,57 @@
1
+ import { formatContext, makeStyle } from "./style.js";
2
+ function whenLabel(when) {
3
+ const label = formatContext(when);
4
+ return label === "" ? "always" : label;
5
+ }
6
+ /**
7
+ * Render an explain {@link Trace} as a human-readable block for stderr: which
8
+ * rules fired (and why not), the final order, the replace/add/forbid effects,
9
+ * and a highlighted "no rule matched" section for unmatched context axes.
10
+ */
11
+ export function renderExplain(trace, color) {
12
+ const s = makeStyle(color);
13
+ const lines = [];
14
+ const contextLabel = whenLabel(trace.context);
15
+ lines.push(`${s.bold(`resolve "${trace.prompt}"`)}${s.dim(` · context: ${contextLabel}`)}`);
16
+ lines.push(s.bold("rules:"));
17
+ if (trace.rules.length === 0) {
18
+ lines.push(` ${s.dim("(none)")}`);
19
+ }
20
+ for (const rule of trace.rules) {
21
+ const head = `#${rule.index} ${rule.action} ${s.dim(`[${whenLabel(rule.when)}]`)}`;
22
+ if (rule.fired) {
23
+ lines.push(` ${s.green("✓")} ${head} → ${rule.effect ?? ""}`);
24
+ }
25
+ else {
26
+ lines.push(` ${s.red("✗")} ${head} ${s.dim(`— ${rule.reason ?? "did not match"}`)}`);
27
+ }
28
+ }
29
+ lines.push(`${s.bold("final order:")} ${trace.finalOrder.join(" → ") || "(empty)"}`);
30
+ if (trace.replaced.length > 0) {
31
+ lines.push(s.bold("replaced:"));
32
+ for (const entry of trace.replaced) {
33
+ lines.push(` ${entry.from} → ${entry.to} ${s.dim(`(#${entry.ruleIndex})`)}`);
34
+ }
35
+ }
36
+ if (trace.added.length > 0) {
37
+ lines.push(s.bold("added:"));
38
+ for (const entry of trace.added) {
39
+ const anchor = entry.after !== undefined ? ` after ${entry.after}` : "";
40
+ lines.push(` ${entry.id}${anchor} ${s.dim(`(#${entry.ruleIndex})`)}`);
41
+ }
42
+ }
43
+ if (trace.forbidden.length > 0) {
44
+ lines.push(s.bold("forbidden:"));
45
+ for (const entry of trace.forbidden) {
46
+ lines.push(` ${entry.id} ${s.dim(`(#${entry.ruleIndex})`)}`);
47
+ }
48
+ }
49
+ if (trace.unmatchedAxes.length > 0) {
50
+ lines.push(s.warn(s.bold("unmatched axes:")));
51
+ for (const axis of trace.unmatchedAxes) {
52
+ lines.push(` ${s.warn(`⚠ no rules matched for ${axis.key}=${axis.value}`)}`);
53
+ }
54
+ }
55
+ return `${lines.join("\n")}\n`;
56
+ }
57
+ //# sourceMappingURL=render-explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-explain.js","sourceRoot":"","sources":["../../src/render-explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { LintReport } from "@markbrutx/promptbook-core";
2
+ /**
3
+ * Render a {@link LintReport} as a human-readable block grouped by severity.
4
+ * A clean report is a single green line; otherwise a summary line is followed
5
+ * by `errors:`/`warnings:`/`info:` sections listing `ruleId: message`.
6
+ */
7
+ export declare function renderLintReport(report: LintReport, label: string, color: boolean): string;
8
+ //# sourceMappingURL=render-lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-lint.d.ts","sourceRoot":"","sources":["../../src/render-lint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,UAAU,EAAY,MAAM,4BAA4B,CAAC;AA0BpF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CA+B1F"}
@@ -0,0 +1,57 @@
1
+ import { makeStyle, plural } from "./style.js";
2
+ const SEVERITY_ORDER = ["error", "warning", "info"];
3
+ function location(finding) {
4
+ const parts = [];
5
+ if (finding.fragmentId !== undefined) {
6
+ parts.push(`fragment: ${finding.fragmentId}`);
7
+ }
8
+ if (finding.ruleIndex !== undefined) {
9
+ parts.push(`rule #${finding.ruleIndex}`);
10
+ }
11
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
12
+ }
13
+ function colorize(s, severity, text) {
14
+ if (severity === "error") {
15
+ return s.red(text);
16
+ }
17
+ if (severity === "warning") {
18
+ return s.warn(text);
19
+ }
20
+ return s.dim(text);
21
+ }
22
+ /**
23
+ * Render a {@link LintReport} as a human-readable block grouped by severity.
24
+ * A clean report is a single green line; otherwise a summary line is followed
25
+ * by `errors:`/`warnings:`/`info:` sections listing `ruleId: message`.
26
+ */
27
+ export function renderLintReport(report, label, color) {
28
+ const s = makeStyle(color);
29
+ const head = s.bold(`lint "${label}"`);
30
+ if (report.findings.length === 0) {
31
+ return `${head} ${s.green("— no findings")}\n`;
32
+ }
33
+ const infoCount = report.findings.length - report.errorCount - report.warningCount;
34
+ const summary = [];
35
+ if (report.errorCount > 0) {
36
+ summary.push(s.red(plural(report.errorCount, "error")));
37
+ }
38
+ if (report.warningCount > 0) {
39
+ summary.push(s.warn(plural(report.warningCount, "warning")));
40
+ }
41
+ if (infoCount > 0) {
42
+ summary.push(s.dim(plural(infoCount, "info")));
43
+ }
44
+ const lines = [`${head} — ${summary.join(", ")}`];
45
+ for (const severity of SEVERITY_ORDER) {
46
+ const group = report.findings.filter((finding) => finding.severity === severity);
47
+ if (group.length === 0) {
48
+ continue;
49
+ }
50
+ lines.push(colorize(s, severity, s.bold(`${severity}s:`)));
51
+ for (const finding of group) {
52
+ lines.push(` ${finding.ruleId}: ${finding.message}${location(finding)}`);
53
+ }
54
+ }
55
+ return `${lines.join("\n")}\n`;
56
+ }
57
+ //# sourceMappingURL=render-lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-lint.js","sourceRoot":"","sources":["../../src/render-lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type IO } from "./io.js";
2
+ /**
3
+ * CLI entry point. Parses argv, handles `--help`/`--version`, then dispatches
4
+ * to a subcommand. Returns the process exit code; never calls `process.exit`
5
+ * so it stays testable. `io` injects all side effects (streams, fs, env, cwd).
6
+ */
7
+ export declare function run(argv: string[], io?: IO): Promise<number>;
8
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAUA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,SAAS,CAAC;AAkD7C;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,EAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC/E"}
@@ -0,0 +1,105 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { parseCliArgs } from "./args.js";
4
+ import { cmdAnnotations } from "./commands/annotations.js";
5
+ import { cmdBundle } from "./commands/bundle.js";
6
+ import { cmdEval } from "./commands/eval.js";
7
+ import { cmdLint } from "./commands/lint.js";
8
+ import { cmdLs } from "./commands/ls.js";
9
+ import { cmdResolve } from "./commands/resolve.js";
10
+ import { cmdView } from "./commands/view.js";
11
+ import { defaultIO } from "./io.js";
12
+ const HELP = `promptbook — compose prompts from reusable fragments
13
+
14
+ Usage:
15
+ promptbook <command> [options]
16
+
17
+ Commands:
18
+ resolve <prompt> Assemble a prompt and print it to stdout
19
+ lint [<prompt>] Run static checks; with no prompt, book rules only
20
+ eval [<name|glob>] Run fixtures through a model adapter, report pass-rate
21
+ bundle [<dir>] Compile a prompts folder into an importable book module
22
+ view Start the local web viewer over the prompts folder
23
+ annotations <action> Drain the viewer's feedback queue: list | resolve <id> | clear
24
+ ls List compositions and fragments
25
+
26
+ Options:
27
+ --dir <path> Prompts folder (default: promptbook.json promptsDir, else ./prompts)
28
+ --ctx key=value Context value, repeatable (coerced to boolean/number/string)
29
+ --context-file <json> Merge context from a JSON file (--ctx overrides it)
30
+ --explain resolve: print the resolution trace to stderr
31
+ --json Emit machine-readable JSON on stdout
32
+ --max-tokens N lint: token-budget ceiling (overrides promptbook.json)
33
+ --strict lint: exit non-zero on warnings too
34
+ --model <id> eval: model id for the adapter (overrides promptbook.json)
35
+ --samples N eval: default samples per fixture (default 1; a fixture's own samples wins)
36
+ --threshold R eval: a fixture passes when passRate >= R (default 1)
37
+ --lint eval: run a static lint gate over every variant first
38
+ -o, --out <file> bundle: write the generated module to a file (default: stdout)
39
+ --plain bundle: emit a plain module (no type-only import; e.g. for Deno)
40
+ --port N view: port for the viewer server (default: a free port)
41
+ --no-open view: do not open the browser after starting
42
+ --fragments ls: list fragments only
43
+ --compositions ls: list compositions only
44
+ -h, --help Show this help
45
+ -v, --version Show the version
46
+
47
+ Streams: stdout = payload (prompt text or JSON); stderr = explanations and errors.
48
+ `;
49
+ function readVersion() {
50
+ try {
51
+ const path = fileURLToPath(new URL("../../package.json", import.meta.url));
52
+ const pkg = JSON.parse(readFileSync(path, "utf8"));
53
+ return pkg.version ?? "0.0.0";
54
+ }
55
+ catch {
56
+ return "0.0.0";
57
+ }
58
+ }
59
+ /**
60
+ * CLI entry point. Parses argv, handles `--help`/`--version`, then dispatches
61
+ * to a subcommand. Returns the process exit code; never calls `process.exit`
62
+ * so it stays testable. `io` injects all side effects (streams, fs, env, cwd).
63
+ */
64
+ export async function run(argv, io = defaultIO()) {
65
+ let args;
66
+ try {
67
+ args = parseCliArgs(argv);
68
+ }
69
+ catch (error) {
70
+ io.stderr(`error: ${error.message}\n`);
71
+ return 1;
72
+ }
73
+ if (args.help) {
74
+ io.stdout(HELP);
75
+ return 0;
76
+ }
77
+ if (args.version) {
78
+ io.stdout(`${readVersion()}\n`);
79
+ return 0;
80
+ }
81
+ if (args.command === undefined) {
82
+ io.stdout(HELP);
83
+ return 0;
84
+ }
85
+ switch (args.command) {
86
+ case "resolve":
87
+ return cmdResolve(args, io);
88
+ case "lint":
89
+ return cmdLint(args, io);
90
+ case "eval":
91
+ return cmdEval(args, io);
92
+ case "bundle":
93
+ return cmdBundle(args, io);
94
+ case "view":
95
+ return cmdView(args, io);
96
+ case "annotations":
97
+ return cmdAnnotations(args, io);
98
+ case "ls":
99
+ return cmdLs(args, io);
100
+ default:
101
+ io.stderr(`error: unknown command "${args.command}". Run "promptbook --help".\n`);
102
+ return 1;
103
+ }
104
+ }
105
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ContextValue } from "@markbrutx/promptbook-core";
2
+ /** Render a context/when bag as `k=v, k=v` (empty string when empty). */
3
+ export declare function formatContext(context: Record<string, ContextValue>): string;
4
+ /** Minimal ANSI styling, used by the explain and lint renderers. */
5
+ export interface Style {
6
+ bold(text: string): string;
7
+ dim(text: string): string;
8
+ green(text: string): string;
9
+ red(text: string): string;
10
+ warn(text: string): string;
11
+ }
12
+ /** Pluralize `word` by `count`, e.g. `plural(2, "error")` -> "2 errors". */
13
+ export declare function plural(count: number, word: string): string;
14
+ /** Build a {@link Style}; when `color` is false every helper is a no-op. */
15
+ export declare function makeStyle(color: boolean): Style;
16
+ //# sourceMappingURL=style.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/style.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,CAI3E;AAED,oEAAoE;AACpE,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAY/C"}
@@ -0,0 +1,22 @@
1
+ /** Render a context/when bag as `k=v, k=v` (empty string when empty). */
2
+ export function formatContext(context) {
3
+ return Object.entries(context)
4
+ .map(([key, value]) => `${key}=${value}`)
5
+ .join(", ");
6
+ }
7
+ /** Pluralize `word` by `count`, e.g. `plural(2, "error")` -> "2 errors". */
8
+ export function plural(count, word) {
9
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
10
+ }
11
+ /** Build a {@link Style}; when `color` is false every helper is a no-op. */
12
+ export function makeStyle(color) {
13
+ const wrap = (code) => (text) => color ? `\x1b[${code}m${text}\x1b[0m` : text;
14
+ return {
15
+ bold: wrap("1"),
16
+ dim: wrap("2"),
17
+ green: wrap("32"),
18
+ red: wrap("31"),
19
+ warn: wrap("33"),
20
+ };
21
+ }
22
+ //# sourceMappingURL=style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/style.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,OAAqC;IACjE,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;SACxC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAWD,4EAA4E;AAC5E,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,IAAY;IAChD,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACrD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,IAAI,GACR,CAAC,IAAY,EAAE,EAAE,CACjB,CAAC,IAAY,EAAU,EAAE,CACvB,KAAK,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;QACf,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;QACd,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;QACjB,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC;QACf,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;KACjB,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@markbrutx/promptbook-cli",
3
+ "version": "0.1.0",
4
+ "description": "Thin terminal surface over @markbrutx/promptbook-core: resolve and ls for agents and CI.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "promptbook": "./dist/bin/promptbook.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/src/index.d.ts",
13
+ "import": "./dist/src/index.js"
14
+ }
15
+ },
16
+ "main": "./dist/src/index.js",
17
+ "types": "./dist/src/index.d.ts",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src",
24
+ "bin"
25
+ ],
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "tsgo -p tsconfig.build.json",
29
+ "typecheck": "tsgo --noEmit -p tsconfig.json",
30
+ "test": "vitest --run",
31
+ "check": "biome check ."
32
+ },
33
+ "dependencies": {
34
+ "@markbrutx/promptbook-core": "^0.1.0",
35
+ "@markbrutx/promptbook-openrouter": "^0.1.0"
36
+ },
37
+ "optionalDependencies": {
38
+ "@markbrutx/promptbook-viewer": "^0.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "@biomejs/biome": "latest",
42
+ "@types/node": "^20.14.0",
43
+ "@typescript/native-preview": "latest",
44
+ "typescript": "^5.6.0",
45
+ "vitest": "^2.1.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=20.6"
49
+ }
50
+ }
package/src/args.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ /** Normalized view of the command line, independent of `parseArgs` shape. */
4
+ export interface ParsedArgs {
5
+ /** First positional: the subcommand, or undefined when none was given. */
6
+ command: string | undefined;
7
+ /** Positionals after the subcommand (e.g. the prompt name). */
8
+ operands: string[];
9
+ help: boolean;
10
+ version: boolean;
11
+ dir?: string;
12
+ json: boolean;
13
+ explain: boolean;
14
+ /** bundle: write the generated module to this file instead of stdout. */
15
+ out?: string;
16
+ /** bundle: emit a plain module without the type-only import/annotation. */
17
+ plain: boolean;
18
+ /** Repeated `--ctx key=value` pairs, parsed later by config. */
19
+ ctx: string[];
20
+ contextFile?: string;
21
+ fragments: boolean;
22
+ compositions: boolean;
23
+ /** lint: estimated token ceiling for the token-budget rule. */
24
+ maxTokens?: number;
25
+ /** lint: treat warnings as failures for the exit code. */
26
+ strict: boolean;
27
+ /** eval: model id passed to the adapter (overrides promptbook.json). */
28
+ model?: string;
29
+ /** eval: default samples per fixture when the fixture sets none. */
30
+ samples?: number;
31
+ /** eval: a fixture passes when passRate >= this threshold. */
32
+ threshold?: number;
33
+ /** eval: run lint on each resolved variant before sampling. */
34
+ lint: boolean;
35
+ /** view: port for the viewer server (0/undefined picks a free port). */
36
+ port?: number;
37
+ /** view: do not open the browser after the server starts. */
38
+ noOpen: boolean;
39
+ }
40
+
41
+ /** Constraints for a numeric flag, used by {@link parseNumberFlag}. */
42
+ interface NumberFlagSpec {
43
+ integer?: boolean;
44
+ min?: number;
45
+ max?: number;
46
+ /** Human-readable expectation, e.g. "a positive integer". */
47
+ expected: string;
48
+ }
49
+
50
+ /**
51
+ * Parse an optional numeric flag, validating against `spec`. Returns undefined
52
+ * when the flag was not given; throws a uniform `invalid --name` error when the
53
+ * value is not a number or falls outside the allowed range.
54
+ */
55
+ function parseNumberFlag(raw: string | undefined, name: string, spec: NumberFlagSpec): number | undefined {
56
+ if (raw === undefined) {
57
+ return undefined;
58
+ }
59
+ const parsed = Number(raw);
60
+ const valid =
61
+ Number.isFinite(parsed) &&
62
+ (!spec.integer || Number.isInteger(parsed)) &&
63
+ (spec.min === undefined || parsed >= spec.min) &&
64
+ (spec.max === undefined || parsed <= spec.max);
65
+ if (!valid) {
66
+ throw new Error(`invalid --${name} "${raw}"; expected ${spec.expected}`);
67
+ }
68
+ return parsed;
69
+ }
70
+
71
+ /**
72
+ * Parse argv into {@link ParsedArgs}. Strict mode throws on unknown flags; the
73
+ * caller turns that into a friendly stderr message and a non-zero exit.
74
+ */
75
+ export function parseCliArgs(argv: string[]): ParsedArgs {
76
+ const { values, positionals } = parseArgs({
77
+ args: argv,
78
+ allowPositionals: true,
79
+ options: {
80
+ help: { type: "boolean", short: "h" },
81
+ version: { type: "boolean", short: "v" },
82
+ dir: { type: "string" },
83
+ json: { type: "boolean" },
84
+ explain: { type: "boolean" },
85
+ out: { type: "string", short: "o" },
86
+ plain: { type: "boolean" },
87
+ ctx: { type: "string", multiple: true },
88
+ "context-file": { type: "string" },
89
+ fragments: { type: "boolean" },
90
+ compositions: { type: "boolean" },
91
+ "max-tokens": { type: "string" },
92
+ strict: { type: "boolean" },
93
+ model: { type: "string" },
94
+ samples: { type: "string" },
95
+ threshold: { type: "string" },
96
+ lint: { type: "boolean" },
97
+ port: { type: "string" },
98
+ "no-open": { type: "boolean" },
99
+ },
100
+ });
101
+ const maxTokens = parseNumberFlag(values["max-tokens"], "max-tokens", {
102
+ integer: true,
103
+ min: 1,
104
+ expected: "a positive integer",
105
+ });
106
+ const samples = parseNumberFlag(values.samples, "samples", {
107
+ integer: true,
108
+ min: 1,
109
+ expected: "a positive integer",
110
+ });
111
+ const threshold = parseNumberFlag(values.threshold, "threshold", {
112
+ min: 0,
113
+ max: 1,
114
+ expected: "a number between 0 and 1",
115
+ });
116
+ const port = parseNumberFlag(values.port, "port", {
117
+ integer: true,
118
+ min: 0,
119
+ max: 65535,
120
+ expected: "a port between 0 and 65535",
121
+ });
122
+ return {
123
+ command: positionals[0],
124
+ operands: positionals.slice(1),
125
+ help: values.help ?? false,
126
+ version: values.version ?? false,
127
+ dir: values.dir,
128
+ json: values.json ?? false,
129
+ explain: values.explain ?? false,
130
+ out: values.out,
131
+ plain: values.plain ?? false,
132
+ ctx: values.ctx ?? [],
133
+ contextFile: values["context-file"],
134
+ fragments: values.fragments ?? false,
135
+ compositions: values.compositions ?? false,
136
+ maxTokens,
137
+ strict: values.strict ?? false,
138
+ model: values.model,
139
+ samples,
140
+ threshold,
141
+ lint: values.lint ?? false,
142
+ port,
143
+ noOpen: values["no-open"] ?? false,
144
+ };
145
+ }