{"id":51527813,"url":"https://github.com/markbrutx/promptbook","last_synced_at":"2026-07-08T23:30:48.945Z","repository":{"id":362028413,"uuid":"1254571130","full_name":"markbrutx/promptbook","owner":"markbrutx","description":"Agnostic, deterministic prompts-as-code: compose prompts from reusable fragments via declarative rules. OSS.","archived":false,"fork":false,"pushed_at":"2026-07-04T13:42:02.000Z","size":4883,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T15:21:21.063Z","etag":null,"topics":["ai-agents","claude-code","developer-tools","evals","llm","prompt-engineering","prompts","typescript"],"latest_commit_sha":null,"homepage":"https://pbook.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markbrutx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T18:28:31.000Z","updated_at":"2026-07-04T13:42:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/markbrutx/promptbook","commit_stats":null,"previous_names":["markbrutx/promptbook"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/markbrutx/promptbook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbrutx%2Fpromptbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbrutx%2Fpromptbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbrutx%2Fpromptbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbrutx%2Fpromptbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markbrutx","download_url":"https://codeload.github.com/markbrutx/promptbook/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbrutx%2Fpromptbook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35281838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude-code","developer-tools","evals","llm","prompt-engineering","prompts","typescript"],"created_at":"2026-07-08T23:30:48.574Z","updated_at":"2026-07-08T23:30:48.937Z","avatar_url":"https://github.com/markbrutx.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"assets/promptbook-logo.png\" alt=\"promptbook\" width=\"360\" /\u003e\u003c/p\u003e\n\n# promptbook\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/markbrutx/promptbook/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/markbrutx/promptbook/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@markbrutx/promptbook-cli\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/%40markbrutx%2Fpromptbook-cli?label=npm\" alt=\"npm\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/markbrutx/promptbook/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"license: MIT\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nYour system prompts are string spaghetti. They started as throwaway text, got\ncopy-pasted across flows, grew `if (model === \"gpt\")` branches, and now live\nscattered through the codebase as template literals. You can't see which pieces\nare shared, what is safe to change, or what the model actually receives — so\nediting is scary and review is impossible.\n\n**promptbook turns prompts into a folder of small plain files.** Fragments\n(Markdown) hold the text, rules (YAML) say when each piece applies, and one\npure `resolve()` assembles the final string. Storybook for prompts: *see* every\nassembled variant. Obsidian for prompts: *see* the whole graph. Deterministic —\nsame folder + same context → byte-identical string, zero model calls in the\nengine. Model-, provider-, language- and platform-agnostic.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"assets/screenshot-graph.png\" alt=\"The graph view: every composition, fragment and code-prompt of a book as a glowing force-directed map\" width=\"920\" /\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eThe graph view over \u003ccode\u003eexamples/sports-broadcast\u003c/code\u003e — every node is a file, every edge a reference.\u003c/sub\u003e\u003c/p\u003e\n\n## Why\n\n- **See the blast radius before prod.** `lint` and the graph show every prompt\n  a fragment edit touches — no model call needed.\n- **Review prompts like code.** A prompt change is a readable diff over small\n  files, not a rewrite of a 200-line template literal.\n- **One prompt compiles per model.** The target model is a context axis: one\n  logical prompt emits prose for one model, JSON for another, XML for a third.\n- **Agents edit safely.** Deterministic resolve + trace + lint is a loop an\n  agent can run without burning tokens; the shipped skills teach it the format.\n\nNot a framework. No orchestration, no chains, no retry loops, no lock-in. The\nengine is a pure function over files; the only stochastic step — the model\ncall — stays in your code behind an adapter.\n\n## Quickstart\n\nThree ways in. Pick one.\n\n**For your agent** — one command and your agent knows how to read, edit and\nmigrate prompt books. Works with Claude Code, Cursor, Codex, Copilot, Gemini\nand 15+ other agents:\n\n```bash\nnpx skills add markbrutx/promptbook\n```\n\nShips four skills: `promptbook-install`, `promptbook-migrate`,\n`promptbook-doctor`, `promptbook-annotations`. The migrate skill moves your\nexisting scattered prompts into a book without changing what the model\nreceives. Browse them on [skills.sh](https://skills.sh/markbrutx/promptbook).\n\n**For the CLI** — in 60 seconds you'll have a real book open in your browser.\nIn your own project, scaffold one and view it:\n\n```bash\nnpx @markbrutx/promptbook-cli init   # promptbook.json + a starter book\nnpx @markbrutx/promptbook-cli view   # browse it: canvas + graph\n```\n\nOr browse a bigger example from a clone of this repo:\n\n```bash\nnpx @markbrutx/promptbook-cli view --dir examples/support-assistant\n```\n\nVerbs: `init` · `ls` · `resolve` · `view` · `lint` · `eval`. Full surface in [CLI](#cli).\n\n**For the library** — five lines to your first assembled prompt, in any Node\nor edge runtime:\n\n```bash\nnpm i @markbrutx/promptbook-core\n```\n\n```ts\nimport { resolve } from \"@markbrutx/promptbook-core\";\n\nconst { text, trace } = await resolve({\n  promptsDir: \"./examples/support-assistant\",\n  prompt: \"reply\",\n  context: { model: \"claude\", locale: \"English\" },\n});\n```\n\n`text` is the assembled prompt — fragments joined with `\\n\\n`, in final order,\nwith `${...}` substituted. `trace` is the explain output: every rule (fired +\nwhy), the final id order, what was replaced / added / forbidden, context axes\nno rule matched, and warnings. A missing `${var}` renders empty and is\nrecorded in `trace.warnings` — the engine never throws on data.\n\n## The model: WHAT / WHEN / HOW\n\n- **WHAT — `fragment`** — a reusable micro-prompt: a Markdown file with YAML\n  frontmatter and a body that may contain `${path}` placeholders.\n- **WHEN — `rule`** — declarative `when \u003ccontext\u003e → add / replace / forbid /\n  order \u003cfragment\u003e`, expressed as *data*, not code.\n- **HOW — `resolve()`** — a pure function returning the assembled string; a thin\n  adapter sends it to a model.\n\nThink of it as CSS for prompts: fragments are declarations, rules are selectors,\nthe resolver is the cascade. Assembly is deterministic (same folder + context →\nbyte-identical string); the only stochastic step, the model call, lives behind\nan adapter.\n\nA prompts folder looks like:\n\n```\nprompts/\n├─ fragments/     *.md     (text + frontmatter)              — WHAT\n├─ rules/         *.yaml    (one composition per file)       — WHEN\n├─ code-prompts/  *.yaml    (builder metadata + samples)     — computed prompts in the menu\n└─ fixtures/      *.json    (eval cases / named variants)\n```\n\nA book indexes **every** prompt of a domain as one menu. A node is either a\n**composition** (declarative, assembled by `resolve`) or a **code-prompt** — a\nbuilder that stays in code, where the book holds only its metadata and frozen\noutput samples and the core never executes it. Declarative prompts compose;\ngenuinely computed ones still register and show up in `ls` and the viewer with a\n`code` badge, so the menu is complete and honest.\n\n## Multi-model compilation\n\nThe target model is just another context axis, and the output format a model\nwants is just a rule — so one logical prompt compiles to a different contract per\nmodel, from a single shared definition:\n\n```yaml\n- when: { model: gpt }\n  replace: { reply-format-prose: reply-format-json }   # JSON object contract\n- when: { model: claude }\n  replace: { reply-format-prose: reply-format-xml }    # XML-tagged output\n```\n\nIncumbents store a flat string per model. Here the model and its format are one\naxis over one prompt. See it end to end in\n[`examples/support-assistant`](examples/support-assistant).\n\n## CLI\n\n```\npromptbook resolve \u003cprompt\u003e   Assemble a prompt and print it to stdout (--all: every book)\npromptbook ls                 List compositions, code-prompts and fragments (--all: cross-book)\npromptbook lint [\u003cprompt\u003e]    Static checks (no model): dead/unused/dangling, budget, banned tokens\npromptbook eval [\u003cname\u003e]      Run fixtures through a model adapter, report pass-rate\npromptbook bundle [\u003cdir\u003e]     Compile a folder into an importable book module (--all/--check)\npromptbook watch [\u003cdir\u003e]      Rebuild book.generated.ts on every fragment/rule edit\npromptbook view               Start the local web viewer over the folder\npromptbook annotations \u003ca\u003e    Drain the viewer's feedback queue: list | resolve \u003cid\u003e | clear\n```\n\n`promptbook watch` is the dev loop: it rebundles each book's\n`book.generated.ts` as soon as a fragment / rule / code-prompt /\n`promptbook.json` changes. `promptbook bundle --check --all` is the CI gate:\nit exits 1 when any checked-in `book.generated.ts` has drifted from the source\nfolder, so the bundled artifact cannot fall out of sync silently. Pair either\nwith `--exclude-code-prompts` to keep `code-prompts/` on disk as metadata\nwhile shipping a runtime-lean bundle.\n\n`--dir` picks the folder (else `promptbook.json` `promptsDir`, else `./prompts`);\n`--ctx key=value` sets context; `--json` emits machine-readable output. stdout is\nthe payload, stderr is explanations and warnings, and `NO_COLOR` is honored.\n\n## Workspaces\n\nA folder can be one book (`fragments/` + `rules/`) or a **workspace** of sibling\nbooks. The CLI and viewer discover the books under a root and address them\nwithout juggling one `--dir` at a time:\n\n```bash\npromptbook ls --all --json           # cross-book inventory: each book's\n                                     # compositions, code-prompts + required context\npromptbook resolve \u003cbook\u003e/\u003ccomp\u003e     # address a composition in a named book\npromptbook resolve \u003ccomp\u003e            # bare name resolves by uniqueness across books\npromptbook resolve --all             # assemble every composition of every book\npromptbook view                      # one viewer with a book switcher in the sidebar\n```\n\nEach composition declares the context it needs — the `${var}` keys across its\nreachable fragments and the `when:`-axes its rules branch on — so you can supply\ndefaults instead of chasing `Missing variable` warnings one at a time. A\nsingle-book `--dir` keeps working unqualified (back-compat).\n\n## Viewer\n\n`promptbook view` opens a local web app over the folder, with two views over\nthe same book:\n\n- **Canvas** — the assembled prompt with colored segments by source fragment;\n  context pickers that re-assemble live; variant diff; inline lint + explain\n  (which rules fired and why).\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"assets/screenshot-canvas.png\" alt=\"The canvas: the assembled prompt in colored segments, with fired rules and the final order explained in the rail\" width=\"920\" /\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eFlip \u003ccode\u003esport=basketball\u003c/code\u003e · \u003ccode\u003etier=premium\u003c/code\u003e and watch the prompt re-assemble — the rail shows exactly which rules fired.\u003c/sub\u003e\u003c/p\u003e\n\n- **Graph** — an Obsidian-style map of the whole book: compositions, fragments\n  and code-prompts as nodes, an edge wherever a base list, rule or `${...}`\n  reference connects them. Shared fragments grow with usage. Click a node to\n  isolate its neighborhood (a local graph, camera and all); double-click to\n  open it in the canvas.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"assets/screenshot-graph-focus.png\" alt=\"Local graph mode: one fragment focused, showing only the prompts that depend on it\" width=\"920\" /\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eLocal mode answers the question that matters: \u003cem\u003ewhich prompts break if I edit this fragment?\u003c/em\u003e\u003c/sub\u003e\u003c/p\u003e\n\nA sidebar tree lists compositions, variants and fragments — press `/` to\nfilter it. Select text in the canvas, attach a comment, and it lands in a file\nqueue an agent drains via `promptbook annotations`.\n\n## Packages\n\n| Package | What it is |\n|---------|------------|\n| [`@markbrutx/promptbook-core`](packages/core) | The library. `resolve()`, `lint()`, `eval()`, bundle. Pure functions, zero CLI/UI deps. Ships a zero-dep `./edge` build for edge runtimes. |\n| [`@markbrutx/promptbook-cli`](packages/cli) | `promptbook resolve \\| ls \\| lint \\| eval \\| bundle \\| watch \\| view \\| annotations`. The surface for agents and CI. |\n| [`@markbrutx/promptbook-viewer`](packages/viewer) | `promptbook view` → a local web app. Canvas + graph views, sidebar tree, context pickers, diff, annotate-to-agent. |\n| [`@markbrutx/promptbook-openrouter`](packages/openrouter) | OpenRouter `ModelAdapter` for `eval`. Network lives here; core stays pure. |\n\n## Develop\n\nnpm workspaces, Node ≥ 20.6, TypeScript (NodeNext, strict), tsgo, vitest, biome.\n\n```\nnpm run build       # tsgo per package (+ edge bundle, + viewer web)\nnpm run typecheck\nnpm run test\nnpm run check       # biome + knip\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the two invariants that keep this\ntoolkit small and agnostic.\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbrutx%2Fpromptbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkbrutx%2Fpromptbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbrutx%2Fpromptbook/lists"}