@markbrutx/promptbook-viewer 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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/server/annotations.d.ts +30 -0
  8. package/dist/server/annotations.d.ts.map +1 -0
  9. package/dist/server/annotations.js +62 -0
  10. package/dist/server/annotations.js.map +1 -0
  11. package/dist/server/api.d.ts +16 -0
  12. package/dist/server/api.d.ts.map +1 -0
  13. package/dist/server/api.js +134 -0
  14. package/dist/server/api.js.map +1 -0
  15. package/dist/server/book-source.d.ts +20 -0
  16. package/dist/server/book-source.d.ts.map +1 -0
  17. package/dist/server/book-source.js +32 -0
  18. package/dist/server/book-source.js.map +1 -0
  19. package/dist/server/responses.d.ts +12 -0
  20. package/dist/server/responses.d.ts.map +1 -0
  21. package/dist/server/responses.js +106 -0
  22. package/dist/server/responses.js.map +1 -0
  23. package/dist/server/segments.d.ts +12 -0
  24. package/dist/server/segments.d.ts.map +1 -0
  25. package/dist/server/segments.js +24 -0
  26. package/dist/server/segments.js.map +1 -0
  27. package/dist/server/server.d.ts +24 -0
  28. package/dist/server/server.d.ts.map +1 -0
  29. package/dist/server/server.js +71 -0
  30. package/dist/server/server.js.map +1 -0
  31. package/dist/server/static.d.ts +8 -0
  32. package/dist/server/static.d.ts.map +1 -0
  33. package/dist/server/static.js +55 -0
  34. package/dist/server/static.js.map +1 -0
  35. package/dist/server/used-in.d.ts +9 -0
  36. package/dist/server/used-in.d.ts.map +1 -0
  37. package/dist/server/used-in.js +19 -0
  38. package/dist/server/used-in.js.map +1 -0
  39. package/dist/shared/types.d.ts +113 -0
  40. package/dist/shared/types.d.ts.map +1 -0
  41. package/dist/shared/types.js +2 -0
  42. package/dist/shared/types.js.map +1 -0
  43. package/dist/web/assets/index-B2Wxtb-f.css +1 -0
  44. package/dist/web/assets/index-C8f_6lr_.js +51 -0
  45. package/dist/web/index.html +13 -0
  46. package/package.json +47 -0
  47. package/src/index.ts +19 -0
  48. package/src/server/annotations.ts +96 -0
  49. package/src/server/api.ts +164 -0
  50. package/src/server/book-source.ts +54 -0
  51. package/src/server/responses.ts +127 -0
  52. package/src/server/segments.ts +26 -0
  53. package/src/server/server.ts +96 -0
  54. package/src/server/static.ts +60 -0
  55. package/src/server/used-in.ts +23 -0
  56. package/src/shared/types.ts +137 -0
  57. package/src/web/App.tsx +307 -0
  58. package/src/web/annotations.ts +58 -0
  59. package/src/web/api.ts +44 -0
  60. package/src/web/colors.ts +21 -0
  61. package/src/web/components/Addons.tsx +109 -0
  62. package/src/web/components/Canvas.tsx +180 -0
  63. package/src/web/components/CodePromptView.tsx +87 -0
  64. package/src/web/components/Controls.tsx +71 -0
  65. package/src/web/components/Diff.tsx +30 -0
  66. package/src/web/components/FragmentView.tsx +54 -0
  67. package/src/web/components/Sidebar.tsx +178 -0
  68. package/src/web/diff.ts +51 -0
  69. package/src/web/env.d.ts +3 -0
  70. package/src/web/format.ts +8 -0
  71. package/src/web/index.html +12 -0
  72. package/src/web/main.tsx +15 -0
  73. package/src/web/selection.ts +5 -0
  74. package/src/web/styles.css +484 -0
  75. package/src/web/tree.ts +134 -0
  76. package/src/web/types.ts +17 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 markbrutx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @markbrutx/promptbook-viewer
