@markbrutx/promptbook-cli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,20 +18,32 @@ interface PromptbookConfig {
18
18
  lint?: unknown;
19
19
  eval?: unknown;
20
20
  }
21
+ /** A loaded `promptbook.json`: parsed data plus the directory that held it. */
22
+ export interface LoadedConfig {
23
+ /** Parsed JSON object (or `{}` when no file was found or parsing failed). */
24
+ data: PromptbookConfig;
25
+ /** Absolute directory where the config was found; `undefined` if nothing matched up the tree. */
26
+ dir?: string;
27
+ }
21
28
  /** lint options sourced from the `lint` section of `promptbook.json`. */
22
29
  export interface LintConfig {
23
30
  maxTokens?: number;
24
31
  bannedTokens?: string[];
25
32
  }
26
33
  /**
27
- * Read and parse `promptbook.json` from cwd once. Missing, unreadable or
28
- * malformed config yields an empty object, so callers treat it as best-effort
29
- * and layer flags on top. Pass the result to {@link resolvePromptsDir} and
30
- * {@link lintConfigFrom} to avoid re-reading the file per command.
34
+ * Walk up from `io.cwd()` to find the first `promptbook.json`, parse it, and
35
+ * return its data + the directory it lived in. Walking up (rather than only
36
+ * checking cwd) is what makes `promptbook` work like `git`/`biome`/`eslint`:
37
+ * one config at the repo root reaches every subfolder. Path-valued keys
38
+ * (currently just `promptsDir`) are resolved relative to {@link LoadedConfig.dir}
39
+ * by {@link resolvePromptsDir}, not relative to wherever the shell happens to
40
+ * be — so `pnpm exec` snapping cwd to a workspace package cannot break the
41
+ * lookup. Missing, unreadable or malformed files yield an empty config
42
+ * (best-effort), so callers can layer flags on top.
31
43
  */
32
- export declare function loadConfig(io: IO): Promise<PromptbookConfig>;
44
+ export declare function loadConfig(io: IO): Promise<LoadedConfig>;
33
45
  /** Extract the `lint` section from an already-loaded config. */
34
- export declare function lintConfigFrom(config: PromptbookConfig): LintConfig;
46
+ export declare function lintConfigFrom(loaded: LoadedConfig): LintConfig;
35
47
  /** Convenience wrapper: load config and extract its `lint` section. */
36
48
  export declare function loadLintConfig(io: IO): Promise<LintConfig>;
37
49
  /** eval options sourced from the `eval` section of `promptbook.json`. */
@@ -40,17 +52,22 @@ export interface EvalConfig {
40
52
  baseUrl?: string;
41
53
  }
42
54
  /** Extract the `eval` section from an already-loaded config. */
43
- export declare function evalConfigFrom(config: PromptbookConfig): EvalConfig;
55
+ export declare function evalConfigFrom(loaded: LoadedConfig): EvalConfig;
44
56
  /**
45
- * Resolve the prompts folder by priority: `--dir` flag > `promptbook.json`
46
- * (`promptsDir` key) in cwd > `./prompts`. All results are absolute. Pass a
47
- * preloaded `config` to reuse a single read; otherwise it is loaded here.
57
+ * Resolve the prompts folder by priority:
58
+ * 1. `--dir <path>` relative to **cwd** (explicit per-invocation override).
59
+ * 2. `promptbook.json` `promptsDir` relative to **the config file's directory**
60
+ * (so the value can stay stable while the user shells around in subfolders).
61
+ * 3. `./prompts` — relative to cwd (back-compat default when no config exists).
62
+ *
63
+ * All results are absolute. Pass a preloaded {@link LoadedConfig} to reuse a
64
+ * single read; otherwise it is loaded here.
48
65
  */
49
- export declare function resolvePromptsDir(io: IO, dirFlag?: string, config?: PromptbookConfig): Promise<string>;
66
+ export declare function resolvePromptsDir(io: IO, dirFlag?: string, loaded?: LoadedConfig): Promise<string>;
50
67
  /**
51
68
  * Resolve the prompts folder and confirm it exists. On a missing folder, write
52
69
  * a clear error to stderr and return null so the caller can exit non-zero.
53
70
  */
54
- export declare function requirePromptsDir(io: IO, dirFlag?: string, config?: PromptbookConfig): Promise<string | null>;
71
+ export declare function requirePromptsDir(io: IO, dirFlag?: string, loaded?: LoadedConfig): Promise<string | null>;
55
72
  export {};
