@markbrutx/promptbook-cli 0.1.0 → 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.
@@ -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;AAkD7C;;;;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":"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"}
package/dist/src/run.js CHANGED
@@ -15,13 +15,16 @@ Usage:
15
15
  promptbook <command> [options]
16
16
 
17
17
  Commands:
18
- resolve <prompt> Assemble a prompt and print it to stdout
18
+ resolve [<book>/]<prompt> Assemble a prompt and print it to stdout (--all: every book)
19
19
  lint [<prompt>] Run static checks; with no prompt, book rules only
20
20
  eval [<name|glob>] Run fixtures through a model adapter, report pass-rate
21
21
  bundle [<dir>] Compile a prompts folder into an importable book module
22
- view Start the local web viewer over the prompts folder
22
+ view Start the local web viewer over the workspace (book switcher)
23
23
  annotations <action> Drain the viewer's feedback queue: list | resolve <id> | clear
24
- ls List compositions and fragments
24
+ ls List compositions and fragments (--all: cross-book inventory)
25
+
26
+ A <book>/<comp> operand addresses one book in a multi-book workspace; a bare
27
+ name resolves by uniqueness. With a single-book --dir, names work unqualified.
25
28
 
26
29
  Options:
27
30
  --dir <path> Prompts folder (default: promptbook.json promptsDir, else ./prompts)
@@ -41,6 +44,7 @@ Options:
41
44
  --no-open view: do not open the browser after starting
42
45
  --fragments ls: list fragments only
43
46
  --compositions ls: list compositions only
47
+ --all ls/resolve: span every book in the workspace
44
48
  -h, --help Show this help
45
49
  -v, --version Show the version
46
50
 
