{"_id":"@ararahq/pluma","_rev":"7-4a91d67538b3a21bfe5419106582307c","name":"@ararahq/pluma","dist-tags":{"latest":"0.4.2"},"versions":{"0.1.0":{"name":"@ararahq/pluma","version":"0.1.0","keywords":["markdown","pdf","typst","typography","no-browser"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.1.0","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"138afaa91de539f7b234ced8e84eddcf796407e5","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.1.0.tgz","fileCount":19,"integrity":"sha512-yr5uGjVayr7T6rs2qRG96gekp2TiSWzK26OfnPjhEsOdFHB3qwWoyY99BRyIQJ/DFoOCoAftWvr5DPAJzamRgA==","signatures":[{"sig":"MEUCIQClqkLHTvs6rbjK8H3CxEvfRjTmTsS2LVq0RaheFVnPRgIgePXMzD03eseqonE3z4y57UHTE197BDd7cmR6xZog4ig=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40833},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=18"},"gitHead":"512fe87e1f74491f0bbfe50037a8236f6385815d","scripts":{"test":"bun test","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","prepublishOnly":"bun test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"micas-ararahq","email":"micael@ararahq.com"},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"_npmVersion":"11.11.0","description":"Markdown para PDF com tipografia de verdade, sem browser. Typst embutido, ~50ms por documento.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"marked":"^18.0.0","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/pluma_0.1.0_1786499514623_0.7893409320903226","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ararahq/pluma","version":"0.1.1","keywords":["markdown","pdf","typst","typography","no-browser"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.1.1","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"8bb2551687193828227ebbad30ddc820dc1b8516","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.1.1.tgz","fileCount":19,"integrity":"sha512-M89/ZhkjCUcELZsrFCdgMgqLx4rGzgB02BQQ3U/oH3GOdP29ibdXtfUKXkoaseb9mnTDHo2JFqYBNhGwnPIThA==","signatures":[{"sig":"MEUCIQDUIPWiGvHrLc+ofpXi3rdluxW6n1U/Y0L5R24FWjj7iwIgC09lIisaJfJerjgyMFn+0qT1VAN+15vpI8XeRutgw2A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ararahq%2fpluma@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":43085},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=18"},"gitHead":"b7e35fd08af4a49210777dbdce413e6edf8442c8","scripts":{"test":"bun test","bench":"node bench/render.mjs","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","prepublishOnly":"bun test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eb916cb8-b686-42f3-b506-849a373be63c"}},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"_npmVersion":"11.16.0","description":"Markdown to PDF with real typography, no browser. Embedded Typst engine, ~50ms per document.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"marked":"^18.0.0","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/pluma_0.1.1_1786500674089_0.49788330664943237","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ararahq/pluma","version":"0.2.0","keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.2.0","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"e00a585aed429136e8f6cc70067fe7a21236a378","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.2.0.tgz","fileCount":69,"integrity":"sha512-kJm0OM4bSrQkzqgIJnzoj5uWkUh89HjCD13o26tOG1FUyErOFO41mr+cScBS0kgVAgEMAMeX8kaZ9jcQGTpaFQ==","signatures":[{"sig":"MEUCIBDfFBm6C3DxevV5AhJXZMVF6Xc7SWBO+LMD+Lw4hcMtAiEAn0f4fEkmyvY+ZSj4Z0umk3inw0WLK5z4NUOyqzKH+Bc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ararahq%2fpluma@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":470753},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=20"},"gitHead":"3bd73ca1a2353c7d0cd88b7915c8f048486484ef","mcpName":"io.github.ararahq/pluma","scripts":{"test":"bun test ./test/","bench":"node bench/render.mjs","build":"tsc -p tsconfig.build.json","test:all":"npm run test && npm --workspace @ararahq/pluma-cloud run test && npm --workspace @pluma/cloud run test","build:all":"npm run build && npm --workspace @ararahq/pluma-cloud run build && npm --workspace @pluma/cloud run build","cloud:dev":"npm --workspace @pluma/cloud run dev","typecheck":"tsc --noEmit","cloud:start":"npm --workspace @pluma/cloud run start","cloud:migrate":"npm --workspace @pluma/cloud run migrate","typecheck:all":"npm run typecheck && npm --workspace @ararahq/pluma-cloud run typecheck && npm --workspace @pluma/cloud run typecheck","prepublishOnly":"npm test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eb916cb8-b686-42f3-b506-849a373be63c"}},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"workspaces":["apps/cloud","packages/*"],"_npmVersion":"11.17.0","description":"Turn Markdown into branded, production-ready PDFs without Chromium or LaTeX.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"marked":"^18.0.0","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/pluma_0.2.0_1787580357382_0.39033685727102374","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ararahq/pluma","version":"0.3.0","keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser","csv","xlsx","parquet","llm-context","data-export"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.3.0","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"adfbb85ec75debcb60e2f399b2c900d3995c0d41","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.3.0.tgz","fileCount":91,"integrity":"sha512-xmswnonrISvgYtG1BQlQZyYm6EteqGhAWJAhJzT62O9KX1aydX9mywr4oZQmSdsHQ5vifDq6K2xJYAYFiEA6ig==","signatures":[{"sig":"MEUCIA2uTk9m/goCA9JM+G/rnhRc2QEyTmNdKRI8DKjDkcbNAiEAo33moPEg8cD54lglCQJKAptH64l83TVtVpWsi2dXUXA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":530339},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=20"},"gitHead":"971992749e8c1dfd3caa90e40f3f5654e1a19bbe","mcpName":"io.github.ararahq/pluma","scripts":{"test":"bun test ./test/","bench":"node bench/render.mjs","build":"tsc -p tsconfig.build.json","test:all":"npm run test","build:all":"npm run build","typecheck":"tsc --noEmit","brand:check":"node brand/scripts/check-brand.mjs","bench:context":"npm run build && node bench/context.mjs","typecheck:all":"npm run typecheck","prepublishOnly":"npm test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"micas-ararahq","email":"micael@ararahq.com"},"overrides":{"uuid":"11.1.1"},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"_npmVersion":"11.11.0","description":"Bounded context and streaming exports for datasets too large for AI, plus browserless Markdown to PDF.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"marked":"^18.0.0","exceljs":"^4.4.0","hysnappy":"^1.1.1","hyparquet":"^1.29.1","js-tiktoken":"^1.0.21","apache-arrow":"^21.2.0","hyparquet-writer":"^0.16.6","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/pluma_0.3.0_1787661506279_0.20316106031811532","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@ararahq/pluma","version":"0.4.0","keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser","csv","xlsx","parquet","llm-context","data-export"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.4.0","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"edcdfadf8705fcbf9c014cabf7d68164b8470ae1","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.4.0.tgz","fileCount":102,"integrity":"sha512-PfaRwe3iM58SThATAQM9H7nEEiu9G5EVgXJZZ8YXZ5ulscwRqyc4lNbQvDsqFUIqqL28y5kywI2OFgd2rh9Gyw==","signatures":[{"sig":"MEUCICN66N/ySZziZ26lCFURz5/qRfq+Lh6unL0mu5vikVyyAiEAyL0zNq6GG6NfWINcWZIDmqGBuuafubWpkNY93nInT3w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":621676},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=20"},"gitHead":"971992749e8c1dfd3caa90e40f3f5654e1a19bbe","mcpName":"io.github.ararahq/pluma","scripts":{"test":"bun test ./test/","bench":"node bench/render.mjs","build":"tsc -p tsconfig.build.json","test:all":"npm run test","build:all":"npm run build","typecheck":"tsc --noEmit","brand:check":"node brand/scripts/check-brand.mjs","bench:context":"npm run build && node bench/context.mjs","typecheck:all":"npm run typecheck","prepublishOnly":"npm test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"micas-ararahq","email":"micael@ararahq.com"},"overrides":{"uuid":"11.1.1"},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"_npmVersion":"11.11.0","description":"Bounded context and streaming exports for datasets too large for AI, plus browserless Markdown to PDF.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"marked":"^18.0.0","exceljs":"^4.4.0","hysnappy":"^1.1.1","hyparquet":"^1.29.1","js-tiktoken":"^1.0.21","apache-arrow":"^21.2.0","hyparquet-writer":"^0.16.6","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/pluma_0.4.0_1787673468163_0.3641874454302163","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@ararahq/pluma","version":"0.4.1","keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser","csv","xlsx","parquet","llm-context","data-export"],"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","_id":"@ararahq/pluma@0.4.1","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"bin":{"pluma":"dist/bin/pluma.js"},"dist":{"shasum":"b569dede5e5c54a76fe2185b1f38a2f8cb3dc514","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.4.1.tgz","fileCount":2482,"integrity":"sha512-0xuIbuK4OJivh9sAxNBG0IYAj7ZKn+ZEcH8ONIKvlJM3EtipWzZRFFJ8aohZvrR/emWtyP/jTBLyV3tk5Plnig==","signatures":[{"sig":"MEYCIQC75to1fvJWkJP1Wzy5FYim7qGpsFwaIjuo2kuGCSRj+gIhAOFASqyh8kihuvxIR3pbEnE7YVV5Y5dO24Sp2agpa/l2","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":32281517},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","engines":{"node":">=20"},"gitHead":"971992749e8c1dfd3caa90e40f3f5654e1a19bbe","mcpName":"io.github.ararahq/pluma","scripts":{"test":"bun test ./test/","bench":"node bench/render.mjs","build":"tsc -p tsconfig.build.json","prepack":"node scripts/patch-exceljs-uuid.mjs","test:all":"npm run test","build:all":"npm run build","typecheck":"tsc --noEmit","brand:check":"node brand/scripts/check-brand.mjs","bench:context":"npm run build && node bench/context.mjs","typecheck:all":"npm run typecheck","prepublishOnly":"npm test && tsc -p tsconfig.build.json"},"_npmUser":{"name":"micas-ararahq","email":"micael@ararahq.com"},"overrides":{"uuid":"11.1.1"},"repository":{"url":"git+https://github.com/ararahq/pluma.git","type":"git"},"_npmVersion":"11.11.0","description":"Bounded context and streaming exports for datasets too large for AI, plus browserless Markdown to PDF.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"marked":"^18.0.0","exceljs":"^4.4.0","hysnappy":"^1.1.1","hyparquet":"^1.29.1","js-tiktoken":"^1.0.21","apache-arrow":"^21.2.0","hyparquet-writer":"^0.16.6","@myriaddreamin/typst-ts-node-compiler":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":true,"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"bundleDependencies":["exceljs"],"_npmOperationalInternal":{"tmp":"tmp/pluma_0.4.1_1787674539150_0.4728295522562218","host":"s3://npm-registry-packages-npm-production"}},"0.4.2":{"name":"@ararahq/pluma","version":"0.4.2","mcpName":"io.github.ararahq/pluma","description":"Bounded context and streaming exports for datasets too large for AI, plus browserless Markdown to PDF.","license":"MIT","type":"module","main":"dist/src/index.js","types":"dist/src/index.d.ts","bin":{"pluma":"dist/bin/pluma.js"},"scripts":{"build":"tsc -p tsconfig.build.json","build:all":"npm run build","prepack":"node scripts/patch-exceljs-uuid.mjs","typecheck":"tsc --noEmit","typecheck:all":"npm run typecheck","test":"bun test ./test/","test:all":"npm run test","prepublishOnly":"npm test && tsc -p tsconfig.build.json","bench":"node bench/render.mjs","bench:context":"npm run build && node bench/context.mjs","bench:suite":"npm run build && node bench/suite.mjs","brand:check":"node brand/scripts/check-brand.mjs"},"keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser","csv","xlsx","parquet","llm-context","data-export"],"repository":{"type":"git","url":"git+https://github.com/ararahq/pluma.git"},"engines":{"node":">=20"},"dependencies":{"@myriaddreamin/typst-ts-node-compiler":"^0.7.0","apache-arrow":"^21.2.0","exceljs":"^4.4.0","hyparquet":"^1.29.1","hyparquet-writer":"^0.16.6","hysnappy":"^1.1.1","js-tiktoken":"^1.0.21","marked":"^18.0.0"},"devDependencies":{"@types/bun":"^1.3.14","typescript":"^5.9.3"},"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"homepage":"https://github.com/ararahq/pluma#readme","bugs":{"url":"https://github.com/ararahq/pluma/issues"},"publishConfig":{"access":"public"},"overrides":{"uuid":"11.1.1"},"gitHead":"971992749e8c1dfd3caa90e40f3f5654e1a19bbe","_id":"@ararahq/pluma@0.4.2","bundleDependencies":["exceljs"],"_nodeVersion":"24.5.0","_npmVersion":"11.11.0","dist":{"integrity":"sha512-yLQj5QGZ+ICJQfwmK8gldv9btD1kT0qituP978eD19gJHMF4Vbgz21oMv3o99FaNn3MpbMu98T6Ez9pF7SqZ0A==","shasum":"7b4dc4e537f5a1e65f7da9458d71f24779502606","tarball":"https://registry.npmjs.org/@ararahq/pluma/-/pluma-0.4.2.tgz","fileCount":2482,"unpackedSize":32282112,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHn83OjfLOBc2MYdXu/N6wzXZFyYriX4Np9rgggXHatsAiEAxSTlG1BONcsDMG0iHgVdjWuNarvKfKJJbZxDJREoZHg="}]},"_npmUser":{"name":"micas-ararahq","email":"micael@ararahq.com"},"directories":{},"maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pluma_0.4.2_1787680105706_0.3461531135511131"},"_hasShrinkwrap":true}},"time":{"created":"2026-08-12T01:51:54.393Z","modified":"2026-08-25T17:48:26.338Z","0.1.0":"2026-08-12T01:51:54.762Z","0.1.1":"2026-08-12T02:11:14.274Z","0.2.0":"2026-08-24T14:05:57.623Z","0.3.0":"2026-08-25T12:38:26.472Z","0.4.0":"2026-08-25T15:57:48.306Z","0.4.1":"2026-08-25T16:15:39.413Z","0.4.2":"2026-08-25T17:48:26.124Z"},"bugs":{"url":"https://github.com/ararahq/pluma/issues"},"author":{"name":"AraraHQ","email":"micael@ararahq.com"},"license":"MIT","homepage":"https://github.com/ararahq/pluma#readme","keywords":["markdown","pdf","html","ai-agents","document-extraction","typst","typography","no-browser","csv","xlsx","parquet","llm-context","data-export"],"repository":{"type":"git","url":"git+https://github.com/ararahq/pluma.git"},"description":"Bounded context and streaming exports for datasets too large for AI, plus browserless Markdown to PDF.","maintainers":[{"name":"micas-ararahq","email":"micael@ararahq.com"}],"readme":"# pluma\n\n**The context and export engine for datasets too large for AI.** Pluma compiles CSV, XLSX, and Parquet into an integrity-checked `.pluma` package, exposes only the exact context an agent asks for, records the blocks used as provenance, and streams the result back to CSV, XLSX, or Parquet.\n\nThe same package includes Pluma Docs: the original open-source Markdown-to-PDF engine, with real typography and no Chromium, LaTeX, Python process, external binary, or hosted dependency.\n\n```bash\nnpm install @ararahq/pluma\n```\n\n## Dataset quickstart\n\n```bash\npluma context compile transactions.csv -o transactions.pluma\npluma context inspect transactions.pluma --view schema --token-budget 2000 --tokenizer o200k_base\npluma context query transactions.pluma --plan query.json --token-budget 4000 --tokenizer o200k_base\npluma context export transactions.pluma -o result.parquet --format parquet\n```\n\nFrom Node:\n\n```ts\nimport { compileContext, openContext } from \"@ararahq/pluma\"\n\nconst job = compileContext(\"transactions.csv\", { output: \"transactions.pluma\" })\nfor await (const progress of job.events()) console.error(progress)\nawait job.result()\n\nconst context = openContext(\"transactions.pluma\")\nconst result = context.query({\n  filter: [{ column: \"status\", op: \"eq\", value: \"paid\" }],\n  groupBy: [\"country\"],\n  metrics: [{ column: \"amount\", aggregate: \"sum\", as: \"revenue\" }],\n  limit: 100,\n})\n\nconsole.log(result.payload({\n  tokenBudget: 4_000,\n  tokenizer: { id: \"o200k_base\", version: \"1\" },\n}))\n```\n\nStream directly from a database or cursor without collecting the result set:\n\n```ts\nimport { compileRows } from \"@ararahq/pluma\"\n\nconst source = {\n  identity: { snapshot: transactionSnapshot, fingerprint: `sha256:${snapshotHash}` },\n  open: (cursor?: string) => database.stream(\n    \"SELECT id, status, amount FROM transactions WHERE id > ? ORDER BY id\",\n    [cursor ?? \"0\"],\n  ),\n  cursor: (row: { id: bigint }) => String(row.id),\n}\n\nawait compileRows(source, {\n  output: \"transactions.pluma\",\n  relation: { id: \"transactions\", name: \"Transactions\", fields },\n  indexes: [{ columns: [\"id\"] }],\n  materializedAggregates: [{\n    groupBy: [\"status\"],\n    metrics: [{ column: \"amount\", aggregate: \"sum\", as: \"total\" }],\n  }],\n}).result()\n```\n\n`context.explain(plan)` makes complexity visible before execution. A compatible ordered index selects candidates in `O(log N + K)`; a declared exact aggregate is looked up in `O(log N + G)`. Exact arbitrary aggregates that are not covered still report `full_scan` and execute in `O(N)`—Pluma never labels a scan logarithmic.\n\nFor large outputs, `context.queryStream(plan)` and `context.exportQuery(plan, { format, output })` preserve backpressure and return evidence without materializing every result row in the application heap.\n\nThe compiler hashes sources incrementally, writes bounded Arrow IPC blocks, commits packages atomically, and verifies every artifact before reading it. Query results include the package fingerprint, normalized plan hash, blocks read, and row counts. Token budgets use the named tokenizer rather than a character-count estimate.\n\n### Open format\n\nA `.pluma` package is a normal directory, not a proprietary binary:\n\n```text\ntransactions.pluma/\n  manifest.json\n  blocks/*.arrow\n  views/overview.md\n  views/schema.md\n  provenance/source.json\n```\n\nThe manifest and package runtime are MIT. You can inspect, verify, move, or rebuild a package without Pluma Cloud.\n\nSee the versioned [capability and complexity matrix](docs/capabilities.md) for the exact Node, CLI, MCP, and Cloud surface.\n\n## Pluma Docs\n\nBuilt for developers shipping customer-facing deliverables: invoices, proposals, reports, audits, contracts, changelogs — and LLM output, which is already Markdown.\n\nOn an M-series Mac, [the included wall-clock benchmark](bench/render.mjs) renders\nthe example document in about 25 ms after warm-up. Run `npm run bench` on the\nmachine that will execute your workload: results vary by document and hardware.\nWhen Chrome is installed the script also reports a separate headless-print timing;\nit does not claim output parity, process cold-start time, or memory measurements.\n\n### Document quickstart\n\n```bash\npluma document.md\n```\n\nThat's it. `document.pdf` comes out with sensible defaults: A4, justified paragraphs, ruled tables, highlighted code blocks, page numbers. Nothing to configure.\n\nFrom Node:\n\n```ts\nimport { renderPdf } from \"@ararahq/pluma\"\nimport { writeFileSync } from \"node:fs\"\n\nconst pdf = renderPdf(\"# Hello\\n\\nA **bold** start.\")\nwriteFileSync(\"hello.pdf\", pdf)\n```\n\n`renderPdf` is synchronous and returns a `Uint8Array` — write it to disk, stream it in an HTTP response, attach it to an email.\n\n## Document metadata\n\nYAML frontmatter becomes the document header:\n\n```markdown\n---\ntitle: Commercial proposal\nsubtitle: Scope and pricing\nauthor: Ana Silva\ndate: August 2026\n---\n\n## First section\n```\n\n`title` renders at 24 pt over an accent rule; `subtitle`, `author` and `date` compose the byline. All optional — omit the frontmatter and the document starts at your first heading.\n\n## Your visual identity\n\nDrop a `brand.json` next to your documents:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/ararahq/pluma/main/schema/brand.schema.json\",\n  \"fonts\": { \"body\": \"Geist\", \"mono\": \"Geist Mono\" },\n  \"colors\": { \"accent\": \"#0e7490\", \"link\": \"#0e7490\" },\n  \"logo\": { \"path\": \"assets/logo.png\", \"width\": \"3.2cm\" },\n  \"footer\": \"Acme Corp — acme.com\"\n}\n```\n\n```bash\npluma proposal.md\n```\n\nNo flags needed: pluma auto-discovers `brand.json` next to the input file, and a `fonts/` directory next to it (drop your `.ttf`/`.otf` files there). The logo lands at the top, the accent colors the title rule, the footer shows on every page with automatic page numbers, and the whole document uses your typeface.\n\nEvery token is optional and falls back to a clean default:\n\n| Token | Controls |\n| --- | --- |\n| `fonts.body` / `fonts.heading` / `fonts.mono` | body, heading and code typefaces |\n| `fonts.size` | base size in pt (default 10.5) |\n| `colors.text` / `muted` / `faint` | text hierarchy |\n| `colors.accent` | title rule |\n| `colors.link` / `border` / `codeBackground` | links, rules, code blocks |\n| `page.paper` / `marginX` / `marginY` | paper size (a4, us-letter, ...) and margins |\n| `logo.path` / `logo.width` | header logo |\n| `footer` | footer text (page number is automatic) |\n\nThe `$schema` line gives you validation and autocomplete in any editor.\n\nThree ways to attach a brand, from most to least specific:\n\n1. `--brand path.json` flag (or `brand:` option in the API)\n2. `brand: ./path.json` in the document's frontmatter\n3. `brand.json` sitting next to the `.md` file\n\n## Iterating on a document\n\n```bash\npluma proposal.md --watch\n```\n\nRecompiles on every save (~50 ms). Keep the PDF open in a viewer that auto-reloads (macOS Preview, most Linux viewers, VS Code) and write.\n\n## Page control\n\nDirectives are HTML comments — invisible on GitHub, in editors, everywhere else Markdown renders:\n\n```markdown\n<!-- pagebreak -->\n\n## This section starts on a fresh page\n\n<!-- columns:2 -->\nThis content flows across two balanced columns. Still plain Markdown inside.\n<!-- /columns -->\n```\n\n## Free-form layout: Typst islands\n\nWhen Markdown can't express what you need — mixed font sizes in one line, callout boxes, grids — open a `typst` fenced block. Its content goes straight to the engine:\n\n````markdown\n```typst\n#text(size: 18pt, weight: 700)[Big opener,]\n#text(size: 8pt, style: \"italic\")[ small italic aside.]\n\n#block(fill: rgb(\"#ecfeff\"), radius: 6pt, inset: 12pt)[A callout box.]\n```\n````\n\nMarkdown covers 95% of a document; islands cover the rest with the full [Typst language](https://typst.app/docs). Islands execute layout code — only use them with documents from trusted authors. Regular Markdown text is always escaped and cannot inject code.\n\n## For AI agents\n\npluma is designed to be driven by LLMs as much as by humans:\n\n- **Built-in MCP server** — `pluma mcp` speaks MCP over stdio and exposes `render_pdf`, `read_pdf`, `read_html`, and `read_url`. Safe mode is the default: document paths stay inside the current working directory, and raw Typst, images, custom font paths, and filesystem roots are rejected. Use `pluma mcp --trusted-local` only for agents and documents you trust. Zero extra dependencies.\n- **`--json`** — every completed CLI operation emits one JSON line with\n  an `ok: true` envelope while preserving its useful fields. Rendering returns\n  `output` and `bytes`; PDF/HTML/URL reads retain `markdown`, metadata, counts,\n  links, and warnings; `--typst` returns `typst`. Failures use\n  `{\"ok\":false,\"error\":{\"message\":\"...\"}}`. Watch mode emits one envelope per\n  rebuild. Human status text stays on stderr.\n- **Forgiving input** — a document wrapped in a ` ```markdown ` fence (a common LLM habit) is unwrapped automatically.\n- **[llms.txt](llms.txt)** — the entire library documented in one page for models, shipped inside the npm package (`node_modules/@ararahq/pluma/llms.txt`).\n- **[brand JSON Schema](schema/brand.schema.json)** — lets models (and editors) generate valid brand files first try.\n\nTypical AI pipeline, three lines:\n\n```ts\nconst markdown = await llm.generate(reportPrompt)   // LLMs speak Markdown natively\nconst pdf = renderPdf(markdown, { brand })          // ~50 ms, no browser\nawait send(pdf)\n```\n\n## Included document readers\n\nPDF, HTML, and URL readers are included for workflows that also need source material as Markdown. They are local utilities, not a claim that Pluma replaces OCR or browser-based extraction platforms.\n\n```ts\nimport { readPdf, readHtml, readUrl } from \"@ararahq/pluma\"\n\nconst report = readPdf(\"./report.pdf\")\nconsole.log(report.markdown)\nconsole.log(report.meta.pageCount)\n\nconst page = readHtml(html, { baseUrl: \"https://example.com/post\" })\nconst remote = await readUrl(\"https://example.com/post\", { maxTokens: 4_000 })\n```\n\n`readPdf` accepts a path, `Buffer`, or `Uint8Array`. It reconstructs headings, paragraphs, lists, links, tables, page order, and repeated headers/footers from the PDF text layer. `readPdfPages` offers an async page iterator for large files, and `countPdfPages` reads the page tree without decoding page contents.\n\n`readHtml` and `readUrl` extract the main article or page content, preserve semantic structures such as code and tables, resolve relative links, and return title, canonical URL, author, dates, JSON-LD, links, word count, and warnings.\n\n```bash\npluma report.pdf                         # writes report.md\npluma page.html -o page.md              # local HTML to Markdown\npluma https://example.com/post          # fetched page to stdout\npluma report.pdf --pages 1-3 --json     # structured result for an agent\n```\n\nRepresentative JSON results:\n\n```json\n{\"ok\":true,\"output\":\"report.pdf\",\"bytes\":18422}\n{\"ok\":true,\"markdown\":\"# Report\\n...\",\"meta\":{\"pageCount\":3},\"warnings\":[]}\n{\"ok\":false,\"error\":{\"message\":\"Input file does not exist\"}}\n```\n\nPDF reading requires an existing text layer. It does not perform OCR, decode images, or accept non-empty passwords. Web fetching supports HTTP(S), explicit timeouts, redirect limits, and response-size limits. `readUrl` resolves every redirect hop and permits only public network addresses by default; trusted intranet callers must opt in explicitly with `{ networkPolicy: \"any\" }` (or `--trusted-local` in the CLI/MCP server).\n\n### Safe hosted rendering\n\n`renderPdf` remains the unrestricted local API, including custom themes, filesystem assets, fonts, and trusted raw Typst islands. Hosted or multi-tenant services should use `renderPdfCloudSafe`: it rejects raw Typst, Markdown images, and filesystem-backed brand fields, validates an allowlisted brand subset, and returns the generated PDF with its exact page count.\n\n```ts\nconst { pdf, pageCount } = renderPdfCloudSafe(markdown, {\n  brand: { colors: { accent: \"#0e7490\" }, footer: \"Acme\" },\n})\n```\n\n## API\n\n```ts\nimport {\n  renderPdf,               // (markdown, options?) => Uint8Array\n  renderPdfCloudSafe,      // restricted hosted wrapper => { pdf, pageCount }\n  readPdf,                 // PDF path/bytes => Markdown + pages + metadata\n  readPdfPages,            // async page iterator\n  readHtml,                // HTML string => Markdown + metadata\n  readUrl,                 // fetched web page => Markdown + metadata\n  markdownToTypstSource,   // (markdown, options?) => string — inspect the generated Typst\n  createTheme,             // (brand) => Theme\n  normalizeInput,          // strips a wrapping ```markdown fence\n} from \"@ararahq/pluma\"\n\nrenderPdf(markdown, {\n  brand: { fonts: { body: \"Geist\" }, footer: \"Acme\" }, // same shape as brand.json\n  fontPaths: [\"./fonts\"],  // directories with .ttf/.otf files\n  root: \"./docs\",          // base dir for logo and image paths\n  meta: { title: \"Override frontmatter\" },\n})\n```\n\nErrors are typed: `InvalidBrandError` for bad hex colors or lengths (values are validated, never interpolated raw), `UnknownThemeError` for a bad theme name.\n\nFull control when tokens aren't enough — a theme is just a Typst preamble:\n\n```ts\nrenderPdf(markdown, {\n  theme: {\n    name: \"compact\",\n    preamble: (meta) => `#set page(paper: \"a5\", margin: 1.5cm)\\n#set text(size: 9pt)`,\n  },\n})\n```\n\n## CLI reference\n\n```\npluma <input.md> [options]   Markdown -> PDF\npluma <input.pdf> [options] PDF -> Markdown\npluma <input.html> [options] HTML -> Markdown\npluma <https://...> [options] URL -> Markdown\npluma mcp [--trusted-local]  MCP server over stdio (safe by default)\n\n-o, --output <file>          output path (default: <input>.pdf)\n-w, --watch                  recompile on save\n-b, --brand <file>           brand JSON (overrides discovery)\n-f, --fonts <dir>            font directory, repeatable (overrides discovery)\n-t, --theme <name>           built-in theme\n    --typst                  print generated Typst source instead of compiling\n    --pages <range>          PDF pages, for example 1-3,7\n    --mode <mode>            HTML mode: article, page, or raw\n    --no-images              omit images from HTML/URL Markdown\n    --max-tokens <n>         truncate HTML/URL output at a block boundary\n    --trusted-local          allow raw Typst/filesystem access in MCP\n    --json                   {\"ok\":...} JSON envelopes on stdout\n```\n\n## Markdown support\n\nHeadings 1–6, paragraphs, bold / italic / strikethrough, inline and fenced code with syntax highlighting, links, images (paths resolve relative to the document), blockquotes, nested ordered and unordered lists, GFM tables with per-column alignment, horizontal rules. Raw HTML is dropped, except the directives above.\n\n## Requirements\n\nNode 20+ (or Bun). The Typst engine ships as a prebuilt native binding for macOS, Linux and Windows (x64 and arm64) — no install step, no runtime downloads.\n\n## Pluma Cloud\n\nThe managed product handles direct uploads, durable jobs, isolated workers, encrypted object storage, retention, API keys, billing, and downloadable exports. The open-source compiler remains useful by itself; Cloud is for teams that do not want to operate the queue, storage, worker isolation, retries, upgrades, or access controls.\n\n## License\n\nMIT © AraraHQ. The HTML article-extraction scoring includes a TypeScript adaptation of Mozilla Readability; see [NOTICE](NOTICE). Example fonts (Geist) are © Vercel under the SIL Open Font License 1.1 — see [examples/fonts/LICENSE-Geist.txt](examples/fonts/LICENSE-Geist.txt).\n","readmeFilename":"README.md"}