2
+
3
+ Storybook-for-prompts: a local web app that renders a prompts folder via
4
+ [`@markbrutx/promptbook-core`](https://www.npmjs.com/package/@markbrutx/promptbook-core).
5
+ Part of [promptbook](https://github.com/markbrutx/promptbook).
6
+
7
+ ```
8
+ npm i -D @markbrutx/promptbook-viewer
9
+ ```
10
+
11
+ Usually you don't import this directly — run `promptbook view` from
12
+ [`@markbrutx/promptbook-cli`](https://www.npmjs.com/package/@markbrutx/promptbook-cli),
13
+ which lazily loads it.
14
+
15
+ ## What it shows
16
+
17
+ - a sidebar tree: Fragments / Compositions → variants
18
+ - the assembled prompt with each fragment colored by source + a legend
19
+ - context pickers (mode, locale, target-model, …) → live re-resolve
20
+ - token count, inline lint warnings, and the explain trace
21
+ - a used-in graph for any fragment ("shared by 10 of 20")
22
+ - a diff between two variants
23
+ - select text → comment → send to the agent inbox queue
24
+
25
+ ## Programmatic use
26
+
27
+ ```ts
28
+ import { startViewer } from "@markbrutx/promptbook-viewer";
29
+
30
+ const { url, close } = await startViewer({ promptsDir: "./prompts", open: true });
31
+ ```
32
+
33
+ The server is a thin wrapper over `core`; the React UI is built into static
34
+ assets at publish time.
35
+
36
+ ## License
37
+
38
+ [MIT](./LICENSE)
@@ -0,0 +1,6 @@
1
+ export type { AnnotateInput, AnnotationStore } from "./server/annotations.js";
2
+ export { createAnnotationStore } from "./server/annotations.js";
3
+ export type { Viewer, ViewerOptions } from "./server/server.js";
4
+ export { startViewer } from "./server/server.js";
5
+ export type { AnnotateRequest, Annotation, AnnotationsResponse, BookResponse, CompositionSummary, FragmentSummary, LintResponse, ResolveResponse, RuleSummary, Segment, UsedInReference, UsedInResponse, VariantSummary, } from "./shared/types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EACV,eAAe,EACf,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,eAAe,EACf,WAAW,EACX,OAAO,EACP,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createAnnotationStore } from "./server/annotations.js";
2
+ export { startViewer } from "./server/server.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { Annotation, Context } from "@markbrutx/promptbook-core";
2
+ /** The flat body the viewer posts; the store expands it into an {@link Annotation}. */
3
+ export interface AnnotateInput {
4
+ /** Composition the variant was assembled from (omit for a fragment target). */
5
+ prompt?: string;
6
+ /** Context the variant was resolved under (kept with `prompt`). */
7
+ context?: Context;
8
+ fragmentId: string;
9
+ anchorText: string;
10
+ comment: string;
11
+ offset?: number;
12
+ /** Source file of the fragment, when annotating a fragment directly. */
13
+ sourceFile?: string;
14
+ }
15
+ /** The append-safe queue under `<promptsDir>/.annotations/inbox.jsonl`. */
16
+ export interface AnnotationStore {
17
+ /** Append a new open annotation and return it. */
18
+ append(input: AnnotateInput): Promise<Annotation>;
19
+ /** Open annotations in queue order (oldest first). */
20
+ list(): Promise<Annotation[]>;
21
+ /** Remove one annotation by id; resolves to whether it existed. */
22
+ remove(id: string): Promise<boolean>;
23
+ }
24
+ /**
25
+ * File-backed annotation queue. Appends are line-atomic (one JSONL line);
26
+ * removals rewrite the file. A missing queue simply reads as empty. This is the
27
+ * viewer's only write surface — `@markbrutx/promptbook-core` stays read-only.
28
+ */
29
+ export declare function createAnnotationStore(promptsDir: string): AnnotationStore;
30
+ //# sourceMappingURL=annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/server/annotations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAsC,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAS1G,uFAAuF;AACvF,MAAM,WAAW,aAAa;IAC5B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAClD,sDAAsD;IACtD,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9B,mEAAmE;IACnE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC;AAuBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAiCzE"}
@@ -0,0 +1,62 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { ANNOTATION_QUEUE_DIR, ANNOTATION_QUEUE_FILE, parseInbox, serializeAnnotationLine, serializeInbox, } from "@markbrutx/promptbook-core";
5
+ /** Expand the posted body into a full annotation (id + timestamp added here). */
6
+ function buildAnnotation(input) {
7
+ const target = input.prompt !== undefined
8
+ ? { prompt: input.prompt, context: input.context ?? {} }
9
+ : { fragmentId: input.fragmentId, ...(input.sourceFile ? { sourceFile: input.sourceFile } : {}) };
10
+ const anchor = {
11
+ fragmentId: input.fragmentId,
12
+ anchorText: input.anchorText,
13
+ ...(input.offset !== undefined ? { offset: input.offset } : {}),
14
+ };
15
+ return {
16
+ id: randomUUID(),
17
+ createdAt: new Date().toISOString(),
18
+ target,
19
+ anchor,
20
+ comment: input.comment,
21
+ status: "open",
22
+ };
23
+ }
24
+ /**
25
+ * File-backed annotation queue. Appends are line-atomic (one JSONL line);
26
+ * removals rewrite the file. A missing queue simply reads as empty. This is the
27
+ * viewer's only write surface — `@markbrutx/promptbook-core` stays read-only.
28
+ */
29
+ export function createAnnotationStore(promptsDir) {
30
+ const dir = join(promptsDir, ANNOTATION_QUEUE_DIR);
31
+ const file = join(dir, ANNOTATION_QUEUE_FILE);
32
+ const readAll = async () => {
33
+ try {
34
+ return parseInbox(await readFile(file, "utf8"));
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ };
40
+ return {
41
+ async append(input) {
42
+ const annotation = buildAnnotation(input);
43
+ await mkdir(dir, { recursive: true });
44
+ await appendFile(file, serializeAnnotationLine(annotation));
45
+ return annotation;
46
+ },
47
+ async list() {
48
+ return (await readAll()).filter((annotation) => annotation.status === "open");
49
+ },
50
+ async remove(id) {
51
+ const all = await readAll();
52
+ const next = all.filter((annotation) => annotation.id !== id);
53
+ if (next.length === all.length) {
54
+ return false;
55
+ }
56
+ // The dir/file already exist (an annotation was present), so no mkdir.
57
+ await writeFile(file, serializeInbox(next));
58
+ return true;
59
+ },
60
+ };
61
+ }
62
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/server/annotations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { BookSource } from "./book-source.js";
3
+ export interface RequestHandlerOptions {
4
+ source: BookSource;
5
+ promptsDir: string;
6
+ /** Directory of the built web bundle (dist/web). */
7
+ webRoot: string;
8
+ }
9
+ /** A request handler plus the hook the folder watcher uses to push reloads. */
10
+ export interface RequestHandler {
11
+ handle(req: IncomingMessage, res: ServerResponse): void;
12
+ /** Invalidate the cached book and notify connected clients to refetch. */
13
+ notifyReload(): void;
14
+ }
15
+ export declare function createRequestHandler(options: RequestHandlerOptions): RequestHandler;
16
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/server/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AASnD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAAC;IACxD,0EAA0E;IAC1E,YAAY,IAAI,IAAI,CAAC;CACtB;AAyDD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAiFnF"}
@@ -0,0 +1,134 @@
1
+ import { createAnnotationStore } from "./annotations.js";
2
+ import { buildBookResponse, buildLintResponse, buildResolveResponse, buildUsedInResponse, } from "./responses.js";
3
+ import { serveStatic } from "./static.js";
4
+ function sendJson(res, status, payload) {
5
+ const body = JSON.stringify(payload);
6
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
7
+ res.end(body);
8
+ }
9
+ async function readJsonBody(req) {
10
+ const chunks = [];
11
+ for await (const chunk of req) {
12
+ chunks.push(chunk);
13
+ }
14
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
15
+ if (raw === "") {
16
+ return {};
17
+ }
18
+ return JSON.parse(raw);
19
+ }
20
+ function asResolveRequest(body) {
21
+ const data = (body ?? {});
22
+ if (typeof data.prompt !== "string" || data.prompt === "") {
23
+ throw new Error('request body must include a "prompt" name');
24
+ }
25
+ return { prompt: data.prompt, context: data.context ?? {} };
26
+ }
27
+ function asAnnotateRequest(body) {
28
+ const data = (body ?? {});
29
+ if (typeof data.fragmentId !== "string" || data.fragmentId === "") {
30
+ throw new Error('request body must include a "fragmentId"');
31
+ }
32
+ if (typeof data.anchorText !== "string" || data.anchorText === "") {
33
+ throw new Error('request body must include "anchorText"');
34
+ }
35
+ if (typeof data.comment !== "string" || data.comment.trim() === "") {
36
+ throw new Error('request body must include a non-empty "comment"');
37
+ }
38
+ const input = {
39
+ fragmentId: data.fragmentId,
40
+ anchorText: data.anchorText,
41
+ comment: data.comment,
42
+ };
43
+ if (typeof data.prompt === "string" && data.prompt !== "") {
44
+ input.prompt = data.prompt;
45
+ input.context = data.context ?? {};
46
+ }
47
+ if (typeof data.offset === "number") {
48
+ input.offset = data.offset;
49
+ }
50
+ if (typeof data.sourceFile === "string") {
51
+ input.sourceFile = data.sourceFile;
52
+ }
53
+ return input;
54
+ }
55
+ export function createRequestHandler(options) {
56
+ const { source, promptsDir, webRoot } = options;
57
+ const sseClients = new Set();
58
+ const annotations = createAnnotationStore(promptsDir);
59
+ const handle = async (req, res) => {
60
+ const url = new URL(req.url ?? "/", "http://localhost");
61
+ const path = url.pathname;
62
+ const method = req.method ?? "GET";
63
+ try {
64
+ if (path === "/api/book" && method === "GET") {
65
+ sendJson(res, 200, buildBookResponse(await source.get(), promptsDir));
66
+ return;
67
+ }
68
+ if (path === "/api/resolve" && method === "POST") {
69
+ const { prompt, context } = asResolveRequest(await readJsonBody(req));
70
+ sendJson(res, 200, buildResolveResponse(await source.get(), prompt, context));
71
+ return;
72
+ }
73
+ if (path === "/api/lint" && method === "POST") {
74
+ const { prompt, context } = asResolveRequest(await readJsonBody(req));
75
+ sendJson(res, 200, buildLintResponse(await source.get(), prompt, context));
76
+ return;
77
+ }
78
+ if (path.startsWith("/api/used-in/") && method === "GET") {
79
+ const id = decodeURIComponent(path.slice("/api/used-in/".length));
80
+ sendJson(res, 200, buildUsedInResponse(await source.get(), id));
81
+ return;
82
+ }
83
+ if (path === "/api/annotate" && method === "POST") {
84
+ const annotation = await annotations.append(asAnnotateRequest(await readJsonBody(req)));
85
+ sendJson(res, 200, annotation);
86
+ return;
87
+ }
88
+ if (path === "/api/annotations" && method === "GET") {
89
+ sendJson(res, 200, { annotations: await annotations.list() });
90
+ return;
91
+ }
92
+ if (path.startsWith("/api/annotations/") && method === "DELETE") {
93
+ const id = decodeURIComponent(path.slice("/api/annotations/".length));
94
+ const removed = await annotations.remove(id);
95
+ sendJson(res, removed ? 200 : 404, { id, removed });
96
+ return;
97
+ }
98
+ if (path === "/api/events" && method === "GET") {
99
+ addSseClient(res);
100
+ return;
101
+ }
102
+ if (path.startsWith("/api/")) {
103
+ sendJson(res, 404, { error: `unknown route ${method} ${path}` });
104
+ return;
105
+ }
106
+ await serveStatic(res, webRoot, path);
107
+ }
108
+ catch (error) {
109
+ sendJson(res, 400, { error: error.message });
110
+ }
111
+ };
112
+ function addSseClient(res) {
113
+ res.writeHead(200, {
114
+ "content-type": "text/event-stream",
115
+ "cache-control": "no-cache",
116
+ connection: "keep-alive",
117
+ });
118
+ res.write(": connected\n\n");
119
+ sseClients.add(res);
120
+ res.on("close", () => sseClients.delete(res));
121
+ }
122
+ return {
123
+ handle(req, res) {
124
+ void handle(req, res);
125
+ },
126
+ notifyReload() {
127
+ source.invalidate();
128
+ for (const client of sseClients) {
129
+ client.write("event: reload\ndata: {}\n\n");
130
+ }
131
+ },
132
+ };
133
+ }
134
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/server/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Fixture, FsAdapter, PromptBook } from "@markbrutx/promptbook-core";
2
+ /** A loaded prompts folder plus the fixtures that supply named variants. */
3
+ export interface LoadedFolder {
4
+ book: PromptBook;
5
+ fixtures: Fixture[];
6
+ }
7
+ /**
8
+ * Caches the loaded folder and reloads lazily after {@link invalidate}. The
9
+ * server wires a folder watcher to `invalidate` for hot-reload; tests can call
10
+ * it directly. Reads are serialized so a burst of requests during a reload
11
+ * shares one load.
12
+ */
13
+ export interface BookSource {
14
+ /** Current folder contents, reloading first if it was invalidated. */
15
+ get(): Promise<LoadedFolder>;
16
+ /** Mark the cache stale; the next {@link get} reloads from disk. */
17
+ invalidate(): void;
18
+ }
19
+ export declare function createBookSource(promptsDir: string, fs?: FsAdapter): BookSource;
20
+ //# sourceMappingURL=book-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"book-source.d.ts","sourceRoot":"","sources":["../../src/server/book-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGjF,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7B,oEAAoE;IACpE,UAAU,IAAI,IAAI,CAAC;CACpB;AAWD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,SAAS,GAAG,UAAU,CAsB/E"}
@@ -0,0 +1,32 @@
1
+ import { loadFixtures, loadPrompts } from "@markbrutx/promptbook-core";
2
+ /** Fixtures are optional: a folder without a `fixtures/` dir simply has none. */
3
+ async function safeLoadFixtures(promptsDir, fs) {
4
+ try {
5
+ return await loadFixtures(promptsDir, fs);
6
+ }
7
+ catch {
8
+ return [];
9
+ }
10
+ }
11
+ export function createBookSource(promptsDir, fs) {
12
+ let cached;
13
+ const load = async () => {
14
+ const [book, fixtures] = await Promise.all([
15
+ loadPrompts(promptsDir, fs),
16
+ safeLoadFixtures(promptsDir, fs),
17
+ ]);
18
+ return { book, fixtures };
19
+ };
20
+ return {
21
+ get() {
22
+ if (cached === undefined) {
23
+ cached = load();
24
+ }
25
+ return cached;
26
+ },
27
+ invalidate() {
28
+ cached = undefined;
29
+ },
30
+ };
31
+ }
32
+ //# sourceMappingURL=book-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"book-source.js","sourceRoot":"","sources":["../../src/server/book-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAqBvE,iFAAiF;AACjF,KAAK,UAAU,gBAAgB,CAAC,UAAkB,EAAE,EAAc;IAChE,IAAI,CAAC;QACH,OAAO,MAAM,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,EAAc;IACjE,IAAI,MAAyC,CAAC;IAE9C,MAAM,IAAI,GAAG,KAAK,IAA2B,EAAE;QAC7C,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACzC,WAAW,CAAC,UAAU,EAAE,EAAE,CAAC;YAC3B,gBAAgB,CAAC,UAAU,EAAE,EAAE,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC;IAEF,OAAO;QACL,GAAG;YACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,GAAG,IAAI,EAAE,CAAC;YAClB,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,UAAU;YACR,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Context } from "@markbrutx/promptbook-core";
2
+ import type { BookResponse, LintResponse, ResolveResponse, UsedInResponse } from "../shared/types.js";
3
+ import type { LoadedFolder } from "./book-source.js";
4
+ /** Build the `GET /api/book` payload: compositions + fragments + warnings. */
5
+ export declare function buildBookResponse(folder: LoadedFolder, promptsDir: string): BookResponse;
6
+ /** Resolve a variant and attach the colored segments. Throws on unknown prompt. */
7
+ export declare function buildResolveResponse(folder: LoadedFolder, prompt: string, context: Context): ResolveResponse;
8
+ /** Lint a resolved variant and estimate its token count. Throws on unknown prompt. */
9
+ export declare function buildLintResponse(folder: LoadedFolder, prompt: string, context: Context): LintResponse;
10
+ /** List the compositions/rules that reference a fragment id. */
11
+ export declare function buildUsedInResponse(folder: LoadedFolder, fragmentId: string): UsedInResponse;
12
+ //# sourceMappingURL=responses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/server/responses.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA2B,OAAO,EAAQ,MAAM,4BAA4B,CAAC;AAEzF,OAAO,KAAK,EACV,YAAY,EAGZ,YAAY,EACZ,eAAe,EAEf,cAAc,EAEf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkErD,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAmBxF;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,eAAe,CAGjB;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,YAAY,CAStG;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAE5F"}
@@ -0,0 +1,106 @@
1
+ import { relative, sep } from "node:path";
2
+ import { estimateTokensByChars, lint, resolveBook } from "@markbrutx/promptbook-core";
3
+ import { deriveSegments } from "./segments.js";
4
+ import { usedIn } from "./used-in.js";
5
+ /** Path relative to the prompts folder, normalized to forward slashes. */
6
+ function relativeSource(promptsDir, sourceFile) {
7
+ const rel = relative(promptsDir, sourceFile);
8
+ return (rel === "" ? sourceFile : rel).split(sep).join("/");
9
+ }
10
+ /** Drop undefined action fields so the wire shape stays minimal. */
11
+ function summarizeRule(rule) {
12
+ const summary = { index: rule.index, when: rule.when, action: rule.action };
13
+ if (rule.add !== undefined)
14
+ summary.add = rule.add;
15
+ if (rule.after !== undefined)
16
+ summary.after = rule.after;
17
+ if (rule.replace !== undefined)
18
+ summary.replace = rule.replace;
19
+ if (rule.forbid !== undefined)
20
+ summary.forbid = rule.forbid;
21
+ if (rule.order !== undefined)
22
+ summary.order = rule.order;
23
+ return summary;
24
+ }
25
+ /** Group fixtures by the composition they target, for variant lookup. */
26
+ function variantsByPrompt(folder) {
27
+ const byPrompt = new Map();
28
+ for (const fixture of folder.fixtures) {
29
+ const list = byPrompt.get(fixture.prompt) ?? [];
30
+ list.push({ name: fixture.name, context: fixture.context ?? {} });
31
+ byPrompt.set(fixture.prompt, list);
32
+ }
33
+ for (const list of byPrompt.values()) {
34
+ list.sort((a, b) => a.name.localeCompare(b.name));
35
+ }
36
+ return byPrompt;
37
+ }
38
+ /** Project a code-prompt to the wire shape, relativizing its manifest path. */
39
+ function summarizeCodePrompt(promptsDir, codePrompt) {
40
+ const summary = {
41
+ name: codePrompt.name,
42
+ samples: codePrompt.samples.map((s) => ({
43
+ label: s.label,
44
+ ...(s.context !== undefined ? { context: s.context } : {}),
45
+ output: s.output,
46
+ })),
47
+ sourceFile: relativeSource(promptsDir, codePrompt.sourceFile),
48
+ };
49
+ if (codePrompt.description !== undefined)
50
+ summary.description = codePrompt.description;
51
+ return summary;
52
+ }
53
+ function summarizeComposition(variants, promptsDir, c) {
54
+ const summary = {
55
+ name: c.name,
56
+ base: c.base,
57
+ rules: c.rules.map(summarizeRule),
58
+ sourceFile: relativeSource(promptsDir, c.sourceFile),
59
+ variants,
60
+ };
61
+ if (c.order !== undefined)
62
+ summary.order = c.order;
63
+ return summary;
64
+ }
65
+ /** Build the `GET /api/book` payload: compositions + fragments + warnings. */
66
+ export function buildBookResponse(folder, promptsDir) {
67
+ const { book } = folder;
68
+ const variants = variantsByPrompt(folder);
69
+ const compositions = [...book.compositions.values()]
70
+ .sort((a, b) => a.name.localeCompare(b.name))
71
+ .map((c) => summarizeComposition(variants.get(c.name) ?? [], promptsDir, c));
72
+ const codePrompts = [...book.codePrompts.values()]
73
+ .sort((a, b) => a.name.localeCompare(b.name))
74
+ .map((c) => summarizeCodePrompt(promptsDir, c));
75
+ const fragments = [...book.fragments.values()]
76
+ .sort((a, b) => a.id.localeCompare(b.id))
77
+ .map((f) => ({
78
+ id: f.id,
79
+ ...(f.kind !== undefined ? { kind: f.kind } : {}),
80
+ tags: f.tags ?? [],
81
+ body: f.body,
82
+ sourceFile: relativeSource(promptsDir, f.sourceFile),
83
+ }));
84
+ return { compositions, codePrompts, fragments, warnings: book.warnings };
85
+ }
86
+ /** Resolve a variant and attach the colored segments. Throws on unknown prompt. */
87
+ export function buildResolveResponse(folder, prompt, context) {
88
+ const { text, trace } = resolveBook(folder.book, prompt, context);
89
+ return { text, trace, segments: deriveSegments(folder.book, trace.finalOrder, context) };
90
+ }
91
+ /** Lint a resolved variant and estimate its token count. Throws on unknown prompt. */
92
+ export function buildLintResponse(folder, prompt, context) {
93
+ const result = resolveBook(folder.book, prompt, context);
94
+ const report = lint({ book: folder.book, result });
95
+ return {
96
+ findings: report.findings,
97
+ errorCount: report.errorCount,
98
+ warningCount: report.warningCount,
99
+ tokens: estimateTokensByChars(result.text),
100
+ };
101
+ }
102
+ /** List the compositions/rules that reference a fragment id. */
103
+ export function buildUsedInResponse(folder, fragmentId) {
104
+ return { fragmentId, references: usedIn(folder.book, fragmentId) };
105
+ }
106
+ //# sourceMappingURL=responses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.js","sourceRoot":"","sources":["../../src/server/responses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Context, PromptBook } from "@markbrutx/promptbook-core";
2
+ import type { Segment } from "../shared/types.js";
3
+ /**
4
+ * Re-derive the per-fragment slices of an assembled prompt for coloring.
5
+ *
6
+ * `resolveBook` joins the interpolated bodies of `trace.finalOrder` with
7
+ * `"\n\n"`. Running the same interpolation over the same ids reproduces those
8
+ * parts exactly, so `segments.map(s => s.text).join("\n\n")` is byte-identical
9
+ * to the resolved `text`. This is asserted in the tests.
10
+ */
11
+ export declare function deriveSegments(book: PromptBook, finalOrder: string[], context: Context): Segment[];
12
+ //# sourceMappingURL=segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.d.ts","sourceRoot":"","sources":["../../src/server/segments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,CAalG"}
@@ -0,0 +1,24 @@
1
+ import { interpolate } from "@markbrutx/promptbook-core";
2
+ /**
3
+ * Re-derive the per-fragment slices of an assembled prompt for coloring.
4
+ *
5
+ * `resolveBook` joins the interpolated bodies of `trace.finalOrder` with
6
+ * `"\n\n"`. Running the same interpolation over the same ids reproduces those
7
+ * parts exactly, so `segments.map(s => s.text).join("\n\n")` is byte-identical
8
+ * to the resolved `text`. This is asserted in the tests.
9
+ */
10
+ export function deriveSegments(book, finalOrder, context) {
11
+ const segments = [];
12
+ for (const fragmentId of finalOrder) {
13
+ const fragment = book.fragments.get(fragmentId);
14
+ if (fragment === undefined) {
15
+ continue;
16
+ }
17
+ segments.push({
18
+ fragmentId,
19
+ text: interpolate(fragment.body, context, () => { }),
20
+ });
21
+ }
22
+ return segments;
23
+ }
24
+ //# sourceMappingURL=segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.js","sourceRoot":"","sources":["../../src/server/segments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAGzD;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,UAAoB,EAAE,OAAgB;IACrF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,UAAU,IAAI,UAAU,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC;YACZ,UAAU;YACV,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC;SACpD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { FsAdapter } from "@markbrutx/promptbook-core";
2
+ export interface ViewerOptions {
3
+ /** Folder containing `fragments/`, `rules/` and optional `fixtures/`. */
4
+ promptsDir: string;
5
+ /** Port to listen on; 0 (default) picks a free port. */
6
+ port?: number;
7
+ /** Open the URL in the default browser once listening. Default false. */
8
+ open?: boolean;
9
+ /** Filesystem adapter forwarded to the core loader (defaults to Node fs). */
10
+ fs?: FsAdapter;
11
+ }
12
+ /** A running viewer: its URL, the bound port, and a shutdown hook. */
13
+ export interface Viewer {
14
+ url: string;
15
+ port: number;
16
+ close(): Promise<void>;
17
+ }
18
+ /**
19
+ * Start the viewer server: serves the built web bundle and the `/api/*` routes
20
+ * over a prompts folder, hot-reloading on folder edits. Render-only; no model
21
+ * calls. Resolves once the server is listening.
22
+ */
23
+ export declare function startViewer(options: ViewerOptions): Promise<Viewer>;
24
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAI5D,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6EAA6E;IAC7E,EAAE,CAAC,EAAE,SAAS,CAAC;CAChB;AAED,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAoCD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzE"}