@@ -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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"}
@@ -0,0 +1,51 @@
1
+ import type { PromptBook } from "@markbrutx/promptbook-core";
2
+ import type { IO } from "./io.js";
3
+ /** A discovered prompts book: its folder name and absolute directory. */
4
+ export interface Book {
5
+ name: string;
6
+ dir: string;
7
+ }
8
+ /** A workspace root and the books found directly under it (sorted by name). */
9
+ export interface Workspace {
10
+ root: string;
11
+ books: Book[];
12
+ }
13
+ /** A `<book>/<comp>` operand split into its parts (`book` absent for bare names). */
14
+ export interface Address {
15
+ book?: string;
16
+ comp: string;
17
+ }
18
+ /** A bare/qualified operand resolved to a concrete book + composition name. */
19
+ export interface ResolvedAddress {
20
+ book: Book;
21
+ comp: string;
22
+ /** The matched book, already loaded during bare-name resolution (reuse to skip a reload). */
23
+ loaded?: PromptBook;
24
+ }
25
+ /** True when `dir` looks like a prompts book (has a config or a loadable form). */
26
+ export declare function isBook(io: IO, dir: string): Promise<boolean>;
27
+ /**
28
+ * Find the books directly under `rootDir`: each non-internal subfolder that is
29
+ * itself a book. One level deep, not recursive; sorted by name for a stable
30
+ * menu. Dot/underscore folders and book internals are skipped.
31
+ */
32
+ export declare function discoverBooks(io: IO, rootDir: string): Promise<Book[]>;
33
+ /**
34
+ * Read a workspace from `rootDir`. When the root is itself a book it is the only
35
+ * book (back-compat single-book path, named after the folder); otherwise the
36
+ * root is a workspace of its sub-books.
37
+ */
38
+ export declare function loadWorkspace(io: IO, rootDir: string): Promise<Workspace>;
39
+ /** Split an operand on the first `/`: `book/comp` (comp may itself contain `/`). */
40
+ export declare function parseAddress(operand: string): Address;
41
+ /** A book's directory relative to the workspace root (`.` when it is the root). */
42
+ export declare function bookDir(workspace: Workspace, book: Book): string;
43
+ /**
44
+ * Resolve an operand to a concrete book + composition. A `<book>/<comp>` prefix
45
+ * that names a known book addresses it directly; otherwise the whole operand is
46
+ * a bare composition name, resolved by uniqueness across the workspace's books
47
+ * (a single-book workspace always uses its one book). Throws a clear error when
48
+ * a bare name is missing or ambiguous.
49
+ */
50
+ export declare function resolveAddress(io: IO, workspace: Workspace, operand: string): Promise<ResolvedAddress>;
51
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAElC,yEAAyE;AACzE,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAQD,mFAAmF;AACnF,wBAAsB,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQlE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAkB5E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAK/E;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEhE;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CAiC1B"}
@@ -0,0 +1,103 @@
1
+ import { basename, join, relative } from "node:path";
2
+ import { loadPrompts } from "@markbrutx/promptbook-core";
3
+ /** Folder names that are book internals or noise, never workspace sub-books. */
4
+ const NON_BOOK_DIRS = new Set(["node_modules", "fragments", "rules", "code-prompts"]);
5
+ /** Markers that make a folder a loadable book (a config or a loadable form). */
6
+ const BOOK_MARKERS = ["promptbook.json", "rules", "code-prompts"];
7
+ /** True when `dir` looks like a prompts book (has a config or a loadable form). */
8
+ export async function isBook(io, dir) {
9
+ let entries;
10
+ try {
11
+ entries = await io.fs.readDir(dir);
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ return BOOK_MARKERS.some((marker) => entries.includes(marker));
17
+ }
18
+ /**
19
+ * Find the books directly under `rootDir`: each non-internal subfolder that is
20
+ * itself a book. One level deep, not recursive; sorted by name for a stable
21
+ * menu. Dot/underscore folders and book internals are skipped.
22
+ */
23
+ export async function discoverBooks(io, rootDir) {
24
+ let entries;
25
+ try {
26
+ entries = await io.fs.readDir(rootDir);
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ const books = [];
32
+ for (const name of [...entries].sort()) {
33
+ if (name.startsWith(".") || name.startsWith("_") || NON_BOOK_DIRS.has(name)) {
34
+ continue;
35
+ }
36
+ const dir = join(rootDir, name);
37
+ if (await isBook(io, dir)) {
38
+ books.push({ name, dir });
39
+ }
40
+ }
41
+ return books;
42
+ }
43
+ /**
44
+ * Read a workspace from `rootDir`. When the root is itself a book it is the only
45
+ * book (back-compat single-book path, named after the folder); otherwise the
46
+ * root is a workspace of its sub-books.
47
+ */
48
+ export async function loadWorkspace(io, rootDir) {
49
+ if (await isBook(io, rootDir)) {
50
+ return { root: rootDir, books: [{ name: basename(rootDir), dir: rootDir }] };
51
+ }
52
+ return { root: rootDir, books: await discoverBooks(io, rootDir) };
53
+ }
54
+ /** Split an operand on the first `/`: `book/comp` (comp may itself contain `/`). */
55
+ export function parseAddress(operand) {
56
+ const slash = operand.indexOf("/");
57
+ if (slash === -1) {
58
+ return { comp: operand };
59
+ }
60
+ return { book: operand.slice(0, slash), comp: operand.slice(slash + 1) };
61
+ }
62
+ /** A book's directory relative to the workspace root (`.` when it is the root). */
63
+ export function bookDir(workspace, book) {
64
+ return relative(workspace.root, book.dir) || ".";
65
+ }
66
+ /**
67
+ * Resolve an operand to a concrete book + composition. A `<book>/<comp>` prefix
68
+ * that names a known book addresses it directly; otherwise the whole operand is
69
+ * a bare composition name, resolved by uniqueness across the workspace's books
70
+ * (a single-book workspace always uses its one book). Throws a clear error when
71
+ * a bare name is missing or ambiguous.
72
+ */
73
+ export async function resolveAddress(io, workspace, operand) {
74
+ const address = parseAddress(operand);
75
+ if (address.book !== undefined) {
76
+ const book = workspace.books.find((b) => b.name === address.book);
77
+ if (book !== undefined) {
78
+ return { book, comp: address.comp };
79
+ }
80
+ // Prefix names no book: fall through and treat the whole operand as a
81
+ // (possibly path-like) bare composition name.
82
+ }
83
+ if (workspace.books.length === 1) {
84
+ return { book: workspace.books[0], comp: operand };
85
+ }
86
+ const matches = [];
87
+ for (const book of workspace.books) {
88
+ const loaded = await loadPrompts(book.dir, io.fs);
89
+ if (loaded.compositions.has(operand)) {
90
+ matches.push({ book, loaded });
91
+ }
92
+ }
93
+ const [first] = matches;
94
+ if (matches.length === 1 && first !== undefined) {
95
+ return { book: first.book, comp: operand, loaded: first.loaded };
96
+ }
97
+ if (matches.length === 0) {
98
+ throw new Error(`Unknown prompt "${operand}" in any book. Qualify it as <book>/<comp>.`);
99
+ }
100
+ const names = matches.map((m) => m.book.name).join(", ");
101
+ throw new Error(`Ambiguous prompt "${operand}"; found in ${matches.length} books (${names}). Qualify it as <book>/<comp>.`);
102
+ }
103
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,28 @@
1
1
  {
2
2
  "name": "@markbrutx/promptbook-cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.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",
7
+ "keywords": [
8
+ "prompt",
9
+ "prompt-engineering",
10
+ "prompt-management",
11
+ "llm",
12
+ "ai",
13
+ "cli",
14
+ "composition",
15
+ "storybook"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/markbrutx/promptbook.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "homepage": "https://github.com/markbrutx/promptbook#readme",
23
+ "bugs": "https://github.com/markbrutx/promptbook/issues",
7
24
  "bin": {
8
- "promptbook": "./dist/bin/promptbook.js"
25
+ "promptbook": "dist/bin/promptbook.js"
9
26
  },
10
27
  "exports": {
11
28
  ".": {
@@ -31,11 +48,11 @@
31
48
  "check": "biome check ."
32
49
  },
33
50
  "dependencies": {
34
- "@markbrutx/promptbook-core": "^0.1.0",
35
- "@markbrutx/promptbook-openrouter": "^0.1.0"
51
+ "@markbrutx/promptbook-core": "^0.3.0",
52
+ "@markbrutx/promptbook-openrouter": "^0.3.0"
36
53
  },
37
54
  "optionalDependencies": {
38
- "@markbrutx/promptbook-viewer": "^0.1.0"
55
+ "@markbrutx/promptbook-viewer": "^0.3.0"
39
56
  },
40
57
  "devDependencies": {
41
58
  "@biomejs/biome": "latest",
package/src/args.ts CHANGED
@@ -20,6 +20,8 @@ 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. */
24
+ all: boolean;
23
25
  /** lint: estimated token ceiling for the token-budget rule. */
24
26
  maxTokens?: number;
25
27
  /** lint: treat warnings as failures for the exit code. */
@@ -88,6 +90,7 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
88
90
  "context-file": { type: "string" },
89
91
  fragments: { type: "boolean" },
90
92
  compositions: { type: "boolean" },
93
+ all: { type: "boolean" },
91
94
  "max-tokens": { type: "string" },
92
95
  strict: { type: "boolean" },
93
96
  model: { type: "string" },
@@ -133,6 +136,7 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
133
136
  contextFile: values["context-file"],
134
137
  fragments: values.fragments ?? false,
135
138
  compositions: values.compositions ?? false,
139
+ all: values.all ?? false,
136
140
  maxTokens,
137
141
  strict: values.strict ?? false,
138
142
  model: values.model,
@@ -1,15 +1,112 @@
1
- import { relative } from "node:path";
2
- import { loadPrompts } from "@markbrutx/promptbook-core";
1
+ import { basename, relative } from "node:path";
2
+ import type { PromptBook, RequiredContext } from "@markbrutx/promptbook-core";
3
+ import { loadPrompts, requiredContext } from "@markbrutx/promptbook-core";
3
4
  import type { ParsedArgs } from "../args.js";
4
5
  import { requirePromptsDir } from "../config.js";
5
6
  import { emitWarnings, type IO } from "../io.js";
7
+ import { bookDir, loadWorkspace } from "../workspace.js";
8
+
9
+ interface CompositionEntry {
10
+ name: string;
11
+ base: number;
12
+ rules: number;
13
+ requiredContext: RequiredContext;
14
+ }
15
+
16
+ interface CodePromptEntry {
17
+ name: string;
18
+ description: string | null;
19
+ samples: number;
20
+ }
21
+
22
+ /** Composition rows for the menu: counts plus the statically-declared context. */
23
+ function compositionEntries(book: PromptBook): CompositionEntry[] {
24
+ return [...book.compositions.values()]
25
+ .sort((a, b) => a.name.localeCompare(b.name))
26
+ .map((c) => ({
27
+ name: c.name,
28
+ base: c.base.length,
29
+ rules: c.rules.length,
30
+ requiredContext: requiredContext(book, c.name),
31
+ }));
32
+ }
33
+
34
+ /** Code-prompt rows: builder metadata only (opaque; no requiredContext). */
35
+ function codePromptEntries(book: PromptBook): CodePromptEntry[] {
36
+ return [...book.codePrompts.values()]
37
+ .sort((a, b) => a.name.localeCompare(b.name))
38
+ .map((c) => ({ name: c.name, description: c.description ?? null, samples: c.samples.length }));
39
+ }
40
+
41
+ /** Compact one-line context summary for the plain `--all` tree. */
42
+ function ctxSummary(rc: RequiredContext): string {
43
+ const vars = rc.vars.length > 0 ? rc.vars.join(",") : "-";
44
+ const axes =
45
+ Object.entries(rc.axes)
46
+ .map(([key, values]) => `${key}=${values.join("|")}`)
47
+ .join(" ") || "-";
48
+ return `vars=${vars} axes=${axes}`;
49
+ }
50
+
51
+ /**
52
+ * `ls --all`: a cross-book inventory of the whole workspace. Discovers every
53
+ * book under the root, loads each, and emits the menu (compositions with their
54
+ * required context + code-prompts) — `--json` as `{ workspace, books[] }`,
55
+ * plain as an indented book→composition tree. Order is deterministic (books and
56
+ * compositions sorted by name).
57
+ */
58
+ async function cmdLsAll(io: IO, root: string, json: boolean): Promise<number> {
59
+ const workspace = await loadWorkspace(io, root);
60
+ const books = await Promise.all(
61
+ workspace.books.map(async (b) => {
62
+ const book = await loadPrompts(b.dir, io.fs);
63
+ return {
64
+ name: b.name,
65
+ dir: bookDir(workspace, b),
66
+ compositions: compositionEntries(book),
67
+ codePrompts: codePromptEntries(book),
68
+ warnings: book.warnings,
69
+ };
70
+ }),
71
+ );
72
+
73
+ if (json) {
74
+ io.stdout(`${JSON.stringify({ workspace: basename(workspace.root), books }, null, 2)}\n`);
75
+ return 0;
76
+ }
77
+
78
+ const lines: string[] = [`workspace: ${basename(workspace.root)}`];
79
+ if (books.length === 0) {
80
+ lines.push(" (no books found)");
81
+ }
82
+ for (const book of books) {
83
+ lines.push("", `${book.name} (${book.dir})`);
84
+ lines.push(" compositions:");
85
+ if (book.compositions.length === 0) {
86
+ lines.push(" (none)");
87
+ }
88
+ for (const c of book.compositions) {
89
+ lines.push(` ${c.name} base=${c.base} rules=${c.rules} ctx: ${ctxSummary(c.requiredContext)}`);
90
+ }
91
+ if (book.codePrompts.length > 0) {
92
+ lines.push(" code-prompts:");
93
+ for (const c of book.codePrompts) {
94
+ lines.push(` ${c.name} kind=code samples=${c.samples}`);
95
+ }
96
+ }
97
+ emitWarnings(io, book.warnings);
98
+ }
99
+ io.stdout(`${lines.join("\n")}\n`);
100
+ return 0;
101
+ }
6
102
 
7
103
  /**
8
- * `ls`: list compositions (name, base length, rule count), code-prompts
9
- * (name, description, sample count) and fragments (id, kind, tags, source) —
10
- * the unified menu of every prompt in the book. `--fragments`/`--compositions`
11
- * narrow the output (code-prompts ride with compositions, both being prompts);
12
- * `--json` emits a structured list instead of the human-readable tree.
104
+ * `ls`: list compositions (name, base length, rule count, required context),
105
+ * code-prompts (name, description, sample count) and fragments (id, kind, tags,
106
+ * source) — the unified menu of every prompt in the book. `--fragments`/
107
+ * `--compositions` narrow the output (code-prompts ride with compositions, both
108
+ * being prompts); `--json` emits a structured list; `--all` spans every book in
109
+ * the workspace instead of one folder.
13
110
  */
14
111
  export async function cmdLs(args: ParsedArgs, io: IO): Promise<number> {
15
112
  const promptsDir = await requirePromptsDir(io, args.dir);
@@ -17,6 +114,10 @@ export async function cmdLs(args: ParsedArgs, io: IO): Promise<number> {
17
114
  return 1;
18
115
  }
19
116
 
117
+ if (args.all) {
118
+ return cmdLsAll(io, promptsDir, args.json);
119
+ }
120
+
20
121
  const book = await loadPrompts(promptsDir, io.fs);
21
122
  emitWarnings(io, book.warnings);
22
123
 
@@ -24,23 +125,15 @@ export async function cmdLs(args: ParsedArgs, io: IO): Promise<number> {
24
125
  const showCompositions = args.compositions || !onlyOneSection;
25
126
  const showFragments = args.fragments || !onlyOneSection;
26
127
 
27
- const compositions = [...book.compositions.values()].sort((a, b) => a.name.localeCompare(b.name));
28
- const codePrompts = [...book.codePrompts.values()].sort((a, b) => a.name.localeCompare(b.name));
128
+ const compositions = compositionEntries(book);
129
+ const codePrompts = codePromptEntries(book);
29
130
  const fragments = [...book.fragments.values()].sort((a, b) => a.id.localeCompare(b.id));
30
131
 
31
132
  if (args.json) {
32
133
  const out: Record<string, unknown> = {};
33
134
  if (showCompositions) {
34
- out.compositions = compositions.map((c) => ({
35
- name: c.name,
36
- base: c.base.length,
37
- rules: c.rules.length,
38
- }));
39
- out.codePrompts = codePrompts.map((c) => ({
40
- name: c.name,
41
- description: c.description ?? null,
42
- samples: c.samples.length,
43
- }));
135
+ out.compositions = compositions;
136
+ out.codePrompts = codePrompts;
44
137
  }
45
138
  if (showFragments) {
46
139
  out.fragments = fragments.map((f) => ({
@@ -61,13 +154,13 @@ export async function cmdLs(args: ParsedArgs, io: IO): Promise<number> {
61
154
  lines.push(" (none)");
62
155
  }
63
156
  for (const c of compositions) {
64
- lines.push(` ${c.name} base=${c.base.length} rules=${c.rules.length}`);
157
+ lines.push(` ${c.name} base=${c.base} rules=${c.rules}`);
65
158
  }
66
159
  if (codePrompts.length > 0) {
67
160
  lines.push("");
68
161
  lines.push("code-prompts:");
69
162
  for (const c of codePrompts) {
70
- lines.push(` ${c.name} kind=code samples=${c.samples.length}`);
163
+ lines.push(` ${c.name} kind=code samples=${c.samples}`);
71
164
  }
72
165
  }
73
166
  }