56
73
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AASlC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAWtD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CActD;AAuBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalG;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAclE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAanE;AAED,uEAAuE;AACvE,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhE;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAanE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAYD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AASlC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAWtD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CActD;AAuBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalG;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,IAAI,EAAE,gBAAgB,CAAC;IACvB,iGAAiG;IACjG,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAwB9D;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAa/D;AAED,uEAAuE;AACvE,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhE;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAa/D;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CASxG;AAYD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
@@ -1,4 +1,4 @@
1
- import { resolve as resolvePath } from "node:path";
1
+ import { dirname, resolve as resolvePath } from "node:path";
2
2
  const NUMERIC = /^-?\d+(?:\.\d+)?$/;
3
3
  /** True for a plain JSON object: an object that is neither null nor an array. */
4
4
  function isJsonObject(value) {
@@ -79,31 +79,46 @@ export async function buildContext(io, pairs, contextFile) {
79
79
  return { ...fileContext, ...parseCtxPairs(pairs) };
80
80
  }
81
81
  /**
82
- * Read and parse `promptbook.json` from cwd once. Missing, unreadable or
83
- * malformed config yields an empty object, so callers treat it as best-effort
84
- * and layer flags on top. Pass the result to {@link resolvePromptsDir} and
85
- * {@link lintConfigFrom} to avoid re-reading the file per command.
82
+ * Walk up from `io.cwd()` to find the first `promptbook.json`, parse it, and
83
+ * return its data + the directory it lived in. Walking up (rather than only
84
+ * checking cwd) is what makes `promptbook` work like `git`/`biome`/`eslint`:
85
+ * one config at the repo root reaches every subfolder. Path-valued keys
86
+ * (currently just `promptsDir`) are resolved relative to {@link LoadedConfig.dir}
87
+ * by {@link resolvePromptsDir}, not relative to wherever the shell happens to
88
+ * be — so `pnpm exec` snapping cwd to a workspace package cannot break the
89
+ * lookup. Missing, unreadable or malformed files yield an empty config
90
+ * (best-effort), so callers can layer flags on top.
86
91
  */
87
92
  export async function loadConfig(io) {
88
- const configPath = resolvePath(io.cwd(), "promptbook.json");
89
- let raw;
90
- try {
91
- raw = await io.fs.readFile(configPath);
92
- }
93
- catch {
94
- return {};
95
- }
96
- try {
97
- const parsed = JSON.parse(raw);
98
- return isJsonObject(parsed) ? parsed : {};
99
- }
100
- catch {
101
- return {};
93
+ let dir = resolvePath(io.cwd());
94
+ for (;;) {
95
+ const configPath = resolvePath(dir, "promptbook.json");
96
+ let raw;
97
+ try {
98
+ raw = await io.fs.readFile(configPath);
99
+ }
100
+ catch {
101
+ // not found at this level; try the parent
102
+ }
103
+ if (raw !== undefined) {
104
+ try {
105
+ const parsed = JSON.parse(raw);
106
+ return { data: isJsonObject(parsed) ? parsed : {}, dir };
107
+ }
108
+ catch {
109
+ return { data: {}, dir };
110
+ }
111
+ }
112
+ const parent = dirname(dir);
113
+ if (parent === dir) {
114
+ return { data: {} };
115
+ }
116
+ dir = parent;
102
117
  }
103
118
  }
104
119
  /** Extract the `lint` section from an already-loaded config. */
105
- export function lintConfigFrom(config) {
106
- const section = config.lint;
120
+ export function lintConfigFrom(loaded) {
121
+ const section = loaded.data.lint;
107
122
  if (!isJsonObject(section)) {
108
123
  return {};
109
124
  }
@@ -121,8 +136,8 @@ export async function loadLintConfig(io) {
121
136
  return lintConfigFrom(await loadConfig(io));
122
137
  }
123
138
  /** Extract the `eval` section from an already-loaded config. */
124
- export function evalConfigFrom(config) {
125
- const section = config.eval;
139
+ export function evalConfigFrom(loaded) {
140
+ const section = loaded.data.eval;
126
141
  if (!isJsonObject(section)) {
127
142
  return {};
128
143
  }
@@ -136,17 +151,22 @@ export function evalConfigFrom(config) {
136
151
  return evalConfig;
137
152
  }
138
153
  /**
139
- * Resolve the prompts folder by priority: `--dir` flag > `promptbook.json`
140
- * (`promptsDir` key) in cwd > `./prompts`. All results are absolute. Pass a
141
- * preloaded `config` to reuse a single read; otherwise it is loaded here.
154
+ * Resolve the prompts folder by priority:
155
+ * 1. `--dir <path>` relative to **cwd** (explicit per-invocation override).
156
+ * 2. `promptbook.json` `promptsDir` relative to **the config file's directory**
157
+ * (so the value can stay stable while the user shells around in subfolders).
158
+ * 3. `./prompts` — relative to cwd (back-compat default when no config exists).
159
+ *
160
+ * All results are absolute. Pass a preloaded {@link LoadedConfig} to reuse a
161
+ * single read; otherwise it is loaded here.
142
162
  */
143
- export async function resolvePromptsDir(io, dirFlag, config) {
163
+ export async function resolvePromptsDir(io, dirFlag, loaded) {
144
164
  if (dirFlag !== undefined) {
145
165
  return resolvePath(io.cwd(), dirFlag);
146
166
  }
147
- const resolved = config ?? (await loadConfig(io));
148
- if (typeof resolved.promptsDir === "string") {
149
- return resolvePath(io.cwd(), resolved.promptsDir);
167
+ const resolved = loaded ?? (await loadConfig(io));
168
+ if (typeof resolved.data.promptsDir === "string" && resolved.dir !== undefined) {
169
+ return resolvePath(resolved.dir, resolved.data.promptsDir);
150
170
  }
151
171
  return resolvePath(io.cwd(), "prompts");
152
172
  }
@@ -164,8 +184,8 @@ async function dirExists(io, dir) {
164
184
  * Resolve the prompts folder and confirm it exists. On a missing folder, write
165
185
  * a clear error to stderr and return null so the caller can exit non-zero.
166
186
  */
167
- export async function requirePromptsDir(io, dirFlag, config) {
168
- const promptsDir = await resolvePromptsDir(io, dirFlag, config);
187
+ export async function requirePromptsDir(io, dirFlag, loaded) {
188
+ const promptsDir = await resolvePromptsDir(io, dirFlag, loaded);
169
189
  if (!(await dirExists(io, promptsDir))) {
170
190
  io.stderr(`error: prompts folder not found: ${promptsDir}\n`);
171
191
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAUA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,SAAS,CAAC;AAsD7C;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,EAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC/E"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAWA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,SAAS,CAAC;AAyD7C;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,EAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2C/E"}
package/dist/src/run.js CHANGED
@@ -8,6 +8,7 @@ import { cmdLint } from "./commands/lint.js";
8
8
  import { cmdLs } from "./commands/ls.js";
9
9
  import { cmdResolve } from "./commands/resolve.js";
10
10
  import { cmdView } from "./commands/view.js";
11
+ import { cmdWatch } from "./commands/watch.js";
11
12
  import { defaultIO } from "./io.js";
12
13
  const HELP = `promptbook — compose prompts from reusable fragments
13
14
 
@@ -18,7 +19,8 @@ Commands:
18
19
  resolve [<book>/]<prompt> Assemble a prompt and print it to stdout (--all: every book)
19
20
  lint [<prompt>] Run static checks; with no prompt, book rules only
20
21
  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
+ bundle [<dir>] Compile a prompts folder into an importable book module (--all/--check)
23
+ watch [<dir>] Rebuild book.generated.ts as fragments/rules/compositions change
22
24
  view Start the local web viewer over the workspace (book switcher)
23
25
  annotations <action> Drain the viewer's feedback queue: list | resolve <id> | clear
24
26
  ls List compositions and fragments (--all: cross-book inventory)
@@ -38,13 +40,15 @@ Options:
38
40
  --samples N eval: default samples per fixture (default 1; a fixture's own samples wins)
39
41
  --threshold R eval: a fixture passes when passRate >= R (default 1)
40
42
  --lint eval: run a static lint gate over every variant first
41
- -o, --out <file> bundle: write the generated module to a file (default: stdout)
42
- --plain bundle: emit a plain module (no type-only import; e.g. for Deno)
43
+ -o, --out <file> bundle/watch: write to a file (default: stdout for bundle, <bookDir>/book.generated.ts for watch/--all)
44
+ --plain bundle/watch: emit a plain module (no type-only import; e.g. for Deno)
45
+ --check bundle: compare with the existing output; exit 1 on drift or missing artifact
46
+ --exclude-code-prompts bundle/watch: serialize code-prompts as an empty map (runtime-lean bundle)
43
47
  --port N view: port for the viewer server (default: a free port)
44
48
  --no-open view: do not open the browser after starting
45
49
  --fragments ls: list fragments only
46
50
  --compositions ls: list compositions only
47
- --all ls/resolve: span every book in the workspace
51
+ --all ls/resolve/bundle: span every book in the workspace
48
52
  -h, --help Show this help
49
53
  -v, --version Show the version
50
54
 
@@ -95,6 +99,8 @@ export async function run(argv, io = defaultIO()) {
95
99
  return cmdEval(args, io);
96
100
  case "bundle":
97
101
  return cmdBundle(args, io);
102
+ case "watch":
103
+ return cmdWatch(args, io);
98
104
  case "view":
99
105
  return cmdView(args, io);
100
106
  case "annotations":
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markbrutx/promptbook-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Thin terminal surface over @markbrutx/promptbook-core: resolve and ls for agents and CI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,11 +48,12 @@
48
48
  "check": "biome check ."
49
49
  },
50
50
  "dependencies": {
51
- "@markbrutx/promptbook-core": "^0.2.0",
52
- "@markbrutx/promptbook-openrouter": "^0.2.0"
51
+ "@markbrutx/promptbook-core": "^0.3.0",
52
+ "@markbrutx/promptbook-openrouter": "^0.3.0",
53
+ "chokidar": "^4.0.3"
53
54
  },
54
55
  "optionalDependencies": {
55
- "@markbrutx/promptbook-viewer": "^0.2.0"
56
+ "@markbrutx/promptbook-viewer": "^0.3.0"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@biomejs/biome": "latest",
package/src/args.ts CHANGED
@@ -20,8 +20,12 @@ export interface ParsedArgs {
20
20
  contextFile?: string;
21
21
  fragments: boolean;
22
22
  compositions: boolean;
23
- /** ls/resolve: operate across every book in the workspace. */
23
+ /** ls/resolve/bundle: operate across every book in the workspace. */
24
24
  all: boolean;
25
+ /** bundle: compare the generated output with the existing artifact and exit non-zero on drift. */
26
+ check: boolean;
27
+ /** bundle: serialize without code-prompts so a runtime bundle stays lean. */
28
+ excludeCodePrompts: boolean;
25
29
  /** lint: estimated token ceiling for the token-budget rule. */
26
30
  maxTokens?: number;
27
31
  /** lint: treat warnings as failures for the exit code. */
@@ -91,6 +95,8 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
91
95
  fragments: { type: "boolean" },
92
96
  compositions: { type: "boolean" },
93
97
  all: { type: "boolean" },
98
+ check: { type: "boolean" },
99
+ "exclude-code-prompts": { type: "boolean" },
94
100
  "max-tokens": { type: "string" },
95
101
  strict: { type: "boolean" },
96
102
  model: { type: "string" },
@@ -137,6 +143,8 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
137
143
  fragments: values.fragments ?? false,
138
144
  compositions: values.compositions ?? false,
139
145
  all: values.all ?? false,
146
+ check: values.check ?? false,
147
+ excludeCodePrompts: values["exclude-code-prompts"] ?? false,
140
148
  maxTokens,
141
149
  strict: values.strict ?? false,
142
150
  model: values.model,
@@ -1,9 +1,13 @@
1
- import { isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
1
+ import { basename, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
2
2
  import type { PromptBook } from "@markbrutx/promptbook-core";
3
3
  import { loadPrompts, serializeBook, serializeBookJson } from "@markbrutx/promptbook-core";
4
4
  import type { ParsedArgs } from "../args.js";
5
5
  import { requirePromptsDir } from "../config.js";
6
6
  import { emitWarnings, type IO } from "../io.js";
7
+ import { loadWorkspace } from "../workspace.js";
8
+
9
+ /** Default name of the artifact file each book emits alongside its sources. */
10
+ const BUNDLE_FILE = "book.generated.ts";
7
11
 
8
12
  /** Rewrite an absolute source path to a portable, forward-slash path relative to `dir`. */
9
13
  function relativizeSource(sourceFile: string, dir: string): string {
@@ -34,32 +38,124 @@ function portableBook(book: PromptBook, dir: string): PromptBook {
34
38
  };
35
39
  }
36
40
 
41
+ /** Return the book with `codePrompts` cleared — used by `--exclude-code-prompts`. */
42
+ function withoutCodePrompts(book: PromptBook): PromptBook {
43
+ return { ...book, codePrompts: new Map() };
44
+ }
45
+
46
+ /** Normalize EOL so a CRLF-checked-in artifact does not falsely look stale on an LF rebuild. */
47
+ function normalizeEol(text: string): string {
48
+ return text.replace(/\r\n/g, "\n");
49
+ }
50
+
51
+ /** First 1-based line index where two strings differ; null when equal. */
52
+ function firstDiffLine(actual: string, expected: string): number | null {
53
+ if (actual === expected) {
54
+ return null;
55
+ }
56
+ const a = actual.split("\n");
57
+ const b = expected.split("\n");
58
+ const max = Math.max(a.length, b.length);
59
+ for (let i = 0; i < max; i += 1) {
60
+ if (a[i] !== b[i]) {
61
+ return i + 1;
62
+ }
63
+ }
64
+ // Unreachable: if the strings differ, at least one index in [0, max) must too.
65
+ throw new Error("firstDiffLine: strings differ but no line diff found");
66
+ }
67
+
68
+ /** Build the output text for one book (TypeScript module or JSON). */
69
+ function renderOutput(book: PromptBook, args: ParsedArgs): string {
70
+ return args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
71
+ }
72
+
73
+ /** Pick the artifact path: explicit `--out`, else `book.generated.ts` next to the prompts folder. */
74
+ function targetPath(io: IO, args: ParsedArgs, promptsDir: string): string {
75
+ if (args.out !== undefined) {
76
+ return resolvePath(io.cwd(), args.out);
77
+ }
78
+ return join(promptsDir, BUNDLE_FILE);
79
+ }
80
+
81
+ /** One book's `--check` outcome: file present and matching, drifted, or missing. */
82
+ type CheckOutcome =
83
+ | { status: "up-to-date"; path: string }
84
+ | { status: "stale"; reason: "missing"; path: string }
85
+ | { status: "stale"; reason: "diff"; firstDiffLine: number; path: string };
86
+
87
+ async function checkOne(io: IO, output: string, path: string): Promise<CheckOutcome> {
88
+ let existing: string;
89
+ try {
90
+ existing = await io.fs.readFile(path);
91
+ } catch {
92
+ return { status: "stale", reason: "missing", path };
93
+ }
94
+ const line = firstDiffLine(normalizeEol(output), normalizeEol(existing));
95
+ if (line === null) {
96
+ return { status: "up-to-date", path };
97
+ }
98
+ return { status: "stale", reason: "diff", firstDiffLine: line, path };
99
+ }
100
+
101
+ function emitCheckResult(io: IO, args: ParsedArgs, bookName: string, outcome: CheckOutcome): void {
102
+ if (args.json) {
103
+ const diff =
104
+ outcome.status === "stale"
105
+ ? outcome.reason === "missing"
106
+ ? { reason: "missing", path: outcome.path }
107
+ : { reason: "diff", firstDiffLine: outcome.firstDiffLine }
108
+ : null;
109
+ io.stderr(`${JSON.stringify({ book: bookName, status: outcome.status, diff })}\n`);
110
+ return;
111
+ }
112
+ if (outcome.status === "up-to-date") {
113
+ io.stderr(`${bookName} up to date\n`);
114
+ return;
115
+ }
116
+ if (outcome.reason === "missing") {
117
+ io.stderr(`${bookName} stale (missing ${outcome.path})\n`);
118
+ return;
119
+ }
120
+ io.stderr(`${bookName} stale (first diff at line ${outcome.firstDiffLine})\n`);
121
+ }
122
+
37
123
  /**
38
- * `bundle [<dir>]`: load a prompts folder and emit it as a single importable
39
- * module exporting `book: PromptBook` (so a runtime can import the book instead
40
- * of reading the disk). Writes to stdout, or to a file with `-o`. `--json`
41
- * emits a structured dump instead of the TypeScript module.
124
+ * Bundle one book: load relativize optionally drop code-prompts serialize.
125
+ * Then either `--check` against the existing artifact, write to `--out`/the
126
+ * default `book.generated.ts`, or print to stdout (single-book, no `--out`).
42
127
  *
43
- * The folder comes from the positional `<dir>`, else `--dir`, else config /
44
- * `./prompts` (the standard resolution).
128
+ * Returns the exit code: 0 on success / up-to-date, 1 on drift / write failure.
129
+ * `bookName` is used in `--check` output and warnings; when `inWorkspace` is
130
+ * true the artifact is always written (not printed), matching `bundle --all`.
45
131
  */
46
- export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
47
- const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
48
- if (promptsDir === null) {
49
- return 1;
50
- }
51
-
52
- const book = portableBook(await loadPrompts(promptsDir, io.fs), promptsDir);
132
+ export async function bundleOne(
133
+ io: IO,
134
+ args: ParsedArgs,
135
+ promptsDir: string,
136
+ bookName: string,
137
+ inWorkspace: boolean,
138
+ ): Promise<number> {
139
+ const loaded = await loadPrompts(promptsDir, io.fs);
140
+ // `--exclude-code-prompts` discards them outright; skipping the relativize pass is the whole point.
141
+ const source = args.excludeCodePrompts ? withoutCodePrompts(loaded) : loaded;
142
+ const book = portableBook(source, promptsDir);
53
143
  emitWarnings(io, book.warnings);
54
144
 
55
- const output = args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
145
+ const output = renderOutput(book, args);
56
146
 
57
- if (args.out === undefined) {
147
+ if (args.check) {
148
+ const outcome = await checkOne(io, output, targetPath(io, args, promptsDir));
149
+ emitCheckResult(io, args, bookName, outcome);
150
+ return outcome.status === "up-to-date" ? 0 : 1;
151
+ }
152
+
153
+ if (args.out === undefined && !inWorkspace) {
58
154
  io.stdout(output);
59
155
  return 0;
60
156
  }
61
157
 
62
- const outPath = resolvePath(io.cwd(), args.out);
158
+ const outPath = targetPath(io, args, promptsDir);
63
159
  try {
64
160
  await io.writeFile(outPath, output);
65
161
  } catch (error) {
@@ -69,3 +165,53 @@ export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
69
165
  io.stderr(`wrote ${outPath}\n`);
70
166
  return 0;
71
167
  }
168
+
169
+ /** Run `bundleOne` over every book in the workspace in parallel; the worst exit code wins. */
170
+ async function bundleAll(io: IO, args: ParsedArgs, root: string): Promise<number> {
171
+ const workspace = await loadWorkspace(io, root);
172
+ if (workspace.books.length === 0) {
173
+ io.stderr(`error: no books found under ${root}\n`);
174
+ return 1;
175
+ }
176
+ const codes = await Promise.all(
177
+ workspace.books.map((book) => bundleOne(io, args, book.dir, book.name, true)),
178
+ );
179
+ return Math.max(0, ...codes);
180
+ }
181
+
182
+ /**
183
+ * `bundle [<dir>]`: load a prompts folder and emit it as an importable module
184
+ * exporting `book: PromptBook`. Writes to stdout, or to a file with `-o`.
185
+ *
186
+ * Flags:
187
+ * - `--json` emits the structured dump instead of the TypeScript module.
188
+ * - `--plain` drops the type-only import (e.g. for Deno consumers).
189
+ * - `--exclude-code-prompts` serializes code-prompts as an empty map so the
190
+ * runtime bundle stays lean while `code-prompts/` keeps living on disk as
191
+ * metadata for `ls` / the viewer.
192
+ * - `--check` compares the would-be output against the existing artifact
193
+ * (`book.generated.ts` next to the prompts folder, or `--out`); exits 1 on
194
+ * drift or a missing artifact and prints a short hint on stderr.
195
+ * - `--all` walks every book in the workspace and writes each to its own
196
+ * `book.generated.ts`; incompatible with `-o`.
197
+ *
198
+ * The folder comes from the positional `<dir>`, else `--dir`, else config /
199
+ * `./prompts`. `--all` does not accept a single-book `<dir>` operand differently.
200
+ */
201
+ export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
202
+ if (args.all && args.out !== undefined) {
203
+ io.stderr("error: --all is not compatible with -o/--out\n");
204
+ return 1;
205
+ }
206
+
207
+ const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
208
+ if (promptsDir === null) {
209
+ return 1;
210
+ }
211
+
212
+ if (args.all) {
213
+ return bundleAll(io, args, promptsDir);
214
+ }
215
+
216
+ return bundleOne(io, args, promptsDir, basename(promptsDir), false);
217
+ }